【问题标题】:Turtle Graphics as a Haskell Monad作为 Haskell Monad 的 Turtle Graphics
【发布时间】:2012-10-31 03:30:21
【问题描述】:

我正在尝试在 Haskell 中实现 turtle graphics。目标是能够编写这样的函数:

draw_something = do
    forward 100
    right 90
    forward 100
    ...

然后让它产生一个点列表(可能带有其他属性):

> draw_something (0,0) 0        -- start at (0,0) facing east (0 degrees)
[(0,0), (0,100), (-100,100), ...]

我所有这些都以“正常”的方式工作,但我未能将其实现为 Haskell Monad 并使用 do-notation。基本代码:

data State a = State (a, a) a -- (x,y), angle
    deriving (Show, Eq)

initstate :: State Float
initstate = State (0.0,0.0) 0.0


-- constrain angles to 0 to 2*pi
fmod :: Float -> Float
fmod a 
    | a >= 2*pi = fmod (a-2*pi)
    | a <  0    = fmod (a+2*pi)
    | otherwise = a

forward :: Float -> State Float -> [State Float]
forward d (State (x,y) angle) = [State (x + d * (sin angle), y + d * (cos angle)) angle]

right :: Float -> State Float -> [State Float]
right d (State pos angle) = [State pos (fmod (angle+d))]


bind :: [State a] -> (State a -> [State a]) -> [State a]
bind xs f = xs ++ (f (head $ reverse xs))

ret :: State a -> [State a]
ret x = [x]

有了这个,我现在可以写了

> [initstate] `bind` (forward 100) `bind` (right (pi/2)) `bind` (forward 100)
[State (0.0,0.0) 0.0,State (0.0,100.0) 0.0,State (0.0,100.0) 1.5707964,State (100.0,99.99999) 1.5707964]

并得到预期的结果。但是我不能让它成为Monad 的实例。

instance Monad [State] where
    ...

结果

`State' is not applied to enough type arguments
Expected kind `*', but `State' has kind `* -> *'
In the instance declaration for `Monad [State]'

如果我将列表包装在一个新对象中

data StateList a = StateList [State a]

instance Monad StateList where
    return x = StateList [x]

我明白了

    Couldn't match type `a' with `State a'
      `a' is a rigid type variable bound by
        the type signature for return :: a -> StateList a 
          at logo.hs:38:9
    In the expression: x        
    In the first argument of `StateList', namely `[x]'
    In the expression: StateList [x]

我尝试了各种其他版本,但始终无法按我的意愿运行。我究竟做错了什么?我理解错了什么?

【问题讨论】:

  • 应该可以实现一个turtlegraphics monad,增加海龟位置和航向的历史。但是您尝试实现Monad 实例的方式将用于表示此历史中数字的类型与一般值的类型混淆了。您的 retbind 关注仅将状态视为值的计算。但是return&gt;&gt;= 应该解释如何组织有关任何类型值的计算,其中一些还可能会扩展海龟的轨迹。
  • 如果您希望“monad”生成点列表,那么您可能需要 Writer+State 而不是 State。 Writer 通常是“积累的单子”,尽管它可以以其他方式工作。为了取得进展,您还需要了解为什么状态单子是由 newtype 包装的函数 - 它们是从当前状态到一对新状态加上答案的函数,而不仅仅是新状态。通常答案是unit aka ()。例如 forward 通常是 forward :: Float -&gt; Turtle () 假设一个名为 Turtle 的单子。

标签: haskell monads turtle-graphics


【解决方案1】:

您正在设计的 monad 需要有两个类型参数。一个用于保存的轨迹(将针对特定的do 序列进行修复),另一个用于计算结果。

您还需要考虑如何组合两个turtle-monadic 值,以便绑定操作具有关联性。例如,

right 90 >> (right 90 >> forward 100)

必须等于

(right 90 >> right 90) >> forward 100

(当然对于&gt;&gt;= 等也是如此)。这意味着如果你用点列表来表示海龟的历史,绑定操作很可能不能将点列表附加在一起; forward 100 单独会产生类似 [(0,0),(100,0)] 的结果,但是当它带有旋转前缀时,保存的点也需要旋转。


我想说最简单的方法是使用Writer monad。但我不会保存点,我只会保存海龟执行的动作(这样我们在组合值时不需要旋转点)。类似的东西

data Action = Rotate Double | Forward Double

type TurtleMonad a = Writer [Action] a

(这也意味着我们不需要跟踪当前方向,它包含在动作中。)然后您的每个函数只需将其参数写入Writer。最后,您可以从中提取最终列表并制作一个简单的函数,将所有动作转换为点列表:

track :: [Action] -> [(Double,Double)]

更新:与其使用[Action],不如使用Data.Sequence 中的Seq。这也是一个monoidconcatenating two sequences 非常快,它的摊销复杂度是 O(log(min(n1,n2))),与 O(n1) 相比>(++)。所以改进的类型是

type TurtleMonad a = Writer (Seq Action) a

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-03-15
    • 2016-07-04
    • 2018-06-14
    • 1970-01-01
    • 2018-12-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多