Каковы виды использования алгебраических типов данных?

16

Я читаю об алгебраических типах данных (благодаря Ричарду Минериху я нашел это отличное объяснение концепции). Хотя я думаю, что понимаю понятие типов сумм, типов продуктов и т. Д., Я не совсем понимаю, как алгебраические типы данных полезны помимо определения соответствия шаблонам. Что еще можно сделать с помощью ADT помимо сопоставления с образцом?


РЕДАКТИРОВАТЬ: Я не спрашиваю, что может сделать разработчик с ADT, что не может быть сделано с объектами. Я спрашиваю, есть ли другие операции, которые допускает ADT; Например, можно ли сделать дополнительные рассуждения о типах участвующих, если используются ADT? Облегчают ли ADT какой-то тип анализа, который был бы невозможен без них?

Онорио Катеначчи
источник
2
Что вы можете делать с объектами, кроме вызова методов?
1
ADT фактически относится к «абстрактному типу данных», а не к алгебраическим типам данных.
Рейн Хенрикс
4
@ Rein: может относиться к любому из них в зависимости от контекста.
sepp2k
4
@ Rein: Это действительно так (что я нахожу весьма удивительным, если честно): однако в статье в Википедии для ADT перечислены как абстрактный тип данных, так и алгебраический тип данных в качестве возможных значений. А ADT очень часто используется в качестве сокращения для алгебраических типов данных, например, в списке рассылки Haskell и канале IRC.
sepp2k
1
@ Рейн, я знаю - просто надоело набирать «Алгебраический тип данных» снова и снова, и я подумал, что люди смогут понять, о чем я говорил, учитывая контекст.
Онорио Катеначчи

Ответы:

10

Алгебраические типы данных отличаются тем, что они могут быть построены из нескольких типов «вещей». Например, Дерево может не содержать ничего (Пусто), Лист или Узел.

data Tree = Empty
          | Leaf Int
          | Node Tree Tree

Поскольку узел состоит из двух деревьев, алгебраические типы данных могут быть рекурсивными.

Сопоставление с образцом позволяет деконструировать алгебраические типы данных таким образом, чтобы обеспечить безопасность типов. Рассмотрим следующую реализацию глубины и ее эквивалент псевдокода:

depth :: Tree -> Int
depth Empty = 0
depth (Leaf n) = 1
depth (Node l r) = 1 + max (depth l) (depth r)

по сравнению с:

switch on (data.constructor)
  case Empty:
    return 0
  case Leaf:
    return 1
  case Node:
    let l = data.field1
    let r = data.field2
    return 1 + max (depth l) (depth r)

Это имеет недостаток, заключающийся в том, что программист должен помнить регистр Empty перед Leaf, чтобы поле field1 не было доступно для пустого дерева. Аналогично, регистр Leaf должен быть объявлен перед регистром Node, чтобы поле 2 не было доступно на Leaf. Таким образом, безопасность типов, таким образом, не поддерживается языком, а скорее накладывает дополнительную когнитивную нагрузку на программиста. Кстати, я беру эти примеры прямо со страниц Википедии.

Конечно, языковой набор утки может сделать что-то вроде этого:

class Empty
  def depth
    0
  end
end

class Leaf
  def depth
    1
  end
end

class Node
  attr_accessor :field1, :field2

  def depth
    1 + [field1.depth, field2.depth].max
  end
end

Таким образом, алгебраические типы данных не могут быть строго лучше, чем их эквивалент ООП, но они предоставляют другой набор напряжений для работы при создании программного обеспечения.

Рейн Хенрикс
источник
9

Я не уверен , что объяснение это все , что отлично.

Алгебраические типы данных используются для создания структур данных, таких как списки и деревья.

Например, деревья разбора легко представляются алгебраическими структурами данных.

data BinOperator = Add
                 | Subtr
                 | Div
                 | Mult
                 | Mod
                 | Eq
                 | NotEq
                 | GreaterThan
                 | LogicAnd
                 | LogicOr
                 | BitAnd
                 | BitOr
                 | ...

data UnOperator = Negate
                | Not
                | Increment
                | Decrement
                | Complement
                | Ref
                | DeRef


data Expression = Empty
                | IntConst Int
                | FloatConst Float
                | StringConst String
                | Ident String
                | BinOp BinOperator Expression Expression
                | UnOp UnOperator Expression Bool //prefix or not
                | If Expression Expression Expression
                | While Expression Expression Bool //while vs. do while
                | Block List<Expression>
                | Call Expression List<Expression>
                | ...

На самом деле это не займет намного больше времени для представления языка Си.

Но на самом деле вы можете делать ВСЕ с алгебраическими типами данных. Lisp доказывает, что вы можете делать все с помощью пар и ADT, просто предоставляя более детальный и безопасный для этого способ.

Конечно, если вы спросите: «Что вы можете сделать с ADT, что вы не можете сделать с объектами?», Ответ будет «ничего». Только иногда (в основном) вы обнаружите, что решения для ADT значительно менее многословны, в то время как решения, основанные на объектах, возможно, более гибкие. Итак, чтобы поместить его в дерево разбора, представленное с помощью ADT:

If(Call(Ident('likes_ADTs'),[Ident('you')]),
   Call(Ident('use_ADTs'),[Ident('you')]),
   Call(Ident('use_no_ADTs'),[Ident('you')]))
back2dos
источник