Есть ли Haskell эквивалент абстрактных классов ООП, используя алгебраические типы данных или полиморфизма?

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

Вопрос

В Haskell можно ли написать функцию с подписью, которая может принять два разных (хотя и аналогичных) типах данных, и работают по-разному в зависимости от того, в каком типе передается?

Примером может сделать мой вопрос яснее. Если у меня есть функция по имени myFunction, и два типа по имени MyTypeA и MyTypeB, я могу определить myFunction так что он может принимать только данные типа MyTypeA или MyTypeB Как его первый параметр?

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

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

На языке ООП вы могли бы написать то, что я пытаюсь достичь так:

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

Я читал об алгебраических типах данных, и я думаю, что мне нужно определить Haskell тип, но я не уверен, как определить его, чтобы он мог хранить один тип или другой, а также, как я использую его в своих собственных функциях.

Это было полезно?

Решение

Да, вы правы, вы ищете алгебраические типы данных. На них есть отличное учебное пособие Узнайте тебя Haskell.

Для записи концепция абстрактного класса от OOP на самом деле имеет три разных перевода в Haskell, а рекламы только один. Вот быстрый обзор методик.

Алгебраические типы данных

Алгебраические типы данных кодируют шаблон абстрактного класса, подклассы которого известны, а если функции проверяют, какой конкретный экземпляр объект является членом непосредственно литья.

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

Переводится в:

data IntBox = Empty | Full Int

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

Запись функций

Этот стиль не разрешает кастинг, поэтому Get Функция выше не будет выражена в этом стиле. Так вот что-то совершенно другое.

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

Его перевод в haskell не отображает типов на типы. Animal это единственный тип, а Dog и Cat раздаются в свои функции конструктора:

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

Есть несколько различных перестановок этой базовой концепции. Инвариант в том, что абстрактный тип является типом записи, где методы являются полями записи.

Редактировать: в комментариях к некоторым тонкостям есть хорошая дискуссия в комментариях, включая ошибку в вышеуказанном коде.

Типэкласс

Это моя наименее любимая кодировка идей OO. Комфортно к программистам удобно, потому что он использует знакомые слова и типы карт для типов. Но запись подхода функций выше, как правило, легче работать, когда все сложно.

Я снова заканчиваю пример животного:

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"

Это выглядит красиво, не так ли? Трудность приходит, когда вы понимаете, что, хотя это выглядит как OO, он не работает как oO. Вы можете захотеть иметь список животных, но лучшее, что вы можете сделать сейчас, это Animal a => [a], список однородных животных, например. Список только кошек или только собак. Тогда вам нужно сделать этот тип обертки:

{-# LANGUAGE ExistentialQuantification #-}

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

А потом [AnyAnimal] это то, что вы хотите за ваш список животных. Тем не менее, оказывается, что AnyAnimal раскрывает точно та же информация о себе как Animal Запись во втором примере, мы только что пошли об этом в обручке. Таким образом, почему я не считаю Typleasases, чтобы быть очень хорошим кодированием OO.

И, таким образом, заключает издание этой недели Слишком много информации!

Другие советы

Похоже, вы можете прочитать Типэкласс.

Рассмотрим этот пример, используя Типэкласс.

Мы определяем C ++ - как «абстрактный класс» MVC на основе трех типов (примечание MultiParamTypeClasses): tState tAction tReaction Для того, чтобы определить ключевую функцию tState -> tAction -> (tState, tReaction) (Когда действие применяется к государству, вы получаете новое состояние и реакцию.

TypeClass имеет три функции «C ++ аннотация», а некоторые более определены на «абстрактных». «Абстрактные» функции будут определены, когда и 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
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top