11

我正在编写一个程序,其中包含几个采用相同参数的函数。为简单起见,这是一个有些人为的示例:

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

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作为参数。实例化的对象本质上是 curried 到time和的三个函数word。如果我想确保函数接收processWord word而不是word作为“参数”,我可以调用processWord构造函数。

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

4

5 回答 5

11

既然您说您已准备好为此创建一个 OO 包装类,我假设您愿意更改您的功能。以下是一个生成您想要的所有三个结果的元组的函数:

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 将资源浪费在计算您不使用的值上,因为它是惰性的。

于 2013-06-18T19:27:33.153 回答
7

您从 OOP 领域描述的解决方案对我来说听起来像是 FP 领域的一个不错的解决方案。以机智:

data UID = UID
    { _time :: Integer
    , _word :: String
    }

在此记录中包括或不包括“印章”是一个设计决策,我们可能没有足够的信息在这里回答。可以将这种数据类型放在它自己的模块中,并定义一个“智能构造函数”和“智能访问器”:

uid = UID
time = _time
word = _word

然后在模块边界隐藏真正的构造函数和访问器,例如导出UID类型、uid智能构造器和timeword智能访问器,而不是UID构造器或_time_word访问器。

module UID (UID, uid, time, word) where

如果我们后来发现智能构造函数应该做一些处理,我们可以改变 的定义uid

uid t w = UID t (processWord w)
于 2013-06-18T20:13:03.083 回答
6

在 Nikita Vokov 的回答之上,您可以使用记录通配符来获得一些简洁的语法,几乎没有重复:

{-# 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...
于 2013-06-18T20:13:52.617 回答
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
于 2013-06-19T00:14:12.267 回答
0

要在 David Wagner 的解决方案和您的 OO 目标的基础上进行构建,您应该将 buildxxx 函数或多个函数移至单独的模块(NameBuilders?),这样您就可以完全控制。

即使采用这种方法,您也应该按照 David 的建议在模块内使用函数“包装”变量。

您将导出变量和 buildxxx 构造函数(返回三元组)或构造函数(三个单独的函数)。

你也可以简化为

    buildDirectoryName time word = show word ++ "_" ++ show time
    buildPhotoFileName stamp = buildDirectoryName + show stamp
    buildAudioFileName =       buildDirectoryName ++ ".mp3"
于 2013-06-28T14:29:43.630 回答