理性的计划者:不理解练习57

时间:2015-07-04 19:48:35

标签: scheme logic racket minikanren

On Exercise(或entry?)57,我只是不了解逻辑是如何流动的。问题是:给定

(define teacupo
  (lambda (x)
    (conde
      ((= tea x ) #s)
      ((= cup x ) #s)
      (else #u))))

其中' ='实际上是三栏统一(?)运算符。运行以下内容:

(run* (r)
  (fresh (x y)
    (conde
      ((teacupo x) (= #t y) #s)
      ((= #f x) (= #t y))
      (else #u)
    (= (cons x (cons y ())) r)))

这本书给出了答案:

((tea #t) (cup #t) (#f #t))

我原以为答案应该是:

(((tea cup) #t) (#f #t))

我的理由是' x' in(teacupo x)应首先通过其所有解决方案,并统一到其所有解决方案的列表中。但似乎茶杯一次只放弃其中一种解决方案。这让我很困惑,因为我对conde的解释是,使用它给出的规则,你应该通过conde的行,并且在一行成功之后,假装它失败了,刷新变量并找到成功的下一行。考虑到解决方案的工作方式,似乎代码中的conde会回到成功的行,然后迫使teaupo conde失败并放弃下一个可能的值。再说一遍,我原以为茶杯解决方案会放弃列表中的所有conde解决方案,然后继续进行外部conde调用。任何人都可以向我提供指导,说明为什么它按照书中提供的方式工作而不是我推理的方式?

2 个答案:

答案 0 :(得分:4)

  

我的理由是' x'在(茶杯x)应该有它的conde   首先完成所有解决方案,并统一到所有解决方案的列表中   它的解决方案。

变量x一次统一为一个值。 表单(run* (x) <goals>)会收集符合目标的x值。

> (run* (x)
    (teacupo x))
'(tea cup)

(conde
  ((teacupo x) (== #t y))
  ((== #f x)   (== #t y)))

有两种方法可以取得成功:要么达到目标(teacupo x)x属于teacup - 或者目标{{1符合,(== #f x)(统一到)x

简而言之,#f会在run*一个可能的值中运行,以便收集满足所有目标的值。这意味着x一次统一为一个值。

一个更简单的例子:

x

想要在DrRacket中尝试片段的人的完整代码:

> (run* (x)
  (fresh (y)
    (== y 10)
    (conde
     [(== x 1) (== y 10)]
     [(== x 2) (== y 20)]
     [(== x 3) (== y 10)])))
'(1 3)

答案 1 :(得分:3)

(teacupo x)表示“成功两次:xtea统一,第二次与x统一cup < / em>的。然后,

(conde
  ((teacupo x) (= #t y) #s)
  ((= #f x) (= #t y))        ; you had a typo here
  (else #u)

表示,

  • (teacupo x)生成的每个解决方案,同时y#t统一并成功;还有
  • (= #f x)生成的每个解决方案,同时y#t统一并成功;还有
  • 不再生成解决方案

因此,x中的每个(tea cup)y中的(#t)配对,x中的(#f)y配对在(#t)中{1}},形成r;然后报告r,即收集到解决方案的最终结果列表中,并提供( (tea #t) (cup #t) (#f #t) )

  

“似乎茶杯一次只放弃其中一种解决方案。”

是的,这在概念上是完全正确的。

  

“一行成功后,假装失败,刷新变量并找到成功的下一行。”

是的,但如果条件(或后续目标)成功多次,则每一行都可以多次成功。

  

“似乎代码中的conde回到了成功的行,然后迫使teaupo conde失败并放弃下一个可能的值。”

它实际上准备提前生产它们(但是作为流,而不是列表),然后每个单独处理,通过整个后续步骤链进行,直到成功到达最后一步,或链被打破,被#u或其他失败的目标缩短。因此,当前一个处理完成时,尝试下一个。

在伪代码中:

for each x in (tea cup):
   for each y in (#t):    ; does _not_ introduce separate scope for `y`;
       collect (x y)      ;   `x` and `y` belong to the same logical scope
for each x in (#f):       ;   so if `x` is used in the nested `for` too,
   for each y in (#t):    ;   its new value must be compatible with the 
       collect (x y)      ;   one known in the outer `for`, or else 
for each _ in ():         ;   it will be rejected (x can't be two different
       collect (x y)      ;   things at the same time)

至于为什么以这种方式工作,我可以指向another answer of mine,这可能会有所帮助(尽管它不使用Scheme语法)。

使用它,作为一个例子,我们可以将你的测试写成一个Haskell代码,它实际上在功能上等同于我认为的书的代码(当然在这个特定的情况下),

data Val = Fresh | B Bool | S String | L [Val] deriving Show 
type Store = [(String,Val)]

teacupo x =   unify x (S "tea") &&: true               -- ((= tea x ) #s)
          ||: unify x (S "cup") &&: true               -- ((= cup x ) #s)
          ||: false                                    -- (else #u)

run = [[("r", Fresh)]]                                 -- (run* (r) ......
  >>: (\s -> [ s ++: [("x", Fresh), ("y", Fresh)] ])   -- (fresh (x,y)
  >>:                                                  -- (conde  
    (    teacupo "x"          &&:  unify "y" (B True)  
                              &&:  true                -- ((teacupo x) (= #t y) #s)
     ||: unify "x" (B False)  &&:  unify "y" (B True)  -- ((= #f x) (= #t y))
     ||: false                                         -- (else #u)
    ) 
     &&: project ["x", "y"] (unify "r" . L)            -- (= r (list x y))
  >>:                                                  
     reporting ["r"]                                   --              ...... )

reporting names store = [[a | a@(n,_) <- store, elem n names]]

只有极少的实现,足以使上面的代码工作,

project vars kont store = 
     kont [val | var <- vars, (Just val) <- [lookup var store]] store

unify :: String -> Val -> Store -> [Store]
unify sym val store = 
   let 
      (Just v) = (lookup sym store)
   in 
      case (val_unify v val) of
        Just newval -> [replace_val sym newval store]  -- [updated store], if unifies
        Nothing     -> []                              -- couldn't unify - reject it

val_unify v     Fresh             = Just v             -- barely working,
val_unify Fresh  v                = Just v             --  initial
val_unify (B v) (B u) | v == u    = Just (B v)         --  implementation
                      | otherwise = Nothing
val_unify (S v) (S u) | v == u    = Just (S v)
                      | otherwise = Nothing
val_unify  _     _                = Nothing

replace_val s n ((a,b):c) | s == a    = (a,n) : c
                          | otherwise = (a,b) : replace_val s n c

产生输出

  

*主&GT;跑   [[(“r”,L [S“tea”,B True])],[(“r”,L [S“cup”,B True])],[(“r”,L [B False, B真])]]

如果我们将翻译的conde表达式中的第二行更改为

       ||: unify "x" (B False)  &&:  unify "x" (B True)  -- ((= #f x) (= #t x))

我们确实只得到两个结果,

  

*主&GT;跑   [[(“r”,L [S“tea”,B True])],[(“r”,L [S“cup”,B True])]]