2016-06-02 41 views
5

Sto imparando a usare i tipi in Haskell.Come si applicano i vincoli di funzione nei metodi di istanza in Haskell?

Considerare la seguente implementazione di una classe di caratteri T con una funzione di classe con tipo vincolato f.

class T t where 
    f :: (Eq u) => t -> u 

data T_Impl = T_Impl_Bool Bool | T_Impl_Int Int | T_Impl_Float Float 
instance T T_Impl where 
    f (T_Impl_Bool x) = x 
    f (T_Impl_Int x) = x 
    f (T_Impl_Float x) = x 

Quando carico questo in GHCI 7.10.2, ottengo il seguente errore:

Couldn't match expected type ‘u’ with actual type ‘Float’ 
     ‘u’ is a rigid type variable bound by 
      the type signature for f :: Eq u => T_Impl -> u 
      at generics.hs:6:5 

Relevant bindings include 
    f :: T_Impl -> u (bound at generics.hs:6:5) 
In the expression: x 
In an equation for ‘f’: f (T_Impl_Float x) = x 

Che cosa sto facendo/comprensione sbagliata? Mi sembra ragionevole che si voglia specializzare una classe di tipografie in un'istanza fornendo un costruttore di dati e un'implementazione della funzione di accompagnamento. La parte

Couldn't match expected type ' u ' with actual type ' Float '

è particolarmente confuso. Perché u non corrisponde a Float se u ha solo il vincolo che deve essere qualificato come un tipo Eq (Float s fare così avanti)?

+3

il vincolo non è un problema tuo - il problema è che stai dicendo: * non importa quale tipo di 'Eq' desideri da me come risultato - Posso fornirlo * – Carsten

+0

Dove dico questo? – FK82

+2

in 'f :: Eq u => t -> u' -' t' è noto dal contesto - il 'u' è lì con un implicito 'allall' – Carsten

risposta

13

La firma

f :: (Eq u) => t -> u 

significa che il chiamante può raccogliere t e u come voleva, con l'unico onere di garantire che u è di classe Eq (e t di classe T - in metodi di classe c'è un vincolo implicito T t).

Ciò non significa che l'implementazione possa scegliere qualsiasi u.

Così, il chiamante può utilizzare f in uno dei seguenti modi: (con t in classe T)

f :: t -> Bool 
f :: t -> Char 
f :: t -> Int 
... 

Il compilatore si lamenta che l'implementazione non è abbastanza generale relativa a tutti questi casi.

Couldn't match expected type ‘u’ with actual type ‘Float’ 

significa "Mi hai dato un Float, ma è necessario fornire un valore del tipo generale u (dove u sarà scelto dal chiamante)"

+4

Questo. 1.000 volte questo. In un tipico linguaggio OO, * il metodo * può scegliere di restituire tutto ciò che desidera, purché corrisponda ai requisiti. In Haskell * il chiamante * deve scegliere, non il metodo. – MathematicalOrchid

+0

Questo vale anche per le normali funzioni che non sono metodi per la tipizzazione; se affermi di poter restituire "un tipo che implementa' Foo' ", devi essere in grado di restituire * qualsiasi tipo * che implementa' Foo'! – MathematicalOrchid

+0

@MathematicalOrchid, chi: È confuso. Qualche idea su come fornire una definizione di istanza per il tipo di dati concreto? – FK82

8

Chi ha già fatto notare il motivo per cui il codice doesn Compilare Ma non è nemmeno il caso che le classificazioni siano il problema; in effetti, il tuo esempio ha una sola istanza, quindi potrebbe anche essere una normale funzione piuttosto che una classe.

Fondamentalmente, il problema è che si sta cercando di fare qualcosa di simile

foobar :: Show x => Either Int Bool -> x 
foobar (Left x) = x 
foobar (Right x) = x 

Questo non funziona. Prova a rendere foobar restituendo un tipo diverso in base al valore che lo alimenta in fase di esecuzione. Ma in Haskell, tutti i tipi devono essere determinati al 100% al in fase di compilazione. Quindi questo non può funzionare.

Ci sono parecchie cose che si può fare do.

Prima di tutto, si può fare questo:

foo :: Either Int Bool -> String 
foo (Left x) = show x 
foo (Right x) = show x 

In altre parole, invece di restituire qualcosa showable, in realtà vederlo. Ciò significa che il tipo di risultato è sempre String. Significa che la versione di show viene chiamata varierà in fase di esecuzione, ma va bene. I percorsi di codice possono variare a run-time, sono i tipi che non possono.

Un'altra cosa che si può fare è questo:

toInt :: Either Int Bool -> Maybe Int 
toInt (Left x) = Just x 
toInt (Right x) = Nothing 

toBool :: Either Int Bool -> Maybe Bool 
toBool (Left x) = Nothing 
toBool (Right x) = Just x 

Anche in questo caso, che funziona perfettamente bene.

Ci sono altre cose che può fare; senza sapere perché lo vuoi, è difficile suggerirne altri.

Come nota a margine, si vuole smettere di pensare a questo come se fosse una programmazione orientata agli oggetti. Non lo è. Richiede un nuovo modo di pensare. In particolare, non cercare un typeclass a meno che non ne abbia davvero bisogno. (Mi rendo conto che questo particolare esempio potrebbe essere solo un esercizio di apprendimento per imparare a proposito di typeclass ...)

+0

Penso di aver capito il problema ora: la dichiarazione del tipo della funzione di classe deve specificare un determinato tipo di dati (questo è il caso più semplice); oppure, se il tipo può essere parametrizzato e quindi vincolato da una classe di tipo, il compilatore deve essere in grado di dedurlo in modo sicuro. Qualcos'altro non passerà la compilazione. – FK82