2010-07-17 3 views
4

У меня есть два неперекрывающихся набора типов и вы хотите создать другой набор, который является объединением этих двух. Образец Код:Объединение/объединение двух классов в один в Haskell

class A a 
class B b 
class AB ab 

instance A a => AB a 
instance B b => AB b 

GHC 6.12.3 не позволяет заявить об этом с сообщением об ошибке:

 
    Duplicate instance declarations: 
     instance (A a) => AB a -- Defined at playground.hs:8:9-19 
     instance (B b) => AB b -- Defined at playground.hs:9:9-19 

Я понимаю, что это заявление приводит к потере контроля над перекрытием экземплярами AB a, потому что экземпляры для A a и B b может возникнуть позже (и я не вижу простого способа справиться с этим).
Я предполагаю, что для такого же поведения должно быть какое-то «обходное».

P.S. Варианты нравится:

newtype A a => WrapA a = WrapA a 
newtype B b => WrapB b = WrapB b 

instance A a => AB (WrapA a) 
instance B b => AB (WrapB b) 

и

data WrapAB a b = A a => WrapA a 
       | B b => WrapB b 

instance AB (WrapAB a b) 

и любой другой, который оборачивает некоторые из этих типов не подходит моим потребностям (выбор реализации сторонних объявленная класса типа)

Комментарий к @camccann: Это отличная идея добавить флаг для управления слиянием/выбором типа по флагом, но я бы хотел избежать таких вещей, как гонки перекрытия d экземпляров. Для Thos, которые заинтересованы в этом ответе, сжатый вариант:

data Yes 
data No 

class IsA a flag | a -> flag 
class IsB b flag | b -> flag 

instance Delay No flag => IsA a flag 
instance Delay No flag => IsB b flag 

instance (IsA ab isA, IsB ab isB, AB' isA isB ab) => AB ab 

class AB' isA isB ab 
instance (A a) => AB' Yes No a 
instance (B b) => AB' No Yes b 
instance (A a) => AB' Yes Yes a 

class Delay a b | a -> b 
instance Delay a a 

instance IsA Bool Yes 
instance A Bool 

ответ

3

Насколько я знаю, нет «хороший» способ для достижения этой цели. Вы застряли в том, чтобы где-то добавить рывок. Поскольку вы не хотите типы обертки, другой вариант, о котором я могу думать, вступает во взаимодействие с определениями классов, а это значит, что мы отключены от типа метапрограммирования.

Теперь причина, по которой этот подход не будет «приятным», заключается в том, что ограничения класса в основном безотзывные. Как только GHC увидит ограничение, он придерживается его, и если он не может удовлетворить компиляцию ограничений, не удается. Это отлично подходит для «пересечения» экземпляров класса, но не полезно для «объединения».

Чтобы обойти эту проблему, мы должны типа предикаты с булевы типа уровня, а не ограничений прямого класса. Для этого мы используем классы с несколькими параметрами типа с функциональными зависимостями для создания функций типа и перекрывающихся экземпляров с задержкой объединения для записи «экземпляров по умолчанию».

Во-первых, нам нужны весело язык прагмами:

{-# LANGUAGE TypeSynonymInstances #-} 
{-# LANGUAGE FlexibleInstances #-} 
{-# LANGUAGE FlexibleContexts #-} 
{-# LANGUAGE ScopedTypeVariables #-} 
{-# LANGUAGE MultiParamTypeClasses #-} 
{-# LANGUAGE FunctionalDependencies #-} 
{-# LANGUAGE OverlappingInstances #-} 
{-# LANGUAGE UndecidableInstances #-} 

определимся булевы уровне типа:

data Yes = Yes deriving Show 
data No = No deriving Show 

class TypeBool b where bval :: b 
instance TypeBool Yes where bval = Yes 
instance TypeBool No where bval = No 

TypeBool класс не является строго необходимым - я в основном использовать его, чтобы избежать работая с undefined.

Далее мы пишем членство предикаты для классов типа, которые мы хотим взять объединение с экземпляров по умолчанию, чтобы служить случай падения проходным:

class (TypeBool flag) => IsA a flag | a -> flag 
class (TypeBool flag) => IsB b flag | b -> flag 

instance (TypeBool flag, TypeCast flag No) => IsA a flag 
instance (TypeBool flag, TypeCast flag No) => IsB b flag 

TypeCast ограничение печально известного типа курса Олега класс унификации. Код для этого можно найти в конце этого ответа. Здесь необходимо отложить выбор типа результата - fundep говорит, что первый параметр определяет второй, а экземпляры по умолчанию полностью универсальны, поэтому размещение No непосредственно в голове экземпляра будет интерпретироваться как предикат, всегда оценивающий значение false, что не помогает. Использование TypeCast вместо этого ожидает, пока GHC не выберет наиболее специфический перекрываемый экземпляр, который заставляет результат быть No, когда и только тогда, когда более конкретный экземпляр не найден.

Я собираюсь сделать еще не строго необходимые настройки для самих классов типа:

class (IsA a Yes) => A a where 
    fA :: a -> Bool 
    gA :: a -> Int 

class (IsB b Yes) => B b where 
    fB :: b -> Bool 
    gB :: b -> b -> String 

Контекст ограничение класса гарантирует, что, если мы напишем экземпляр для класса без написания также экземпляр согласования предиката , мы получим загадочную ошибку немедленно, а не очень запутывающие ошибки позже. Я также добавил несколько функций для классов в демонстрационных целях.

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

class AB ab where 
    fAB :: ab -> Bool 
instance (IsA ab isA, IsB ab isB, AB' isA isB ab) => AB ab where 
    fAB = fAB' (bval :: isA) (bval :: isB) 

class AB' isA isB ab where fAB' :: isA -> isB -> ab -> Bool 
instance (A a) => AB' Yes No a where fAB' Yes No = fA 
instance (B b) => AB' No Yes b where fAB' No Yes = fB 
instance (A ab) => AB' Yes Yes ab where fAB' Yes Yes = fA 
-- instance (B ab) => AB' Yes Yes ab where fAB' Yes Yes = fB 

Обратите внимание, что, если оба предикаты истинны, мы явно выбирая экземпляр A. Пронумерованный экземпляр делает то же самое, но вместо этого использует B. Вы также можете удалить оба, и в этом случае вы получите исключительную дизъюнкцию двух классов. bval здесь, где я использую класс TypeBool. Обратите внимание также на сигнатуры типа для получения правильного типа boolean - для этого требуется ScopedTypeVariables, который мы включили выше.

Чтобы обернуть вещи, некоторые экземпляры, чтобы попробовать:

instance IsA Int Yes 
instance A Int where 
    fA = (> 0) 
    gA = (+ 1) 

instance IsB String Yes 
instance B String where 
    fB = not . null 
    gB = (++) 

instance IsA Bool Yes 
instance A Bool where 
    fA = id 
    gA = fromEnum 

instance IsB Bool Yes 
instance B Bool where 
    fB = not 
    gB x y = show (x && y) 

Попытка его в GHCi:

> fAB True 
True 
> fAB "" 
False 
> fAB (5 :: Int) 
True 
> fAB() 
No instance for (AB' No No()) 
    . . . 

А вот TypeCast код, любезно Oleg.

class TypeCast a b | a -> b, b->a where typeCast :: a -> b 
class TypeCast' t a b | t a -> b, t b -> a where typeCast' :: t->a->b 
class TypeCast'' t a b | t a -> b, t b -> a where typeCast'' :: t->a->b 
instance TypeCast' () a b => TypeCast a b where typeCast x = typeCast'() x 
instance TypeCast'' t a b => TypeCast' t a b where typeCast' = typeCast'' 
instance TypeCast''() a a where typeCast'' _ x = x 
+0

Это выглядит как реальный рабочий процесс (особенно с задержкой, разрешающей выбор другого перекрывающегося экземпляра). Нет смысла использовать это, когда вам еще нужно объявить 'IsA Int Yes'. Мне нужно приклеить несколько внешних наборов экземпляров, и я не могу использовать 'A a => IsA a Yes', потому что он выведет' A a'. – ony

+0

@ony: То, что вы пытаетесь сделать, действительно не подходит для того, как работают классы классов Haskell. Если вы не хотите использовать обертки типов (что, вероятно, самый «идиоматический» подход) или писать дополнительные объявления экземпляров, осталось немного вариантов. Возможно, попробуйте использовать Template Haskell, чтобы просто создать экземпляр 'AB' для каждого типа, который является экземпляром' A' или 'B'? –

+0

Падение в TH похоже на выпадение из Haskell. Не будет никакой забавы. – ony