【问题标题】:Haskell: Monads of monadHaskell:单子的单子
【发布时间】:2015-08-17 12:18:20
【问题描述】:

我正在学习一些 Haskell,但我在使用这些 Monad 时遇到了一些问题,我了解它们并且知道它们是关于什么的,但在这种特殊情况下我遇到了一些问题。在 LYAH 上学习它时,我遇到了一个练习,该练习是关于计算你可以用一个骑士(来自国际象棋游戏)在 3 个动作中达到的位置,我们使用了这样的列表单子:

假设,

type KnightPos = (Int,Int)

moveKnight :: KnightPos -> [KnightPos]
moveKnight (c,r) = do
   (c',r') <- [(c+2,r-1),(c+2,r+1),(c-2,r-1),(c-2,r+1)
              ,(c+1,r-2),(c+1,r+2),(c-1,r-2),(c-1,r+2)
              ]
   guard (c' `elem` [1..8] && r' `elem` [1..8])
   return (c',r')

这很有效,如果我将我的位置赋予这个函数,它会成功计算未来可能的位置,但现在我希望在其中实现 Writer monad,以便我可以检索我是如何到达这一点的。 所以我做了这个功能,

假设,

type KnightRoute = Writer [KnightPos] KnightPos

moveKnight' :: KnightPos -> [KnightRoute]
moveKnight' (c,r) = do
   (c',r') <- [(c+2,r-1),(c+2,r+1),(c-2,r-1),(c-2,r+1)
              ,(c+1,r-2),(c+1,r+2),(c-1,r-2),(c-1,r+2)
              ]
   guard (c' `elem` [1..8] && r' `elem` [1..8])
   return $ toKr (c',r') (c,r)
 where toKr pos oldpos = Writer (pos,[oldpos])

如果我给它一个 KnightPos 它会起作用,但是使用 monads 我无法从 KnightRoute 中提取一个 KnightPos 以再次执行该函数...

*Main> let a = moveKnight' (2,4) !! 0
*Main> runWriter a
((4,3),[(2,4)])
*Main> a >>= moveKnight'

<interactive>:4:7:
Couldn't match type ‘[]’ with ‘Writer [KnightPos]’
Expected type: KnightPos -> Writer [KnightPos] KnightRoute
  Actual type: KnightPos -> [KnightRoute]
In the second argument of ‘(>>=)’, namely ‘moveKnight'’
In the expression: a >>= moveKnight'

我明白为什么它不起作用,我从我的 Writer 中提取了 (4,3),然后我将它交给了 KnightPos'。但是KnightPos' 返回KnightRoute 的列表,我需要KnightRoute,这是逻辑错误,但我不知道该怎么做。有没有一种简单的方法可以用 Monads 做到这一点?

提前致谢:)

【问题讨论】:

  • 请注意,将WriterWriterT 与列表一起用作log-monoid 效率非常低(二次时间),因为您一直在向右追加。差异列表或更专业的容器是更好的选择。
  • 我最近了解到,确实使用差异列表似乎更有效。在任何情况下,我们都应该每次都使用差异列表作为 log-monoids,因为它只是更快?

标签: haskell monads


【解决方案1】:

这种“两个 monad 的组合”在 Haskell 中是极其常见的事情。幸运的是,该语言足够灵活,我们可以很好地对其进行抽象。

从数学上讲,您想要的是composition of two functors。而不是那种新类型,这通常用 transformers 的概念来表达:不是直接使用Writer monad,而是使用WriterT monad 转换器。 WriterT w [] abasically the same[Writer w a],所以在你的情况下你可以使用:

import Control.Monad.Trans.Class
import Control.Monad.Trans.Writer

moveKnight'' :: KnightPos -> WriterT [] [KnightPos] KnightPos
moveKnight'' (c,r) = do
   (c',r') <- lift [(c+2,r-1),(c+2,r+1),(c-2,r-1),(c-2,r+1)
                   ,(c+1,r-2),(c+1,r+2),(c-1,r-2),(c-1,r+2)
                   ]
   guard (c' `elem` [1..8] && r' `elem` [1..8])
   tell [(c,r)]
   return (c',r')

【讨论】:

  • 谢谢它的工作!我会更多地研究单子变压器。我不知道他们:)
【解决方案2】:

你可以写

a' :: Int -> KnightRoute
a' i = a >>= \p -> moveKnight' p !! i

这里i 用于消除作者的内部列表。而且,由于懒惰,您可以将Int -&gt; a 变成[a]

asList :: (Int -> a) -> [a]
asList f = map f [1..]

那么a'的所有路由列表就是

a's :: [KnightRoute]
a's = asList a'

把所有东西放在一起:

moveKnight :: KnightRoute -> [KnightRoute]
moveKnight k = map (\i -> k >>= \p -> moveKnight' p !! i) [1..]

【讨论】:

  • 您好,非常感谢您的回答。对不起,我没看清楚……你写的是伪代码吗?我看不到你的名为“a”的变量是什么,因为你将它用作单子但从未定义它?也许我错了:)
  • @Bryce Tichit,不客气。 a 是你的 a: let a = moveKnight' (2,4) !! 0,即它只是一个例子。实际功能 moveKnight 适用于您提供的任何 KnightRoute
  • 我现在明白这是一种非常聪明的方法,因为它使用了惰性,我喜欢它,它显示了 Haskell 的强大:)
猜你喜欢
  • 1970-01-01
  • 2021-12-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多