如何创建一个递归类型类,其行为类似于另一个递归类型类,但没有“父”类那样多的实例?
以下是一个例子:
data Atom = Atom
data (Formula a) => Negation a = Negation a
class Formula a where
instance Formula Atom where
instance (Formula a) => Formula (Negation a) where
class SubFormula a where
instance SubFormula Atom where
该代码编译得很好但是当我添加一个将超类型类的实例转换为子类型类之一的函数时
formulaToSubFormula :: (Formula a, SubFormula b) => a -> b
formulaToSubFormula _ = Atom
我收到错误
test.hs:12:25:
Could not deduce (b ~ Atom)
from the context (Formula a, SubFormula b)
bound by the type signature for
formulaToSubFormula :: (Formula a, SubFormula b) => a -> b
at test.hs:12:1-28
`b' is a rigid type variable bound by
the type signature for
formulaToSubFormula :: (Formula a, SubFormula b) => a -> b
at test.hs:12:1
In the expression: Atom
In an equation for `formulaToSubFormula':
formulaToSubFormula _ = Atom
我最初的意图是使用普通类型执行此操作,但使用类型类时问题似乎更平易近人,而且通常更灵活。
例如:
data Formula = Atom | Negation Formula | Conjunction Formula Formula
data SubFormula = Atom | Negation SubFormula
澄清我尝试实现的目标:我想在类型级别验证输入类型上的操作将仅返回该类型的子集作为结果。
扩展示例(命题逻辑;没有有效的Haskell语法):
data Formula = Atom
| Negation Formula
| Disjunction Formula Formula
| Implication Formula Formula
data SimpleFormula = Atom
| Negation SimpleFormula
| Disjunction SimpleFormula SimpleFormula
-- removeImplication is not implemented correctly but shows what I mean
removeImplication :: Formula -> SimpleFormula
removeImplication (Implication a b) = (Negation a) `Disjunction` b
removeImplication a = a
稍后我可能会有一个联合正常形式的公式(没有有效的Haskell语法)
data CNF = CNFElem
| Conjunction CNF CNF
data CNFElem = Atom
| Negation Atom
| Disjunction CNFElem CNFElem
因此,我需要一个工具来表示这种层次结构。
答案 0 :(得分:2)
将超类型类的实例转换为子类型类
之一
Haskell类型类不能像这样工作。
他们不提供强制或子类型。返回Atom
的函数只能是Atom
返回类型,因为它返回一个构造Atom
值的显式构造函数。
对于像这样的表达式语言的建模,代数数据类型(有时,广义代数数据类型)是绝对优先选择的选项:
data Proposition
= LITERAL Bool
| ALL (Set Proposition)
| ANY (Set Proposition)
| NOT Proposition
可以使用参数化类型或GADT任意表达,具体取决于您的应用程序。
答案 1 :(得分:2)
我已经做了这个答案,因为它很长,我想要格式化。真的,我认为这是一个评论,因为它更多的是一种观点,而不是一种解决方案。
看起来您需要可扩展/模块化语法,尽管您正在将您的需求从通用描述为特定 - 大多数关于可扩展语法的写作都采用其他视图和考虑添加额外的案例以使“小”语法更大。
有很多方法可以在Haskell中实现可扩展的语法,例如“最终无标记”风格[1]或Sheard和Pasalic的两种类型[2]。
但实际上,获取模块化语法的“协议”代码是复杂且重复的,并且您丢失了常规Haskell数据类型的特性,特别是模式匹配。你也失去了很多的清晰度。最后一点至关重要 - 模块化语法在清晰度上是一种“税收”,很少值得使用。通常最好使用与当前问题完全匹配的数据类型,如果以后需要扩展它们,可以编辑源代码。
答案 2 :(得分:1)
您的代码的问题是在formulaToSubFormula _ = Atom
中,输出是使用Atom
构造函数创建的,因此它始终是Atom
类型,而类型签名声明它是具有SubFormula
实例的任何类型。一种选择是向SubFormula
类添加一个函数:
class SubFormula a where
atom :: a
instance SubFormula Atom where
atom = Atom
formulaToSubFormula :: (Formula a, SubFormula b) => a -> b
formulaToSubFormula _ = atom
当然,如果您只有一个子类型的实例,您可以完全免除该类:
formulaToSubFormula2 :: Formula a => a -> Atom
另请注意
data (Formula a) => Negation a = Negation a
可能不会做你想要的。我的意图可能是只有Formula a
类型可以被否定,并且始终可以使用Formula
上下文,但这意味着每次使用Negation a
时都需要提供Formula a
{1}}上下文,即使它未被使用。您可以使用GADT syntax:
data Negation a where
Negation :: Formula a => a -> Negation a
答案 3 :(得分:1)
这里可能会发生很多事情,我怀疑是否涉及引入类型类。 (在这里可能出现的奇特之处是GADT。)以下是非常简单的;它只是为了让你更清楚地说出你想要的东西......
这是一个类似于你最初的多态类型。由于它是多态的,你可以使用任何东西来表示原子公式。
data Formula a = Atom a
| Negation (Formula a)
| Conjunction (Formula a) (Formula a) deriving (Show, Eq, Ord)
这是一个提取所有子公式的函数:
subformulas (Atom a) = [Atom a]
subformulas (Negation p) = Negation p : subformulas p
subformulas (Conjunction p q) = Conjunction p q : (subformulas p ++ subformulas q)
如果你没有考虑很多原子命题,这是一种使用的类型:
data Atoms = P | Q | R | S | T | U | V deriving (Show, Eq, Ord)
以下是一些随机助手:
k p q = Conjunction p q
n p = Negation p
p = Atom P
q = Atom Q
r = Atom R
s = Atom S
x --> y = n $ k x (n y) -- note lame syntax highlighting
-- Main> ((p --> q) --> q)
-- Negation (Conjunction (Negation (Conjunction (Atom P) (Negation (Atom Q)))) (Negation (Atom Q)))
-- Main> subformulas ((p --> q) --> q)
-- [Negation (Conjunction (Negation (Conjunction (Atom P) (Negation (Atom Q)))) (Negation (Atom Q))),
-- Conjunction (Negation (Conjunction (Atom P) (Negation (Atom Q)))) (Negation (Atom Q)),
-- Negation (Conjunction (Atom P) (Negation (Atom Q))),
-- Conjunction (Atom P) (Negation (Atom Q)),Atom P,
-- Negation (Atom Q),Atom Q,Negation (Atom Q),Atom Q]
让我们制作布尔原子!:
t = Atom True
f = Atom False
-- Main> t --> f
-- Main> subformulas ( t --> f)
-- [Negation (Conjunction (Atom True) (Negation (Atom False))),
-- Conjunction (Atom True) (Negation (Atom False)),
-- Atom True,Negation (Atom False),Atom False]
为什么不是一个简单的评估函数?
eval :: Formula Bool -> Bool
eval (Atom p) = p
eval (Negation p) = not (eval p)
eval (Conjunction p q) = eval p && eval q
一些随机结果:
-- Main> eval (t --> f )
-- False
-- Main> map eval $ subformulas (t --> f)
-- [False,True,True,True,False]
稍后添加:请注意Formula
是一个Functor
,如果您将Functor添加到derinding子句和语言编译语{-#LANGUAGE DeriveFunctor#-}
,则GHC可以推断出明显的实例。然后,您可以使用任何函数f :: a -> Bool
作为真值的赋值:
-- *Main> let f p = p == P || p == R
-- *Main> fmap f (p --> q)
-- Negation (Conjunction (Atom True) (Negation (Atom False)))
-- *Main> eval it
-- False
-- *Main> fmap f ((p --> q) --> r)
-- Negation (Conjunction (Negation (Conjunction (Atom True) (Negation (Atom False)))) (Negation (Atom True)))
-- *Main> eval it
-- True
答案 4 :(得分:0)
我发现在数据类型中表示嵌套约束的唯一方法是通过类型类的某种规则系统,如下所示:
data Formula t val = Formula val deriving Show
-- formulae of type t allow negation of values of type a
class Negatable t a
instance Negatable Fancy a
instance Negatable Normal a
instance Negatable NNF Atom
instance Negatable CNF Atom
instance Negatable DNF Atom
class Conjunctable t a
instance Conjunctable Fancy a
instance Conjunctable Normal a
instance Conjunctable NNF a
instance Conjunctable CNF a
instance Conjunctable DNF Atom
instance Conjunctable DNF (Negation a)
instance Conjunctable DNF (Conjunction a b)
---
negate :: (Negatable t a) => Formula t a -> Formula t (Negation a)
negate (Formula x) = Formula $ Negation x
conjunct :: (Conjunctable t a, Conjunctable t b)
=> Formula t a -> Formula t b -> Formula t (Conjunction a b)
conjunct (Formula x) (Formula y) = Formula $ Conjunction x y
你提到的论文,尤其是Data types a la cart,确实很有帮助。