Gibt es eine Haskell-Äquivalent von abstrakten Klassen der OOP, mit algebraischen Datentypen oder Polymorphismus?
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.
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