如何在Haskell成功之前继续调用具有不同参数的函数

时间:2013-12-23 16:57:57

标签: function haskell

我有一个函数调用另一个函数,它可以根据某些条件返回一些东西或什么都没有。我想知道的是,如果它什么都不返回,我怎么用不同的参数再次调用它。

例如,我说有一个函数可以以30分钟的间隔返回时间,而另一个函数只能在某个时间没有返回任何内容,即:

certainTimeFunction :: (Int,Int,Int) -> Maybe String
certainTimeFunction (h,m,s) =
    | (11,00,00) = Just "It's eleven o'clock"
    | otherwise = Nothing

timeFunction :: (Int,Int,Int) -> (Int,Int,Int)
timeFunction time = certainTimeFunction time

时间必须从(00,00,00)开始,当然会返回Nothing但是然后(00,30,00)尝试再次返回Nothing直到(11,00,00)然后触发函数为了返回第一个守卫中的内容,整个周期应该结束。

2 个答案:

答案 0 :(得分:6)

与Haskell一样,答案是使用递归!

timeFunction :: (Int, Int, Int) -> (Int, Int, Int)
timeFunction time0 = case certainTimeFunction time0 of
  Nothing  -> timeFunction (perturbTime time0)
  Just res -> res

请注意,perturbTimecertainTimeFunction应该仔细调整以协同工作,因为在这样的情况下构建无限循环非常容易。更简洁的方法是使用iterate将递归“展开”到一个惰性列表中。

首先,我们创建一个“所有扰动”的无限列表

... iterate perturbTime :: [(Int, Int, Int)]

然后我们map certainTimeFunction检查是否有任何此类扰动返回Just

... map certainTimeFunction . iterate perturbTime :: [Maybe (Int, Int, Int)]

然后我们可以通过删除所有timeFunction来恢复Nothing的确切行为。

timeFunction = fromJust . head . dropWhile isNothing 
             . map certainTimeFunction 
             . iterate perturbTime

我们还可以创建timeFunction的版本,如果perturbTime未在n扰动中达到成功点,则会失败

timeFunctionN n = safeJustHead 
                . dropWhile isNothing
                . take n
                . map certainTimeFunction 
                . iterate perturbTime
  where safeJustHead :: [Maybe a] -> Maybe a  -- note this is just `msum`
        safeJustHead []    = Nothing          -- it's also easy to implement
        safeJustHead (x:_) = x                -- using `Data.Maybe.listToMaybe`
                                              -- per @bheklilr's comment below

答案 1 :(得分:2)

如果你只想以30分钟的间隔喂它,直到成功,你可以

import Data.Maybe

timeFunction :: (Int, Int, Int) -> (Int, Int, Int)
timeFunction initialTime = head $ catMaybes $ map certainTimeFunction times
    where
        add30 (h, m, s) =
            let newM = m + 30
                newH = h + newM `div` 60
            in (newH, newM `mod` 60, s)
        times = iterate add30 initialTime

这将懒惰地计算从您的初始时间起30分钟的所有增量(我的add30是@JAbrahamson的perturbTime),然后certainTimeFunction被映射到所有这些时间,然后{{ 1}}将此压缩为catMaybes值(懒惰地,再次),并且第一个被采用。请注意,如果你没有得到Just从中返回成功值的时间,这将永远循环!

如果您想在小时数大于23时停止,请使用我们的好朋友certainTimeFunction并将takeWhile更改为

times

并将times = takeWhile (\(h', _, _) -> h' < 24) $ iterate add30 initialTime 的类型更改为timeFunction


这是一个很好的例子,说明为什么你应该定义自己的类型而不是使用元组。如果你有

Time -> Maybe String

然后是一些辅助函数

data Time = Time
    { hours :: Int
    , minutes :: Int
    , seconds :: Int
    } deriving (Eq, Show)

可以更清晰地写成

import Data.Function (on)

asSeconds :: Int -> Time
asSeconds x = Time (x `div` 3600) (x `div` 60 `mod` 60) (x `mod` 60)

asMinutes :: Int -> Time
asMinutes = asSeconds . (* 60)

asHours :: Int -> Time
asHours x = asMinutes . (* 60)

toSeconds :: Time -> Int
toSeconds (Time h m s) = h * 3600 + m * 60 + s

(+:) :: Time -> Time
(+:) = asSeconds .: on (+) toSeconds where (.:) = (.).(.)

timeFunction :: Time -> Maybe Time
timeFunction initialTime = listToMaybe $ catMaybes $ map certainTimeFunction times
    where
        times = takeWhile ((< 23) . hours) $ iterate (+: asMinutes 30) initialTime

您还可以非常轻松地timeFunction = listToMaybe . catMaybes . map certainTimeFunction . takeWhile ((< 24) . hours) . iterate (+: asMinutes 30) Time个实例

Ord