Я знаю, что Python не является Haskell или Ocaml, но что является лучшим способом определить типы алгебраических данных в Python (2 или 3)? Спасибо!
Лучший способ определить типы алгебраических данных в Python?
Ответ 1
Macropy предоставляет алгебраические типы данных, сопоставление образцов и многое другое!
Ответ 2
В Python переменные уже могут иметь несколько экземпляров (конечно, не одновременно).
>>> x = 5
>>> type(x)
<type 'int'>
>>> x = ["you", "me", "them"]
>>> type(x)
<type 'list'>
Например, в вашем коде вы можете сделать:
def f(x):
if isinstance(x, int):
pass
elif isinstance(x, float):
pass
else:
raise TypeError
Если вы хотите остаться ближе к Хаскеллу, вы можете сделать что-то вроде этого. Скажи в Хаскеле
data Item = Person String Int String | Car String Bool
В Python 3.6 вы пишете
def g(x):
tag, *values = x
if tag == 'Person':
name, age, e_mail_address = values
# do something
pass
elif tag == 'Car':
brand, is_diesel = values
# do something
pass
else:
raise TypeError
В Хаскеле это также называют "суммами".
Альтернативой является использование классов. Делает более ясным, что происходит. Например, Haskell Either
is
data Either a b = Left a | Right b
В Python Either Int Float
это будет что-то вроде
class Either:
def __init__(self, a=None, b=None):
if (a is None) and (b is not None):
self._left = None
self._right = float(b)
elif (a is not None) and (b is None):
self._left = int(a)
self._right = None
else:
raise TypeError
@property
def is_left(self):
return self._left is not None
@property
def is_right(self):
return self._right is not None
@property
def value(self):
if self.is_left:
return self._left
elif self.is_right:
return self._right
def __eq__(self, other):
if isinstance(other, Either):
if self.is_left == other.is_left:
return self.value == other.value
else:
return False
else:
raise TypeError
def __str__(self):
return str(self.value)
Ответ 3
Здесь реализация типов сумм относительно Pythonic пути.
import attr
@attr.s(frozen=True)
class CombineMode(object):
kind = attr.ib(type=str)
params = attr.ib(factory=list)
def match(self, expected_kind, f):
if self.kind == expected_kind:
return f(*self.params)
else:
return None
@classmethod
def join(cls):
return cls("join")
@classmethod
def select(cls, column: str):
return cls("select", params=[column])
Взломайте переводчика, и вы увидите знакомое поведение:
>>> CombineMode.join()
CombineMode(kind='join_by_entity', params=[])
>>> CombineMode.select('a') == CombineMode.select('b')
False
>>> CombineMode.select('a') == CombineMode.select('a')
True
>>> CombineMode.select('foo').match('select', print)
foo
Примечание: декоратор @attr.s
происходит из библиотеки attrs, он реализует __init__
, __repr__
и __eq__
, но он также замораживает объект. Я включил его, потому что он сокращает размер реализации, но он также широко доступен и довольно стабилен.
Типы сумм иногда называют помеченными объединениями. Здесь я использовал kind
член для реализации тега. Дополнительные параметры для каждого варианта реализуются через список. В истинно Pythonic-стиле это типизировано по сторонам на входе и выходе, но не строго соблюдается внутри.
Я также включил функцию match
которая выполняет базовое сопоставление с образцом. Безопасность типов также реализована с помощью утилитарной типизации, а TypeError
будет вызываться, если переданная сигнатура лямбда-функции не совпадает с фактическим вариантом, которому вы пытаетесь сопоставить.
Эти типы сумм можно комбинировать с типами продуктов (list
или tuple
) и при этом сохранять большую часть критических функций, необходимых для алгебраических типов данных.
Проблемы
Это не строго ограничивает набор вариантов.