我正在编写一个程序,其中包含多个具有相同参数的函数。为简单起见,这是一个有点人为的例子:
buildPhotoFileName time word stamp = show word ++ "-" ++ show time ++ show stamp
buildAudioFileName time word = show word ++ "-" ++ show time ++ ".mp3"
buildDirectoryName time word = show word ++ "_" ++ show time
假设我在IO上循环一个资源,以便在运行时获取time
和word
参数。在这个循环中,我需要加入上述函数的结果以便进一步处理,所以我这样做:
let photo = buildPhotoFileName time word stamp
audio = buildAudioFileName time word
dir = buildDirectoryName time word
in ....
这似乎违反了“不要重复自己”的原则。如果我在路上发现我想将word
更改为word
的函数,我可能会在let
表达式的开头创建一个新的绑定,如下所示:
let wrd = processWord word
photo = buildPhotoFileName time wrd stamp
audio = buildAudioFileName time wrd
dir = buildDirectoryName time wrd
in ....
并且每次我将word
写入wrd
时都必须更改,如果我记得更改某些函数调用而导致错误,而不是其他函数调用。
在OOP中,我会通过将上述函数放在一个类中来解决这个问题,该类的构造函数将time
和word
作为参数。实例化的对象基本上是time
和word
的三个函数。如果我想确保函数接收processWord word
而不是word
作为“参数”,我可以在构造函数中调用processWord
。
有什么更好的方法可以更适合函数式编程和Haskell?
答案 0 :(得分:11)
既然你说你已经准备好为此创建一个OO-wrapper-class,我认为你可以改变你的功能。以下是一个函数,可以生成您想要的所有三个结果的元组:
buildFileNames time word stamp =
( show word ++ "-" ++ show time ++ show stamp,
show word ++ "-" ++ show time ++ ".mp3",
show word ++ "_" ++ show time )
你可以像这样使用它:
let wrd = processWord word
(photo, audio, dir) = buildFileNames time wrd stamp
in ....
如果你不需要任何结果,你可以像这样跳过它们:
let wrd = processWord word
(_, audio, _) = buildFileNames time wrd stamp
in ....
值得注意的是,你不必担心Haskell在你不使用的计算值上浪费资源,因为它很懒。
答案 1 :(得分:7)
你在OOP土地上描述的解决方案对我来说听起来像是一个很好的解决方案。即:
data UID = UID
{ _time :: Integer
, _word :: String
}
在此记录中包含或不包括“印章”是一项设计决定,我们可能没有足够的信息在这里回答。可以将此数据类型放在自己的模块中,并定义“智能构造函数”和“智能访问器”:
uid = UID
time = _time
word = _word
然后隐藏模块边界处的真实构造函数和访问器,例如导出UID
类型,uid
智能构造函数以及time
和word
智能访问者,但不导出UID
构造函数或_time
和{{ 1}}访问者。
_word
如果我们后来发现智能构造函数应该进行一些处理,我们可以更改module UID (UID, uid, time, word) where
的定义:
uid
答案 2 :(得分:6)
在Nikita Vokov的回答之上,您可以使用record wild cards获得一些简洁的语法,而且重复性很小:
{-# LANGUAGE RecordWildCards #-}
data FileNames = FileNames { photo :: String, audio :: String, dir :: String }
buildFileNames :: Word -> Time -> Stamp -> FileNames
buildFileNames time word stamp = FileNames
(show word ++ "-" ++ show time ++ show stamp)
(show word ++ "-" ++ show time ++ ".mp3")
(show word ++ "_" ++ show time )
let FileNames {...} = buildFileNames time wrd stamp
in ... photo ... audio ... dir...
答案 3 :(得分:4)
为了给你另一个例子,如果你将相同的参数传递给多个函数,你可以使用Reader monad:
import Control.Monad.Reader
runR = flip runReader
type Params = (String, String, String)
buildPhotoFileName :: Reader Params String
buildPhotoFileName = do
(time, word, stamp) <- ask
return $ show word ++ "-" ++ show time ++ show stamp
main = do
runR (time, word, stamp) $ do
photo <- buildPhotoFileName
audio <- buildAudioFileName
dir <- buildDirectoryName
processStuff photo audio dir
答案 4 :(得分:0)
要构建David Wagner的解决方案和您的OO目标,您应该将buildxxx函数或函数移动到一个单独的模块(NameBuilders?),这样可以完全控制。
即使采用这种方法,你也应该像David建议的那样,用模块中的函数“包装”变量。
您将导出变量和buildxxx构造函数(返回三元组)或构造函数(三个单独的函数)。
您也可以通过
进行简化 buildDirectoryName time word = show word ++ "_" ++ show time
buildPhotoFileName stamp = buildDirectoryName + show stamp
buildAudioFileName = buildDirectoryName ++ ".mp3"