在OO Prolog中通过多方法碰撞恒星物体?

Tra*_*ers 5 prolog logtalk

我想知道如何将Prolog中的统一和OO结合起来.我想在术语对象上实现多方法调度.

如果没有术语对象和简单术语,我会执行以下操作,并且可以从多参数索引中获益:

collide_with(asteroid(_), asteroid(_)) :- /* case 1 */
collide_with(asteroid(_), spaceship(_,_)) :- /* case 2 */
collide_with(spaceship(_,_), asteroid(_)) :- /* case 3 */
collide_with(spaceship(_,_), spaceship(_,_)) :- /* case 4 */
Run Code Online (Sandbox Code Playgroud)

但上面只给出了一个确切的类型匹配.

如果我想要一个子类类型匹配,我该怎么办(可能有更多的太空船子类,如excelsior,galaxy等......在情况2,3和4中也应该匹配).

我还可以使用统一和索引吗?

再见

PS:这个例子来自这里没有Prolog解决方案:https:
//en.wikipedia.org/wiki/Multiple_dispatch

Dan*_*ons 3

你的问题有点乱:术语对象、多方法分派等等。Prolog 确实没有术语对象或分派,但我认为这个问题的精神很有趣。

\n\n

在我们拥有多种方法和多重分派之前,我们需要分派。我认为您担心的是您希望能够编写如下所示的过程:

\n\n
frob(spaceship(X, Y...)) :- % do something with spaceships\nfrob(asteroid(X, Y...))  :- % do something with asteroids\n
Run Code Online (Sandbox Code Playgroud)\n\n

然后你希望能够说,frob(excelsior(X, Y, ...))并以某种方式将其结束在第一个子句中。这显然不能开箱即用,但这并不意味着你不能让它工作。以下是我会尝试的方法:

\n\n

选择更简单的函子形状

\n\n

不要试图让它与 一起工作excelsior(...),而是改变你的表示方式以使其更容易内省。一个非常通用的方法可能如下所示:

\n\n
object(Type, Properties...)\n
Run Code Online (Sandbox Code Playgroud)\n\n

如果您不关心继承,这可能会起作用,但您确实关心。那么,如果您为子类型信息创建一个槽会怎么样?然后你可以在你关心的情况下匹配它,否则忽略它。你的结构看起来像这样:

\n\n
type(SubtypeInfo, Properties...)\n
Run Code Online (Sandbox Code Playgroud)\n\n

然后你可以这样写 frob :

\n\n
frob(spaceship(_, X, Y)) :- % stuff\n
Run Code Online (Sandbox Code Playgroud)\n\n

如果你用 Excelsior 调用它,它可能看起来像这样:

\n\n
?- frob(spaceship(excelsior(SpecialProperties...), X, Y)).\n
Run Code Online (Sandbox Code Playgroud)\n\n

换句话说,让您的术语在外部具有最通用的类​​型,并在内部内容中包含更具体的信息。

\n\n
frob2(spaceship(excelsior(_, ...), X, Y)) :- % do something with excelsiors\n
Run Code Online (Sandbox Code Playgroud)\n\n

使用元解释器

\n\n

编写您自己的 Prolog 方言是可能的。如果您向数据库添加一些关于您的类型是其子类型的事实,您自己的元解释器可以拦截评估过程并重试父类型。

\n\n

不幸的是,我对此并不擅长,下面的元解释器应该被视为一个有缺陷的草图/概念验证,而不是一个可以遵循的模型。

\n\n
:- op(500, xfx, is_kind_of).\n\nexcelsior is_kind_of spaceship.\n\nfrob(spaceship(X, Y)) :- !, write(\'I frobbed a spaceship\'), nl.\nfrob(_) :- write(\'I frobbed something else\'), nl.\n\nexecute(true).\nexecute((A,B)) :- execute(A), execute(B).\nexecute(A) :-\n    predicate_property(A, built_in)\n       -> call(A)\n       ;  execute_goal(A).\n\nexecute_goal(Goal) :- clause(Goal, Body), call(Body).\nexecute_goal(Goal) :- supertype_goal(Goal, NewGoal), execute_goal(NewGoal).\n\nsupertype_goal(Goal, NewGoal) :-\n    Goal =.. [Head, Term],\n    Term =.. [Type|Args],\n    Type is_kind_of Supertype,\n    NewTerm =.. [Supertype|Args],\n    NewGoal =.. [Head, NewTerm].\n
Run Code Online (Sandbox Code Playgroud)\n\n

这里的想法是尝试按原样执行目标,然后重新执行重写部分目标。虽然supertype_goal不是很通用,替换例程也不全面,但是可以说明意图:

\n\n
?- execute(frob(excelsior(this,that))).\nI frobbed something else\ntrue ;\nI frobbed a spaceship\ntrue ;\nI frobbed something else\ntrue ;\nfalse.\n
Run Code Online (Sandbox Code Playgroud)\n\n

是的,所以,不是很好,但是比我更熟练的 Prolog 用户可能可以清理它并使其工作。

\n\n

讨论

\n\n

Prolog 中的数据实际上只有两个位置:它可以存在于调用堆栈中,或者可以存在于数据库中。我展示的第一个方法实际上是第一个方法的示例:找到一种方法来根据您的目的重新打包“子类型”,以便它可以存在于调用堆栈中,而不会干扰(某些)统一。如果您仔细构建术语(并仔细编码),您可能可以完成这项工作,并且调试起来也不会很困难。但读起来可能有点困难。

\n\n

第二种方法使用数据库中的单独关系来具体化不同“子类型”之间的关系。一旦有了它,您需要修改解释器才能使用它。这说起来容易做起来难,而且有点棘手,但我不认为这是世界上最糟糕的主意。不过,仔细想想,你想要做的那种统一必须由元解释器来设计。

\n\n

您会发现 Logtalk 在“参数对象”和普通对象之间也有类似的二分法,“参数对象”的标识符本质上是完整的 Prolog 术语,而普通对象创建了一个完整的名称空间,将其封装在一个单独的数据库中。对于非参数对象,对象结构上的统一不会像术语那样发生。

\n\n

性能问题

\n\n

假设我在某个方法中采用两个对象作为参数。如果我使用第一种方法,我认为如果索引可用的话我会受益于索引,并且我不会太深入地研究术语\xe2\x80\x94,我认为一般编程应该更好。我不知道 Prolog 系统如何响应统一到某种结构的深处;我想他们做得很好,但我不知道参数索引。感觉会很烦恼。

\n\n

第二种方法根本站不住脚。如果我的层次结构可以是 N 类深,我可能会尝试 N^2 不同的组合。这听起来毫无成效。显然 Paulo 在 Logtalk 中找到了一些办法,它似乎不存在这个性能问题。

\n\n

双重调度转移

\n\n

当我学习 Smalltalk 时,这对我来说是一个很大的启示,所以如果你已经知道了,请原谅我。您可以使用“双重分派”在单分派语言中获得多重分派的类型优势。基本上,您让所有对象实现collide_with,并以“其他”对象作为参数,因此您拥有Asteroid::collide_with(Other)andShip::collide_with(Other)Bullet::collide_with(Other)。然后,这些方法中的每一个都调用 Other's collide_with_type,并传入 self. 您将获得一堆方法(其中许多方法将委托给另一方),但您可以在运行时安全地重新创建所有丢失的类型信息。

\n\n

我前段时间用 Lua 写了一个蹩脚的 Asteroids 克隆,你可以在其中看到它是如何工作的:

\n\n
-- double dispatch for post collision handling\nfunction Asteroid:collideWith(other)\n   return other:collideWithAsteroid(self)\nend\n\nfunction Asteroid:collideWithShot(s) \n   -- remove this asteroid from the map\n   if index[self] then\n      table.remove(asteroids, index[self])\n      index[self] = nil\n      s:remove()\n   end\nend\n\nfunction Asteroid:collideWithPlayer(p) \n   p:collideWithAsteroid(self)\nend\n\nfunction Asteroid:collideWithAsteroid(ast) end\n
Run Code Online (Sandbox Code Playgroud)\n\n

因此,您可以看到其中的一些内容:Asteroid:collideWithShot将小行星从游戏中移除,但它将委托给Asteroid:collideWithPlayer(p)Player:collideWithAsteroid(a)并且两颗小行星碰撞不会执行任何操作。

\n\n

Logtalk 中的基本草图如下:

\n\n
:- protocol(physical).\n\n  :- public(collides_with/1).\n\n:- end_protocol.\n\n:- object(asteroid, implements(physical)).\n\n  collides_with(Other) :- self(Self), Other::collides_with_asteroid(Self).\n\n  collides_with_asteroid(AnotherAsteroid).\n  collides_with_ship(Ship) :- % do stuff with a ship\n\n:- end_object.\n
Run Code Online (Sandbox Code Playgroud)\n\n

请耐心等待,我很少使用 Logtalk!

\n\n

更新:遗憾的是,Jan Burse(Jekejeke Prolog 的作者)指出 cut 操作符会对双重调度造成严重破坏。这并不一定意味着带有子类型的多重分派与统一不兼容,但它确实意味着作为解决方法的双重分派与削减不兼容,这将使非确定性变得复杂,并可能破坏这种方法。请参阅下面的评论以获取更多讨论。

\n\n

结论

\n\n

我不认为子类型和统一是相互排斥的,因为 Logtalk 两者都有。我不认为子类型和带有参数索引的多重分派是相互排斥的,但 Logtalk 没有多重分派,所以我不能确定。在大多数情况下,即使在 Java 中,我也会避免子类型化,所以我可能有偏见。不过,多重调度是一种需要 100 美元的语言功能;我不能说很多语言都有它,但你可以通过双重调度非常有效地伪造它。

\n\n

如果你对 Logtalk 感兴趣的话,我会深入研究一下。参数化示例尤其引人注目。

\n\n

我有些怀疑这是否真的回答了你的问题,甚至是否在同一个范围内,但我希望它有帮助!

\n

  • 我添加了一些草图。这个问题有点笼统,所以我认为一个好的答案可能也应该如此。:) (2认同)