2014-11-12 6 views
11

Capisco che quando avendoPerché il contesto non viene preso in considerazione quando si seleziona l'istanza di classe tipo in Haskell?

instance (Foo a) => Bar a 
instance (Xyy a) => Bar a 

GHC non prende in considerazione i contesti e le istanze sono riportati come duplicato.

Ciò che è controintuitivo, che (credo) dopo aver selezionato un'istanza, deve ancora verificare se il contesto corrisponde, e in caso contrario, scartare l'istanza. Quindi, perché non invertire l'ordine e scartare le istanze con contesti non corrispondenti e procedere con il set rimanente.

Sarebbe intrattabile in qualche modo? Vedo come potrebbe causare una maggiore risoluzione dei vincoli in anticipo, ma proprio come c'è UndecidableInstances/IncoherentInstances, non potrebbe esserci un ConsiderInstanceContexts quando "So cosa sto facendo"?

+1

Quale istanza deve GHC scegliere se 'a' è un' foo' e un 'Xyy'? – mb14

+1

@ mb14: un arbitrario. ('IncoherentInstances' già fa qualcosa di simile, e posso conviverci). – ron

+0

In effetti, l'unica differenza sembra essere che "IncoherentInstances" consente a GHC di eseguire il commit su entrambe le istanze, eventualmente scartando una con un contesto soddisfacente e impegnandosi in quella non soddisfacente (che attiverà un errore). Questa domanda invece chiede perché GHC non abbia il flag 'BacktrackOnContextFailures', se ho capito bene, in modo che alla fine venga tentata l'istanza corretta. Sicuramente porterà all'intrattabilità nei casi peggiori, ma abbiamo già "UndecidableInstances" che può avere un impatto significativo sulle prestazioni. – chi

risposta

3

Questo non risponde alla domanda sul perché questo è il caso. Si noti, tuttavia, che è sempre possibile definire un involucro newtype per disambiguare tra le due istanze:

newtype FooWrapper a = FooWrapper a 
newtype XyyWrapper a = XyyWrapper a 

instance (Foo a) => Bar (FooWrapper a) 
instance (Xyy a) => Bar (XyyWrapper a) 

Questo ha il vantaggio che facendo passare attorno o un FooWrapper o un XyyWrapper è possibile controllare in modo esplicito quale delle due istanze si 'Mi piacerebbe usare se il tuo a soddisfa entrambi.

+0

Sicuro. Il mio caso d'uso sarebbe più come "selezionare una migliore istanza", senza che il cliente debba avvolgere/sapere nulla. Ad esempio disponendo di una classe 'LinearSearch',' BinarySearch' e 'Search', dove' Search' fa il default alla ricerca binaria se l'istanza esiste, altrimenti la ricerca lineare (se esiste). (Beh, la scelta arbitraria non sarebbe abbastanza buona qui, più come una scelta ordinata, ma mettiamola da parte). – ron

+0

Per divertimento, non consideriamo l'alternativa di creare istanze esplicite di 'Cerca' per ogni tipo di dati. – ron

+4

Vedo che questo è un esperimento mentale e una domanda interessante (a cui non posso rispondere). Tuttavia, FWIW, lo considererei un cattivo progetto per il compilatore per fare scelte arbitrarie o addirittura non deterministiche che alterano completamente il comportamento del codice. – DanielM

2

Le lezioni sono un po 'strane. L'idea originale (che funziona ancora abbastanza) è una sorta di zucchero sintattico attorno a ciò che altrimenti sarebbero le dichiarazioni data. Ad esempio, puoi immaginare:

data Num a = Num {plus :: a -> a -> a, ... , fromInt :: Integer -> a} 
numInteger :: Num Integer 
numInteger = Num (+) ... id 

quindi puoi scrivere funzioni che hanno ad es. tipo:

test :: Num x -> x -> x -> x -> x 
test lib a b c = a + b * (abs (c + b)) 
    where (+) = plus lib 
      (*) = times lib 
      abs = absoluteValue lib 

Quindi l'idea è "stiamo per derivare automaticamente tutto questo codice di libreria." La domanda è: come troviamo la biblioteca che vogliamo? E 'facile se abbiamo una biblioteca di tipo Num Int, ma come facciamo a estenderlo a "istanze vincolati" sulla base di funzioni di tipo:

fooLib :: Foo x -> Bar x 
xyyLib :: Xyy x -> Bar x 

La presente soluzione in Haskell è quello di fare un tipo reticolo corrispondere ai tipi di output di tali funzioni e propagare gli input alla dichiarazione risultante. Ma quando ci sono due uscite dello stesso tipo, avremmo bisogno di un combinatore che si fonde questi in:

eitherLib :: Either (Foo x) (Xyy x) -> Bar x 

e fondamentalmente il problema è che non c'è un buon vincolo-combinatore di questo tipo al momento. Questa è la tua obiezione.

Bene, questo è vero, ma ci sono modi per ottenere qualcosa di moralmente simile nella pratica. Supponiamo di definire alcune funzioni con i tipi:

data F 
data X 
foobar'lib :: Foo x -> Bar' x F 
xyybar'lib :: Xyy x -> Bar' x X 
bar'barlib :: Bar' x y -> Bar x 

Chiaramente il y è una sorta di "tipo fantasma" threaded attraverso tutto questo, ma rimane potente perché, dato che vogliamo un Bar x saremo propagare la necessità di un Bar' x y e data la necessità per il Bar' x y genereremo uno Bar' x X o un Bar' x y. Quindi con i tipi di fantasmi e le classi di tipi a più parametri, otteniamo il risultato che vogliamo.

Maggiori informazioni: https://www.haskell.org/haskellwiki/GHC/AdvancedOverlap

Problemi correlati