1

我正在用 Ruby 制作一个镜头组合器,但我不知道它的普遍接受的名称是什么。未知函数由两个具有相同源类型的镜头组成,它们的目标类型(使用 Benjamin C. Pierce 的术语)是一个哈希映射。未知函数接收这两个镜头并返回一个与原始镜头具有相同源类型和目标类型的新镜头。

它看起来像这样(Ruby 语法):

lens_a.get(source)
> {:title => "some title"}
lens_b.get(source)
> {:description => "some description"}
new_lens = unknown_function(lens_a, lens_b)
new_lens.get(source)
> {:title => "some title", :description => "some description"}

我正在尝试构建的组合器的图表可以在此演示文稿的幻灯片 18 上看到(幻灯片的标题是“合并?”)。

我查看了 Haskell 的镜头文档(我能理解的一小部分),但我不知道这是哪个组合器。

上面的 unknown_function 的标准名称是什么?如果这个镜头没有标准的名字,有没有几个标准的函数可以组合成它?如果没有,我可能会称之为合并。

4

2 回答 2

6

我相信在 Haskell 中,您的组合器大致正确的想法是

merge :: Lens' s t -> Lens' s t -> Lens' s (t, t)

可能概括为

merge :: Lens' s t -> Lens' s t' -> Lens' s (t, t')

这样您的两个目标可能在类型上有所不同。我们可以如下“实现”它,但这样做会暴露一个问题

merge l1 l2 = lens pull push where
  pull s          = (view l1 s, view l2 s)
  push s (t1, t2) = set l2 t2 (set l1 t1 s)

特别是,在Setter这个等式的一边,我们明确地排序了我们放回我们的价值观的方式。这意味着我们可以使用它merge来轻松构建违反镜头定律的镜头。特别是,它们违反了 PUT-GET 法。这是一个反例

newtype Only a = Only a

-- A lens focused on the only slot in Only
only :: Lens' (Only a) a
only inj (Only a) = Only <$> inj a

-- We merge this lens with *itself*
testLens :: Lens' (Only a) (a, a)
testLens = merge only only

-- and now it violates the lens laws
> view testLens (Only 1 & testLens .~ (1,2))
(2,2)

或者,用简单的英语来说,如果我们merge是一个镜头本身,那么Setter侧面会在同一位置变成两个连续的集合。这意味着如果我们尝试使用一对不同的值设置它,则只有第二个设置会持续存在,因此我们违反了 PUT-GET 法则。

lens库倾向于避开无效的镜头,因此该组合器不可用。我们最接近的是alongside具有以下(受限)类型

alongside :: Lens'  s     a
          -> Lens'  s'    a'
          -> Lens' (s,s') (a,a')

但是,如您所见,这确保了我们的也是一种产品类型,因此Setters 唯一地适用于源的每一侧。如果我们尝试编写一个dup Lens可以组合alongside构建的程序merge,我们将遇到与以前相同的问题

dup  :: Lens' a (a, a) -- there are two possible implementations, neither are lenses
dup1 inj a = fst <$> inj a
dup2 inj a = snd <$> inj a

现在,从 Ruby 的角度来看,所有这些都可能是相当技术性且毫无意义的。在 Ruby 中,您不会让编译器强制执行类型安全,因此您可能会将许多规则混合在一起并降低严格性。在这种情况下,merge使用其顺序写入语义来实现可能是有意义的。

实际上,根据您给出的示例,我指出的反例似乎在 Ruby 中甚至都不会发生,因为您的目标类型是 Hash 并且已经确保了唯一键。

但重要的是要注意,merge如果你足够努力地观察它,它可以用来制造不合适的镜片。从根本上说,这意味着在更复杂的场景中,使用merge很容易导致奇怪的错误,因为它违反了我们对镜头的直觉。这对 Ruby 来说可能不是一个大问题,因为无论如何复杂的抽象在 Ruby 社区中都是不受欢迎的,但这就是我们有法律的原因。

于 2014-04-04T15:37:27.400 回答
0

好像你想把两个镜头组合成一个遍历。我在任何地方都没有看到那个组合器,但我是镜头新手,所以我不确定它会被称为什么。

就像是:

compoundEye :: Traversable tl => tl (Lens s t a b) -> Traversal s t a b

但是,胡歌的结果并不乐观。

于 2014-04-04T15:21:02.903 回答