2016-01-07 5 views
19

Ich versuche, ein Gefühl von MultiParamTypeClasses und FunctionalDependencies, und die folgende fiel mir als eine offensichtliche Sache zu bekommen, um zu versuchen:Kann ich die Gleichheit aus einer funktionalen Abhängigkeit herauszaubern?

{-# LANGUAGE MultiParamTypeClasses 
      , FunctionalDependencies 
      , TypeOperators #-} 

import Data.Type.Equality 

class C a b | a -> b 

fob :: (C a b, C a b') => proxy a -> b :~: b' 
fob _ = Refl 

Leider ist dies nicht funktioniert; GHC schließt b ~ b' aus diesem Kontext nicht ab. Gibt es eine Möglichkeit, dies zum Funktionieren zu bringen, oder ist die funktionale Abhängigkeit nicht "intern" verfügbar?

+7

Wenn Sie das fundep als 'Klasse (F a ~ b) => C ab kodieren, wo Typ F a' wie in [GHC docs] beschrieben (https://downloads.haskell.org/~ghc/7.8. 4/docs/html/users_guide/Gleichheitseinschränkungen.html), dann funktioniert es. Aber das fühlt sich an wie Betrug, weil man eine Gleichheitsbeschränkung einführt, während man fragt, ob man etwas herausholen kann ... –

+0

@JonPurdy, tatsächlich, in beiden Hinsichten. – dfeuer

+0

Am ärgerlichsten kann GHC nicht einmal herausfinden, dass das eine das andere impliziert. Der Versuch, von der Version fundep in die Version type family zu wechseln, bietet keine Möglichkeit, die erforderliche Typfamilie zu erstellen. Der Versuch, von der Typ-Familienversion in die Fundep-Version zu wechseln, scheitert an der liberalen Deckungsbedingung (was ich so interpretiere, dass man sieht, dass sie die Funde nicht befriedigt). – dfeuer

Antwort

4

Ich glaube nicht, dass diese Tatsache (wie von der Art der fob angegeben) tatsächlich wahr ist. Aufgrund der Open-World-Eigenschaft von Typklassen können Sie die Fundecke mit Modulgrenzen verletzen.

Dies wird durch das folgende Beispiel gezeigt. Dieser Code wurde nur mit GHC 7.10.3 getestet (fundeps waren in älteren Versionen massiv kaputt - weiß nicht, was dann passiert). Angenommen, Sie in der Tat die folgende implementieren können:

module A 
    (module A 
    ,module Data.Type.Equality 
    ,module Data.Proxy 
)where 

import Data.Type.Equality 
import Data.Proxy 

class C a b | a -> b 

inj_C :: (C a b, C a b') => Proxy a -> b :~: b' 
inj_C = error "oops" 

dann noch ein paar Module:

module C where 
import A 

instance C() Int 

testC :: C() b => Int :~: b 
testC = inj_C (Proxy :: Proxy()) 

und

module B where 
import A 

instance C() Bool 

testB :: C() b => b :~: Bool 
testB = inj_C (Proxy :: Proxy()) 

und

module D where 

import A 
import B 
import C 

oops :: Int :~: Bool 
oops = testB 

oops_again :: Int :~: Bool 
oops_again = testC 

Int :~: Bool ist eindeutig nicht wahr , so vorbei Widerspruch, inj_C kann nicht existieren.

Ich glaube, Sie können inj_C mit unsafeCoerce immer noch sicher schreiben, wenn Sie nicht die Klasse C aus dem Modul exportieren, wo es definiert ist. Ich habe diese Technik benutzt, habe es ausgiebig versucht und konnte keinen Widerspruch schreiben. Nicht zu sagen, es ist unmöglich, aber zumindest sehr schwierig und ein seltener Randfall.

+0

Ich könnte mich irren, aber ich denke, Sie können bereits ähnlich beunruhigende Modultricks mit Typfamilien und/oder GADTs machen. – dfeuer

+0

Ich glaube, das waren echte Bugs - man könnte diese Dinge schreiben, ohne die Existenz von irgendetwas anzunehmen. Auch in diesem Fall weiß der Compiler * noch etwas über die Injektivität und lässt auch in Modul D keine Brüche zu - nur die angenommene Funktion "inj_C" ist gebrochen. Zum Beispiel 'x :: (C() Int, C() Bool) =>(); x =() 'tippt das Eincheckmodul D nicht ein. Vielleicht noch wichtiger ist auch: x :: forall a b (C() b, C() a) =>(); x =() '! Es löst sich sofort für die Typvariablen und leitet daraus ab, dass es einen Widerspruch gibt. – user2407038

+0

Ich weiß nicht, dass der Fehler behoben ist, und ich sehe definitiv nicht, warum die Korrektur Ihr Beispiel auch nicht ausschließen würde. Was macht das anders? – dfeuer

3

Sie müssen nicht auf mehrere Module zurückgreifen, um den funktionalen Abhängigkeitsüberprüfer zu täuschen. Hier sind zwei Beispiele für falsche Funde, die immer noch mit HEAD erstellt werden. Sie sind von der GHC-Testsuite angepasst.

{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, 
      FlexibleInstances, FlexibleContexts, 
      UndecidableInstances, DataKinds, PolyKinds, 
      GADTs #-} 

module M where 


data K x a = K x 

class Het a b | a -> b where 
    het :: m (f c) -> a -> m b 
instance Het t t where het = undefined 

class GHet (a :: * -> *) (b :: * -> *) | a -> b 
instance   GHet (K a) (K [a]) 
instance Het a b => GHet (K a) (K b) 


data HBool = HFalse | HTrue 

class TypeEq x y b | x y -> b 
instance {-# OVERLAPS #-} (HTrue ~ b) => TypeEq x x b 
instance {-# OVERLAPS #-} (HFalse ~ b) => TypeEq x y b 

Der fundep checker ist immer noch viel besser als früher!

+0

Der erste ist schlecht kaputt, klar. Der zweite scheint merkwürdig, da "b" nur vom Instanzkontext bestimmt wird, aber ist es eigentlich * falsch *? – dfeuer

+1

Ah. Ich nehme an, wenn die Instanzen im selben Modul sind, wie hier, ist es nicht wirklich falsch. Sie können die beiden Instanzen jedoch nicht in zwei Instanzen einer offenen Typfamilie konvertieren, die die funktionale Abhängigkeit codiert. –

+0

Ja. Nun, das ist, weil wir Typfamilien geschlossen haben, aber nicht (noch?) Geschlossene Klassen. Ich vermute, dass Überschneidungen eigentlich immer mit geschlossenen Klassen gemacht worden sein sollten (unmittelbar gefolgt von ihren Instanzen in der Prioritätsreihenfolge). Eine Typfamilie, die einer geschlossenen Klasse zugeordnet ist, wäre geschlossen. – dfeuer

Verwandte Themen