resueltos poo interfaces ejercicios ejemplo clases clase abstractas abstracta haskell functional-programming polymorphism typeclass algebraic-data-types

poo - ¿Hay un equivalente de Haskell de las clases abstractas de OOP, usando tipos de datos algebraicos o polimorfismo?



ejercicios resueltos de clases abstractas en java (3)

En Haskell, ¿es posible escribir una función con una firma que pueda aceptar dos tipos de datos diferentes (aunque similares), y operar de manera diferente dependiendo de qué tipo se pase?

Un ejemplo podría aclarar mi pregunta. Si tengo una función llamada myFunction , y dos tipos llamados MyTypeA y MyTypeB , ¿puedo definir myFunction para que solo pueda aceptar datos de tipo MyTypeA o MyTypeB como su 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 OOP, podrías escribir lo que intento lograr así:

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 sobre tipos de datos algebraicos y creo que necesito definir un tipo de Haskell, pero no estoy seguro de cómo definirlo para que pueda almacenar un tipo u otro, y también cómo lo uso en mi funciones propias


Considere este ejemplo usando TypeClasses .

Definimos un MVC "clase abstracta" similar a c ++ basado en tres tipos (note MultiParamTypeClasses ): tState tAction tReaction para definir una función clave tState -> tAction -> (tState, tReaction) (cuando se aplica una acción al estado , obtienes un nuevo estado y una reacción.

La clase de tipo tiene tres funciones "abstractas c ++" y algunas más definidas en las "abstractas". Las funciones "abstractas" se definirán cuando se necesite la instance MVC .

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



Sí, tiene razón, está buscando tipos de datos algebraicos. Hay un gran tutorial sobre ellos en Learn You a Haskell .

Para el registro, el concepto de una clase abstracta de OOP en realidad tiene tres traducciones diferentes en Haskell, y los ADT son solo uno. Aquí hay una descripción general rápida de las técnicas.

Tipos de datos algebraicos

Los tipos de datos algebraicos codifican el patrón de una clase abstracta cuyas subclases son conocidas, y donde las funciones comprueban a qué instancia particular pertenece el objeto al realizar un down-casting.

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 como:

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

Registro de funciones

Este estilo no permite el down-casting, por lo que la función Get anterior no se puede expresar en este estilo. Entonces aquí hay 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 mapea tipos en tipos. Animal es el único tipo, y Dog y Cat son aplastados en sus funciones de constructor:

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. Lo invariante es que el tipo abstracto es un tipo de registro donde los métodos son los campos del registro.

EDITAR: Hay una buena discusión en los comentarios sobre algunas de las sutilezas de este enfoque, incluida una falla en el código anterior.

Tipos de clases

Esta es mi codificación favorita menos de ideas OO. Es cómodo para los programadores OO porque usa tipos de palabras y mapas conocidos para los tipos. Pero el enfoque del registro de funciones arriba es más fácil de usar cuando las cosas se complican.

Volveré a codificar el ejemplo Animal:

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? La dificultad surge cuando te das cuenta de que, aunque parezca OO, en realidad no funciona como OO. Es posible que desee tener una lista de Animales, pero lo mejor que puede hacer ahora es Animal a => [a] , una lista de animales homogéneos, por ej. una lista de solo gatos o solo perros. Entonces necesitas hacer este tipo de envoltura:

{-# 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 luego [AnyAnimal] es lo que quieres para tu lista de animales. Sin embargo, resulta que AnyAnimal expone exactamente la misma información sobre sí mismo que el registro de Animal en el segundo ejemplo, lo hemos seguido de una manera indirecta. Entonces, ¿por qué no considero que las clases de tipos son una muy buena codificación de OO?

¡Y así concluye la edición de esta semana de Way Too Much Information!