我是C#开发人员。来自世界的OO方面,我首先考虑接口,类和类型层次结构。由于Haskell中缺少OO,有时我发现自己陷入困境,我无法想到用Haskell模拟某些问题的方法。
如何在Haskell中建模涉及类层次结构的真实世界情况,如下所示:http://www.braindelay.com/danielbray/endangered-object-oriented-programming/isHierarchy-4.gif
答案 0 :(得分:32)
首先:标准OO设计在Haskell中不能很好地工作。你可以对抗这种语言并尝试制作类似的东西,但这将是一种令人沮丧的练习。因此,第一步是为您的问题寻找Haskell风格的解决方案,而不是寻找在Haskell中编写OOP风格解决方案的方法。
但这说起来容易做起来难!哪里开始?
所以,让我们拆解OOP为我们做的事情的细节,并考虑一下Haskell的外观。
protected
或internal
成员相当。map
及其参数被翻转。如果您将其应用于Int
的列表,您将获得类型为(Int -> b) -> [b]
的函数。从某种意义上说,你给它的列表仍然是“那里”,但除了通过函数之外没有别的东西可以使用它。这与private
成员相当,并且部分应用的原始函数与OOP样式的构造函数相当。fold
函数,它们概括了几乎所有迭代循环,列表转换和线性递归函数。sort
的类型为(Ord a) => [a] -> [a]
;除了它必须是实现Ord
的某种类型的列表之外,它与您提供的类型的细节完全分离。您可能还会发现this blog post有帮助;它简要概述了您在Haskell中使用的内容,以解决在OOP中经常使用某些标准设计模式的相同问题。
作为最终的附录,作为C#程序员,您可能会发现研究它与Haskell之间的联系很有意思。很多负责C#的人也是Haskell程序员,最近C#的一些新增内容深受Haskell的影响。最值得注意的可能是LINQ下的monadic结构,IEnumerable本质上是列表monad。
答案 1 :(得分:13)
让我们假设以下操作:人类可以说话,狗可以吠叫,如果物种的性别相反,物种的所有成员都可以与同一物种的成员交配。我会像这样在haskell中定义:
data Gender = Male | Female deriving Eq
class Species s where
gender :: s -> Gender
-- Returns true if s1 and s2 can conceive offspring
matable :: Species a => a -> a -> Bool
matable s1 s2 = gender s1 /= gender s2
data Human = Man | Woman
data Canine = Dog | Bitch
instance Species Human where
gender Man = Male
gender Woman = Female
instance Species Canine where
gender Dog = Male
gender Bitch = Female
bark Dog = "woof"
bark Bitch = "wow"
speak Man s = "The man says " ++ s
speak Woman s = "The woman says " ++ s
现在,操作matable
的类型为Species s => s -> s -> Bool
,bark
的类型为Canine -> String
,speak
的类型为Human -> String -> String
。
我不知道这是否有帮助,但考虑到问题的相当抽象性,这是我能想到的最好的。
编辑:回应丹尼尔的评论:
集合的简单层次结构可能如下所示(忽略现有的类,如Foldable和Functor):
class Foldable f where
fold :: (a -> b -> a) -> a -> f b -> a
class Foldable m => Collection m where
cmap :: (a -> b) -> m a -> m b
cfilter :: (a -> Bool) -> m a -> m a
class Indexable i where
atIndex :: i a -> Int -> a
instance Foldable [] where
fold = foldl
instance Collection [] where
cmap = map
cfilter = filter
instance Indexable [] where
atIndex = (!!)
sumOfEvenElements :: (Integral a, Collection c) => c a -> a
sumOfEvenElements c = fold (+) 0 (cfilter even c)
现在sumOfEvenElements采用任何类型的积分集合,并返回该集合中所有偶数元素的总和。
答案 2 :(得分:6)
Haskell使用抽象数据类型而不是类和对象。这些是关于组织构建和观察信息的方法的问题的两个兼容视图。关于这个问题,我所知道的最好的帮助是威廉库克的论文Object-Oriented Programming Versus Abstract Data Types。他对
的影响有一些非常明确的解释在基于类的系统中,代码是围绕构造抽象的不同方式组织的。通常,构造抽象的每种不同方式都被赋予其自己的类。这些方法只知道如何观察该结构的属性。
在基于ADT的系统(如Haskell)中,代码围绕观察抽象的不同方式进行组织。通常,观察抽象的每种不同方式都被赋予其自己的功能。该函数知道 all 可以构造抽象的方式,它知道如何观察单个属性,但是知道如何构造。
Cook的论文将向您展示一个很好的抽象矩阵布局,并教你如何组织任何类作为ADY,反之亦然。
类层次结构涉及另一个元素:通过继承重用实现。在Haskell中,这种重用是通过第一类函数实现的:Primate
抽象中的函数是一个值,Human
抽象的实现可以重用Primate
抽象的任何函数,可以包装它们来修改它们的结果,等等。
设计与类层次结构和设计与抽象数据类型之间没有完全契合。如果你试图从一个音译到另一个,你将会遇到一些尴尬而不是惯用的东西 - 就像用Java编写的FORTRAN程序。 但是,如果您了解类层次结构的原则和抽象数据类型的原则,您可以在一种风格中解决问题,并为另一种风格的同一问题制定合理的惯用解决方案。它确实需要练习。
附录:也可以使用Haskell的类型系统来尝试模拟类层次结构,但这是一个不同的鱼类。类型类与普通类很相似,许多标准示例都有效,但它们不同,可能会有一些非常大的意外和不适应。虽然类型类是Haskell程序员非常宝贵的工具,但我建议任何学习Haskell的人都学会使用抽象数据类型设计程序。
答案 3 :(得分:2)
Haskell是我最喜欢的语言,是一种纯粹的功能语言。 它没有副作用,没有任务。 如果你发现很难过渡到这种语言,也许F#是一个更好的开始函数式编程的地方。 F#并不纯粹。
对象封装状态,有一种方法可以在Haskell中实现这一点,但这是需要花费更多时间学习的问题之一,因为你必须学习一些类别理论概念才能深入理解monad。有一种语法糖可以让你看到像非破坏性赋值的单子,但在我看来,花更多的时间来理解类别理论的基础(类别的概念)以获得更好的理解更好。
在尝试在Haskell中以OO风格编程之前,你应该问自己是否真的在C#中使用面向对象的风格,许多程序员使用OO语言,但他们的程序是用结构化风格编写的。
数据声明允许您定义组合产品(相当于C语言中的结构)和联合(相当于C中的union)的数据结构,声明的派生部分允许继承默认方法。
如果类中包含方法集的实现,则数据类型(数据结构)属于类。 例如,如果您可以定义show :: a - >您的数据类型的String方法,然后它属于Show类,您可以将您的数据类型定义为Show类的实例。
这与某些OO语言中类的使用不同,后者用它来定义结构+方法。
如果数据类型独立于其实现,则它是抽象的。您可以通过抽象接口创建,变异和销毁对象,您不需要知道它是如何实现的。
Haskell支持抽象,很容易声明。 例如,来自Haskell站点的代码:
data Tree a = Nil
| Node { left :: Tree a,
value :: a,
right :: Tree a }
声明选择器left,value,right。 如果要将它们添加到模块声明中的导出列表中,可以将构造函数定义如下:
node = Node
nil = Nil
模块的构建方式与Modula类似。以下是同一网站的另一个例子:
module Stack (Stack, empty, isEmpty, push, top, pop) where
empty :: Stack a
isEmpty :: Stack a -> Bool
push :: a -> Stack a -> Stack a
top :: Stack a -> a
pop :: Stack a -> (a,Stack a)
newtype Stack a = StackImpl [a] -- opaque!
empty = StackImpl []
isEmpty (StackImpl s) = null s
push x (StackImpl s) = StackImpl (x:s)
top (StackImpl s) = head s
pop (StackImpl (s:ss)) = (s,StackImpl ss)
关于这个主题还有更多的话要说,我希望这个评论有所帮助!