【问题标题】:Haskell's type system and logic programming - how to port Prolog programs to type levelHaskell 的类型系统和逻辑编程——如何将 Prolog 程序移植到类型级别
【发布时间】:2012-12-16 08:01:37
【问题描述】:

我试图理解一种逻辑编程语言(在我的例子中是 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

此代码的问题是最后两个实例导致了资金冲突。我可以理解为什么,但在我看来这应该不是问题,因为它们的保护部分(或其他任何名称,我的意思是 (Greaterthan a b c) => 部分)是不同的,所以 as 和 b最后两个实例声明中的 s 实际上是不同的值,没有冲突。


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

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 book)

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”之后找到一些关于此的提示。

【问题讨论】:

  • 如果您还没有完成一些简单的“类型级序言”,请参阅 Thomas Hallgren 的函数依赖乐趣cse.chalmers.se/~hallgren/Papers/wm01.html。最后两行总是会得到重复的实例,因为它们是重复的 - GHC 并不关心约束是否不同。
  • 您可能对Mercury 感兴趣:Mercury 是一种面向实际应用的函数式逻辑编程语言。 ... Mercury 是一种纯粹的声明性逻辑语言。它与 Prolog 和 Haskell 都有关。它具有强大的静态多态类型系统以及强大的模式和确定性系统。
  • 您可能可以从-XTypeFamilies 获得更多相关类型同义词,但我不确定多少。有趣的问题
  • @stephentetley 感谢您的链接,但后记下载链接不起作用,您知道其他镜像吗?

标签: haskell prolog type-systems logic-programming


【解决方案1】:

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

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

第二个例子虽然困难得多。我怀疑要让它在 Haskell 中工作,我们需要一个类型级函数,它可以根据特定实例是否为特定类型参数定义(即,如果有实例Child Name1 Name2 - 递归地使用Name2 做某事,否则返回BFalse)。我不确定是否可以使用 GHC 类型对此进行编码(我怀疑不是)。

但是,我可以提出一个“解决方案”,它适用于稍微不同类型的输入:而不是暗示不存在 parent->child 关系(当没有为此类对定义实例时),我们可以使用 type- 显式编码所有现有关系级别列表。然后我们可以定义 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 的第二个和第三个实例都可以匹配children 不是空列表的情况,但 GHC 可以在我们的情况下确定更具体的情况,具体取决于列表的头部是否相等到to 参数(如果是,则意味着我们找到了路径,即后代)。

【讨论】:

    【解决方案2】:

    至于GreaterThan 示例,我看不出引入那些Booleans 是如何忠实于原始Prolog 代码的一步。您似乎正在尝试在您的 Haskell 版本中编码一种 Prolog 版本中不存在的可判定性。

    总而言之,你可以做到

    {-# 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)
    

    【讨论】:

    • 如果没有FunDeps 或类似的帮助,我认为您无法运行任何类型级别的函数。在您的代码中,Greaterthan 类如何帮助我们确定参数是否处于定义关系中?我认为您的类型类应该具有Greaterthan a b something | a b -> something 的主体,以便能够实际使用ab 来派生something。在我的情况下,这个 somethingr 这是一个 Boolean 类型。
    • @sinan:这就是“编码可判定性”的意思。这表达了一个约束,而不是一个可用的函数——如果约束不满足,它会通过给出编译器错误来帮助您做出决定。回想起在 prolog 中运行一个大型程序并得到一个完全无用的“不”作为答复的美好回忆。
    【解决方案3】:

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

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

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

    【讨论】:

    • @sinan:请注意 HList 在这一点上有点过时,因为较新的 GHC 扩展直接提供 HList 从头开始​​实现的功能。另外,我建议阅读 okmij.org/ftp 的内容,而不是试图理解 HList 的来源。
    • 我在 HList 库的文档中找不到该模块。你能描述在哪里可以找到它并详细说明它的作用吗?我已尝试阅读 Oleg 网站的摘要(由上面的 @CAMcCann 链接),但那里有 很多 信息,但找不到有关此特定类型类的任何信息。
    【解决方案4】:

    我已经解决了第二个问题,这就是我发现的。可以制定问题“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 都是正确的。这不是 Prolog 示例的情况,递归提前一步终止。

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

    1. 首先匹配Descend Anne CarolineDescend a c,方便a=Annec=Caroline
    2. 因此,我们查找实例 Child Anne bDescend b Caroline
    3. 一般来说,GHC 会在这里放弃,因为它不知道b 是什么意思。但是由于在Childb 在功能上依赖于a,所以Child Anne b 被解析为Child Anne Bridget,因此b=Bridget,我们尝试解析Descend Bridget Caroline
    4. Descend Bridget Caroline 再次匹配 Descend a ca=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' 以“向前看”。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-10-27
      • 1970-01-01
      • 1970-01-01
      • 2012-06-29
      • 1970-01-01
      • 1970-01-01
      • 2016-04-28
      • 1970-01-01
      相关资源
      最近更新 更多