在函数式编程(FP)上下文中使用面向对象编程(OOP)有什么好处吗?
我已经使用F#一段时间了,我注意到我的函数越多,无状态,我就越少需要它们作为对象的方法。特别是,依靠类型推断使它们在尽可能多的情况下可用是有好处的。
这并不排除需要某种形式的名称空间,这与OOP正交。也不鼓励使用数据结构。实际上,FP语言的实际使用在很大程度上依赖于数据结构。如果你看一下 F Sharp Programming/Advanced Data Structures 中实现的F#堆栈,你会发现它不是面向对象的。
在我看来,OOP与拥有对象状态的方法密切相关,主要是 mutate 对象。在纯粹的FP上下文中,不需要也不需要。
一个实际的原因可能是能够与OOP代码交互,就像F#与.NET一样工作。除此之外,有什么理由吗? Haskell世界的经验是什么,编程是更纯粹的FP?
我将非常感谢有关该问题的论文或反事实现实世界的例子。
答案 0 :(得分:53)
答案 1 :(得分:8)
至于Haskell,类在那里不太有用,因为某些OO功能更容易以其他方式实现。
封装或“数据隐藏”通常通过功能闭包或存在类型而不是私有成员来完成。例如,这是具有封装状态的随机数发生器的数据类型。 RNG包含生成值和种子值的方法。因为类型'seed'是封装的,所以你唯一可以做的就是将它传递给方法。
data RNG a where RNG :: (seed -> (a, seed)) -> seed -> RNG a
参数多态或“泛型编程”上下文中的动态方法调度由类型类(不是OO类)提供。类型类就像OO类的虚方法表。但是,没有数据隐藏。类类不像类方法那样“属于”数据类型。
data Coordinate = C Int Int instance Eq Coordinate where C a b == C d e = a == b && d == e
在子类型多态或“子类化”的上下文中的动态方法分派几乎是使用记录和函数在Haskell中对类模式的转换。
-- An "abstract base class" with two "virtual methods" data Object = Object { draw :: Image -> IO () , translate :: Coord -> Object } -- A "subclass constructor" circle center radius = Object draw_circle translate_circle where -- the "subclass methods" translate_circle center radius offset = circle (center + offset) radius draw_circle center radius image = ...
答案 2 :(得分:6)
我认为有几种方法可以理解OOP的含义。对我来说,它不是关于封装可变状态,而是关于组织和构建程序的更多信息。 OOP的这个方面可以与FP概念完美结合使用。
我认为在F#中混合使用这两个概念是一种非常有用的方法 - 您可以将不可变状态与处理该状态的操作相关联。您将获得标识符'点'完成的好功能,易于使用C#中的F#代码等功能,但您仍然可以使您的代码完全正常运行。例如,您可以编写如下内容:
type GameWorld(characters) =
let calculateSomething character =
// ...
member x.Tick() =
let newCharacters = characters |> Seq.map calculateSomething
GameWorld(newCharacters)
一开始,人们通常不会在F#中声明类型 - 你可以通过编写函数开始,然后进化代码来使用它们(当你更好地理解域并知道构造代码的最佳方法时) 。上面的例子: