8

我想在 Haskell 中按顺序组合两个 monad 动作,丢弃第二个产生的任何值,并将参数传递给这两个动作。目前我正在使用这样的do-block:

ask = do
  result <- getLine
  putStrLn result
  return result

我希望写这个更自由和整洁,所以我尝试了这个:

ask' = getLine <* putStrLn

但是,这甚至没有类型检查,问题是<*不会将第一个操作的结果传输到第二个操作。我想像这样链接动作>>=,但不改变结果。类型应该是(a -> m b) -> (a -> m c) -> (a -> m b),但Hoogle没有产生合适的结果。实现此功能组合的运算符是什么?

4

3 回答 3

9

作为一种趋势,如果你在两个不同的地方使用一个值,最好一个清晰的do块中给它一个名字,而不是强调毫无意义的风格。

将信息流拆分为不同动作的抽象概念由笛卡尔幺半群类别捕获,Haskellers 将其称为箭头。就您而言,您基本上是在IOKleisli 类别中工作:

import Prelude hiding (id)
import Control.Arrow

ask' :: Kleisli IO () String
ask' = Kleisli (\()->getLine) >>> (putStrLn &&& id) >>> arr snd

我认为编写这样的代码不是一个好主意。

于 2015-12-08T17:59:20.987 回答
2

为了完整起见,在这种特殊情况下 (the IO) monad,您也可以bracket为此目的滥用:

bracket getLine putStrLn return

但我强烈反对它,因为这将比原来的do-notation 块可读性差得多,它只是丑陋。

如前所述,在这种特殊情况下,命名结果似乎是最好的方法。

另请参阅Haskell 中是否应避免使用 do-notation?

于 2015-12-09T17:58:42.460 回答
2

我想在 Haskell 中按顺序组合两个 monad 动作,丢弃第二个产生的任何值,并将参数传递给这两个动作。

在我看来,这听起来像是Reader——函数类型r -> m a与 同构ReaderT r m a,并且 monad 通过将相同的r值隐式插入所有“洞”来工作。例如:

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
于 2015-12-09T01:46:29.670 回答