我有一些软件设计经验,现在我正在学习Haskell。在许多现实世界的软件开发中,人们面临的情况类似于给定的情况,例如:
假设我有这段代码
f1 a b c d = e
where
e1 = f2 b c (f3 a)
e2 = f4 d
e = e1 + e2
f2 b c d = n + c + d
where
n = f5 b
f5 n = n*n
f3 a = a * 2
f4 a = a + 3
现在,如果我想更改f5以便它需要另一个参数,我将不得不将所有函数链更改为f1。 它可以如下所示完成。请注意添加的参数x。
f1 a b c d x = e -- f1 needs to be changed
where
e1 = f2 b c (f3 a) x
e2 = f4 d
e = e1 + e2
f2 b c d x = n + c + d -- f2 needs to be changed
where
n = f5 b x
f5 n x = n*n -- f5 changed (**bang**)
f3 a = a * 2
f4 a = a + 3
这是一种正常的Haskell方式来做这种事情还是有更好的(更多Haskell-ish)方式? 我知道API中的这种变化会干扰客户端代码,但影响如何保持最小,是否有任何Hasekll方式呢?
在更一般的层面上:Haskell在这种情况下的表现如何(特别是考虑到其不可变状态功能)?它在这方面为开发人员提供了什么?或者说,Haskell本身没有扮演这个角色,而这只是一个困难的软件工程问题(没有 future proof ),我们必须跟上它们的步伐?
我为在一个帖子中提出多个问题而道歉,但我无法帮助,因为这些问题彼此相关。此外,我找不到类似的问题,对不起,如果我可能错过了它。
答案 0 :(得分:0)
你可以做的一件事是将这样的参数捆绑到一个参数对象中,正如bhelkir在评论中所建议的那样。如果向此对象添加新参数,则仍需要更改调用f1
的客户端代码,并更改该新参数的直接使用者(此处为f5
),但这些都是不可避免的:有人必须在某个时候提供x
,你需要它作为客户;并且你必须以某种方式使用x
否则为什么要将它添加到开头?
但是,您可以避免更改f1
和f2
等中间功能,因为他们可以忽略他们不关心的新字段。您可以使用Applicative
((->) t)
实例(通常称为Reader
)来传递此对象,而不是手动执行此操作。这是写这个的一种方法:
module Test where
import Control.Applicative
data Settings = Settings {getA :: Int,
getB :: Int,
getC :: Int,
getD :: Int}
f1 :: Settings -> Int
f1 = liftA2 (+) f2 f4
-- f1 = do
-- e1 <- f2
-- e2 <- f4
-- return $ e1 + e2
f2 :: Settings -> Int
-- probably something clever with liftA3 and (+) is possible here too
f2 s = f5 s + getC s + f3 s
f3 :: Settings -> Int
f3 = liftA (* 2) getA
f4 :: Settings -> Int
f4 = liftA (+ 3) getD
f5 :: Settings -> Int
-- f5 = liftA (join (*)) getB -- perhaps a bit opaque
f5 = liftA square getB
where square b = b * b
现在,这有其优点和缺点:f1
中的逻辑(即知道您需要使用f3
调用a
)已进入{{1本身,这将发生在最初读取参数并在将其传递给某个辅助功能之前与其混淆的任何函数。这可能比原始版本更清晰,或者它可能会模糊f3
背后的意图,具体取决于您的问题域。你总是可以更明确地编写一个函数,比如让它在传递它之前修改传入的f1
对象来改变它的Settings
字段,就像我用a
做的那样。 。更一般地说,您可以使用最方便的样式编写任何函数:do-notation,applicative函数或传入的记录对象上的普通旧模式匹配。
但最重要的是,添加新参数非常容易:只需在f2
记录中添加一个字段,然后在需要它的函数中读取它:
Settings
请注意除module Test where
import Control.Applicative
data Settings = Settings {getA :: Int,
getB :: Int,
getC :: Int,
getD :: Int,
getX :: Int}
f1 :: Settings -> Int
f1 = liftA2 (+) f2 f4
-- f1 = do
-- e1 <- f2
-- e2 <- f4
-- return $ e1 + e2
f2 :: Settings -> Int
-- probably something clever with liftA3 and (+) is possible here too
f2 s = f5 s + getC s + f3 s
f3 :: Settings -> Int
f3 = liftA (* 2) getA
f4 :: Settings -> Int
f4 = liftA (+ 3) getD
f5 :: Settings -> Int
f5 = liftA2 squareAdd getB getX
where squareAdd b x = b * b + x
和data Settings
之外的所有内容都相同。