Dlaczego nie mogę ustawić ciągu jako instancji typeklasy?

85

Biorąc pod uwagę :

data Foo =
  FooString String
class Fooable a where --(is this a good way to name this?)
  toFoo :: a -> Foo

Chcę utworzyć Stringprzykład Fooable:

instance Fooable String where
  toFoo = FooString

GHC następnie narzeka:

Illegal instance declaration for `Fooable String'
    (All instance types must be of the form (T t1 ... tn)
     where T is not a synonym.
     Use -XTypeSynonymInstances if you want to disable this.)
In the instance declaration for `Fooable String'

Jeśli zamiast tego użyję [Char]:

instance Fooable [Char] where
  toFoo = FooString

GHC narzeka:

Illegal instance declaration for `Fooable [Char]'
   (All instance types must be of the form (T a1 ... an)
    where a1 ... an are type *variables*,
    and each type variable appears at most once in the instance head.
    Use -XFlexibleInstances if you want to disable this.)
In the instance declaration for `Fooable [Char]'

Pytanie 30 :

  • Dlaczego nie mogę utworzyć ciągu znaków i wystąpienia typeklasy?
  • Wydaje się, że GHC chce pozwolić mi ujść na sucho, jeśli dodam dodatkową flagę. Czy to dobry pomysł?
John F. Miller
źródło
6
Takie pytania głosuję za i zaznaczam jako ulubione, bo inaczej wiem, że w niedalekiej przyszłości bym je zadawał;)
Oscar Mederos
3
Odnośnie dodatkowej flagi: to prawdopodobnie dobry pomysł, o ile ufasz GHC i rozumiesz, co robi flaga. Przychodzi mi na myśl Yesod : zachęca do zawsze korzystania z pragmy OverloadedStrings podczas pisania aplikacji Yesod, a QuasiQuotes jest koniecznością dla reguł routingu Yesod. Zauważ, że zamiast flagi w czasie kompilacji możesz również umieścić {-# LANGUAGE FlexibleInstances #-}(lub dowolną inną pragmę) na początku swojego pliku .hs.
Dan Burton

Odpowiedzi:

65

Dzieje się tak, ponieważ Stringjest to tylko alias typu dla [Char], który jest po prostu zastosowaniem konstruktora []typu w typie Char, więc będzie to formularz ([] Char). która nie ma postaci, (T a1 .. an)ponieważ Charnie jest zmienną typu.

Powodem tego ograniczenia jest zapobieganie nakładaniu się wystąpień. Na przykład, powiedzmy, że masz plik instance Fooable [Char], a potem ktoś przyszedł i zdefiniował plik instance Fooable [a]. Teraz kompilator nie będzie w stanie dowiedzieć się, którego chcesz użyć, i wyświetli błąd.

Używając -XFlexibleInstances, zasadniczo obiecujesz kompilatorowi, że nie zdefiniujesz żadnych takich instancji.

W zależności od tego, co próbujesz osiągnąć, lepiej zdefiniować opakowanie:

newtype Wrapper = Wrapper String
instance Fooable Wrapper where
    ...
hammar
źródło
4
Powiedzmy dla argumentacji, że ja też chciałem instance Fooable [a]. Czy istnieje sposób, aby toFoofunkcja działała inaczej, jeśli ajest Char?
John F. Miller
7
@John: Istnieje rozszerzenie, -XOverlappingInstancesktóre na to pozwala i wybiera najbardziej konkretną instancję. Szczegółowe informacje można znaleźć w podręczniku użytkownika GHC .
hammar
18

Napotykasz dwa ograniczenia klasycznych typeklas Haskell98:

  • nie zezwalają na pisanie synonimów w instancjach
  • nie zezwalają na typy zagnieżdżone, które z kolei nie zawierają zmiennych typu.

Te uciążliwe ograniczenia są znoszone przez dwa rozszerzenia językowe:

  • -XTypeSynonymInstances

co pozwala na użycie synoymów typu (takich jak Stringdla [Char]) oraz:

  • -XFlexibleInstances

które znoszą ograniczenia dotyczące typów instancji, które mają postać, w T a b ..której parametry są zmiennymi typu. -XFlexibleInstancesFlaga pozwala szef deklaracji instancji wspomnieć arbitralnych zagnieżdżone typy.

Należy pamiętać, że zniesienie tych ograniczeń może czasami prowadzić do nakładania się wystąpień , w którym to momencie może być potrzebne dodatkowe rozszerzenie języka, aby rozwiązać tę niejednoznaczność, umożliwiając GHC wybranie wystąpienia za Ciebie.


Odniesienia ::

Don Stewart
źródło
4

W większości przypadków elastyczne warunki nie są dobrą odpowiedzią. Lepsze alternatywy to zawijanie String w nowy typ lub wprowadzenie klasy pomocniczej, jak poniżej:

class Element a where
   listToFoo :: [a] -> Foo

instance Element Char where
   listToFoo = FooString

instance Element a => Fooable [a] where
   toFoo = listToFoo

Zobacz także: http://www.haskell.org/haskellwiki/List_instance

Leming
źródło
2

Dodając do tych odpowiedzi, jeśli nie czujesz się komfortowo z zniesieniem ograniczeń, mogą zaistnieć przypadki, w których sensowne może być umieszczenie ciągu znaków w nowym typie, który może być instancją klasy. Kompromisem byłaby potencjalna brzydota, konieczność zawijania i rozpakowywania kodu.

kowey
źródło