2013-08-26 11 views
9

Zajmuję się wyspecjalizowaną biblioteką przetwarzania danych numerycznych i napotkałem błąd, którego nie potrafię rozwiązać. Myślę, że łatwiej będzie najpierw pokazać przykład, a potem wyjaśnić mój problem. Przepraszam również za dziwne nazwiska, które muszę zaciemnić dla celów prawnych.Rozwiązywanie niejednoznacznych wystąpień dla typu wielobajtowego klasy

{-# LANGUAGE MultiParamTypeClasses #-} 
{-# LANGUAGE FlexibleInstances  #-} 

data MyError = MyError String deriving (Eq, Show) 

data MyList = MyList [Double] deriving (Eq, Show) 
data NamedList = NamedList String MyList deriving (Eq, Show) 

class MyNum a b ret where 
    myAdd  :: a -> b -> Either MyError ret 
    myLessThan :: a -> b -> Either MyError Bool 

instance MyNum MyList Double MyList where 
    myAdd (MyList xs) x = Right $ MyList $ map (+x) xs 
    myLessThan (MyList xs) x = Right $ all (< x) xs 

instance MyNum NamedList Double NamedList where 
    myAdd (NamedList n l) x = fmap (NamedList n) $ myAdd l x 
    myLessThan (NamedList n l) x = myLessThan l x 

Gdy próbuję skompilować ten, pojawia się błąd

No instance for (MyNum MyList Double ret0) 
    arising from a use of `myLessThan' 
The type variable `ret0' is ambiguous 
Possible fix: add a type signature that fixes these type variable(s) 
Note: there is a potential instance available: 
    instance MyNum MyList Double MyList 
    -- Defined at testing_instances.hs:13:10 
Possible fix: 
    add an instance declaration for (MyNum MyList Double ret0) 
In the expression: myLessThan l x 
In an equation for `myLessThan': 
    myLessThan (NamedList n l) x = myLessThan l x 
In the instance declaration for `MyNum NamedList Double NamedList' 

Ponieważ kompilator nie może dowiedzieć się, jakie konkretne wystąpienie MyNum użyć do MyList. Działa dla myAdd, ponieważ typ zwrotny dla MyNum jest łatwo wyprowadzony, ale nie może go znaleźć dla myLessThan. Chcę użyć tej czcionki, aby móc łatwo dodać drobnoziarnistą obsługę błędów przez cały czas, a ponieważ mój aktualny kod ma odpowiednik +, -, *, /, <, < =,> i> = i chcę aby utworzyć instancję dla MyNum Double MyList MyList, MyNum MyList MyList MyList i podobne dla instancji NamedList. O ile nie ma prostszego sposobu, aby to zrobić, to mogę mieć polimorficzne przemienne operatory.

Jednak nie mogę określić, jaki typ podpisu dodać do myLessThan dla drugiej instancji, aby mógł wiedzieć, której instancji użyć. Wiem, że jednym z rozwiązań byłoby podzielenie operatorów arytmetycznych i porównawczych na dwie oddzielne klasy typów, ale chciałbym tego uniknąć, jeśli to w ogóle możliwe.

Odpowiedz

11

Można użyć functional dependencies aby określić, że „ret jest jednoznacznie określona przez a i b”.

... 
{-# LANGUAGE FunctionalDependencies #-} 
... 
class MyNum a b ret | a b -> ret where 
... 

Pozwala to typechecker wiedzieć, że może wybrać właściwą definicję instancji, znając tylko a i b z argumentów w twojej:

myLessThan (NamedList n l) x = myLessThan l x 

Kompilator będzie teraz skarżą jeśli zdefiniować dodatkowy przykład z tego samego a i b ale innym ret, jak

instance MyNum MyList Double SomeOtherType where 
+0

Jest to dla mnie wcześniej nieznany konstrukt w Haskell i dokładnie to, czego potrzebowałem, dzięki! – bheklilr

+2

@bheklilr cieszę się, że mogę pomóc. Możesz również dowiedzieć się o TypeFamilies, które są łatwiejsze w obsłudze i na większą skalę. – jberryman

4

Jak zauważył jberryman, możesz użyć TypeFamilies. I tak:

-{-# LANGUAGE FlexibleInstances  #-} 
+{-# LANGUAGE TypeFamilies #-} 

-class MyNum a b ret where 
- myAdd  :: a -> b -> Either MyError ret 
+class MyNum a b where 
+ type Ret a b 
+ myAdd  :: a -> b -> Either MyError (Ret a b) 

-instance MyNum MyList Double MyList where 
+instance MyNum MyList Double where 
+ type Ret MyList Double = MyList 

-instance MyNum NamedList Double NamedList where 
+instance MyNum NamedList Double where 
+ type Ret NamedList Double = NamedList 

prostu przeniósł ret typ z parametrem klasy do associated typeRet.
Jest to TypeFamily -stwierdzenie, że istnieje funkcja z parametrów klasy a i b do Ret.

+0

Podoba mi się, że usuwa trzeci parametr typu explicit z klasy 'MyNum'. Czy ktokolwiek ma jakieś informacje na temat tego, który z nich działa lepiej? Nie mam nic przeciwko nieco bardziej zagraconym kodom, jeśli oznacza to, że działa on szybciej, lub jeśli rodziny typów są bardziej wydajne, wolę używać tego rozwiązania. – bheklilr

+1

O ile mi wiadomo, oba rozwiązania będą miały taką samą wydajność.Możesz sprawdzić skompilowany rdzeń, aby zobaczyć, czy jest jakaś różnica - ale w tym przypadku nie spodziewałbym się, że taki będzie. – ocharles

+0

@ Charles Wypróbuję profilowanie, gdy będę miał okazję, a postaram się opublikować aktualizację później z moimi wynikami – bheklilr

Powiązane problemy