Scala:求和类型的函数映射
Scala: Map of functions on sum types
给定以下代码
sealed trait Fruit
case class Apple(color: String) extends Fruit
case class Orange(color: String) extends Fruit
def getAppleColor(apple: Apple) = apple.color
def getOrangeColor(orange: Orange) = orange.color
val myMap: Map[String, Fruit] = Map(
"myApple" -> Apple("red"),
"myOrange" -> Orange("orange"),
)
val myMapOfFunctions: Map[String, Apple with Orange => String] = Map(
"myAppleColorFun" -> getAppleColor,
"myOrangeColorFun" -> getOrangeColor,
)
为什么 myMapOfFunctions
不是类似于 myMap
的 Map[String, Fruit => String]
?我猜是因为它是关于功能的,但我想更好地理解为什么。谢谢!
I am just trying to understand why the compiler says that the type of the map is Apple with Orange and not Fruit
好的,好处是这“容易”解释。
不好的是可能没那么容易理解。
让我们退后几步,通过使用 if
而不是集合来稍微简化代码。
当你做这样的事情时:
val foo = if (bar) x else y
编译器必须推断 foo
的类型,为此它首先会获取/推断 x
和 y
的类型;让我们分别调用它们 X
和 Y
,然后计算两者之间的 LUB (最小上限),得到一个新类型 Z
,它将是分配给 foo
的类型
这是有道理的,因为 X <: Z
和 Y <: Z
因此 Liskov 受到尊重。
Quick note, if X
and Y
are the same types A
then the LUB is just A
Another quick one, if X
is a subtype of Y
then the LUB is simply Y
让我们看看那些应用于简单类型的:
val fruit = if (true) Apple(color = "red") else Orange(color = "green")
此处,一个分支的类型为 Apple
,另一个分支的类型为 Orange
,两者之间的 LUB 为 Fruit
.
到目前为止,一切都很简单。
现在,让我们来点趣味剂:
val optApple: Option[Apple] = Apple(color = "red")
val optOrange: Option[Orange] = Orange(color = "green")
val optFruit = if (true) optApple else optOrange
这里一个分支是Option[Apple]
,另一个分支是Option[Orange]
,我们知道结果会是Option[Fruit]
,但是为什么呢?
好吧,因为 Option
被定义为在其类型参数上是协变的,因此 Option[Fruit]
是两个分支的超类型;主要是 LUB。
好的,但是函数会怎样?
// Implementations do not matter.
val appleFunction : Apple => Apple = ???
val orangeFunction: Orange => Orange = ???
val fruitFunction = if (true) appleFunction else orangeFunction
在这种情况下,LUB 将是 (Apple with Orange) => Fruit
... 但是为什么呢?
嗯,return 很简单,因为函数在它们的 return 上也是协变的,这意味着 LUB 将再次是 Fruit
但是,为什么输入不是那样的呢?好吧,因为函数在它们的输入上是逆变的,因此函数 f
是另一个函数 g
的子类型,f
的输入类型必须是g
的输入;即它以相反的顺序进行,这就是为什么它被称为逆变。
这就解释了编译器推断该类型的原因。
但是,您可能想知道这种差异业务是什么,为什么它很重要,以及为什么有一个看起来违反直觉的业务。但这超出了本问答的范围。
不过,我可以分享一些可能有用的资源:
- https://www.youtube.com/watch?v=aUmj7jnXet4&t=53s
- https://www.youtube.com/watch?v=b1ftkK1zhxI&t=3s
- Covariant type A accepted in contravariant position of function argument A => B
给定以下代码
sealed trait Fruit
case class Apple(color: String) extends Fruit
case class Orange(color: String) extends Fruit
def getAppleColor(apple: Apple) = apple.color
def getOrangeColor(orange: Orange) = orange.color
val myMap: Map[String, Fruit] = Map(
"myApple" -> Apple("red"),
"myOrange" -> Orange("orange"),
)
val myMapOfFunctions: Map[String, Apple with Orange => String] = Map(
"myAppleColorFun" -> getAppleColor,
"myOrangeColorFun" -> getOrangeColor,
)
为什么 myMapOfFunctions
不是类似于 myMap
的 Map[String, Fruit => String]
?我猜是因为它是关于功能的,但我想更好地理解为什么。谢谢!
I am just trying to understand why the compiler says that the type of the map is Apple with Orange and not Fruit
好的,好处是这“容易”解释。
不好的是可能没那么容易理解。
让我们退后几步,通过使用 if
而不是集合来稍微简化代码。
当你做这样的事情时:
val foo = if (bar) x else y
编译器必须推断 foo
的类型,为此它首先会获取/推断 x
和 y
的类型;让我们分别调用它们 X
和 Y
,然后计算两者之间的 LUB (最小上限),得到一个新类型 Z
,它将是分配给 foo
的类型
这是有道理的,因为 X <: Z
和 Y <: Z
因此 Liskov 受到尊重。
Quick note, if
X
andY
are the same typesA
then the LUB is justA
Another quick one, ifX
is a subtype ofY
then the LUB is simplyY
让我们看看那些应用于简单类型的:
val fruit = if (true) Apple(color = "red") else Orange(color = "green")
此处,一个分支的类型为 Apple
,另一个分支的类型为 Orange
,两者之间的 LUB 为 Fruit
.
到目前为止,一切都很简单。
现在,让我们来点趣味剂:
val optApple: Option[Apple] = Apple(color = "red")
val optOrange: Option[Orange] = Orange(color = "green")
val optFruit = if (true) optApple else optOrange
这里一个分支是Option[Apple]
,另一个分支是Option[Orange]
,我们知道结果会是Option[Fruit]
,但是为什么呢?
好吧,因为 Option
被定义为在其类型参数上是协变的,因此 Option[Fruit]
是两个分支的超类型;主要是 LUB。
好的,但是函数会怎样?
// Implementations do not matter.
val appleFunction : Apple => Apple = ???
val orangeFunction: Orange => Orange = ???
val fruitFunction = if (true) appleFunction else orangeFunction
在这种情况下,LUB 将是 (Apple with Orange) => Fruit
... 但是为什么呢?
嗯,return 很简单,因为函数在它们的 return 上也是协变的,这意味着 LUB 将再次是 Fruit
但是,为什么输入不是那样的呢?好吧,因为函数在它们的输入上是逆变的,因此函数 f
是另一个函数 g
的子类型,f
的输入类型必须是g
的输入;即它以相反的顺序进行,这就是为什么它被称为逆变。
这就解释了编译器推断该类型的原因。
但是,您可能想知道这种差异业务是什么,为什么它很重要,以及为什么有一个看起来违反直觉的业务。但这超出了本问答的范围。
不过,我可以分享一些可能有用的资源:
- https://www.youtube.com/watch?v=aUmj7jnXet4&t=53s
- https://www.youtube.com/watch?v=b1ftkK1zhxI&t=3s
- Covariant type A accepted in contravariant position of function argument A => B