在Haskell,是否有可能编写一个函数的签名,可以接受两种不同的(虽然类似)数据类型,并以不同的方式运作,取决于什么类型是传递?

一个例子可能会使我的问题更加清晰。如果我有一个功能,名为 myFunction, 和两个类型命名的 MyTypeAMyTypeB, 我可以定义 myFunction 所以,它只能接受数据的类型 MyTypeAMyTypeB 作为其第一个参数?

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.

为了记录在案,这一概念的一个抽象的类从面向对象实际上有三个不同的翻译到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 是的只的类型,和 DogCat 被压扁走到他们的构造功能:

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

有几个不同的排列的这一基本概念。不变的是,抽象的类型是一个记录类型在方法领域的记录。

编辑:有一个良好的讨论,在该评论中有些微妙的这种方法,包括一个错误在上述码。

Typeclasses

这是我最喜欢的编码的OO的想法。它是舒适的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 记录在第二个例子,我们刚刚走了关于它以一种迂回的方式。因此我为什么不考虑typeclasses是一个很好的编码的OO。

并因此结束本周的版本 太多的信息!

其他提示

这听起来像你可能想在类型类读了。

使用考虑这个例子的类型类

我们定义一个C ++ - 像基于三种类型(注MVC)“抽象类” MultiParamTypeClassestState tAction tReaction为了 定义键功能tState -> tAction -> (tState, tReaction)(当一个动作被施加到状态,则得到一个新的状态,并进行反应。

在类型类具有 三“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