Haskell:如何组织一组所有采用相同参数的函数

时间:2013-06-18 19:03:00

标签: haskell dry

我正在编写一个程序,其中包含多个具有相同参数的函数。为简单起见,这是一个有点人为的例子:

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上循环一个资源,以便在运行时获取timeword参数。在这个循环中,我需要加入上述函数的结果以便进一步处理,所以我这样做:

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中,我会通过将上述函数放在一个类中来解决这个问题,该类的构造函数将timeword作为参数。实例化的对象基本上是timeword的三个函数。如果我想确保函数接收processWord word而不是word作为“参数”,我可以在构造函数中调用processWord

有什么更好的方法可以更适合函数式编程和Haskell?

5 个答案:

答案 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智能构造函数以及timeword智能访问者,但不导出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"