Frage

In Haskell ist es möglich, eine Funktion mit einer Signatur zu schreiben, die zwei verschiedene akzeptieren kann (obwohl ähnliche) Datentypen und arbeitet unterschiedlich, je nachdem, welche Art übergeben wird?

Ein Beispiel könnte meine Frage klarer machen. Wenn ich eine Funktion mit dem Namen myFunction habe, und zwei Typen namens MyTypeA und MyTypeB, kann ich myFunction definieren, so dass sie nur Daten vom Typ MyTypeA oder MyTypeB als ersten Parameter akzeptieren können?

type MyTypeA = (Int, Int, Char, Char)
type MyTypeB = ([Int], [Char])

myFunction :: MyTypeA_or_MyTypeB -> Char
myFunction constrainedToTypeA = something
myFunction constrainedToTypeB = somethingElse

In einer OOP-Sprache, könnten Sie schreiben, was ich versuche wie so zu erreichen:

public abstract class ConstrainedType {
}

public class MyTypeA extends ConstrainedType {
    ...various members...
}

public class MyTypeB extends ConstrainedType {
    ...various members...
}

...

public Char myFunction(ConstrainedType a) {
    if (a TypeOf MyTypeA) {
        return doStuffA();
    }
    else if (a TypeOf MyTypeB) {
        return doStuffB();
    }
}

Ich habe über algebraische Datentypen lesen, und ich glaube, ich brauche eine Haskell type zu definieren, aber ich bin nicht sicher, wie zu gehen, um es so zu definieren, dass es eine Art speichern kann oder ein anderer, und auch, wie ich es in meinem eigenen Funktionen nutzen.

War es hilfreich?

Lösung

Ja, Sie sind richtig, Sie suchen algebraische Datentypen. Es gibt ein großes Tutorial auf sie unter Erfahren Sie eine Haskell .

Für das Protokoll, das Konzept einer abstrakten Klasse von OOP hat tatsächlich drei verschiedene Übersetzungen in Haskell und ADT sind nur ein. Hier ist ein kurzer Überblick über die Techniken.

Algebraische Datentypen

Algebraische Datentypen kodieren das Muster einer abstrakten Klasse, deren Unterklassen bekannt sind, und wo Funktionen überprüfen, welche bestimmte Instanz das Objekt ein Mitglied der durch Herunterguss.

abstract class IntBox { }

class Empty : IntBox { }

class Full : IntBox {
    int inside;
    Full(int inside) { this.inside = inside; }
}

int Get(IntBox a) {
    if (a is Empty) { return 0; }
    if (a is Full)  { return ((Full)a).inside; }
    error("IntBox not of expected type");
}

schlägt sich in:

data IntBox = Empty | Full Int

get :: IntBox -> Int
get Empty = 0
get (Full x) = x

Datensatz von Funktionen

Dieser Stil läßt nicht nach unten Guss, so dass die Get Funktion würde oben nicht in diesem Stil ausdrückbar sein. So, hier ist etwas ganz anderes.

abstract class Animal { 
    abstract string CatchPhrase();
    virtual void Speak() { print(CatchPhrase()); }
}

class Cat : Animal {
    override string CatchPhrase() { return "Meow"; }
}

class Dog : Animal {
    override string CatchPhrase() { return "Woof"; }
    override void Speak() { print("Rowwrlrw"); }
}

Die Übersetzung in Haskell Karte nicht Typen in Typen. Animal ist die einzige Art und Dog und Cat in ihre Konstruktorfunktionen zerquetscht weg:

data Animal = Animal {
    catchPhrase :: String,
    speak       :: IO ()
}

protoAnimal :: Animal
protoAnimal = Animal {
    speak = putStrLn (catchPhrase protoAnimal)
}

cat :: Animal
cat = protoAnimal { catchPhrase = "Meow" }

dog :: Animal
dog = protoAnimal { catchPhrase = "Woof", speak = putStrLn "Rowwrlrw" }

Es gibt ein paar verschiedene Permutationen von diesem Grundkonzept. Die Invariante ist, dass der abstrakten Typ ist ein Record-Typ, wo die Methoden die Felder des Datensatzes sind.

EDIT:. Es gibt eine gute Diskussion in den Kommentaren auf einige der Feinheiten dieses Ansatzes, einen Fehler in dem obigen Code einschließlich

Typeclasses

Das ist meine wenigsten Codierung von OO Ideen. Es ist angenehm zu OO-Programmierer, weil es vertraute Wörter verwendet und Karten-Typen Typen. Aber die Aufzeichnung von Funktionen nähert oben neigt dazu, die Arbeit leichter zu sein mit, wenn die Dinge kompliziert werden.

Ich werde das Tier Beispiel kodieren wieder:

class Animal a where
    catchPhrase :: a -> String
    speak       :: a -> IO ()

    speak a = putStrLn (catchPhrase a)

data Cat = Cat 
instance Animal Cat where
    catchPhrase Cat = "Meow"

data Dog = Dog
instance Animal Dog where
    catchPhrase Dog = "Woof"
    speak Dog = putStrLn "Rowwrlrw"

Dieses schönes Aussehen, nicht wahr? Die Schwierigkeit kommt, wenn man bedenkt, dass, obwohl es wie OO aussieht, ist es nicht wirklich Arbeit wie OO. Sie können eine Liste der Tiere haben wollen, aber das Beste, was Sie jetzt tun können, ist Animal a => [a], eine Liste von homogenen Tieren, zB. eine Liste von nur Katzen oder nur Hunde. Dann müssen Sie diese Wrapper machen:

{-# LANGUAGE ExistentialQuantification #-}

data AnyAnimal = forall a. Animal a => AnyAnimal a
instance Animal AnyAnimal where
    catchPhrase (AnyAnimal a) = catchPhrase a
    speak (AnyAnimal a) = speak a

Und dann [AnyAnimal] ist, was Sie für Ihre Liste der Tiere wollen. Allerdings stellt sich, dass AnyAnimal Exposes aus genau die gleichen Informationen über sich selbst als Animal Datensatz im zweiten Beispiel haben wir nur darüber auf Umwegen gegangen. So warum ich typeclasses nicht betrachten eine sehr gute Codierung von OO sein.

Und so schließt dieser Woche die Ausgabe von zu viel Information!

Andere Tipps

Es klingt wie Sie auf typeclasses .

Betrachten Sie dieses Beispiel mit TypeClasses .

Wir definieren eine c ++ - wie „abstrakte Klasse“ MVC basiert auf drei Arten (Anmerkung MultiParamTypeClasses): tState tAction tReaction, um definieren eine Schlüsselfunktion tState -> tAction -> (tState, tReaction) (wenn eine Aktion in den Zustand angewendet wird, erhalten Sie einen neuen Staat und eine Reaktion.

Das hat typeclass drei „c ++ abstrakt“ Funktionen und einige mehr auf die „abstrakte“ Einsen definiert. Die „abstrakt“ Funktionen definiert werden, wann und instance MVC benötigt wird.

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


-- -------------------------------------------------------------------------------

class MVC tState tAction tReaction | tState -> tAction tReaction where
      changeState :: tState -> tAction -> tState       -- get a new state given the current state and an action ("abstract")
      whatReaction :: tState -> tReaction              -- get the reaction given a new state ("abstract")
      view :: (tState, tReaction) -> IO ()             -- show a state and reaction pair ("abstract")

      -- get a new state and a reaction given an state and an action (defined using previous functions)
      runModel :: tState -> tAction -> (tState, tReaction) 
      runModel s a = let
                                ns = (changeState s a) 
                                r = (whatReaction ns) 
                  in (ns, r)

      -- get a new state given the current state and an action, calling 'view' in the middle (defined using previous functions)
      run :: tState -> tAction -> IO tState
      run s a = do
                        let (s', r) = runModel s a
                        view (s', r)
                        return s'

      -- get a new state given the current state and a function 'getAction' that provides actions from "the user" (defined using previous functions)
      control :: tState -> IO (Maybe tAction) -> IO tState
      control s getAction = do
              ma <- getAction
              case ma of
                   Nothing -> return s
                   Just a -> do
                              ns <- run s a
                              control ns getAction


-- -------------------------------------------------------------------------------

-- concrete instance for MVC, where
-- tState=Int tAction=Char ('u' 'd') tReaction=Char ('z' 'p' 'n')
-- Define here the "abstract" functions
instance MVC Int Char Char where
         changeState i c 
                     | c == 'u' = i+1 -- up: add 1 to state
                     | c == 'd' = i-1 -- down: add -1 to state
                     | otherwise = i -- no change in state

         whatReaction i
                      | i == 0 = 'z' -- reaction is zero if state is 0
                      | i < 0 = 'n' -- reaction is negative if state < 0                     
                      | otherwise = 'p' -- reaction is positive if state > 0

         view (s, r) = do
                  putStrLn $ "view: state=" ++ (show s) ++ " reaction=" ++ (show r) ++ "\n"

--

-- define here the function "asking the user"
getAChar :: IO (Maybe Char) -- return (Just a char) or Nothing when 'x' (exit) is typed
getAChar = do
         putStrLn "?"
         str <- getLine
         putStrLn ""
         let c = str !! 0
         case c of
              'x' -> return Nothing
              _ -> return (Just c)


-- --------------------------------------------------------------------------------------------
-- --------------------------------------------------------------------------------------------

-- call 'control' giving the initial state and the "input from the user" function 
finalState = control 0 getAChar :: IO Int

-- 

main = do
     s <- finalState
     print s
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top