您可以做的一件事就是将这样的参数捆绑到一个参数对象中,正如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
</code>
现在,这有其优点和缺点:存在的逻辑
f1
(即知道你需要打电话
f3
同
a
)已进入
f3
本身,这将发生在最初读取参数并在将其传递给某个辅助功能之前与其混淆的任何函数。这可能比原版更清晰,也可能掩盖背后的意图
f1
,取决于您的问题域。您总是可以更明确地编写函数,例如通过修改传入函数
Settings
反对改变它的对象
a
传递它之前的字段,就像我做的那样
f2
举个例子。更一般地说,您可以使用最方便的样式编写任何函数:do-notation,applicative函数或传入的记录对象上的普通旧模式匹配。
但最重要的是,添加一个新参数非常容易:你只需要添加一个字段
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
</code>
请注意,除了之外,一切都是一样的
data Settings
和
f5
。