2014-10-20 11 views
6

Angenommen, Sie haben eine schöne induktive Definition, die Sie in Haskell als Datentyp definieren möchten. Ihre induktive Definition ist jedoch (so viele induktive Definitionen) von einer solchen Form, dass die Erzeugungsregeln ihre "Prämissen" benötigen, um eine bestimmte Struktur zu haben. Zum Beispiel: Angenommen wir die folgende Definition haben:Einschränken von Datentypen

  • wenn x eine gerade ganze Zahl, dann ist T x eine Waffe,
  • wenn x eine ungerade ganze Zahl ist, dann S x eine Waffe ist.

Wenn ich das definieren will (als Einzel) Datentyp in Haskell, würde ich so etwas wie

data Weapon = T Int | S Int 

Offensichtlich schreiben, das wird nicht funktionieren, wie Sie jetzt T 5 und S 4 erzeugen können, für Beispiel. Gibt es eine natürliche Möglichkeit, Einschränkungen für die Konstruktorargumente weiterzuleiten, so dass ich etwas Ähnliches wie den obigen Code schreiben könnte, der die richtige Definition geben würde?

+2

Smart Konstruktoren, meist.Verbieten Sie direkt mit 'T' und' S' und erstellen Sie 'newT' und' newS' Funktionen, die die übergebenen Zahlen überprüfen. –

Antwort

9

Ihr bester Schuss ist nicht T und S explizit zu exportieren, aber einem benutzerdefinierten Konstruktor erlauben:

module YourModule (Weapon, smartConstruct) where 

data Weapon = T Int | S Int 

smartConstruct :: Int -> Weapon 
smartConstruct x 
    | even x  = T x 
    | otherwise = S x 

Nun, wenn YourModule Import Benutzer nicht in der Lage zu erstellen T und S explizit, sondern nur mit Ihre smartConstruct Funktion.

10

Dies ist ein bisschen un-Haskelly, aber ist idiomatischer in z.B. Agda: Ändere die Interpretation deiner Darstellung, so dass sie durch die Konstruktion korrigiert werden muss.

In diesem Fall beachten Sie, dass wenn n :: Int, dann even (2 * n) und odd (2 * n + 1). Wenn wir den Fall von zu großen Int s Handwave wegwinken, können wir sagen, dass es eine Bijektion zwischen den geraden Int s und Int s gibt; und ein weiterer zwischen den ungeraden Int s und der Int s.

diese So verwenden, können Sie diese Darstellung wählen:

data Weapon = T Int | S Int 

und seine Auslegung so ändern, dass der Wert T n repräsentiert tatsächlich T (2 * n) und S n der Wert repräsentiert S (2 * n + 1). Egal, was n :: Int Sie wählen, T n wird gültig sein, da Sie es als den "T -of-2 * n" Wert betrachten werden.

+0

Schön. Weißt du zufällig, wo ich weitere Beispiele für diesen Trick finden könnte? – danidiaz

+2

Und Sie können die Handwelle mit 'Integer' entfernen. – dfeuer

+2

@ danidiaz: Es gibt mehrere Instanzen dieses Musters in der Agda-Standardbibliothek, z. check out http://agda.github.io/agda-stdlib/html/Data.Integer.html#915 – Cactus

5

Wenn Sie bereit sind, sich auf die Verwendung von Nats zu beschränken, und mit der Verwendung einigermaßen fortgeschrittener Typenmagie einverstanden sind, können Sie GHC.TypeLits verwenden.

{-# LANGUAGE DataKinds, GADTs, TypeOperators, KindSignatures #-} 

import GHC.TypeLits 

data Weapon (n :: Nat) where 
    Banana  :: n ~ (2 * m) => Weapon n 
    PointyStick :: n ~ (2 * m + 1) => Weapon n 

banana :: Weapon 2 
banana = Banana 

pointyStick :: Weapon 3 
pointyStick = PointyStick 

versuchen Sie für sich selbst, dass es nicht mit falschen (ungeraden/geraden) Zahlen kompilieren wird.

Edit: Praktischer ist wahrscheinlich Kaktus 'Ansatz obwohl.

+0

Dies scheint eine Art Dual zur Anfrage. Interessant, obwohl. – dfeuer

+0

Ich schätze auch die Monty Python-Referenz. – dfeuer

+0

warum denkst du es ist dual? Sie müssen einige Längen gehen, um diese naty Waffen zur Laufzeit zu bauen, aber es ist nicht schwer (und eine andere Art von Frage). es garantiert aber, dass nur Bananen und ein paar spitz zulaufende Stäbchen gebaut werden. – ibotty