Как создать 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 для этого?


person Andrew    schedule 23.03.2019    source источник


Ответы (1)


В Scala ваш АТД делает 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. Использование 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
    

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

person chi    schedule 23.03.2019
comment
Однако последний пример требует нескольких расширений, верно? (Кажется, DataKinds, GADTs и KindSignatures.) - person chepner; 23.03.2019