是否有一个Haskell相当于面向对象是抽象的班级,采用代数数据类型或多?
题
在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.
为了记录在案,这一概念的一个抽象的类从面向对象实际上有三个不同的翻译到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" }
有几个不同的排列的这一基本概念。不变的是,抽象的类型是一个记录类型在方法领域的记录。
编辑:有一个良好的讨论,在该评论中有些微妙的这种方法,包括一个错误在上述码。
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
)“抽象类” MultiParamTypeClasses
:tState
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