В Scala алгебраические типы данных кодируются как 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
es и case object
s 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>
Фактически, вы получаете дополнительный инвариант времени компиляции относительно значения. (Можно ли назвать это ограниченной версией зависимой типизации?)
Это можно найти с пользой - как только вы узнаете, какой конструктор данных использовался для создания значения, соответствующий тип можно распространить через остальную часть потока, чтобы повысить безопасность типов. Например, Play JSON, в котором используется эта кодировка Scala, позволит вам извлекать только fields
из JsObject
, а не из произвольного 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 и т. д. К сожалению, у меня нет ссылок ни на один из них, за исключением пары - этот ответ Трэвиса Брауна и Argonaut, чисто функциональная библиотека JSON для Scala.
Argonaut сознательно использует подход Haskell (private
анализируя классы случаев и предоставляя конструкторы данных вручную) . Вы можете видеть, что проблема, о которой я упоминал, с кодировкой 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 здесь: cs.cmu.edu/~rwh/plbook/book. pdf - person J. Abrahamson   schedule 15.08.2014