简体   繁体   中英

scala type mismatch Higher kinded type

I would like to model a Mapper that takes in a container of A s ( T[A] ) so that with a function of f: A => B we get another container T[B] . I have, after many hours of experimentation (see commented code) come up with the following solution:

  sealed trait Mapper[ A, T[ A ], B ] {
    //type Out <: T[B]
    type Out[X] //= T[X]
    def map( l: T[ A ], f: A => B ): Out[B]
  }

  object Mappers {

    implicit def typedMapper[ A, T[ A ] <: Iterable[ A ], B ]: Mapper[ A, T, B ] =
      new Mapper[ A, T, B ] {
        override type Out[X] = Iterable[X]
        //override type Out <: Iterable[ B ]
        //def map( l: T[ A ], f: A => B ) : this.Out = {
        def map( l: T[ A ], f: A => B ) : Out[B] = {
          println( "map" )
          l.map( f )
        }
      }

    implicit def IntMapper = typedMapper[Int, List, Int]
  }

  //def testMapper[ A, T[ A ], B ]( l: T[ A ], f: A => B )( implicit mapper: Mapper[ A, T, B ] ): T[B] = {
  def testMapper[ A, T[ A ], B ]( l: T[ A ], f: A => B )( implicit mapper: Mapper[ A, T, B ] ) : Mapper[A, T, B]#Out[B]= {
    println( mapper )
    mapper.map(l, f)
  }

I can now use it as follows:

import Mappers.IntMapper
val l9 = testMapper( List( 1, 2, 3 ), { x: Int => x + 1 } )
println(l9)

Although it works I am still a loss as to how I can directly restrict the Out to T[B] . If I do that I always seem to get a type mismatch. Can anyone point out a cleaner/simpler way of doing this without the type alias or using T[B] directly?

TIA

Here's a commented first approximation to what you want. The type members have been eliminated, revealing a slightly deeper problem in what you want to do.

trait Mapper[A, T[_], B] {
  def map(ta: T[A])(f: A => B): T[B]
}

// Note that Iterable[A]#map has type [B](A => B)Iterable[B]. You can't have typedMapper
// like yours from above just yet, because T#map is not certain to return another T;
// it only promises an Iterable.
implicit def iterableMapper[A, B]: Mapper[A, Iterable, B] = new Mapper[A, Iterable, B] {
  // Multiple param lists support the type inferencer
  override def map(i: Iterable[A])(f: A => B) = i.map(f)
}

// Curried and arg-swapped version of Mapper
type MapperOf[A, B] = { type l[T[_]] = Mapper[A, T, B] }
def map[A, B, T[_]: MapperOf[A, B]#l](ta: T[A])(f: A => B): T[B] = implicitly[Mapper[A, T, B]].map(ta)(f)

map(??? : Iterable[Any])(_.toString) // OK (at compile time, at least :P)
map(List(1,2,3))(_*2) // NOPE! The inferencer has already decided T = List, before
                      // looking for implicits, so the resolution fails to notice that
                      // iterableMapper would work.
map[Int, Int, Iterable](List(1,2,3))(_*2) // Works

This is pushing the limits of the type inferencer, which is why you need the manually specified type arguments.

Note that Iterable isn't really important to the collections hierarchy. It's mostly there because Java has it. In order for this to be properly generic to the collections, you need some delicious CanBuildFrom dark magic.

import collection._, generic._ // Open the gates of hell

implicit def collectionMapper[A, Coll[A] <: GenTraversableLike[A, Coll[A]], B]
    (implicit builderFactory: CanBuildFrom[Coll[A], B, Coll[B]]):
    Mapper[A, Coll, B] =
  new Mapper[A, Coll, B] {
    override def map(coll: Coll[A])(f: A => B): Coll[B] = coll.map(f)
  }

map(List(1))(_*2): List[Int] // Works
map(Seq(1).view)(_*2): Seq[Int] // Works, but note how we lose the knowledge of the view
                                // Exercise for the reader: fix that.
map(BitSet(1))(_*2): SortedSet[Int] // Works, but we lose the BitSet-ness
                                    // Another exercise: fix it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM