In Scala I can describe such ADT:
sealed trait Foo
case class A(a: Int) extends Foo
case class B(b: String) extends Foo
case class C(a: A, b: B) extends Foo
How can I do the same in Haskell?
data Foo = A Int | B String | C A B
It doesn't work, because A and B are not types. Should I use GHC extensions to do it?
Definition. An abstract data type is a type with associated operations, but whose representation is hidden. Common examples of abstract data types are the built-in primitive types in Haskell, Integer and Float. Haskell supports the definition of abstract data types via the module system.
Haskell. Leaving pets aside, probably the most common example of algebraic data type in Haskell is a Tree , which describes elements of a tree-like data structure. A Tree elements might come in one of two forms: a node, which contains a value, together with its left and right “descendants” (or nested tree elements)
In Haskell, the newtype declaration creates a new type from an existing one. For example, natural numbers can be represented by the type Integer using the following declaration: newtype Natural = MakeNatural Integer. This creates an entirely new type, Natural, whose only constructor contains a single Integer.
In Scala, your ADT makes A
,B
,C
to be subtypes of Foo
. In Haskell we do not have subtypes, so A
,B
,C
are instead constructors of type Foo
.
A few possible workarounds:
Repeat the fields. This is the most basic option.
data Foo = A Int | B String | C Int String
Define additional types, so that we can reuse them more than once.
data AT = AT Int -- can have many arguments
data BT = BT String -- can have many arguments
data Foo = A AT | B BT | C AT BT
Exploit a GADT
data FooTag = AT | BT | CT
data Foo (tag :: FooTag) where
A :: Int -> Foo 'AT
B :: String -> Foo 'BT
C :: Foo 'AT -> Foo 'BT -> Foo 'CT
Here, in the last line we are able to refer to "the values constructed using A
" using the type Foo 'AT
, since tag AT
is only used by constructor A
.
Note that this approach adds a tag parameter to Foo
, so it slightly changes the interface: we can no longer write bar :: Foo -> ...
, but we have to write bar :: Foo t -> ...
(or to use existential types).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With