Haskell: Как организовать группу функций, которые принимают одинаковые аргументы
Я пишу программу с несколькими функциями, которые принимают те же аргументы. Вот несколько надуманный пример для простоты:
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
, приводя к ошибкам, если не помню, чтобы сменить некоторые вызовы функций, но не другие.
В ООП я решил бы это, поставив вышеуказанные функции в класс, конструктор которого примет в качестве аргументов time
и word
. Эквивалентным объектом будет, по существу, три функции, рассчитанные на time
и word
. Если бы я хотел убедиться, что функции получают processWord word
вместо word
как "аргумент", я мог бы вызвать processWord
в конструкторе.
Каков наилучший способ сделать это, который больше подходит для функционального программирования и Haskell?
Ответы
Ответ 1
Поскольку вы говорите, что вы готовы создать класс OO-wrapper только для этого, я предполагаю, что вы открыты для изменения своих функций. Ниже приведена функция, производящая кортеж из всех трех результатов, которые вы хотели:
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 тратит ресурсы на вычисления значений, которые вы не используете, поскольку они ленивы.
Ответ 2
Решение, которое вы описали на земле ООП, звучит как хорошая в земле FP для меня. К остроумию:
data UID = UID
{ _time :: Integer
, _word :: String
}
Включение или отсутствие "штампа" в этой записи является конструктивным решением, которое, вероятно, не содержит достаточной информации для ответа здесь. Этот тип данных можно поместить в свой собственный модуль и определить "умный конструктор" и "интеллектуальные аксессоры":
uid = UID
time = _time
word = _word
Затем скройте реальный конструктор и аксессоры на границе модуля, например. экспортируйте UID
тип, UID
интеллектуальный конструктор и time
и word
интеллектуальные аксессоры, но не конструкторы UID
или _time
и _word
.
module UID (UID, uid, time, word) where
Если позже мы обнаружим, что интеллектуальный конструктор должен выполнить некоторую обработку, мы можем изменить определение UID
:
uid t w = UID t (processWord w)
Ответ 3
На вершине Никиты Вокова ответьте, вы можете использовать запись диких карт для некоторого аккуратного синтаксиса с небольшим повторением:
{-# 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...
Ответ 4
Чтобы дать вам другой пример, если вы передаете одни и те же параметры нескольким функциям, вместо этого вы можете использовать монаду читателя:
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
Ответ 5
Чтобы опираться на решение Дэвида Вагнера и ваши цели OO, вы должны переместить функцию или функции buildxxx в отдельный модуль (NameBuilders?), который даст вам полный контроль.
Даже при таком подходе вы также должны "обернуть" переменные функциями внутри модуля, как предложил Дэвид.
Вы экспортируете переменные и конструктор buildxxx (возвращаете триплет) или конструкторы (три отдельные функции).
вы также можете упростить
buildDirectoryName time word = show word ++ "_" ++ show time
buildPhotoFileName stamp = buildDirectoryName + show stamp
buildAudioFileName = buildDirectoryName ++ ".mp3"