代码之家  ›  专栏  ›  技术社区  ›  ultrafez

有没有Haskell等价于OOP的抽象类,使用代数数据类型或多态性?

  •  32
  • ultrafez  · 技术社区  · 15 年前

    在Haskell中,是否可以编写一个带有签名的函数,该函数可以接受两种不同(尽管相似)的数据类型,并根据传入的类型进行不同的操作?

    myFunction ,和两种类型 MyTypeA MyTypeB ,我可以定义 以便它只能接受类型为 MyTypeA型 我的B型 作为它的第一个参数?

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

    类型 ,但我不知道如何定义它以便它可以存储一种或另一种类型,也不知道如何在自己的函数中使用它。

    3 回复  |  直到 15 年前
        1
  •  64
  •   luqui    8 年前

    是的,您是对的,您正在查找代数数据类型。有一个很好的关于他们的教程 Learn You a 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" }
    

    编辑:在评论中对这种方法的一些微妙之处进行了很好的讨论,包括上面代码中的一个bug。

    类型类

    我将再次编码动物示例:

    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"
    

    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 暴露 记录在第二个例子中,我们只是绕着弯子走。因此,我不认为类型类是一种很好的面向对象编码。

    本周出版的

        2
  •  3
  •   Jack Kelly    15 年前

    听起来你可能想继续读下去 typeclasses

        3
  •  1
  •   cibercitizen1    10 年前

    考虑使用以下示例 .

    我们定义了一个类似于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
    
    推荐文章