在有尖头的容器上随意走动

时间:2019-08-29 13:38:52

标签: haskell comonad

让我们考虑一个侏儒在隧道中游荡。我将定义一个代表这种类型的矮人。 这样的情况:

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进行比较,而traversefmap进行比较。) 但是我不知道任何现有技术。

1 个答案:

答案 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。)

这样可以回答您一半的问题;就是说,如何通过共性获得概率性行为。下半部分是:

  

…而且它绝不能将两个小矮人放在同一位置……

我不太确定,但是一种解决方案可能是将您的声波计算分为三个阶段:

  1. 将每个小矮人概率性地转换为一个差异,说明该小矮人将向左,向右移动还是停留。此操作的类型:w
  2. 执行每个差异,但保持原始的矮级位置。此操作的类型:mkDiffs :: X Dwarf -> Rand (X DwarfDiff)
  3. 解决矮人相撞的情况。此操作的类型: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可能不是此应用程序的最佳选择。