0

单挑:这是一个跨语言问题。

我将通过实施差异列表来演示该问题。这是 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;

thunk(() => ...)行中的表达式A创建一个隐式 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));

好吧,这是可行的,但是像 monoid 实例这样简单的事情的实现相当复杂。必须进行模式匹配cons(...)cons_(...)在行中B)以获得部分应用的List.appendList a -> List a)是多余的。并且不必要地复杂化。

也许它就像完全放弃 Scott 编码一样简单,但我不想在类型级别上List a -> List a丢失类型抽象。DList a希望有更多经验的人可以指出正确的方法。

感谢使用 Haskell 或 JS 的答案。

4

1 回答 1

1

DList.append我们可以将and的实现简化DList.empty如下。

const comp = f => g => x => f(g(x));

const id = x => x;

DList.append = xs => ys =>
    xs.run(f =>
        ys.run(g =>
            DList.Cons(comp(f)(g))));

DList.empty = DList.Cons(id);

但是,如果我们根本不使用 CPS,它会更简单。

// (List a -> List a) -> DList a
const DList = run => ({ [Symbol.toStringTag]: "DList", run });

DList.append = xs => ys => DList(comp(xs.run)(ys.run));

DList.empty = DList(id);
于 2021-06-26T07:32:18.040 回答