Как создать ADT в Haskell?

В Scala я могу описать такой 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

Как я могу сделать то же самое в Haskell?

data Foo = A Int | B String | C A B

Это не работает, потому что A и B не являются типами. Должен ли я использовать расширения GHC, чтобы сделать это?

Ответ 1

В Scala ваша ADT делает A, B, C подтипами Foo. В Haskell у нас нет подтипов, поэтому A, B, C являются конструкторами типа Foo.

Несколько возможных обходных путей:

  1. Повторите поля. Это самый основной вариант.

    data Foo = A Int | B String | C Int String
    
  2. Определите дополнительные типы, чтобы мы могли использовать их более одного раза.

    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. Эксплуатировать ГАДТ

    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
    

    Здесь, в последней строке мы можем ссылаться на "значения, построенные с использованием A ", используя тип Foo 'AT, поскольку тег AT используется только конструктором A Обратите внимание, что этот подход добавляет параметр тега в Foo, поэтому он немного меняет интерфейс: мы больше не можем писать bar :: Foo ->..., но мы должны написать bar :: Foo t ->... (или использовать экзистенциальные типы).