Y at-il un équivalent Haskell des classes abstraites de POO, en utilisant des types de données algébriques ou polymorphisme?

StackOverflow https://stackoverflow.com/questions/4029455

Question

Dans Haskell, est-il possible d'écrire une fonction avec une signature qui peut accepter deux différents (bien que similaires) types de données, et fonctionnent différemment selon le type est passé dans?

exemple pourrait prendre une ma question plus claire. Si j'ai une fonction nommée myFunction, et deux types nommés MyTypeA et MyTypeB, puis-je définir myFunction de sorte qu'il ne peut accepter que des données de type MyTypeA ou MyTypeB comme premier paramètre?

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

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

Dans un langage OOP, vous pouvez écrire ce que je suis en train de réaliser comme ceci:

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();
    }
}

J'ai lu sur les types de données algébriques et je pense que je dois définir une Haskell type , mais je ne suis pas sûr de savoir comment s'y prendre pour définir afin qu'il puisse stocker un type ou une autre, et aussi comment je l'utilise dans mes fonctions.

Était-ce utile?

La solution

Oui, vous avez raison, vous êtes à la recherche des types de données algébriques. Il y a un grand tutoriel sur les Vous apprenez Haskell .

Pour mémoire, le concept d'une classe abstraite de POO a en fait trois traductions différentes en Haskell et ADTs sont juste un. Voici un aperçu rapide des techniques.

Types de données Algebraic

types de données algébriques coder le modèle d'une classe abstraite dont les sous-classes sont connues, et où les fonctions de vérifier quelle instance particulière l'objet est un élément de terre-par coulée.

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");
}

se traduit:

data IntBox = Empty | Full Int

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

Enregistrement des fonctions

Ce style ne permet pas vers le bas de coulée, de sorte que la fonction Get ci-dessus ne serait pas exprimable dans ce style. Voici donc quelque chose de complètement différent.

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"); }
}

La traduction en Haskell ne mappe pas les types en types. Animal est le seul type et Dog et Cat sont écrasées loin dans leurs fonctions constructeur:

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" }

Il y a quelques permutations différentes de ce concept de base. L'invariant est que le type abstrait est un type d'enregistrement où les méthodes sont les champs de l'enregistrement.

EDIT:. Il y a une bonne discussion dans les commentaires sur quelques-unes des subtilités de cette approche, y compris un bogue dans le code ci-dessus

classes de types

Ceci est mon préféré moins le codage des idées OO. Il est à l'aise pour les programmeurs OO, car il utilise des mots familiers et des cartes types de types. Mais le bilan des fonctions approche ci-dessus tend à être plus facile de travailler quand les choses se compliquent.

Je vais encode à nouveau l'exemple des animaux:

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"

Cette apparence agréable, non? La difficulté vient quand on se rend compte que même si elle ressemble à OO, il n'a vraiment pas le travail comme OO. Vous voudrez peut-être d'avoir une liste des animaux, mais le mieux que vous pouvez faire est en ce moment Animal a => [a], une liste d'animaux homogènes, par exemple. une liste de seulement chats ou chiens seulement. Ensuite, vous devez faire ce type d'emballage:

{-# LANGUAGE ExistentialQuantification #-}

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

Et [AnyAnimal] alors est ce que vous voulez pour votre liste d'animaux. Cependant, il se avère que AnyAnimal expose de exactement les mêmes informations sur lui-même que le dossier de Animal dans le second exemple, nous venons de faire à ce sujet d'une manière détournée. Ainsi pourquoi je ne considère pas comme une classes de types très bon encodage OO.

Et conclut ainsi l'édition de cette semaine de Way Too Much Information!

Autres conseils

On dirait que vous pouvez lire sur rel="nofollow"> classes de types.

Considérez cet exemple en utilisant classes de types .

Nous définissons un c ++ - comme "classe abstraite" MVC basée sur trois types (note MultiParamTypeClasses): tState tAction tReaction pour définir une fonction clé tState -> tAction -> (tState, tReaction) (lorsqu'une action est appliquée à l'état, vous obtenez un nouvel état et une réaction.

La classe de types a trois fonctions « c ++ abstrait », et un peu plus défini sur les « abstraits ». Les fonctions « abstraites » seront définis quand et instance MVC est nécessaire.

{-# 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
scroll top