1

让我们考虑一个在隧道中徘徊的侏儒。因此,我将定义一个代表这种情况的类型:

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
-*---

发现一个指向容器是 的一个实例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))
-*---
--*--
---*-
----*
-----

壮观的是,同样的操作适用于任意数量的矮人,在任何尖头容器中,因此如果需要,可以扩展到整个矮人堡垒。我可以类似地定义一个向左移动小矮人的函数,或者以任何其他确定性的方式。

但是现在如果我想让我的小矮人漫无目的地四处游荡怎么办?现在我的“随机移动”只能在同一个小矮人没有被放在左边的情况下把一个小矮人放在右边(因为这会使两个小矮人从一个小矮人中脱颖而出),而且它绝不能把两个小矮人放在同一个地方(这将使两个侏儒)。换句话说,“随机移动”必须是线性 的(就像在 “线性逻辑”中一样,当应用于一个 comonadic 堡垒时。

我想到的一种方法是为矮人分配某种状态,以跟踪矮人的可用动作,当我们决定该位置由其中一个人占据时,从每个相关矮人中删除动作。这样一来,剩下的矮人就无法采取这一行动了。或者我们可以跟踪位置的可用性。我在想某种“单子” extendM 可能有用。 (它会与通常extend相比traversefmap 但我不知道任何现有技术。

4

1 回答 1

2

解决这个问题的最简单方法是使用MonadRandom,它为随机计算引入了一个新的 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! 正如文档所述,如果我们应用sequenceAa w (Rand b),它将运行每个Rand计算,然后累积结果以获得 a 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(因为我们只需要每个应用计算都可以依次运行),但必须wTraversible(因此我们可以遍历 中的每个值w)。

(关于这类事情的更多信息,我推荐这篇博文,以及它的第二部分。如果你想看到上述技术的实际效果,我推荐我自己的概率元胞自动机库,当时它仍然使用共单原子而不是我自己的类型类。)

这样就回答了你问题的一半;也就是说,如何使用comonads获得概率行为。下半场是:

……而且它绝不能将两个矮人放在同一个地方……</p>

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

  1. 将每个 dwarf 概率转换为 diff,说明该 dwarf 是向左移动、向右移动还是停留。为此操作键入:mkDiffs :: X Dwarf -> Rand (X DwarfDiff)
  2. 执行每个差异,但保持原始的矮人位置。此操作的类型:execDiffs :: X DwarfDiff -> X (DwarfDiff, [DwarfDiffed])
  3. 解决矮人相撞的情况。此操作的类型:resolve :: X (Dwarf, [DwarfDiffed]) -> Rand (X Dwarf)

上面使用的类型:

data Dwarf = Dwarf | NoDwarf
data DwarfDiff = MoveLeft | MoveRight | DontMove | NoDiff
data DwarfDiffed = MovedFromLeft | MovedFromRight | NothingMoved

我正在谈论的示例:

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

如您所见,上述解决方案非常复杂。我有一个替代建议:不要使用comonads 来解决这个问题!当您需要根据其上下文更新一个值时, Comonads非常有用,但在同时更新多个值时却很糟糕。问题是像你这样的comonads是拉链X,它将数据结构存储为单个“聚焦”值加上周围的“上下文”。正如我所说,这对于根据上下文更新焦点值非常有用,但是如果您需要更新多个值,则必须将计算硬塞到这个值+上下文模型中……正如我们在上面看到的,这可能非常棘手. 因此,comonads 可能不是此应用程序的最佳选择。

于 2019-08-30T02:34:26.857 回答