Haskell的类型系统和逻辑编程 - 如何将Prolog程序移植到类型级别

时间:2012-12-16 08:01:37

标签: haskell prolog type-systems logic-programming

我试图理解逻辑编程语言(在我的案例中是Prolog)和Haskell的类型系统之间的关系。

我知道两者都使用统一和变量来根据关系查找值(或类型,在Haskell的类型系统中)。为了更好地理解它们之间的相似点和不同点,我尝试用Haskell的类型级别重写一些简单的prolog程序,但是我遇到了一些问题。

首先,我重写了这个简单的prolog程序:

numeral(0).
numeral(succ(X)) :- numeral(X).

add(0,Y,Y).
add(succ(X),Y,succ(Z)) :- add(X,Y,Z).

为:

class Numeral a where
    numeral :: a
    numeral = u

data Zero
data Succ a

instance Numeral Zero
instance (Numeral a) => Numeral (Succ a)

class (Numeral a, Numeral b, Numeral c) => Add a b c | b c -> a where
    add :: b -> c -> a
    add = u

instance (Numeral a) => Add a Zero a
instance (Add x y z) => Add (Succ x) (Succ y) z

它工作正常,但我不能用这个Prolog扩展它:

greater_than(succ(_),0).
greater_than(succ(X),succ(Y)) :- greater_than(X,Y).

我试过的是:

class Boolean a
data BTrue
data BFalse
instance Boolean BTrue
instance Boolean BFalse

class (Numeral a, Numeral b, Boolean r) => Greaterthan a b r | a b -> r where
    greaterthan :: a -> b -> r
    greaterthan = u

instance Greaterthan Zero Zero BFalse
instance (Numeral a) => Greaterthan (Succ a) Zero BTrue
instance (Numeral a) => Greaterthan Zero (Succ a) BFalse
instance (Greaterthan a b BTrue)  => Greaterthan (Succ a) (Succ b) BTrue
instance (Greaterthan a b BFalse) => Greaterthan (Succ a) (Succ b) BFalse

此代码的问题是最后两个实例导致fundep冲突。我可以理解为什么,但在我看来它不应该是一个问题,因为它们的后卫部分(或者它所谓的任何部分,我的意思是(Greaterthan a b c) =>部分)是不同的,所以a s最后两个insance声明中的b实际上是不同的值,并且没有冲突。


我试图重写的另一个程序是:

child(anne,bridget).
child(bridget,caroline).
child(caroline,donna).
child(donna,emily).

descend(X,Y) :- child(X,Y).
descend(X,Y) :- child(X,Z),
                descend(Z,Y).

(顺便说一句,例子来自Learn Prolog Now书)

data Anne
data Bridget
data Caroline
data Donna
data Emily

class Child a b | a -> b where
    child :: a -> b
    child = u

instance Child Anne Bridget
instance Child Bridget Caroline
instance Child Caroline Donna
instance Child Donna Emily

class Descend a b | b -> a where
    descend :: b -> a
    descend = u

instance (Child a b) => Descend a b
instance (Child a c, Descend c b) => Descend a b

我在最后一行收到“重复实例”错误。我认为这是一个类似的问题,即使我有不同的防护部分,我也会收到错误,因为身体部位(我的意思是Descend a b部分)是相同的。

所以我正在寻找将Prolog程序移植到Haskell类型级别的方法,如果可能的话。任何帮助将不胜感激。

编辑:

Ed'ka的解决方案有效,但方式完全不同。我还在努力了解何时我们可以在类型系统中运行Prolog程序,何时/为什么我们需要编写不同的算法来使其工作(如Ed'ka的解决方案),以及何时/为什么没有办法在Haskell的类型系统中实现一个程序。

在阅读“Fun With Functional Dependencies”之后,也许我可以找到一些关于此的指示。

4 个答案:

答案 0 :(得分:6)

由于@ stephen tetley已经指出GHC尝试匹配实例声明时它只考虑实例头(=>之后的东西)完全忽略实例上下文(在=>之前的东西),一旦明确的实例发现它试图匹配实例上下文。您的第一个有问题的示例显然在实例头中有重复,但可以通过用一个更通用的实例替换两个冲突的实例来轻松修复:

instance (Greaterthan a b r)  => Greaterthan (Succ a) (Succ b) r

第二个例子虽然难得多。我怀疑为了使它在Haskell中工作,我们需要一个类型级函数,它可以产生两种不同的结果,具体取决于特定类型参数的特定实例是否定义(即,是否存在实例Child Name1 Name2 - 以Name2递归执行某些操作,否则返回BFalse)。我不确定是否可以用GHC类型编码(我怀疑它不是)。

但是,我可以提出一个“解决方案”,它适用于稍微不同类型的输入:而不是暗示没有parent->child关系(当没有为这样的对定义实例时),我们可以使用显式编码所有现有关系类型级列表。然后我们可以定义Descend类型级函数,尽管它必须依赖于可怕的OverlappingInstances扩展名:

{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies,
  FlexibleInstances, FlexibleContexts, TypeOperators,
  UndecidableInstances, OverlappingInstances #-}

data Anne
data Bridget
data Caroline
data Donna
data Emily
data Fred
data George

-- Type-level list
data Nil
infixr 5 :::
data x ::: xs

-- `bs` are children of `a`
class Children a bs | a -> bs

instance Children Anne (Bridget ::: Nil)
instance Children Bridget (Caroline ::: Donna ::: Nil)
instance Children Caroline (Emily ::: Nil)
-- Note that we have to specify children list for everyone
-- (`Nil` instead of missing instance declaration)
instance Children Donna Nil
instance Children Emily Nil
instance Children Fred (George ::: Nil)
instance Children George Nil

-- `or` operation for type-level booleans 
class OR a b bool | a b -> bool
instance OR BTrue b BTrue
instance OR BFalse BTrue BTrue
instance OR BFalse BFalse BFalse

-- Is `a` a descendant of `b`?
class Descend  a b bool | a b -> bool
instance (Children b cs, PathExists cs a r) => Descend a b r

-- auxiliary function which checks if there is a transitive relation
-- to `to` using all possible paths passing `children`
class PathExists children to bool | children to -> bool

instance PathExists Nil a BFalse
instance PathExists (c ::: cs) c BTrue
instance (PathExists cs a r1, Children c cs1, PathExists cs1 a r2, OR r1 r2 r)
    => PathExists (c ::: cs) a r

-- Some tests
instance Show BTrue where
    show _ = "BTrue"

instance Show BFalse where
    show _ = "BFalse"

t1 :: Descend Donna Anne r => r
t1 = undefined -- outputs `BTrue`

t2 :: Descend Fred Anne r => r
t2 = undefined -- outputs `BFalse`
此处需要

OverlappingInstances,因为PathExists的第2和第3个实例都可以匹配children不是空列表时的情况,但GHC可以根据我们的情况确定更具体的情况,具体取决于列表的头部等于to参数(如果是这意味着我们找到了路径即后代)。

答案 1 :(得分:6)

至于GreaterThan示例,我不知道如何引入这些Boolean是忠实于原始Prolog代码的步骤。您似乎正在尝试编写Prask版本中不存在的Haskell版本中的可判定性。

总而言之,你可以做到

{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies #-}
class Numeral a where

data Zero
data Succ a

instance Numeral Zero
instance (Numeral a) => Numeral (Succ a)

class (Numeral a, Numeral b) => Greaterthan a b where

instance (Numeral a) => Greaterthan Zero (Succ a)
instance (Greaterthan a b)  => Greaterthan (Succ a) (Succ b)

实际上使用data kinds,你可以写得更好(但我现在不能尝试,因为我这里只安装了ghc 7.2):

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies #-}

data Numeral = Zero | Succ Numeral

class Greaterthan (a :: Numeral) (b :: Numeral) where

instance Greaterthan Zero (Succ a)
instance (Greaterthan a b)  => Greaterthan (Succ a) (Succ b)

答案 2 :(得分:2)

对于Ed'ka解决方案,您可以使用:

import Data.HList.TypeCastGeneric2
instance TypeCast nil Nil => Children a nil

而不是每个没有孩子的人的一个实例。

答案 3 :(得分:0)

我已经解决了第二个问题,这是我发现的。可以制定问题“a-la”Prolog,但有一些警告适用。其中一个警告是,Descend 在参数之间实际上没有任何函数依赖关系,它是一个二元谓词,而不是一个一元函数。

首先,让我展示一下代码:

{-# LANGUAGE FunctionalDependencies
           , FlexibleInstances
           , UndecidableInstances
           , NoImplicitPrelude
           , AllowAmbiguousTypes
           #-}

data Anne
data Bridget
data Caroline
data Donna
data Emily

class Child a b | a -> b

instance Child Anne Bridget
instance Child Bridget Caroline
instance Child Caroline Donna
instance Child Donna Emily

----------------------------------------------------

data True -- just for nice output

class Descend a b where descend :: True

instance {-# OVERLAPPING #-} Descend a a
instance (Child a b, Descend b c) => Descend a c

(您可以通过启用 :set -XTypeApplications 并运行诸如 :t descend @Anne @Caroline:t descend @Caroline @Anne 之类的东西在 GHCi 中对此进行测试)

因此,这主要遵循 Prolog 示例,但有一个重要区别:我们使用的是 descend(X,Y) :- child(X,Y) 而非 instance {-# OVERLAPS #-} Descend a a

Descend

我将暂时解释为什么会这样,但首先我将解释它的变化:基本上,Descend a a 关系变得反射,即 a 对所有 Descend Anne Caroline 都成立。这不是 Prolog 示例的情况,其中递归提前一步终止。

现在解释为什么会这样。考虑 GHC 在类型实例解析期间如何实现类型变量替换:它匹配实例头,统一类型变量,然后检查实例约束。因此,例如,Descend Anne Caroline 将按以下顺序解析:

  1. 第一个Descend a ca=Anne匹配,方便c=CarolineChild Anne b
  2. 因此,我们查找实例 Descend b Carolineb
  3. 通常,GHC 会在这里放弃,因为它不知道 Child 是什么意思。但是由于在ba在功能上依赖于Child Anne bChild Anne Bridget被解析为b=Bridget,因此Descend Bridget Caroline,我们尝试解析{ {1}}。
  4. Descend Bridget Caroline 再次与 Descend a c 匹配,a=Bridgetc 再次与 Caroline 匹配。
  5. 查找 Child Bridget b,解析为 Child Bridget Caroline。然后尝试解析 Descend Caroline Caroline
  6. Descend Caroline Caroline 与重叠实例 Descend a a 匹配,进程终止。

因此,由于实例的匹配方式,GHC 实际上无法提前停止迭代。

也就是说,如果我们将 Child 替换为封闭类型系列,它变得可行:

{-# LANGUAGE TypeFamilies
           , FunctionalDependencies
           , FlexibleInstances
           , UndecidableInstances
           , TypeOperators
           , NoImplicitPrelude
           , AllowAmbiguousTypes
           , ScopedTypeVariables
           #-}

data Anne
data Bridget
data Caroline
data Donna
data Emily

data True
data False

type family Child' a where
 Child' Anne     = Bridget
 Child' Bridget  = Caroline
 Child' Caroline = Donna
 Child' Donna    = Emily
 Child' a        = False

class Child a b | a -> b

instance (Child' a ~ b) => Child a b

----------------------------------------------------

class Descend' a b flag
class Descend a b where descend :: True

data Direct
data Indirect

type family F a b where
  F False a = Direct
  F a a = Direct
  F a b = Indirect

instance (Child' a ~ c) => Descend' a c Direct
instance (Child a b, Descend' b c (F (Child' b) c))
  => Descend' a c Indirect
instance (Descend' a b (F (Child' a) b))
  => Descend a b

Descend' 的优势在于能够根据上下文重载实例选择,如 https://wiki.haskell.org/GHC/AdvancedOverlap 中所述。主要区别在于我们可以多次应用 Child' 以“向前看”。