在不更改结果的情况下添加操作以重构 do-notation
Add action without changing result to refactor do-notation
我想在 Haskell 中顺序组合两个 monad 动作,丢弃第二个产生的任何值,并将参数传递给两个动作。目前我正在使用这样的 do-block:
ask = do
result <- getLine
putStrLn result
return result
我希望写得更自由、更整洁一点,所以我尝试了这个:
ask' = getLine <* putStrLn
然而,这甚至没有类型检查,问题是 <*
does not transfer the result of the first action to the second. I want to chain the actions like >>=
does, but not change the result. The type should be (a -> m b) -> (a -> m c) -> (a -> m b)
, but Hoogle 没有产生合适的结果。实现这个功能组合的运算符是什么?
作为一种趋势,如果您在两个不同的地方使用同一个值,可能 最好在清晰的 do
块中为其命名,而不是而不是按毫无意义的风格。
cartesian monoidal categories, known to Haskellers as arrows 捕获了将信息流拆分为不同操作的抽象概念。就您而言,您基本上是在 IO
Kleisli 类别中工作:
import Prelude hiding (id)
import Control.Arrow
ask' :: Kleisli IO () String
ask' = Kleisli (\()->getLine) >>> (putStrLn &&& id) >>> arr snd
我不认为写这样的代码是个好主意。
I want to sequentially compose two monad actions in Haskell, discarding any value produced by the second, and passing the argument to both actions.
这听起来像 Reader
——函数类型 r -> m a
与 ReaderT r m a
同构,monad 通过隐式插入相同的 r
值来工作进入所有 "holes." 例如:
import Control.Applicative
import Control.Monad.Reader
example :: IO String
example = getLine >>= discarding putStrLn
discarding :: Monad m => (a -> m b) -> a -> m a
discarding action = runReaderT (ReaderT action *> ask)
你想要的运算符是这样的:
action `thingy` extra = action >>= discarding extra
当然 discarding
有一个更简单的实现:
discarding :: Applicative f => (a -> f b) -> a -> f a
discarding action a = action a *> return a
...所以最后我认为这真的是代码高尔夫。但在一个更复杂的程序中,这是一个更大规模的常见模式,它可能值得一试。基本上,如果你有:
a0 :: r -> m a0
a1 :: r -> m a1
.
.
.
an :: r -> m an
那么就是:
ReaderT a0 :: ReaderT r m a0
ReaderT a1 :: ReaderT r m a1
.
.
.
ReaderT an :: ReaderT r m an
然后:
runReaderT (ReaderT a0 <* ReaderT a1 <* ... <* ReaderT an) :: r -> m a0
为了完整起见,在这种特殊情况下 (IO
) monad,您也可以为此目的滥用 bracket
:
bracket getLine putStrLn return
但我强烈反对,因为这比原来的 do
-notation 块可读性差得多,它太丑了。
如前所述,在这种特殊情况下,为结果命名似乎是最好的方法。
另见 Should do-notation be avoided in Haskell?
我想在 Haskell 中顺序组合两个 monad 动作,丢弃第二个产生的任何值,并将参数传递给两个动作。目前我正在使用这样的 do-block:
ask = do
result <- getLine
putStrLn result
return result
我希望写得更自由、更整洁一点,所以我尝试了这个:
ask' = getLine <* putStrLn
然而,这甚至没有类型检查,问题是 <*
does not transfer the result of the first action to the second. I want to chain the actions like >>=
does, but not change the result. The type should be (a -> m b) -> (a -> m c) -> (a -> m b)
, but Hoogle 没有产生合适的结果。实现这个功能组合的运算符是什么?
作为一种趋势,如果您在两个不同的地方使用同一个值,可能 最好在清晰的 do
块中为其命名,而不是而不是按毫无意义的风格。
cartesian monoidal categories, known to Haskellers as arrows 捕获了将信息流拆分为不同操作的抽象概念。就您而言,您基本上是在 IO
Kleisli 类别中工作:
import Prelude hiding (id)
import Control.Arrow
ask' :: Kleisli IO () String
ask' = Kleisli (\()->getLine) >>> (putStrLn &&& id) >>> arr snd
我不认为写这样的代码是个好主意。
I want to sequentially compose two monad actions in Haskell, discarding any value produced by the second, and passing the argument to both actions.
这听起来像 Reader
——函数类型 r -> m a
与 ReaderT r m a
同构,monad 通过隐式插入相同的 r
值来工作进入所有 "holes." 例如:
import Control.Applicative
import Control.Monad.Reader
example :: IO String
example = getLine >>= discarding putStrLn
discarding :: Monad m => (a -> m b) -> a -> m a
discarding action = runReaderT (ReaderT action *> ask)
你想要的运算符是这样的:
action `thingy` extra = action >>= discarding extra
当然 discarding
有一个更简单的实现:
discarding :: Applicative f => (a -> f b) -> a -> f a
discarding action a = action a *> return a
...所以最后我认为这真的是代码高尔夫。但在一个更复杂的程序中,这是一个更大规模的常见模式,它可能值得一试。基本上,如果你有:
a0 :: r -> m a0
a1 :: r -> m a1
.
.
.
an :: r -> m an
那么就是:
ReaderT a0 :: ReaderT r m a0
ReaderT a1 :: ReaderT r m a1
.
.
.
ReaderT an :: ReaderT r m an
然后:
runReaderT (ReaderT a0 <* ReaderT a1 <* ... <* ReaderT an) :: r -> m a0
为了完整起见,在这种特殊情况下 (IO
) monad,您也可以为此目的滥用 bracket
:
bracket getLine putStrLn return
但我强烈反对,因为这比原来的 do
-notation 块可读性差得多,它太丑了。
如前所述,在这种特殊情况下,为结果命名似乎是最好的方法。
另见 Should do-notation be avoided in Haskell?