【发布时间】:2021-09-08 01:34:06
【问题描述】:
提醒:这是一个跨语言问题。
我将通过实施差异列表来演示该问题。这是 Scott 编码的List,它提供了基本类型。我将它与动态类型验证器一起使用,因此我需要一个包装器将类型 List a 与(在下面的示例中简化)相关联:
// (forall r. r -> (a -> List a -> r) -> r) -> List a
const List = k =>
({[Symbol.toStringTag]: "List", run: k}); // type wrapper + namespace
// a -> List a -> List a
List.Cons = x => xs =>
List(nil => cons => cons(x) (xs));
// List a
List.Nil = List(nil => cons => nil);
// List a -> List a -> List a
List.append = xs => ys => function go(acc) {
return acc.run(ys)
(x => xs_ =>
List.Cons(x) (thunk(() => go(xs_)))); // A
} (xs);
// List a
List.empty = List.Nil;
A 行中的表达式 thunk(() => ...) 创建了一个隐式 thunk,即(=== 除外)您可以将其视为 thunk 正在延迟的表达式。在这种情况下,它的类型为 Last a。
这在没有 ADT 的热切语言中几乎是标准的。接下来我想提供由差异列表提供的高效append 和snoc 操作。在这一点上,事情变得一团糟。在 Haskell 中,这种类型是用 newtype 包装器声明的,但我不知道如何使用 Scott 编码来实现它。所以我坚持使用正常的编码:
// (forall r. ((List a -> List a) -> r) -> r) -> DList a
const DList = k =>
({[Symbol.toStringTag]: "DList", run: k}); // type wrapper + namespace
// (List a -> List a) -> DList a
DList.Cons = fun(
f => DList(cons => cons(f));
// DList<a> => DList<a> => DList<a>
DList.append = f => g => DList.Cons(
xs => f.run(
cons => cons(g.run( // B
cons_ => cons_(xs))))); // B
// DList a
DList.empty = DList.Cons(
xs => List.append(List.Nil) (xs));
嗯,这是可行的,但是像幺半群实例这样简单的事情的实现相当复杂。必须模式匹配(cons(...) 和cons_(...) 在行B)以获得部分应用的List.append(List a -> List a)是多余的。并且不必要地复杂化。
也许它就像完全放弃 Scott 编码一样简单,但我不想在类型级别上丢失从 List a -> List a 到 DList a 的类型抽象。希望有更多经验的人能指出正确的方法。
感谢使用 Haskell 或 JS 的答案。
【问题讨论】:
-
所有代码sn-ps都是JS
-
哦,哇——他们也是!!对此感到抱歉-我一直在喝酒:p
-
在
DList.append中,您在DList构造函数上进行模式匹配,如果您将newtypes 视为具有一个构造函数的数据类型,这与在Haskell 中相同。cons和cons_在那里名不副实;它们对应于DList.Cons中的f。然而,Haskell 中 newtypes 的主要目的是在没有运行时开销的情况下静态地执行抽象,这在动态类型语言中是没有意义的。 -
为什么 CPS 你的
DList?为什么不直接使用DList a = List a -> List a,类似于在 Haskell 中所做的那样?在这种情况下,monoid 实例要容易得多——mappend = (.). -
@IvenMarquardt 据我所知,我没有提议任何 ADT 实现。直接使用
List a -> List a类型——你已经实现了List,我假设->可用,不是吗?
标签: javascript haskell functional-programming newtype scott-encoding