Каков синтаксис копроизведения (несвязанного объединения) типов в Haskell?
рассмотрим следующее
data Point=Point{x::Float,y::Float}
data Shape=Circle{centre::Point,radius::Float}
|Rectangle {uleft::Point,bRight::Point}
Здесь тип Shape является копроизведением двух типов Circle и Rectangle. Я могу повторно использовать типы Circle и Rectangle в другом месте. Поэтому было бы полезно сделать это вместо этого:
data Point=Point{x::Float,y::Float}
data Circle=Circle{centre::Point,radius::Float}
data Rectangle=Rectangle {uleft::Point,bRight::Point}
data Shape =Circle | Rectangle
но я получаю ошибку компиляции, когда я это делаю: Circle объявляется дважды.
Какой правильный синтаксис для этого, или это невозможно?
Ответы
Ответ 1
Копроизведение типов в Haskell обычно обозначается символом Either
:
data Either a b = Left a | Right b
type Shape = Either Circle Rectangle
-- so you have shapes as either Left c for some Circle c
-- or Right r for some Rectangle r
Это работает довольно хорошо, хотя по техническим причинам это не совсем копродукт. Другим распространенным способом было бы определить такой тип:
data Shape = CircleShape Circle | RectangleShape Rectangle
так что CircleShape :: Circle -> Shape
и RectangleShape :: Rectangle -> Shape
являются вашими двумя инъекциями.
Неправильно сказать, как вы это делаете в своем вопросе, что исходный Shape
является копроизведением типов Circle
и Rectangle
, потому что последние два не являются типами. Если вы хотите настроить все так, чтобы Circle p r
было как значением типа Circle
, так и значением типа Shape
, то это действительно противоречит духу системы типа Haskell (хотя что-то подобное может быть возможно при достаточно многие типы системных расширений).
Ответ 2
Это невозможно, но у вас есть несколько вариантов. В этом случае я бы пошел с GADT
с индексом DataKind
:
{-# LANGUAGE DataKinds, GADTs, KindSignatures #-}
data ShapeType = Circle | Rectangle
data Shape :: ShapeType -> * where
CircleShape :: { centre :: Point, radius :: Float } -> Shape Circle
RectangleShape { uleft :: Point, bRight :: Point } -> Shape Rectangle
Затем, когда вы хотите обрабатывать фигуры вообще, вы просто используете Shape a
, и если вам нужен прямоугольник или круг, используйте Shape Rectangle
или Shape Circle
соответственно.