Un caso in cui (1) fallisce ma (2) non è banale; perché il tipo sinonimi (type ExampleOfATypeSynonym = ...
) non sono ammessi nelle dichiarazioni di istanza, ma essi sono autorizzati a vincoli, qualsiasi situazione in cui si ha solo una esempio così:
-- (1)
class Foo a
type Bla =()
instance Foo Bla
... può essere trasformato in:
-- (2)
class Foo a
type Bla =()
instance (a ~ Bla) => Foo a
L'unico motivo per cui (1) non riesce è perché sinonimi di tipo non sono ammessi nelle dichiarazioni di istanza, e questo perché di tipo sinonimi sono come funzioni di tipo: essi forniscono una mappatura di sola andata da un nome di tipo per un nome di tipo, quindi se hai un type B = A
e un instance Foo B
, non è ovvio che venga creata un'istanza di Foo A
. La regola esiste in modo che sia necessario scrivere instance Foo A
per rendere chiaro che che è il tipo che riceve effettivamente l'istanza.
L'uso di famiglie di tipi è irrilevante in questo contesto, poiché il problema è piuttosto che si sta utilizzando un sinonimo di tipo, il tipo NameRecord
. Devi anche tenere a mente che se il sinonimo di tipo viene rimosso e sostituito da FieldOf Name
direttamente, la compilazione fallirà ancora; questo perché una "famiglia tipo" è solo una versione avanzata di sinonimi tipo, quindi FieldOf Name
è anche un "sinonimo di tipo" per Name :> Text
in questo contesto. Devi invece utilizzare una famiglia di dati e un'istanza di dati per ottenere un'associazione "bidirezionale".
Ulteriori informazioni sulle famiglie di dati sono disponibili nello GHC documentation.
Penso tu voglia dire "... dove (2) fallisce ma (1) non ..."
Immaginiamo che abbiamo una classe di tipo in questo modo:
class Foo a where
foo :: a
Ora, è possibile scrivere le istanze in questo modo:.
instance Foo Int where
foo = 0
instance Foo Float where
foo = 0
main :: IO()
main = print (foo :: Float)
Questo funziona come ci si aspetterebbe Tuttavia, se si trasformare il codice in questo:
{-# LANGUAGE FlexibleInstances, TypeFamilies #-}
class Foo a where
foo :: a
instance (a ~ Int) => Foo a where
foo = 0
instance (a ~ Float) => Foo a where
foo = 0
main :: IO()
main = print (foo :: Float)
non viene compilato, viene visualizzato l'errore:
test.hs:5:10:
Duplicate instance declarations:
instance a ~ Int => Foo a -- Defined at test.hs:5:10-27
instance a ~ Float => Foo a -- Defined at test.hs:8:10-29
Quindi, questo è l'esempio che si sperava stessimo cercando. Ora, questo succede solo se c'è più di un'istanza di Foo
che usa questo trucco. Perché?
Quando GHC risolve le classi di tipi, esamina solo la testata di istanza; cioè ignora tutto prima dello =>
. Quando ha scelto un'istanza, "si impegna" su di essa e controlla i vincoli prima dello =>
per vedere se sono veri. Così, in un primo momento vede due istanze:
instance Foo a where ...
instance Foo a where ...
E 'chiaramente impossibile decidere quale istanza da utilizzare in base a queste informazioni da solo.
Per motivi di interesse, sapete se il metodo GHC di risoluzione delle classi di tipi è una decisione di progettazione esplicita? E se è così, la ragione per questo? (O forse l'alternativa è troppo complicata per essere implementata in modo pulito?) – huon
La ragione è che il report Haskell richiede che si comporti in questo modo. Il motivo per ** that ** è che se non si comportava in questo modo, sarebbe necessario un algoritmo che fornisse un'euristica per "quanto bene un tipo si adatta a un vincolo"; dovresti discutere "Sì, questo tipo si adatta a questa istanza di classe, ma quell'altra istanza si adatta molto meglio perché {meno vincoli, minore distanza di riduzione, ...}." Potrebbe essere possibile sviluppare una tale euristica, ma si romperebbe l'Assunzione mondiale aperto che è un concetto chiave quando si tratta di classi di tipi. – dflemstr
Immaginate 'istanza String ~ a => Foo a' e' istanza a ~ [b] => Foo a'. Questo è un esempio di casi in cui è necessario un algoritmo per risolvere 'Foo [Char]'. – dflemstr