scala / scala-library-next

backwards-binary-compatible Scala standard library additions
Apache License 2.0
67 stars 17 forks source link

Add ability to retrieve iterator constructor and IterableFactory instance via type inference. #146

Closed rssh closed 1 year ago

rssh commented 1 year ago

Currently, it is impossible to construct a collection iterator knowing only the type of a type constructor. Something like

trait IteratorAccess[C[_]] {
    def apply[A](ca:C[A]):Iterator[A] = .... oops
}

Therefore it is hard to write generic algorithms over collections.

Proposal:

BalmungSan commented 1 year ago

Uhm not sure what you mean, you can just use IterableOnce to accept any collection and obtain its Iterator

Or you may use the IsIterableOnce typeclass to be even more generic.

rssh commented 1 year ago
  1. Sorry, I can't understand your statement about IterableOnce. The criteria are simple: is it possible to implement trait IteratorAccess for generic C over the current standard library?

  2. As I know, we have no something like IsIterableOnce typeclass in the standard library. Can you be more specific?

rssh commented 1 year ago

oops, found https://www.scala-lang.org/api/2.13.5/scala/collection/generic/IsIterableOnce.html But it's. defined over C[A], not overC[_]. Therefore it is still impossible to write a generic algorithm knowing only C[_] without an instance of C[A].

BalmungSan commented 1 year ago

Ham no, it is defined over any C.

Maybe I am the one who is not understanding you, what kind of meta-problem do you want to solve? And how do you think your proposed typeclass would solve it.

rssh commented 1 year ago

As I see in code:

trait IsIterableOnce[Repr] {
    type A
    ....
}

i.e. Repr. here is C[A], because you know A. (When it over C[_] we don't know A, it will supplied later as type parameter)

I need a typeclass over a class constructor.
The most simple example: Implement IteratorAccess[C[_] <: ???] in generic way. Another simple example (MB more understandable]:

ForEachRunner[C[_] (type-constraints-should-be-here)] {
      def  run[A](a:C[A])(fu:  A=>Unit): Unit
}

I can't find a way to build ForEachRunner[List]. from a List constructor.

If we will add to IterableFactory method retrieveIterator and add a given instance of IterableFactory in a List scope, this will become possible:

ForEachRunner[C[_]:IterableFactory] {
      def  run[A](a:C[A])(fu:  A=>Unit): Unit = {
          val it = summon[IterableFactory[C]].retrieveIterator(ca)
          ......
     }
}
BalmungSan commented 1 year ago

So you want an Iterator of a class rather than a value. How would that even work? What values would it yield?

rssh commented 1 year ago

Signature should be

trait IterableFactory[C[_]] {
   ....
  def retrieveIterator[A](c:C[A]):Iterator[A]
}

Get collection with a given type as parameter and return it's iterator. I.e. the main value, is that we can use retrieveIterator in a situation when we have no C[A] in supplied of inferred type parameters but have only C[_]. (or. CC[_] is use current standard library conventions).

Note, that IterableFactory already have newBuilder method which works in a similar way.

BalmungSan commented 1 year ago

What other methods would have that typeclass? And are you sure IsIterableOnce and IterableOnceOps don't already support that?

Again, instead of repeating what you want, why not show an actual real problem you need to solve and show how that typeclass of yours would solve it.

rssh commented 1 year ago

Is an example with ForEachRunner is not sufficient for you?

rssh commented 1 year ago

I.e. I mean, if you will start trying to implement given IteratorAccess[C[_]] or ForEachRunner[C[_]] then you will see the problematic.

BalmungSan commented 1 year ago

No, is not clear at all. What is ForEachRunner a class, a method? If a class, why it has to be a class? Who would instantiate it, when? How? Who would use it? How would use it? What is the implementation of run? What are those extra type constraints?

rssh commented 1 year ago

O'key, let's try to specify all details.

Let we have definition:

trait  ForEachRunner[C[_]]. {
      ///.  run function over all elements of C
      def run[A](ca:C[A])(f:  A=>Unit): Unit
}

Now I want to implement ForEachRunner for the set of collections. for example, here is an implementation for List

 class ListForEachRunner extends ForEachRunner[List] {
      def run[A](ca:C[A])(f:  A=>Unit): Unit = {
           val it = ca.iterator
           while(it.hasNext) {
                f(it.next)
           }
      }
 }

(of course, foreach is a simplification; in reality, this can be any generic algorithm. [C++ STL is built in such a way] )

I want to write something other than a definition for each concrete collection. But for this, I need to retrieve an iterator in a generic way, knowing only the collection constructor CC[_].

rssh commented 1 year ago

Usage:

val algo = summon[ForEachRunner[List]].
algo.run(List(1,2,3))(i => println(i))
algo.run(List("1","2","3"))(i => println(i))
BalmungSan commented 1 year ago

Is it important for it to be a type? Rather than just add the extension method.

Anyways, both versions are implementable using IsIterableOnce quite easily; you just need to ask for it on the run method. Or even, no need the indirection, just use IterableOnce and subtyping.

rssh commented 1 year ago

How ? To use IsIterableOnce I. should know the element type (or pass an implicit parameter to run)

rssh commented 1 year ago

I.e. we have a quite clear task: implement ForEachRunner for generic collection type without knowing an element type and without passing an additional parameter to run.

rssh commented 1 year ago

I.e. we can pass something to the constructor of our IterableOnceForEachRunner implementation, but not to the run method.

BalmungSan commented 1 year ago
  1. I still don't see why it is important to have a class at all, rather than just extension methods.
  2. I also don't see why run can't accept an implicit IsIterableOnce
  3. Anyways, it doesn't matter, just use [C[x] <: IterableOnce[x]] and that is all, you don't need any indirection.
rssh commented 1 year ago
  1. Algorithm represent a class, we build bigger algorithms from smaller.
  2. Carriers for algorithms are not only collections, for example, these can be databases. It's why we don't want extra implicit parameters to run. I.e. we want to incapsulate all knowledge about carriers in our typeclasses.
  3. O! 3 is sufficient: the ability to write C[x] <: IterableOnce[x] in constraint -- it allows us to use iterator directly. (so, retrieveIterator is not needed). Great, thanks!
rssh commented 1 year ago

What-s left: an implicit instance of IterableFactory into the companion object of each collection type.

Example: let we have algorithm. Empty:

trait Empty[C[_]] {
      def run[A](): C[A]
}

And again, we can build this for list

class ListEmpty extends Empty[List] {
      def run[A](): List[A] = List()
}

In generic case ... only one implicit is BuildFrom and it's dependent from element type.... :

class IterableEmpty[CC[x]](using buildFrom: BuildFrom[CC[?],?,CC[?]) extends Empty[List] {
      def run[A](): CC[A] =
         ...  naive buildFrom.newBuilder  will give us incorrect type
}
rssh commented 1 year ago

//. better be discussed separately in another thread.

BalmungSan commented 1 year ago

@rssh you may use IterableFactory: https://www.scala-lang.org/api/current/scala/collection/IterableFactory.html

Sadly, the values are not implicit, only the Factory ones; I really find the whole Factory hierarchy confusing. Anyways, if you are okay with passing the companion object rather than the type, you can do something like this: https://scastie.scala-lang.org/zQeS83ygQDaupa0dH7nncw

rssh commented 1 year ago

@BalmungSan thanks. yes Finally, I retrieved the companion object from type by macros, but this looks like cheating. // https://github.com/rssh/dotty-cps-async/blob/master/shared/src/main/scala/cps/macros/misc/CollectionHelper.scala

(and given instance based only on type in a situation close to described: https://github.com/rssh/dotty-cps-async/blob/9032c425b5dee2eef6682ca5f42cc61a59d84761/shared/src/main/scala/cps/monads/IterableCpsMonad.scala#L55 )

Still plan to provide a minimal changeset to make a 'normal' solution possible.