【问题标题】:Implementing Either Monad in Typescript在 Typescript 中实现 Either Monad
【发布时间】:2019-08-17 03:15:59
【问题描述】:

我最近一直在尝试使用 TypeScript,并且正在尝试实现一些基本的 monad。我已经有了一个功能合理的 Maybe(至少使用与我的以下 Either 相同的方法),但是由于与类型相关的原因我不太明白,但 Either 正在躲避我。

我对高级类型的打字策略是从这篇文章中借用的:https://medium.com/@gcanti/higher-kinded-types-in-typescript-static-and-fantasy-land-d41c361d0dbe。我知道有些库已经拥有所有这些 monad 和其他 FP 好东西,但是像这样实现它们是我尝试更深入地学习 TypeScript 的方式。

declare module './HKT' {
  interface TypeIDtoHKT2<E, A> {
    Either: Either<E, A>;
  }
}

export const TypeID = 'Either';
export type TypeID = typeof TypeID;

export class Left<E> {
  readonly _F!: TypeID;
  readonly _A!: E;
  readonly _tag: 'Left' = 'Left';
  constructor(readonly left: E) {}
  static of<E, A>(e: E): Either<E, A> {
    return new Left(e);
  }
  map<A, B>(f: (a: A) => B): Either<E, B> {
    return this;
  }
  chain<A, B>(f: (a: A) => Either<A, B>): Either<E, B> {
    return this;
  }
  ap<A, B>(f: Either<A, (a: A) => B>): Either<E, B> {
    return this;
  }
}

export class Right<A> {
  readonly _F!: TypeID;
  readonly _A!: A;
  readonly _tag: 'Right' = 'Right';
  constructor(readonly right: A) {}
  static of<A, E>(a: A): Either<E, A> {
    return new Right(a);
  }
  map<E, B>(f: (a: A) => B): Either<E, B> {
    return Right.of(f(this.right));
  }
  chain<E, B>(f: (a: A) => Either<E, B>): Either<E, B> {
    return f(this.right);
  }
  ap<E, B>(f: Either<E, (a: A) => B>): Either<E, B> {
    if (f instanceof Left) return f;
    return this.map(f.right);
  }
}

export type Either<E, A> = Left<E> | Right<A>;

export const left = <E, A>(e: E): Either<E, A> => {
  return new Left(e);
};

export const right = <E, A>(a: A): Either<E, A> => {
  return new Right(a);
};

当我尝试运行测试断言时,例如right(4).map(isEven) === true,我收到以下错误:

Cannot invoke an expression whose type lacks a call signature. Type '(&lt;E, B&gt;(f: (a: number) =&gt; B) =&gt; Either&lt;E, B&gt;) | (&lt;A, B&gt;(f: (a: A) =&gt; B) =&gt; Either&lt;unknown, B&gt;)' has no compatible call signatures.

我不明白为什么 E 类型在这里是 unknown 或者我如何才能让它知道......或者这是否是正确的尝试。任何指导表示赞赏。

【问题讨论】:

  • 我在这里尝试了你所有的例子repl.it/repls/ExtralargeFixedAssembly 并且工作得很好。你用的是什么版本的 Node 和 TS?
  • @DamiánRafaelLattenero 我正在使用节点 12.6.0 和 TS 3.5.3。但更重要的是,如果我添加 right(4).map(isEven) 它不会编译。见repl.it/repls/BaggySuperficialGlueware
  • 当我以一种不适合它的方式使用 Javascript 语法时,它被称为 hack。使用 TS 接口来伪造更高种类的类型就是这种滥用,我猜你正在经历的是依赖 hack 的后果。
  • @bob 这是一个公平的观点。我同意这是一个笨拙的解决方案。您是否建议 TS 在这里不是一个好的语言选择,我应该回到在 JS 中使用代数结构?或者您对此类结构有更好的 TS 实现建议?
  • 如果你真的想应用类型化的纯功能习语,那么你应该使用 purescript,因为它是经过行业验证的。但是,这是一个艰难的步骤,尤其是当您来自命令式 Javascript 时。如果你坚持 OOP 风格,Typescript 可能是一个不错的选择。

标签: typescript functional-programming either


【解决方案1】:

问题出在这里:

export const left = <E, A>(e: E): Either<E, A> => {
  return new Left(e);
};

export const right = <E, A>(a: A): Either<E, A> => {
  return new Right(a);
};

这很好用:

console.log(isEven(3))
console.log((new Right(4)).map(isEven))

这也可以:

console.log(right(3))

但是当你尝试这样做时:

right(3).map(isEven)

编译器发疯了,无法正确绑定类型。我认为这是语言的限制。只是不要使用那个常量并直接使用构造函数。

【讨论】:

    猜你喜欢
    • 2021-09-04
    • 2012-06-02
    • 2014-01-10
    • 1970-01-01
    • 2019-05-30
    • 1970-01-01
    • 1970-01-01
    • 2018-02-09
    • 1970-01-01
    相关资源
    最近更新 更多