2015-10-11 12 views
7

Ist es möglich, eine Haskell-Funktion zu schreiben, die einen parametrisierten Typ liefert, bei dem der genaue Typparameter verborgen ist? I.e. etwas wie f :: T -> (exists a. U a)? Der offensichtliche Versuch:Haskell-Funktion gibt den existentiellen Typ zurück

{-# LANGUAGE ExistentialQuantification #-} 

data D a = D a 

data Wrap = forall a. Wrap (D a) 

unwrap :: Wrap -> D a 
unwrap (Wrap d) = d 

irgendwie zu kompilieren:

Couldn't match type `a1' with `a' 
    `a1' is a rigid type variable bound by 
     a pattern with constructor 
     Wrap :: forall a. D a -> Wrap, 
     in an equation for `unwrap' 
     at test.hs:8:9 
    `a' is a rigid type variable bound by 
     the type signature for unwrap :: Wrap -> D a at test.hs:7:11 
Expected type: D a 
    Actual type: D a1 
In the expression: d 
In an equation for `unwrap': unwrap (Wrap d) = d 

Ich weiß, das ist ein konstruiertes Beispiel, aber ich bin gespannt, ob es einen Weg gibt ist GHC davon zu überzeugen, dass ich interessiere mich nicht für der genaue Typ, mit dem parametrisiert ist, ohne einen weiteren existenziellen Wrapper-Typ für das Ergebnis unwrap einzuführen.

Um zu klären, ich Typsicherheit will, sondern auch eine Funktion dToString :: D a -> String anwenden können, möchte, die nicht über a (z, weil es nur ein String-Feld aus D extrahiert) scheren auf das Ergebnis der unwrap. Ich weiß, dass es andere Wege gibt, dies zu erreichen (z. B. Definieren von wrapToString (Wrap d) = dToString d), aber ich bin mehr daran interessiert, ob es einen fundamentalen Grund gibt, warum solch ein Verstecken unter existenziellem nicht erlaubt ist.

+3

Ich denke, der Grund, warum 'existiert a. D a' ist kein Teil von Haskell ist ein einfacher Wunsch, Redundanz zu vermeiden. Dieser Typ entspricht "Forall r". (forall a. D a -> r) -> r ', also kann man das auch nutzen, um existenzielle Werte darzustellen. –

+0

@ n.m. streng genommen ist es nicht gleichwertig, ich stimme zu, dass das einzig Nützliche, was einem solchen existenziellen Wert angetan werden kann, die Anwendung einer Funktion "Forall a" ist. D a -> r ', so macht die Unfähigkeit, diese Ad-hoc-Existenz auszudrücken, einen Sinn - danke. –

Antwort

11

Ja, Sie können, aber nicht auf eine direkte Art und Weise.

{-# LANGUAGE ExistentialQuantification #-} 
{-# LANGUAGE RankNTypes #-} 

data D a = D a 

data Wrap = forall a. Wrap (D a) 

unwrap :: Wrap -> forall r. (forall a. D a -> r) -> r 
unwrap (Wrap x) k = k x 

test :: D a -> IO() 
test (D a) = putStrLn "Got a D something" 

main = unwrap (Wrap (D 5)) test 

Sie können keine D something_unknown aus Ihrer Funktion zurückkehren, aber man kann es extrahieren und sie sofort an eine andere Funktion übergeben, die D a akzeptiert, wie dargestellt.  

7

Ja, Sie können GHC davon überzeugen, dass Sie sich nicht für den genauen Typ interessieren, mit dem parametriert ist. Nur, es ist eine schreckliche Idee.

{-# LANGUAGE GADTs #-} 

import Unsafe.Coerce 

data D a = D a deriving (Show) 

data Wrap where  -- this GADT is equivalent to your `ExistentialQuantification` version 
    Wrap :: D a -> Wrap 

unwrap :: Wrap -> D a 
unwrap (Wrap (D a)) = D (unsafeCoerce a) 

main = print (unwrap (Wrap $ D "bla") :: D Integer) 

Dies ist, was passiert, wenn ich das einfache Programm ausführen:

enter image description here

und so weiter, bis das System senkt den Speicherverbrauch.

Typen sind wichtig! Wenn Sie das Typsystem umgehen, umgehen Sie die Vorhersagbarkeit Ihres Programms (d. H. Es kann alles passieren, einschließlich thermonuclear war oder das berühmte demons flying out of your nose).


Nun, offenbar dachten Sie, dass Typen irgendwie anders arbeiten. In dynamischen Sprachen wie Python und auch in OO-Sprachen wie Java ist ein Typ in gewisser Weise eine Eigenschaft, die ein Wert haben kann. Die (Referenz-) Werte enthalten also nicht nur die Informationen, die zur Unterscheidung verschiedener Werte eines einzelnen Typs benötigt werden, sondern auch Informationen zur Unterscheidung verschiedener (Teil-) Typen. Das ist in vieler Hinsicht ziemlich ineffizient – es ist ein Hauptgrund, warum Python so langsam ist und Java solch eine riesige VM braucht.

In Haskell existieren die Typen nicht zur Laufzeit. Eine Funktion weiß nie, mit welchem ​​Typ die Werte arbeiten. Nur weil der Compiler alles über die Typen weiß, die es haben wird, hat die Funktion irgendein solches Wissen – nicht, den der Compiler bereits hart-codiert hat! (Das heißt, wenn Sie es mit unsafeCoerce, umgehen, die, wie ich gezeigt, wie unsicher, wie es klingt.)

Wenn Sie nicht möchten, die Art als “ Eigenschaft befestigen ” auf einen Wert, Sie müssen es tun ausdrücklich und darum sind diese existentiellen Wrapper da. Es gibt jedoch normalerweise bessere Möglichkeiten, dies in einer funktionalen Sprache zu tun. Für was ist die Anwendung, für die Sie das haben wollten?


Vielleicht ist es auch hilfreich, sich daran zu erinnern, was eine Signatur mit polymorphem Ergebnis bedeutet. unwrap :: Wrap -> D a bedeutet nicht “ das Ergebnis ist einige D a ... und der Anrufer besser kümmern sich nicht um die a verwendet ”. Das wäre in Java der Fall, aber in Haskell wäre es ziemlich nutzlos, weil man mit einem Wert unbekannten Typs nichts machen kann.

Stattdessen heißt es: für was auch immer Typen a Des Anrufer dieser Funktion der Lage ist, einen geeigneten D a Wert zu liefern. Natürlich ist dies schwer zu liefern – ohne zusätzliche Informationen ist es genauso unmöglich wie etwas mit einem Wert von unbekannten Typ zu tun. Aber wenn es bereits a Werte in den Argumenten oder a ist irgendwie auf eine Typklasse beschränkt (zB fromInteger :: Num a => Integer -> a, dann ist es durchaus möglich, und sehr nützlich.


Um ein String Feld – unabhängig von der zu erhalten a Parameter – Sie können direkt auf dem umwickelten Wert arbeiten:

data D a = D 
    { dLabel :: String 
    , dValue :: a 
    } 

data Wrap where Wrap :: D a -> Wrap 

labelFromWrap :: Wrap -> String 
labelFromWrap (Wrap (D l _)) = l 

Um solche Funktionen auf Wrap zu schreiben mehr Allgemein (mit jedem “ Label-Accessor, der a ” nicht interessiert), verwende Rank2-Polymorphismus, wie in der Antwort von N.M.

+0

Sie müssen das Typsystem nicht umgehen. Natürlich kann alles passieren, wenn Sie 'unsafeCoerce' machen, aber warum? –

+1

@ n.m. weil das OP darum gebeten hat. – leftaroundabout

+1

Ich sehe nicht, wo OP nach 'unsafeCoerce' fragt. –

Verwandte Themen