¿Hay un equivalente de Haskell clases abstractas de programación orientada a objetos, utilizando tipos de datos algebraicos o polimorfismo?

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

Pregunta

En Haskell, es posible escribir una función con una firma que puede aceptar dos tipos de datos diferentes (aunque similar), y funcionan de manera diferente dependiendo de qué tipo se pasa en?

Un ejemplo podría hacer mi pregunta clara. Si tengo una función llamada myFunction, y dos tipos con nombre MyTypeA y MyTypeB, puedo definir myFunction de modo que sólo puede aceptar datos de MyTypeA tipo o MyTypeB como primer parámetro?

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

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

En un lenguaje de programación orientada a objetos, se podría escribir lo que estoy tratando de lograr de esta manera:

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

He estado leyendo acerca de los tipos de datos algebraicos y yo creo que tenga que definir una Haskell type , pero no estoy seguro de cómo hacer para definirlo para que pueda almacenar un tipo o otra, y también cómo lo uso en mis propias funciones.

¿Fue útil?

Solución

Sí, estás en lo correcto, que busca los tipos de datos algebraicos. Hay un gran tutorial sobre ellos en Aprender Eres una Haskell .

Para el registro, el concepto de una clase abstracta de la programación orientada a objetos en realidad tiene tres diferentes traducciones en Haskell, y los ADT son sólo uno. Aquí está una descripción rápida de las técnicas.

Tipos algebraica de datos

tipos de datos algebraicos codificar el patrón de una clase abstracta cuya subclases son conocidos, y donde las funciones de comprobar qué instancia particular el objeto es un miembro de por abajo de la fundición.

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 traduce en:

data IntBox = Empty | Full Int

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

Registro de funciones

Este estilo no permite bajar a presión, por lo que la función de Get anterior no sería expresable en este estilo. Así que aquí es algo completamente diferente.

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

Su traducción en Haskell no asignar tipos de un tipo determinado. Animal es el único tipo, y Dog y Cat se aplastan de distancia en sus funciones constructoras:

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

Hay algunas permutaciones diferentes de este concepto básico. El invariante es que el tipo abstracto es un tipo de registro, donde los métodos son los campos del registro.

EDIT:. Hay una buena discusión en los comentarios sobre algunas de las sutilezas de este enfoque, incluyendo un error en el código anterior

clases de tipos

Esta es mi menos favorito de codificación de ideas OO. Es cómodo para los programadores orientados a objetos, ya que utiliza palabras familiares y los mapas de tipos de tipos. Pero el registro de las funciones de enfoque anterior tiende a ser más fácil de trabajar cuando las cosas se complican.

Voy a codificar de nuevo el ejemplo de los animales:

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"

Esto se ve bien, no es cierto? La dificultad viene cuando se da cuenta de que a pesar de que parece OO, que en realidad no funciona como OO. Es posible que desee tener una lista de los animales, pero lo mejor que puede hacer en este momento es Animal a => [a], una lista de animales homogéneos, por ejemplo. una lista de sólo los gatos o perros solamente. Luego hay que hacer este tipo de envoltorio:

{-# LANGUAGE ExistentialQuantification #-}

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

Y [AnyAnimal] a continuación es lo que quiere para su lista de animales. Sin embargo, resulta que expone AnyAnimal exactamente la misma información sobre sí mismo como el registro Animal en el segundo ejemplo, se acaba de ir sobre él de una manera indirecta. Por lo tanto ¿por qué no considero clases de tipos a ser una muy buena codificación de OO.

Y así concluye la edición de esta semana de demasiada información!

Otros consejos

Parece que es posible que desee leer sobre clases de tipos .

Considere este ejemplo utilizando clases de tipos .

Se define un c ++ - como MVC "clase abstracta" basado en tres tipos (nota): MultiParamTypeClasses tState tAction tReaction con el fin de definir una función clave tState -> tAction -> (tState, tReaction) (cuando se aplica una acción para el estado, se obtiene un nuevo estado y una reacción.

La clase de tipos tiene tres funciones "C ++ abstracto", y un poco más definido en los "abstractos". Las funciones "abstractos" se definirán cuándo y instance MVC que se necesita.

{-# 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
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top