我正在设计一个Haskell模块,该模块设计用于解决数学问题,可能有各种参数化。该模块导出一个函数:
run_and_output_parameterization :: ProblemParams -> String -> IO ()
其中的想法是在某些“控制器”中生成ProblemParams
个对象,并按如下方式调用:
map (\(pp, name) -> run_and_output_parameterization pp name) (zip pp_list names_list)
我的问题是,在模块中,有一些函数,如索引函数,我想部分应用于特定的参数化。例如,
evenly_spaced_point_approx :: Int -> Int -> Int -> Double -> Double -> Int
evenly_spaced_point_approx xmin xmax xstep_i xstep_d target = pt
where
pt = max (min (round (target/xstep_d) * xstep_i) xmax) xmin
evenly_spaced_si_approx target = evenly_spaced_point_approx (_pp_si_min pp) (_pp_si_max pp) (_pp_nstep_s pp) (_pp_nstep_sd pp) target
evenly_spaced_wi_approx target = evenly_spaced_point_approx (_pp_wi_min pp) (_pp_wi_max pp) (_pp_nstep_w pp) (_pp_nstep_wd pp) target
我想在模块中使用函数evenly_spaced_si_approx
和evenly_spaced_wi_approx
来处理特定的ProblemParameter数据结构(称为pp
)。
有没有办法告诉Haskell部分应用所有依赖函数,还是我必须手工完成的事情?此外,我对函数式编程术语不精确表示歉意。
答案 0 :(得分:1)
如果您有许多需要相同参数的函数,并且这是他们所采用的唯一(或最后一个)参数,那么您可以利用Monad
的{{1}}实例。或者,您可以将所有内容包装在(->) r
monad中,其定义基本上是
Reader
与newtype Reader r a = Reader { runReader :: r -> a }
instance Monad (Reader r) where
return a = Reader $ \_ -> a
m >>= f = Reader $ \r -> runReader (f (runReader m r)) r
的{{1}}实例相比:
Monad
你怎么用这个?例如,如果您有一个参数(->) r
,那么您可以将函数编写为
instance Monad ((->) r) where
return a = const a
m >>= f = \r -> f (m r) r
这非常有效,您只需要确保pp :: ProblemParams
,-- Some declarations
smallFunc1 :: ProblemParams -> Double
smallFunc2 :: ProblemParams -> Double
smallFunc3 :: Int -> ProblemParams -> Double
doStuff :: ProblemParams -> Double -- Just a random return type
doStuff = do -- Keep the parameter implicit
result1 <- smallFunc1 -- The ProblemParams are automatically passed
result2 <- smallFunc2
result3 <- smallFunc3 10
return $ result1 + result2 + result3
和smallFunc1
的所有内容都以smallFunc2
作为最后一个参数(请注意将smallFunc3 10
与ProblemParams
一起包含在内。函数的10
实例将在所有绑定中隐式传递该参数。可以把它想象成在计算该值之前返回一个值。您可以将smallFunc3
的“未来”返回值绑定到Monad
。
或者,您可以使用smallFunc1
monad:
result1
我们必须创建一个Reader
函数,将我们的基元提升为type Problem a = Reader ProblemParams a
reader :: (r -> a) -> Reader r a
reader f = do
r <- ask
return $ f r
-- reader f = ask >>= return . f
smallFunc1' :: Problem Double
smallFunc1' = reader smallFunc1
smallFunc2' :: Problem Double
smallFunc2' = reader smallFunc2
smallFunc3' :: Int -> Problem Double
smallFunc3' i = reader (smallFunc3 i)
doStuff :: ProblemParams -> Double
doStuff pp = flip runReader pp $ do
result1 <- smallFunc1'
result2 <- smallFunc2'
result3 <- smallFunc3' 10
return $ result1 + result2 + result3
monad的原因是reader
实际上是根据变换器Reader
定义的
Reader
围绕ReaderT
monad。
无论您决定使用哪个都取决于您。我想大多数人会更熟悉type Reader r a = ReaderT r Identity a
版本,如果你决定再堆叠一些变形金刚以后就会非常简单。 Identity
monad基本上有助于使函数签名看起来是monadic,因为Reader
看起来不像普通的monad签名。它将使用更多的代码,但它可能会帮助您推理您的程序。
注意:我没有运行任何此代码,因此请注意可能存在小错误。如果有人发现问题,请告诉我,我会解决它。
Reader
monad和ProblemParams -> Double
的示例:
Par
然后,您只需使用ReaderT
将type App a = ReaderT ProblemParams Par a
runApp :: ProblemParams -> App a -> a
runApp pp app = runPar $ runReaderT app pp
次操作提升为lift
次操作:
Par
我大约99%确定monad堆栈根本不会影响你的并行性,因为App
monad首先执行,基本上将parReader :: (ProblemParams -> Par a) -> App a
parReader f = do
r <- ask
lift $ f r
-- parReader f = ask >>= lift . f
doStuff :: ProblemParams -> Double
doStuff pp = runApp pp $ do
result1 <- parReader parAction1
result2 <- parReader parAction2
result3 <- parReader (parAction3 10)
return $ result1 + result2 + result3
应用于所有函数,然后运行Reader
行动。