Есть ли способ ограничения типа соединения?

В Haskell существует ли способ OR совместно с несколькими ограничениями типа, так что объединение выполняется, если любой из них выполняется?

Например, предположим, что у меня был параметр GADT, заданный параметром DataKind, и я хотел, чтобы некоторые конструкторы возвращали значения только для определенных конструкторов данного типа, псевдо-Haskell:

{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE DataKinds #-}
module Temp where
data Color = White | Red | Blue | Yellow | Green | Tawny | Purple | Black
data Fruit (c :: Color) where
 Banana :: (c ~ Green | c ~ Yellow | c ~ Black) => Fruit c
 Apple :: (c ~ Red | c ~ Green ) => Fruit c
 Grape :: (c ~ Red | c ~ Green | c ~ White) => Fruit c
 Orange :: (c ~ Tawny ) => Fruit c

Я могу попытаться реализовать OR с помощью классных классов:

{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE DataKinds #-}
module Temp where
data Color = White | Red | Blue | Yellow | Green | Tawny | Purple | Black
data Fruit (c :: Color) where
 Banana :: BananaColor c => Fruit c
 Apple :: AppleColor c => Fruit c
 Grape :: GrapeColor c => Fruit c
 Orange :: OrangeColor c => Fruit c
class BananaColor (c :: Color)
instance BananaColor Green
instance BananaColor Yellow
instance BananaColor Black
class AppleColor (c :: Color)
instance AppleColor Red
instance AppleColor Green
class GrapeColor (c :: Color)
instance GrapeColor Red
instance GrapeColor Green
instance GrapeColor White
class OrangeColor (c :: Color)
instance OrangeColor Tawny

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

instance OrangeColor Blue

И поскольку он открыт, компилятор не может сделать вывод, что [Apple, Grape, Banana] должен иметь тип [Fruit Green], если не указано.

2 ответа

Я не могу придумать способ буквально реализовать или для Constraint s, к сожалению, но если мы просто объединяем равенства, как в вашем примере, мы можем подправить ваш подход к классу классов и сделать он закрыт семействами типов и поднял булевы. Это будет работать только в GHC 7.6 и выше; в конце я упомянул и то, как в GHC 7.8 будет лучше, и как сделать это в GHC 7.4.

Идея такова: так же, как мы могли объявить функцию уровня значения isBananaColor :: Color -> Bool, так же мы можем объявить функцию уровня типа isBananaColor :: Color -> Bool:

type family IsBananaColor (c :: Color) :: Bool
type instance IsBananaColor Green = True
type instance IsBananaColor Yellow = True
type instance IsBananaColor Black = True
type instance IsBananaColor White = False
type instance IsBananaColor Red = False
type instance IsBananaColor Blue = False
type instance IsBananaColor Tawny = False
type instance IsBananaColor Purple = False

Если нам нравится, мы можем даже добавить

type BananaColor c = IsBananaColor c ~ True

Затем мы повторяем это для каждого цвета фруктов и определяем Fruit как в вашем втором примере:

{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE TypeFamilies #-}
data Color = White | Red | Blue | Yellow | Green | Tawny | Purple | Black
data Fruit (c :: Color) where
 Banana :: BananaColor c => Fruit c
 Apple :: AppleColor c => Fruit c
 Grape :: GrapeColor c => Fruit c
 Orange :: OrangeColor c => Fruit c
type family IsBananaColor (c :: Color) :: Bool
type instance IsBananaColor Green = True
type instance IsBananaColor Yellow = True
type instance IsBananaColor Black = True
type instance IsBananaColor White = False
type instance IsBananaColor Red = False
type instance IsBananaColor Blue = False
type instance IsBananaColor Tawny = False
type instance IsBananaColor Purple = False
type BananaColor c = IsBananaColor c ~ True
type family IsAppleColor (c :: Color) :: Bool
type instance IsAppleColor Red = True
type instance IsAppleColor Green = True
type instance IsAppleColor White = False
type instance IsAppleColor Blue = False
type instance IsAppleColor Yellow = False
type instance IsAppleColor Tawny = False
type instance IsAppleColor Purple = False
type instance IsAppleColor Black = False
type AppleColor c = IsAppleColor c ~ True
type family IsGrapeColor (c :: Color) :: Bool
type instance IsGrapeColor Red = True
type instance IsGrapeColor Green = True
type instance IsGrapeColor White = True
type instance IsGrapeColor Blue = False
type instance IsGrapeColor Yellow = False
type instance IsGrapeColor Tawny = False
type instance IsGrapeColor Purple = False
type instance IsGrapeColor Black = False
type GrapeColor c = IsGrapeColor c ~ True
-- For consistency
type family IsOrangeColor (c :: Color) :: Bool
type instance IsOrangeColor Tawny = True
type instance IsOrangeColor White = False
type instance IsOrangeColor Red = False
type instance IsOrangeColor Blue = False
type instance IsOrangeColor Yellow = False
type instance IsOrangeColor Green = False
type instance IsOrangeColor Purple = False
type instance IsOrangeColor Black = False
type OrangeColor c = IsOrangeColor c ~ True

(Если вы хотите, вы можете избавиться от типов -XConstraintKinds и type XYZColor c = IsXYZColor c ~ True и просто определить конструкторы Fruit как XYZ :: IsXYZColor c ~ True => Fruit c.)

Теперь, что это вы покупаете, и что он вас не покупает? С положительной стороны вы получаете возможность определять свой тип так, как хотите, что определенно является победой; и поскольку Color закрыт, никто не может добавлять больше экземпляров семейства экземпляров и нарушать это.

Однако есть и недостатки. Вы не получите вывод, который хотите сказать вам автоматически, что [Apple, Grape, Banana] имеет тип Fruit Green; что хуже того, что [Apple, Grape, Banana] имеет вполне допустимый тип (AppleColor c, GrapeColor c, BananaColor c) => [Fruit c]. Да, нет способа мономорфизировать это, но GHC не может понять это. Чтобы быть абсолютно честным, я не могу представить, какое решение вам дадут эти свойства, хотя я всегда готов удивляться. Другая очевидная проблема с этим решением - это то, как долго это нужно: вам нужно определить все восемь случаев цвета для каждого семейства типов IsXYZColor! (Использование семьи нового типа для каждого также раздражает, но неизбежно с решениями этой формы.)

Я упомянул выше, что GHC 7.8 сделает это лучше; это будет сделано, устраняя необходимость перечислять каждый отдельный случай для каждого отдельного класса IsXYZColor. Как? Ну, Ричард Эйзенберг и др. ввел закрытые перекрывающиеся упорядоченные семейства типов в GHC HEAD, и он будет доступен в 7.8. Там документ, озаглавленный POPL 2014расширенная версия) по этой теме, и Ричард также написал вводное сообщение в блоге (похоже, устаревший синтаксис).

Идея состоит в том, чтобы разрешить типовые экземпляры типов объявляться как обычные функции: все уравнения должны быть объявлены в одном месте (исключая предположение открытого мира) и проверяются по порядку, что позволяет перекрывать. Что-то вроде

type family IsBananaColor (c :: Color) :: Bool
type instance IsBananaColor Green = True
type instance IsBananaColor Yellow = True
type instance IsBananaColor Black = True
type instance IsBananaColor c = False

неоднозначно, потому что IsBananaColor Green соответствует как первому, так и последнему уравнениям; но в обычной функции он будет работать нормально. Таким образом, новый синтаксис:

type family IsBananaColor (c :: Color) :: Bool where
 IsBananaColor Green = True
 IsBananaColor Yellow = True
 IsBananaColor Black = True
 IsBananaColor c = False

Этот блок type family ... where { ... } определяет семейство типов так, как вы хотите его определить; он сигнализирует, что это семейство типов закрыто, упорядочено и перекрывается, как описано выше. Таким образом, код в GHC 7.8 будет выглядеть следующим образом: (непроверенный, поскольку он не установлен на моей машине):

{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeFamilies #-}
data Color = White | Red | Blue | Yellow | Green | Tawny | Purple | Black
data Fruit (c :: Color) where
 Banana :: IsBananaColor c ~ True => Fruit c
 Apple :: IsAppleColor c ~ True => Fruit c
 Grape :: IsGrapeColor c ~ True => Fruit c
 Orange :: IsOrangeColor c ~ True => Fruit c
type family IsBananaColor (c :: Color) :: Bool where
 IsBananaColor Green = True
 IsBananaColor Yellow = True
 IsBananaColor Black = True
 IsBananaColor c = False
type family IsAppleColor (c :: Color) :: Bool where
 IsAppleColor Red = True
 IsAppleColor Green = True
 IsAppleColor c = False
type IsGrapeColor (c :: Color) :: Bool where
 IsGrapeColor Red = True
 IsGrapeColor Green = True
 IsGrapeColor White = True
 IsGrapeColor c = False
type family IsOrangeColor (c :: Color) :: Bool where
 IsOrangeColor Tawny = True
 IsOrangeColor c = False

Ура, мы можем прочитать это, не засыпая от скуки! Фактически, вы заметите, что я переключился на явную версию IsXYZColor c ~ True для этого кода; Я сделал это потому, что, поскольку шаблон для дополнительных четырех синонимов стал намного более очевидным и раздражающим с этими более короткими определениями!

Однако отпустите в противоположном направлении и сделайте этот код более уродливым. Зачем? Ну, GHC 7.4 (что, увы, у меня еще есть на моей машине) не поддерживает семейства типов с типом результата не *. Что мы можем сделать вместо этого? Мы можем использовать классы типов и функциональные зависимости для подделки. Идея состоит в том, что вместо isBananaColor :: Color -> Bool мы имеем класс типа IsBananaColor :: Color -> Bool -> Constraint, и мы добавляем функциональную зависимость от цвета к булевому. Тогда IsBananaColor c b выполнимо тогда и только тогда, когда IsBananaColor c ~ b в более приятной версии; потому что Color является замкнутым, и у нас есть функциональная зависимость от него, это все равно дает нам те же свойства, что оно просто более уродливое (хотя в основном концептуально). Без дальнейших церемоний, полный код:

{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE FlexibleContexts #-}
data Color = White | Red | Blue | Yellow | Green | Tawny | Purple | Black
data Fruit (c :: Color) where
 Banana :: BananaColor c => Fruit c
 Apple :: AppleColor c => Fruit c
 Grape :: GrapeColor c => Fruit c
 Orange :: OrangeColor c => Fruit c
class IsBananaColor (c :: Color) (b :: Bool) | c -> b
instance IsBananaColor Green True
instance IsBananaColor Yellow True
instance IsBananaColor Black True
instance IsBananaColor White False
instance IsBananaColor Red False
instance IsBananaColor Blue False
instance IsBananaColor Tawny False
instance IsBananaColor Purple False
type BananaColor c = IsBananaColor c True
class IsAppleColor (c :: Color) (b :: Bool) | c -> b
instance IsAppleColor Red True
instance IsAppleColor Green True
instance IsAppleColor White False
instance IsAppleColor Blue False
instance IsAppleColor Yellow False
instance IsAppleColor Tawny False
instance IsAppleColor Purple False
instance IsAppleColor Black False
type AppleColor c = IsAppleColor c True
class IsGrapeColor (c :: Color) (b :: Bool) | c -> b
instance IsGrapeColor Red True
instance IsGrapeColor Green True
instance IsGrapeColor White True
instance IsGrapeColor Blue False
instance IsGrapeColor Yellow False
instance IsGrapeColor Tawny False
instance IsGrapeColor Purple False
instance IsGrapeColor Black False
type GrapeColor c = IsGrapeColor c True
class IsOrangeColor (c :: Color) (b :: Bool) | c -> b
instance IsOrangeColor Tawny True
instance IsOrangeColor White False
instance IsOrangeColor Red False
instance IsOrangeColor Blue False
instance IsOrangeColor Yellow False
instance IsOrangeColor Green False
instance IsOrangeColor Purple False
instance IsOrangeColor Black False
type OrangeColor c = IsOrangeColor c True


Ниже приведена моя попытка кодировать проблему. Основная идея состоит в том, чтобы представлять фрукты как класс типа и различные типы фруктов как типы, реализующие этот тип класса

data Color = White | Red | Blue | Yellow | Green | Tawny | Purple | Black
class Fruit a where
 getColor :: a -> Color
data Banana where
 GreenBanana :: Banana
 YellowBanana :: Banana
 BlackBanana :: Banana
instance Fruit Banana where
 getColor GreenBanana = Green
 getColor YellowBanana = Yellow
 getColor BlackBanana = Black
data Apple where
 GreenApple :: Apple
 RedApple :: Apple
instance Fruit Apple where
 getColor GreenApple = Green
 getColor RedApple = Red

В вашей последней строке вопросов указано, что вы хотите что-то типа [Fruit Green], что, очевидно, означает, что Fruit Green должен быть типом, где в указанном выше коде используется значение конструктор значений. Мы должны сделать Green как тип, как показано ниже:

data Red = Red
data Green = Green
data Black = Black
data Fruit c where
 GreenBanana :: Fruit Green
 BlackBanana :: Fruit Black
 RedApple :: Fruit Red
 GreenApple :: Fruit Green
greenFruits :: [Fruit Green]
greenFruits = [GreenBanana, GreenApple]

licensed under cc by-sa 3.0 with attribution.