18

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?

mfirry
  • 3,474
  • 1
  • 22
  • 33
Andrew
  • 183
  • 4

1 Answers1

27

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:

  1. Repeat the fields. This is the most basic option.

    data Foo = A Int | B String | C Int String
    
  2. 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
    
  3. 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).

chi
  • 101,733
  • 3
  • 114
  • 189
  • 5
    The last example requires several extensions, though, right? (`DataKinds`, `GADTs`, and `KindSignatures`, I think.) – chepner Mar 23 '19 at 15:10