有人能向我解释一下为什么flatMap在猫身上的签名如下:
def flatMap[C, AA <: A](f: B => Kleisli[F, AA, C])(implicit F: FlatMap[F]): Kleisli[F, AA, C] =
Kleisli.shift(a => F.flatMap[B, C](run(a))((b: B) => f(b).run(a)))我真正不明白的是,AA <: A为什么AA必须是A的subType?
我得到了克莱斯里的平面图操作,这是我没有得到的subTyping吗?
发布于 2021-03-24 08:46:03
因为你可以,这有时会让生活更轻松。
假设我们有一些子类型
trait Animal {
def makeNoise: IO[Unit]
}
case class DogFood(label: String)
case class Dog(name: String) extends Animal {
def makeNoise = IO.println(s"$name says Woof!")
def consumeEdibles(df: DogFood) = IO.println(s"$name ate '${df.label}'. Yum!")
}我们能制造出很少的Kleislis:
val doNoise = Kleisli((_: Animal).makeNoise)
val eatNewFood = Kleisli((_: Dog).consumeEdibles(new DogFood("Fancy Dog Food")))注意,它们中的第一个只需要一个Animal,但是可以用一个Dog调用它们。这听起来是合理的,我们可以以某种方式将两者组合成一个单独的Kleisli,可以用一只狗来命名。对吗?让我们试一试。
如果你从一只狗开始工作,克莱斯里:
// Both valid - contravariance makes it so that doNoise : Kleisli[IO, Animal, Unit]
// extends Kleisli[IO, Dog, Unit] and compiler figures it out. Result is Kleisli[IO, Dog, Unit]
eatNewFood.flatMap(_ => doNoise)
(eatNewFood >> doNoise)但请注意,您是如何不能这样做的:
(doNoise >> eatNewFood)那是因为>>是“愚蠢的”。因为您从Kleisli[IO, Animal, Unit]开始,所以需要下一个也是Kleisli[IO, Animal, something]。
不过,我们可以通过让编译器在推断>>的类型之前扩展Kleisli来纠正它。
((doNoise: Kleisli[IO, Dog, Unit]) >> eatNewFood)
这是冗长而丑陋的。如果我们能告诉你“嘿,而不是要求右手边是一个更宽的类型,允许结果是一个更窄的类型,对两者都是有效的”,那就太好了。
flatMap的签名就是这么说的。亚型意味着较窄的AA型可以代替更宽的A型,用于左手侧和右手侧。
// Valid because of this trick you're asking about
// Here AA = Dog <: Animal = A
// Result value is Kleisli[F, AA, C] which resolves to Kleisli[IO, Dog, Unit]
doNoise.flatMap(_ => eatNewFood)我们失去的只是一些缩小输入的类型,没有人喜欢这些。
请注意,AA和A一样满足AA <:A。因此,虽然它不必是不同的子类型,但它可以而且是合法的,而且是合理的(可以使用Dog =>调用两者,可以将它们组合成可以用Dog调用的东西)。
https://stackoverflow.com/questions/66724859
复制相似问题