Каков синтаксис копроизведения (несвязанного объединения) типов в 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 соответственно.