让我们考虑一个侏儒在隧道中游荡。我将定义一个代表这种类型的矮人。 这样的情况:
data X a = X { xs :: [a], i :: Int }
display :: X Bool -> IO ()
display X{..} = putStrLn (concatMap f xs) where { f True = "*" ; f False = "-" }
在这里,您会在隧道的一部分中看到一个矮人:
λ display x
-*---
发现a pointed container is an instance of Comonad
。我可以用这个
实例定义一个函数来模拟我的矮人向右移动:
shiftRight :: X Bool -> Bool
shiftRight x@X{..} | let i' = i - 1 in i' `isInRange` x && xs !! i' = True
| otherwise = False
请参阅:
λ traverse_ display $ scanl (&) x (replicate 4 (extend shiftRight))
-*---
--*--
---*-
----*
-----
引人注目的是,该操作适用于任何尖头容器中的任意数量的矮人, 因此可以根据需要扩展到整个矮人堡垒。我可以类似地定义一个函数 向左移动矮星,或以其他任何确定性方式移动。
但是现在,如果我想让我的矮人漫无目的地游荡怎么办?现在,我的“随机移动” 必须 仅在未将同一个矮星放置在左侧的情况下,才将矮星放置在右侧(因为那样会 ,并且绝对不能将两个矮人放在同一位置( 会使其中的一个变小)。换句话说,“随机移动” 必须是 linear (如 在线性堡垒上使用“线性逻辑” )。
我想到的一种方法是为矮人分配某种状态,以跟踪可用状态
为矮人移动,当我们确定位置为
由他们之一采取。这样,其余的矮人将无法采取这一行动。或者我们
可能会跟踪位置的可用性。 我正在考虑某种“单子” extendM
(它将与通常的extend
进行比较,而traverse
与fmap
进行比较。)
但是我不知道任何现有技术。
答案 0 :(得分:2)
解决此问题的最简单方法是使用MonadRandom
library,它为随机计算引入了new monad。因此,让我们使用随机数进行计算:
-- normal comonadic computation
type CoKleisli w a b = w a -> b
-- randomised comonadic computation
type RCoKleisli w a b = w a -> Rand b
现在,如何应用这个东西? extend
很简单:
halfApply :: Comonad w => (w a -> Rand b) -> (w a -> w (Rand b))
halfApply = extend
但这并不是很有效:它给了我们一个随机值的容器,而我们想要一个随机值的容器。换句话说,我们需要找到可以做w (Rand b) -> Rand (w b)
的事情。实际上,确实存在这样的功能:sequenceA
!如文档所述,如果将sequenceA
应用于w (Rand b)
,它将运行每个Rand
计算,然后累加结果以获得Rand (w b)
-这正是我们所要做的想!所以:
fullApply :: (Comonad w, Traversible w, Applicative f)
=> (w a -> f b) -> (w a -> f (w b))
fullApply c = sequenceA . extend c
从上面的类型签名中可以看到,这实际上适用于任何Applicative
(因为我们所需要的就是可以依次运行每个应用计算),但是要求w
是{ {1}}(因此我们可以遍历Traversible
中的每个值)。
(有关此类事情的更多信息,我建议this博客文章及其第二部分。如果您想了解上述技术的实际应用,我建议您回到my own probabilistic cellular automata library使用comonads而不是my own typeclass。)
这样可以回答您一半的问题;就是说,如何通过共性获得概率性行为。下半部分是:
…而且它绝不能将两个小矮人放在同一位置……
我不太确定,但是一种解决方案可能是将您的声波计算分为三个阶段:
w
mkDiffs :: X Dwarf -> Rand (X DwarfDiff)
。execDiffs :: X DwarfDiff -> X (DwarfDiff, [DwarfDiffed])
。上面使用的类型:
resolve :: X (Dwarf, [DwarfDiffed]) -> Rand (X Dwarf)
我所谈论的例子:
data Dwarf = Dwarf | NoDwarf
data DwarfDiff = MoveLeft | MoveRight | DontMove | NoDiff
data DwarfDiffed = MovedFromLeft | MovedFromRight | NothingMoved
如您所见,以上解决方案非常复杂。我还有另一种建议:不要对这个问题使用逗号!当您需要根据其上下文更新一个值时,Comonads非常好,但是同时更新多个值时很糟糕。问题是诸如myDwarfs = X [NoDwarf ,Dwarf ,NoDwarf ,Dwarf ,Dwarf ,Dwarf ] 0
mkDiffs myDwarfs
= X [NoDiff ,MoveRight ,NoDiff ,MoveLeft ,MoveRight ,DontMove ] 0
execDiffs (mkDiffs myDwarfs)
= X [(NoDiff,[NothingMoved]),(MoveRight,[NothingMoved]),(NoDiff,[MovedFromRight,MovedFromLeft]),(MoveLeft,[NothingMoved]),(MoveRight,[NothingMoved]),(DontMove,[MovedFromLeft])] 0
resolve (execDiffs (mkDiffs myDwarfs))
= X [NoDwarf ,NoDwarf ,Dwarf ,Dwarf ,Dwarf , Dwarf ] 0
之类的符号是zippers,它们将数据结构存储为单个“焦点”值和周围的“上下文”。就像我说的那样,这对于根据上下文更新关注的值非常有用,但是如果您需要更新多个值,则必须将计算插入到该值+上下文模型中……如上所述,这很棘手。因此,comonads可能不是此应用程序的最佳选择。