康达, 康迪, 康德, 康杜
Posted
技术标签:
【中文标题】康达, 康迪, 康德, 康杜【英文标题】:conda, condi, conde, condu 【发布时间】:2012-06-06 07:09:39 【问题描述】:我正在阅读the Reasoned Schemer。
我对@987654323@ 的工作原理有一些直觉。
但是,我找不到关于 conde
/conda
/condu
/condi
做什么的正式定义。
我知道https://www.cs.indiana.edu/~webyrd/,但这似乎有例子而不是定义。
在某处有conde
、conda
、condi
、condu
的正式定义吗?
【问题讨论】:
【参考方案1】:用 Prolog 的话来说,
condA
是 “软剪辑” 又名 *->
,其中 A *-> B ; C
类似于 (A, B ; not(A), C)
,只是 更好 ;而
condU
是“承诺的选择”,是once
和软切的组合,因此(once(A) *-> B ; false)
表示(A, !, B)
(带有切里面):
condA: A *-> B ; C % soft cut,
% (A , B ; not(A) , C)
condU: once(A) *-> B ; C % committed choice,
% (A , !, B ; not(A) , C)
(其中;
表示“或”,,
表示“和”,即disjunction 和 conjunction 目标)。
在condA
中,如果目标A
成功,则所有解决方案都会传递到第一个子句B
,并且不会尝试替代子句C
。
在 condU
中,once/1
只允许其参数目标成功一次(如果有的话,只保留一个解决方案)。
condE
是连词的简单析取,condI
是其成分的解之间交替的析取, 交错其中的流。
这是一个尝试将本书的代码忠实地翻译成 18 行 Haskell 的代码,不包括逻辑变量和统一,这主要是一个懒惰的 Lisp 语法。(*) 看看这是否说明了问题:
顺序流组合(书名“mplus
”):
(1) [] ++: ys = ys
(2) (x:xs) ++: ys = x : (xs ++: ys)
交替流组合(“mplusI
”):
(3) [] ++/ ys = ys
(4) (x:xs) ++/ ys = x : (ys ++/ xs)
连续供稿(“bind
”):
(5) [] >>: g = []
(6) (x:xs) >>: g = g x ++: (xs >>: g)
交替供稿(“bindI
”):
(7) [] >>/ g = []
(8) (x:xs) >>/ g = g x ++/ (xs >>/ g)
“OR
”目标组合(“condE
”):
(9) (f ||: g) x = f x ++: g x
“交替OR
”目标组合(“condI
”):
(10) (f ||/ g) x = f x ++/ g x
“AND
”目标组合(“all
”):
(11) (f &&: g) x = f x >>: g
“交替AND
”目标组合(“allI
”书):
(12) (f &&/ g) x = f x >>/ g
特殊目标
(13) true x = [x] -- a sigleton list with the same solution repackaged
(14) false x = [] -- an empty list, meaning the solution is rejected
目标在给定问题的(可能部分)解决方案的情况下产生(可能是更新的)解决方案的流(可能是空的)。
all
的重写规则是:
(all) = true
(all g1) = g1
(all g1 g2 g3 ...) = (\x -> g1 x >>: (all g2 g3 ...))
= g1 &&: (g2 &&: (g3 &&: ... ))
(allI g1 g2 g3 ...) = (\x -> g1 x >>/ (allI g2 g3 ...))
= g1 &&/ (g2 &&/ (g3 &&/ ... ))
condX
的重写规则是:
(condX) = false
(condX (else g1 g2 ...)) = (all g1 g2 ...) = g1 &&: (g2 &&: (...))
(condX (g1 g2 ...)) = (all g1 g2 ...) = g1 &&: (g2 &&: (...))
(condX (g1 g2 ...) (h1 h2 ...) ...) = (ifX g1 (all g2 ...)
(ifX h1 (all h2 ...) (...) ))
要得到最终的condE
和condI
的翻译,无需实现本书的ifE
和 ifI
,因为它们进一步简化为简单的运算符组合,所有运算符都被认为是右结合:
(condE (g1 g2 ...) (h1 h2 ...) ...) =
(g1 &&: g2 &&: ... ) ||: (h1 &&: h2 &&: ...) ||: ...
(condI (g1 g2 ...) (h1 h2 ...) ...) =
(g1 &&: g2 &&: ... ) ||/ (h1 &&: h2 &&: ...) ||/ ...
因此在 Haskell 中不需要任何特殊的“语法”,简单的二进制中缀运算符就足够了。任何组合都可以在任何地方使用,根据需要使用&&/
而不是&&:
。但另一方面,condI
也可以实现为接受要实现的目标集合(列表、树等)的函数,这将使用一些聪明的策略来挑选它们最可能或最需要等,而不仅仅是像||/
运算符(或本书的ifI
)中的简单二进制交替。
接下来,本书的condA
可以由两个新的运算符~~>
和||~
一起工作来建模。我们可以以自然的方式使用它们,例如
g1 ~~> g2 &&: ... ||~ h1 ~~> h2 &&: ... ||~ ... ||~ gelse
可以直观地读作“IF g1 THEN g2 AND ... OR-ELSE IF h1 THEN ... OR-ELSE gelse
”:
"IF-THEN
" 目标组合是产生一个 "try" 目标,该目标必须与失败继续目标一起调用:
(15) (g ~~> h) f x = case g x of [] -> f x ; ys -> ys >>: h
"OR-ELSE
" 目标组合 try 目标和简单目标只需将其 try 目标称为第二个失败时目标,因此仅此而已比用于操作数自动分组的便捷语法:
(16) (g ||~ f) x = g f x
“OR-ELSE
”||~
运算符的绑定能力低于“IF-THEN
”~~>
运算符并且也具有右关联性,而~~>
运算符的绑定能力仍低于&&:
等等,上述示例的合理分组会自动生成为
(g1 ~~> (g2 &&: ...)) ||~ ( (h1 ~~> (h2 &&: ...)) ||~ (... ||~ gelse ...) )
||~
链中的最后一个目标因此必须是一个简单的目标。这实际上没有限制,因为 condA
形式的最后一个子句无论如何都等同于简单的“AND
”-其目标组合(或者也可以使用简单的false
)。
就是这样。如果我们愿意,我们甚至可以有更多类型的 try 目标,由不同类型的“IF
”运算符表示:
在成功的子句中使用交替提要(如果书中有的话,可以称为 condAI
建模):
(17) (g ~~>/ h) f x = case g x of [] -> f x ; ys -> ys >>/ h
只使用成功的解流一次来产生cut效果,模拟condU
:
(18) (g ~~>! h) f x = case g x of [] -> f x ; (y:_) -> h y
所以,最后,本书condA
和condU
的重写规则很简单:
(condA (g1 g2 ...) (h1 h2 ...) ...) =
g1 ~~> g2 &&: ... ||~ h1 ~~> h2 &&: ... ||~ ...
(condU (g1 g2 ...) (h1 h2 ...) ...) =
g1 ~~>! g2 &&: ... ||~ h1 ~~>! h2 &&: ... ||~ ...
(*) 即:
简单并列就是柯里化函数应用, f a b c =~= (((f a) b) c) =~= f(a, b, c)
(\ a -> b )
是 lambda 函数, (lambda (a) b)
foo x = y
是 foo = (\ x -> y )
的快捷方式
a @@ b = y
是(@@) a b = y
的快捷方式,定义了中缀运算符@@
括号 (
)
仅用于分组
[]
是空列表,和
:
表示 cons -- 都作为构造函数(lazy,因为整个语言都是lazy,即按需调用 ),在定义中=
的右边;作为解构模式,在左边(或在模式匹配case
表达式中)。
【讨论】:
5 向上箭头堆栈溢出不值得此响应。我希望你把它变成一篇博文或其他东西。 作为后续:是 COND 吗? == 或者?和所有? == 和? ? @user1383359 或多或少。 CONDe 是 Prolog 的 OR(析取),ALL - Prolog 的 AND(析取)。 CONDi、ALLi 是子目标解决方案合并顺序的一种变体,试图将非生产性行为的预防提前内置到系统中。【参考方案2】:Reasoned Schemer 涵盖 conda(软剪辑)和 condu(承诺选择)。您还可以在 William Byrd 出色的 dissertation on miniKanren 中找到对他们行为的解释。您已将此帖子标记为关于 core.logic。需要明确的是,core.logic 是基于 miniKanren 的更新版本,而不是 The Reasoned Schemer 中介绍的版本。 miniKanren 总是交错分离目标 - condi 并且交错变体不再存在。 conde 是 condi 现在。
【讨论】:
FWIW 今天阅读的人们,The Reasoned Schemer 的第二版现在默认使用交错的 conde。【参考方案3】:例如,使用 core.logic:
conde 将运行每个组,如果至少一个组成功则成功,并返回所有成功组的所有结果。
user> (run* [w q]
(conde [u#]
[(or* [(== w 1) (== w 2)])
(== q :first)]
[(== q :second)]))
([_0 :second] [1 :first] [2 :first])
conda 和 condu:都将在第一个成功组后停止(从上到下)
conda 仅返回第一个成功组的所有结果。
user> (run* [w q]
(conda [u#]
[(or* [(== w 1) (== w 2)])
(== q :first)]
[(== q :second)]))
([1 :first] [2 :first])
condu 只返回第一个成功组的结果。
user> (run* [w q]
(condu [u#]
[(or* [(== w 1) (== w 2)])
(== q :first)]
[(== q :second)]))
([1 :first])
不知道 condi 做了什么。
【讨论】:
不是一个正式的定义,但它可能会有所帮助 正如 dnolen 在他的回答中所写,core.logic 的 conde 是本书的 condi。本书的 conde 按顺序处理其子目标。如果第一个连续的子目标产生了无限个答案流,则组合的解决方案流中不会出现其他成功的子目标的解决方案。本书的 condi 通过交错流来解决这个问题。 太棒了!现在我明白了。什么时候没有赶上流/交错部分。从现在开始重新访问 core.logic src【参考方案4】:根据 ISO Prolog 核心标准,控制结构,例如 (,)/2、(;)/2 和 (->)/2 是透明的。 (*->)/2 在 ISO Prolog 核心标准中找不到,但通常 Prolog 系统实现它也很透明。
这意味着无法翻译:
once(A) *-> B;C
进入A, !, B; C
。因为后者可能嵌入在其他控制结构中,如果它们之间存在分离,这些选择点也会被切掉。另一方面,似乎是合理的,将其视为A -> B; C
,
简称为 ISO Prolog 核心标准if-then-else。如此定义的剪切行为例如对于打破重复循环很有用,而不会引发异常。通常的编程模式使用 if-then-else 更难归档。
【讨论】:
您可能是指像(X=1;X=2),(Y=1,!,Z=1 ; Z=2)
这样的目标只产生一个结果,而(X=1;X=2),(Y=1 -> Z=1 ; Z=2)
和(X=1;X=2),(once(Y=1) *-> Z=1 ; Z=2)
都产生两个结果?但是这本书一开始就没有!
或->
。它所拥有的只是这些流操作。
感谢您的链接。我的回答不是关于 Prolog 的翻译。其中的 Haskell 代码是对 book 中的 Scheme 代码的翻译。并且由于本书的语言没有删减,我认为这两个 Prolog sn-ps 也是将 conda
和 condu
正确翻译成 Prolog(即使它根本不是答案的目标;只是一个例子)。级联*->
应该不是问题,对吧?即使在 Prolog 中,这也是关于括号问题的,它总是可以通过定义辅助谓词来修复/明确,以防止剪切泄漏。
(相对而言,我假设很少有图书读者 / minikanren 用户 / 知道 Prolog ......即使是 Haskell 在当时也是一个前卫的选择,但它已经变得更加流行了这么多年,而且代码很简单,基本都是伪代码)。以上是关于康达, 康迪, 康德, 康杜的主要内容,如果未能解决你的问题,请参考以下文章