在Scala中,代数数据类型被编码为
使用
然而,有一个关键区别-在Scala中,“数据构造函数”有它们自己的类型。例如,比较以下两个(从各自的REPL中复制)。
我一直认为Scala变体处于优势方面。
毕竟,没有类型信息的丢失。例如,AppendIf [Int]是Positioning [Int]的子类型。
事实上,您将获得有关该值的附加编译时不变式。(我们可以称之为依赖类型的有限版本吗?)
这可以很好地利用 - 一旦您知道使用哪个数据构造函数创建了一个值,相应的类型就可以通过其余流程传播以增加更多的类型安全性。例如,使用此Scala编码的Play JSON仅允许您从
在Haskell中,
关于Scala对数据构造函数的处理方式有误的观点已经多次表达过——在Twitter、邮件列表、IRC、SO等平台上。不幸的是,我没有任何链接可以提供,除了Travis Brown的this answer和Scala的纯函数JSON库Argonaut。 Argonaut有意地采用了Haskell的方法(通过
sealed
一级类型层次结构。例如:-- Haskell
data Positioning a = Append
| AppendIf (a -> Bool)
| Explicit ([a] -> [a])
// Scala
sealed trait Positioning[A]
case object Append extends Positioning[Nothing]
case class AppendIf[A](condition: A => Boolean) extends Positioning[A]
case class Explicit[A](f: Seq[A] => Seq[A]) extends Positioning[A]
使用
case class
和case object
,Scala会生成一些东西,例如equals
、hashCode
、unapply
(由模式匹配使用)等,这为我们带来了许多传统ADT的关键属性和功能。然而,有一个关键区别-在Scala中,“数据构造函数”有它们自己的类型。例如,比较以下两个(从各自的REPL中复制)。
// Scala
scala> :t Append
Append.type
scala> :t AppendIf[Int](Function const true)
AppendIf[Int]
-- Haskell
haskell> :t Append
Append :: Positioning a
haskell> :t AppendIf (const True)
AppendIf (const True) :: Positioning a
我一直认为Scala变体处于优势方面。
毕竟,没有类型信息的丢失。例如,AppendIf [Int]是Positioning [Int]的子类型。
scala> val subtypeProof = implicitly[AppendIf[Int] <:< Positioning[Int]]
subtypeProof: <:<[AppendIf[Int],Positioning[Int]] = <function1>
事实上,您将获得有关该值的附加编译时不变式。(我们可以称之为依赖类型的有限版本吗?)
这可以很好地利用 - 一旦您知道使用哪个数据构造函数创建了一个值,相应的类型就可以通过其余流程传播以增加更多的类型安全性。例如,使用此Scala编码的Play JSON仅允许您从
JsObject
中提取fields
,而不是从任意JsValue
中提取。scala> import play.api.libs.json._
import play.api.libs.json._
scala> val obj = Json.obj("key" -> 3)
obj: play.api.libs.json.JsObject = {"key":3}
scala> obj.fields
res0: Seq[(String, play.api.libs.json.JsValue)] = ArrayBuffer((key,3))
scala> val arr = Json.arr(3, 4)
arr: play.api.libs.json.JsArray = [3,4]
scala> arr.fields
<console>:15: error: value fields is not a member of play.api.libs.json.JsArray
arr.fields
^
scala> val jsons = Set(obj, arr)
jsons: scala.collection.immutable.Set[Product with Serializable with play.api.libs.json.JsValue] = Set({"key":3}, [3,4])
在Haskell中,
fields
可能的类型是 JsValue -> Set (String, JsValue)
。这意味着对于一个 JsArray
等类型来说,它将在运行时失败。这个问题也表现在众所周知的部分记录访问器中。关于Scala对数据构造函数的处理方式有误的观点已经多次表达过——在Twitter、邮件列表、IRC、SO等平台上。不幸的是,我没有任何链接可以提供,除了Travis Brown的this answer和Scala的纯函数JSON库Argonaut。 Argonaut有意地采用了Haskell的方法(通过
private
case类和手动提供数据构造函数)。你可以看到我提到的Haskell编码中存在的问题Argonaut也存在。(除了使用Option
表示部分性)scala> import argonaut._, Argonaut._
import argonaut._
import Argonaut._
scala> val obj = Json.obj("k" := 3)
obj: argonaut.Json = {"k":3}
scala> obj.obj.map(_.toList)
res6: Option[List[(argonaut.Json.JsonField, argonaut.Json)]] = Some(List((k,3)))
scala> val arr = Json.array(jNumber(3), jNumber(4))
arr: argonaut.Json = [3,4]
scala> arr.obj.map(_.toList)
res7: Option[List[(argonaut.Json.JsonField, argonaut.Json)]] = None
我已经思考了很长时间,但仍然不理解Scala编码的错误之处。当然,有时会阻碍类型推断,但这似乎不足以宣布它是错误的。我错过了什么?
(a, b)
这个语法,你就知道正在处理一对... 直到添加子类型,因为现在可能涉及任何超类型的类型判断。详见此处的23.1节:https://www.cs.cmu.edu/~rwh/plbook/book.pdf - J. Abrahamson