简体   繁体   English

Scala更高的kinded类型方差

[英]Scala higher kinded type variance

I am dipping my toes in higher kinded types, exploring a very basic Scala example: 我正在用更高级的类型浸泡我的脚趾,探索一个非常基本的Scala示例:

trait Mappable[F[_]] {
  def map[A, B](fa: F[A])(f: A => B): F[B]
}

object Mappable {
  implicit object MappableOption extends Mappable[Option] {
    def map[A, B](fa: Option[A])(f: A => B): Option[B] = fa.map(f)
  }
  implicit object MappableSeq extends Mappable[Seq] {
    def map[A, B](fa: Seq[A])(f: A => B): Seq[B] = fa.map(f)
  }
}

def bananaTuple[F[_], T](f: F[T])(implicit F: Mappable[F]): F[(String, T)] =
  F.map(f)(("banana", _))

This works: 这有效:

bananaTuple(Option(42)) // Some((banana,42))
bananaTuple(Seq(42))    // List((banana,42))

But this does not compile: 但这不编译:

bananaTuple(Some(42))
bananaTuple(List(42))

The compile errors I get: 我得到的编译错误:

could not find implicit value for parameter F: ch.netzwerg.hkt.HigherKindedTypes.Mappable[Some] bananaTuple(Some(42))

not enough arguments for method bananaTuple: (implicit F: ch.netzwerg.hkt.HigherKindedTypes.Mappable[Some])Some[(String, Int)]. Unspecified value parameter F. bananaTuple(Some(42))

How can I bring variance into the game? 如何在游戏中带来变化?

We can make this work with a little more parameteric polymorphism: 我们可以使用更多参数多态来完成这项工作:

object MappableExample {
  trait Mappable[F[_]] {
    type Res[_]
    def map[A, B](f: A => B)(c: F[A]): Res[B]
  }

  implicit def seqMappable[C[X] <: Seq[X]] = new Mappable[C] {
    type Res[X] = Seq[X]
    override def map[A, B](f:A => B)(c: C[A]): Seq[B] = c.map(f)
  }

  implicit def optionMappable[C[X] <: Option[X]]: Mappable[C] = new Mappable[C] {
    type Res[X] = Option[X]
    override def map[A, B](f: A => B)(c: C[A]): Option[B] = c.map(f)
  }

  def map[A, B, C[_]](xs: C[A])(f: A => B)(implicit mappable: Mappable[C]): mappable.Res[B] = {
    mappable.map(f)(xs)
  }

  def main(args: Array[String]): Unit = {
    println(map(List(1,2,3))(("banana", _)))
    println(map(Some(1))(("banana", _)))
  }
}

Yields: 产量:

List((banana,1), (banana,2), (banana,3))
Some((banana,1))

The compiler now infers Some as Mappable[Some]#Res[Int] and Mappable[List]#Res[Int] which is quite ugly. 编译器现在将Some推断为Mappable[Some]#Res[Int]Mappable[List]#Res[Int] ,这非常难看。 One would expect the compiler to actually be able to infer the right type without needing for any co/contravariance on the Mappable trait, which we can't do since we're using it in an invariant position. 人们可以期望编译器实际上能够推断出正确的类型,而不需要对Mappable特性进行任何协变/ Mappable ,这是我们无法做到的,因为我们在不变的位置使用它。

Subtype polymorphism allows us to pass values of a certain type or any of its subtypes to a method. 子类型多态性允许我们将某种类型或其任何子类型的值传递给方法。 If a method takes a value of type Fruit, we can also pass an Apple inside (an apple is a fruit after all). 如果方法采用Fruit类型的值,我们也可以在内部传递Apple(毕竟苹果是一种水果)。 So if you want to be able to pass a Mappable.MappableOption to your bananaTuple method, you have to make that MappableOption a subtype of MappableSome (since the type of your first parameter of bananaTuple dictates the implicit one). 因此,如果您希望能够将Mappable.MappableOption传递给bananaTuple方法,则必须使MappableOption成为MappableSome的子类型(因为bananaTuple的第一个参数的类型指示了隐含的类型)。 This means that you want your Mappable contravariant (if Some <: Option , then Mappable[Some] >: Mappable[Option] ). 这意味着您需要Mappable逆变(如果Some <: Option ,则可以使用Mappable[Some] >: Mappable[Option] )。

But you cannot have Mappable[F[_]] contravariant in F because F appears in covariant position of map (as a function parameter). 但是你不能在Mappable[F[_]]Mappable[F[_]]逆变量,因为F出现在map协变位置(作为函数参数)。 Note that F also appears in contravariant position of map (as a return value). 请注意,F也出现在map逆变位置(作为返回值)。

If you manage to make Mappable[F[_]] contravariant in F, it should work, but I'm not sure if making it contravariant makes sense. 如果你设法在Mappable[F[_]]制作Mappable[F[_]]逆变,它应该可以工作,但我不确定是否使逆变量变得有意义。 That is, if you want a subtype relationship such as eg Apple <: Fruit to result in Mappable[Apple] >: Mappable[Fruit] (this would not compile since Apple and Fruit are not type constructors, but I'm just using simple types to make a point here). 也就是说,如果你想要一个子类型关系,例如Apple <: Fruit导致Mappable[Apple] >: Mappable[Fruit] (这不会编译,因为Apple和Fruit不是类型构造函数,但我只是使用简单在这里提出要点的类型)。

Making a type contravariant in its type and solving the problem of contravariant type appearing in covariant position is a common problem and perhaps it's better if you search for it elsewhere ( here is one example). 在类型中进行类型逆变并解决出现在协变位置的逆变类型问题是一个常见问题,如果你在其他地方搜索它可能会更好( 这里是一个例子)。 I still think that it's better to provide an implicit object for every type you want to use, that is, to provide separate implicit objects for eg Seq and List . 我仍然认为最好为你想要使用的每个类型提供一个隐式对象,也就是说,为SeqList提供单独的隐式对象。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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