函数依赖/类型族 - AST

发布于 2024-10-07 02:35:54 字数 2118 浏览 0 评论 0原文

最近向我介绍了函数依赖性和类型族。在一个班级项目中,我用 Java 和 Haskell 编写(完成)了 C 子集的解释器。 Haskell 实现的术语求值函数需要通过显式模式匹配构建“函数表”并展开表示文字的值构造函数。一个不愉快的情况(但比 Java 漂亮得多)。

经过一段时间的搜索后,我遇到了“集合”示例,想知道是否可以将其应用于我的抽象语法来为文字生成通用的“注入”和“项目”函数。我提出了两个初步测试尝试:(

使用函数依赖关系:注入和投影函数无需显式类型注释即可工作,就像使用 lit 注入 Lit 一样。但是, Lit 中的投影函数不会键入,给出错误“无法将预期类型 l 与推断类型 l' 匹配”。)

class Prim l a | l -> a, a -> l where
 inj  :: a -> l
 proj :: l -> a

instance Prim LB Bool where inj = LB; proj = lb
instance Prim LI Int  where inj = LI; proj = li

data LB = LB {lb :: Bool}
data LI = LI {li :: Int}

data E where Lit :: Prim l a => l -> E

lit :: Prim l a => l -> E
lit = Lit

unlit :: Prim l a => E -> l
unlit (Lit a) = a

(使用这里的问题是我无法让 Haskell 在没有显式注释的情况下从参数推断出正确的返回类型,并且我无法编写泛型函数 lit :: Val l -> E 和 unlit :: E -> Val l。)

class Show l => Prim l where
 type Val l :: *
 inj  :: Val l -> l
 proj :: l -> Val l

data LB a = LB {lb :: Bool}
data LI a = LI {li :: Int }

instance Prim (LB a) where type Val (LB a) = Bool; inj = LB; proj = lb
instance Prim (LI a) where type Val (LI a) = Int;  inj = LI; proj = li;

data E where
 Lit :: Prim l => l -> E
 Bin :: Op -> E -> E -> E

我不太了解类型族,并且对函数依赖关系的掌握很薄弱。但我想知道两件事:(a)我想要的功能是否可以键入并实现; (b) 如果我误解了一些基本的东西。它几乎可以工作,但我已经与类型检查器斗争了一段时间了。

编辑这是我想要的一个简单模型,因为它还不清楚。 Bin 类基本上实现了我想要的功能。但我无法将各种“可包装和不可包装”值收集在一起来制作 AST。

class L t l => Bin t l where
 bAp :: (t -> t -> t) -> l -> l -> l
 bAp f l r = inj (proj l `f` proj r)

class Show l => L t l | t -> l, l -> t where
  inj  :: t -> l
  proj :: l -> t
  typ  :: l -> T

instance Bin Int LI
instance Bin Bool LB

instance L Int  LI where inj = LI; proj = li; typ = const TI
instance L Bool LB where inj = LB; proj = lb; typ = const TB

data LI = LI {li :: Int}  deriving (Eq, Show)
data LB = LB {lb :: Bool} deriving (Eq, Show)

data T = TI | TB | TC | TF | TU deriving (Eq, Show)

I was recently introduced to functional dependencies and type families. For a class project I wrote (completed) an interpreter for a subset of C in Java and Haskell. The Haskell implementation of an evaluation function for terms required building "function tables" with explicit pattern matching and unwrapping of value constructors representing literals. An unhappy situation (but much prettier than the Java).

After searching for a while, I came across the "collections" example, wondering if I could apply this to my abstract syntax to produce generic "inject to" and "project from" functions for literals. I came up with two initial test attempts:

(Using functional dependencies: the injection and projection functions work without explicit type annotations, as does injection into Lit with lit. However, the projection function from Lit will not type, giving the error "couldn't match expected type l against inferred type l'".)

class Prim l a | l -> a, a -> l where
 inj  :: a -> l
 proj :: l -> a

instance Prim LB Bool where inj = LB; proj = lb
instance Prim LI Int  where inj = LI; proj = li

data LB = LB {lb :: Bool}
data LI = LI {li :: Int}

data E where Lit :: Prim l a => l -> E

lit :: Prim l a => l -> E
lit = Lit

unlit :: Prim l a => E -> l
unlit (Lit a) = a

(Using type families. The problem here is that I can't get Haskell to infer from an argument the correct return type without explicit annotation, and I can't write the generic functions lit :: Val l -> E and unlit :: E -> Val l.)

class Show l => Prim l where
 type Val l :: *
 inj  :: Val l -> l
 proj :: l -> Val l

data LB a = LB {lb :: Bool}
data LI a = LI {li :: Int }

instance Prim (LB a) where type Val (LB a) = Bool; inj = LB; proj = lb
instance Prim (LI a) where type Val (LI a) = Int;  inj = LI; proj = li;

data E where
 Lit :: Prim l => l -> E
 Bin :: Op -> E -> E -> E

I don't understanding type families well, and have a flimsy grasp on functional dependencies. But I'd like to know two things: (a) if the functions I want can be typed and implemented; (b) If I am misunderstanding something fundamental here. It almost works, but I've been fighting with the type checker for a while now.

EDIT This is a simple model of what I want, since it was unclear. The class Bin implements the functionality I want, basically. But I can't collect the various "wrappable and unwrappable" values together to make an AST out of this.

class L t l => Bin t l where
 bAp :: (t -> t -> t) -> l -> l -> l
 bAp f l r = inj (proj l `f` proj r)

class Show l => L t l | t -> l, l -> t where
  inj  :: t -> l
  proj :: l -> t
  typ  :: l -> T

instance Bin Int LI
instance Bin Bool LB

instance L Int  LI where inj = LI; proj = li; typ = const TI
instance L Bool LB where inj = LB; proj = lb; typ = const TB

data LI = LI {li :: Int}  deriving (Eq, Show)
data LB = LB {lb :: Bool} deriving (Eq, Show)

data T = TI | TB | TC | TF | TU deriving (Eq, Show)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

寂寞清仓 2024-10-14 02:35:54

无论您如何定义投影函数,定义 Lit 构造函数的方式都会阻止您投影出它包含的值。

让我们看看构造函数的类型:

Lit :: Prim l => l -> E

类型变量l出现在参数中,但没有返回类型。这意味着当您构造 Lit 时,您放入了一个属于 Prim 成员的某种类型的值,然后永远忘记了它的类型是什么

我不确定您想如何消除模式匹配和值构造函数的展开。对于如何进行投影,您基本上有两种选择:

  1. 在运行时投影值、使用模式匹配或等效的东西。
  2. 通过使用类型系统证明您拥有的数据类型等于您想要的数据类型,在编译时投影值。

有理由进行编译时证明,但看起来您没有任何这些理由。

The way you have defined the Lit constructor will prevent you from projecting out the value it contains, regardless of how you define the projection function.

Let's look at the constructor's type:

Lit :: Prim l => l -> E

The type variable l appears in the parameters, but not the return type. That means that when you construct a Lit, you put in a value of some type that's a member of Prim and then permanently forget what its type was.

I'm not sure how you want to eliminate pattern matching and unwrapping of value constructors. You basically have two choices for how to do projections:

  1. Project values at run time, using pattern matching or something equivalent to it.
  2. Project values at compile time, by proving using the type system that the data type you have is equal to the data type you want.

There are reasons to go with compile-time proofs, but it doesn't look like you have any of those reasons.

妥活 2024-10-14 02:35:54

如果您仍然想坚持多态 E 的想法。您可以使用多态函数:

withUnlit :: E -> (forall l . Prim l => l -> b) -> b
withUnlit (Lit a) f = f a

但您唯一可以做的事情(使用您赋予 Prim l 的特征)是:

showE :: E -> String
showE e = withUnlit e show

以及 injproj。但是,除了使用一些 Data.Dynamic (如果我是这么认为的话),您无法使用 Val l

If you still want to stick with idea of polymorphic E. You can use polymorphic functions:

withUnlit :: E -> (forall l . Prim l => l -> b) -> b
withUnlit (Lit a) f = f a

But the only thing you can do (with traits you've given to Prim l) is:

showE :: E -> String
showE e = withUnlit e show

And inj and proj. But you have no way to work with Val l except of using some Data.Dynamic (if that is what I think).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文