0

我有一个像这样的数据构造函数

class FooClass a where
    foo :: a -> b

class BarClass a where
    bar :: a -> b

data FooBar = Foo :: FooClass a => a -> IO ()
            | Bar :: BarClass a => a -> IO ()

这样我就可以使用模式匹配:

foobar :: FooBar -> a -> IO ()
foobar (Foo f) x = f (foo x)
foobar (Bar f) x = f (bar x)

然而,这打破了开/关原则。
我希望能够FooBar使用基于其他类的其他方法进行扩展。

我将如何在 Haskell 中实现这一点?

4

2 回答 2

2

在 Haskell 中做例子的技巧是使用函数而不是类:

-- FooBar is like a base class
-- with methods foo and bar.
-- I've interpreted your example liberally
-- for purposes of illustration.
-- In particular, FooBar has two methods -
-- foo and bar - with different signatures.
data FooBar = FooBar {
  foo :: IO (),
  bar :: Int -> Int
}

-- Use functions for classes, like in Javascript.
-- This doesn't mean Haskell is untyped, it just means classes are not types.
-- Classes are really functions that make objects.
fooClass :: Int -> FooBar
fooClass n = FooBar {
    foo = putStrLn ("Foo " ++ show n)
    bar = \n -> n+1
}

barClass :: FooBar
barClass = FooBar {
    foo = putStrLn "Bar ",
    bar = \n -> n * 2
}

-- Now we can define a function that uses FooBar and it doesn't matter
-- if the FooBar we pass in came from fooClass, barClass or something else,
-- bazClass, say.
foobar (FooBar foo bar) = do
    -- invoke foo
    foo
    -- use bar
    print (bar 7)

这里FooBar是“开放扩展”,因为我们可以FooBar用不同的行为创造尽可能多的价值。

FooBar要使用另一个字段“扩展” baz,而不更改FooBarfooClass或者barClass,我们需要声明一个FooBarBaz包含 的类型FooBar。我们仍然可以使用我们的foobar函数,我们只需要先从第一个中FooBar提取FooBarBaz

到目前为止,我一直在接近 OOP。这是因为 Bertrand Meyer 将开放封闭原则表述为需要 OOP 或类似的东西:

软件实体(类、模块、函数等)应该对扩展开放,对修改关闭

特别是,“扩展”一词传统上被解释为“子类化”。如果您准备将原理解释为仅仅是“具有扩展点”,那么任何以另一个函数作为参数的函数都是“开放扩展”。这在函数式编程中很常见,以至于它不被视为原则。“参数化原则”听起来不一样。

于 2014-06-20T16:23:40.990 回答
2

正如其他人所指出的那样,这段代码在掩盖您的问题方面存在缺陷。尝试过分思考 OO 原则如何转化为 FP 也可能很危险。它们有一席之地,因为很多面向对象的内容都自然地嵌入到了 FP 中,但是最好先直接学习 FP,然后作为某些特殊情况观察规律。

特别是,我们可以讨论如何更好地细化类型是一种扩展形式。例如,比较像这样的类型

(Num a)         => a -> IO ()
(Num a, Show a) => a -> IO ()

我们可以讨论第二个函数如何接收一组类型,这些类型是第一个函数输入的自然子类型。特别是,可以输入到第二个函数的一组可能类型是对第一个函数的输入的改进。作为这些功能的用户,使用第二个功能的有效方法较少。作为这些功能的实现者,有更多有效的方法来实现第二个功能。其实我们知道以下

  1. 作为第二个函数的有效输入的所有值也是第一个函数的有效输入
  2. 第一个签名正确键入的所有函数也由第二个签名正确键入。

这种给予和索取的二元性在游戏语义学的研究中得到了探索。“对扩展开放”的想法很简单,因为我们总是可以决定要求一个更精炼的类型,但这几乎完全无趣,因为这在精炼类型的使用方式中很明显。


那么data直接使用 ADT(声明)呢?然后打开/关闭?Mu —ADT 不是对象,因此该规则不直接适用。

于 2014-06-20T13:28:57.250 回答