In Scala, algebraic data types are encoded as hierarchies of sealed sibling types. Example:
-- 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]
With case class es and case object s, Scala generates a bunch of things like equals , hashCode , unapply (used by pattern matching), etc., which brings us many key properties and features of traditional ADTs.
There is one key difference: In Scala, data constructors have their own types . Compare the following two examples (copied from the corresponding 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
I have always considered the Scala option on the good side.
After all, there is no type information . AppendIf[Int] , for example, is a subtype of Positioning[Int] .
scala> val subtypeProof = implicitly[AppendIf[Int] <:< Positioning[Int]] subtypeProof: <:<[AppendIf[Int],Positioning[Int]] = <function1>
In fact, you get an additional compile time parameter relative to the value . (Can this limited version of the dependent set be called?)
This may be useful to use. Once you know which data constructor was used to create the value, the corresponding type can propagate through the rest of the stream to add additional type safety. For example, Play JSON, which uses this Scala encoding, will allow you to extract fields from a JsObject , and not from any arbitrary 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])
In Haskell, fields will probably be of type JsValue -> Set (String, JsValue) . This means that it will not work at runtime for JsArray , etc. This problem also manifests itself in the form of well-known accessories for partial recording.
That Scala data constructor processing was incorrect was repeatedly expressed - on Twitter, mailing lists, IRC, SO, etc. Unfortunately, I have no links to any of these, except for a couple - this answer is from Travis Brown and Argonaut , a purely functional JSON library for Scala.
Argonaut deliberately uses the Haskell approach (via private case classes and provides manual data constructors). You can see that the problem that I mentioned with Haskell encoding also exists with Argonaut. (In addition, Option used to indicate bias.)
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
I have been thinking about this for quite some time, but still do not understand what makes Scala encoding incorrect. I am sure that this sometimes interferes with the type of output, but this does not seem like a reason strong enough to mislead it. What am I missing?