Pergunta

Em Haskell, é possível escrever uma função com uma assinatura que possa aceitar dois tipos de dados diferentes (embora semelhantes) e operar de maneira diferente, dependendo de que tipo é passado?

Um exemplo pode deixar minha pergunta mais clara. Se eu tiver uma função chamada myFunction, e dois tipos nomeados MyTypeA e MyTypeB, posso definir myFunction para que só possa aceitar dados do tipo MyTypeA ou MyTypeB Como seu primeiro parâmetro?

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

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

Em uma linguagem OOP, você pode escrever o que estou tentando alcançar assim:

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

Eu tenho lido sobre tipos de dados algébricos e acho que preciso definir um haskell modelo, mas não tenho certeza de como defini -lo para que ele possa armazenar um tipo ou outro, e também como eu o uso em minhas próprias funções.

Foi útil?

Solução

Sim, você está correto, está procurando tipos de dados algébricos. Há um ótimo tutorial sobre eles em Aprender você um haskell.

Para o registro, o conceito de classe abstrata da OOP realmente tem três traduções diferentes em Haskell, e o ADTS é apenas um. Aqui está uma rápida visão geral das técnicas.

Tipos de dados algébricos

Os tipos de dados algébricos codificam o padrão de uma classe abstrata cujas subclasses são conhecidas e onde as funções verificam qual instância específica o objeto é um membro da fundição.

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

Traduz em:

data IntBox = Empty | Full Int

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

Registro de funções

Este estilo não permite a decepagem, então o Get A função acima não seria expressa nesse estilo. Então, aqui está 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"); }
}

Sua tradução em Haskell não mapeia tipos em tipos. Animal é o único tipo, e Dog e Cat são esmagados em suas funções de construtor:

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

Existem algumas permutações diferentes desse conceito básico. O invariante é que o tipo abstrato é um tipo de registro em que os métodos são os campos do registro.

EDIT: Há uma boa discussão nos comentários sobre algumas das sutilezas dessa abordagem, incluindo um bug no código acima.

TypeCelasses

Esta é a minha codificação menos favorita de idéias de OO. É confortável para os programadores OO porque usa tipos familiares de palavras e mapas para tipos. Mas a abordagem do registro de funções acima tende a ser mais fácil de trabalhar quando as coisas ficam complicadas.

Vou codificar o exemplo animal novamente:

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"

Isso parece bom, não é? A dificuldade ocorre quando você percebe que, embora pareça oo, ele realmente não funciona como OO. Você pode querer ter uma lista de animais, mas o melhor que você pode fazer agora é Animal a => [a], uma lista de animais homogêneos, por exemplo. uma lista de apenas gatos ou apenas cães. Então você precisa fazer este wrapper tipo:

{-# LANGUAGE ExistentialQuantification #-}

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

E depois [AnyAnimal] é o que você deseja para a sua lista de animais. No entanto, acontece que AnyAnimal expõe exatamente a mesma informação sobre si mesma que o Animal Registre no segundo exemplo, acabamos de fazer isso de uma maneira indireta. Portanto, por que não considero o TypeCelasses uma codificação muito boa do OO.

E assim conclui a edição desta semana de Muita informação!

Outras dicas

Parece que você pode querer ler em TypeCelasses.

Considere este exemplo usando TypeCelasses.

Definimos um C ++-como "classe abstrata" MVC com base em três tipos (nota MultiParamTypeClasses): tState tAction tReaction Para definir uma função chave tState -> tAction -> (tState, tReaction) (Quando uma ação é aplicada ao estado, você obtém um novo estado e uma reação.

O TypeClass possui três funções "C ++ Abstract" e algumas mais definidas nos "abstratos". As funções "abstratas" serão definidas quando e instance MVC é preciso.

{-# 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 em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top