在应用函数 monad 之前对输入执行转换

Executing a transformation to the input before applying the function monad

我知道我可以使用函数 monad 来实现如下所示的构造(我在多个调用中重用该参数而没有显式引用它):

compute_v0 :: String -> String
compute_v0 = do 
    x <- length -- (using the argument implicitly here)
    top <- head -- (and here)
    return (replicate x top)

上述函数的结果:compute "1234" 将是 "1111"

我的问题是:在执行 do 块之前如何对 'hidden' 参数应用转换(假设我想将 "abcd" 附加到列表)。

我的第一个解决方案:

compute_v1 :: String -> String
compute_v1 = compute_v1' . (++ "abcd")

compute_v1' ::String -> String
compute_v1' = do 
    x <- length 
    top <- head
    return (replicate x top)

compute "1234" 的结果现在为 "11111111"。这实际上完成了工作,但我宁愿尝试将其全部定义在一个简洁的代码块中。

我最接近实际包含转换同时仍然保持代码 (v0) 样式的是这个:

compute_v2 :: String -> String 
compute_v2 = (++ "abcd") >>= \r -> do
    let x = length r
    let top = head r
    return $ replicate x top

但我仍然必须包含一个 lambda,使用大量 let 绑定并显式引用 lambda 参数。有没有更好的方法来实现这样的构造?

因为所有 Monad 个实例也有 Functor 个实例并且 Functor 的函数实例有 fmap = (.),你可以有

compute :: String -> String 
compute = flip fmap (++ "abcd") $ do
    x   <- length
    top <- head
    return $ replicate x top

一些包(如 microlenslens)定义 (<&>) = flip fmap,允许您编写

compute :: String -> String 
compute = (++ "abcd") <&> do
    x   <- length
    top <- head
    return $ replicate x top

(->) 还有一个 Category 实例,它给我们 (>>>) = flip (.)。从视觉上看,这可能会更清楚一些:

compute :: String -> String 
compute = (++ "abcd") >>> do
    x   <- length
    top <- head
    return $ replicate x top

你可以这样做:

compute_v2 :: String -> String
compute_v2 = do
    x <- length
    top <- head
    return $ replicate x top
    <$> (++ "abcd")

AFAIK,有问题的 monad 称为 Reader monad,它也是一个 Functor.

*Q46393211> compute_v2 "1234"
"11111111"
*Q46393211> compute_v2 "71"
"777777"

MonadReader class为此有方法local(->) r是一个实例,所以

import Control.Monad.Reader (local)

compute_v3 ::String -> String
compute_v3 = local (++ "abcd") $ do 
    x <- length 
    top <- head
    return (replicate x top)

应该可以(目前无法测试)。