我正在使用https://github.com/fantasyland/fantasy-land中的精彩规范,在Typescript中创建一个简单的Monad库。
此处创作者建议三个一元法则:
M.of(a).chain(f)
相当于f(a)
(左侧身份)m.chain(M.of)
相当于m
(正确的身份)m.chain(f).chain(g)
相当于m.chain(x => f(x).chain(g))
( associativity )所以我实施了法律和这个经典实现的测试。
import { setoid, functor, apply } from './common';
const isEmpty = (value: any) => value === null || value === undefined;
const chain = function<T, U>(fn: (val: T) => IMonad<U>) {
return this.isNone() ? this : fn(this.lift());
};
class Some<T> implements IMonad<T> {
private _value: T;
constructor(value: T) {
this._value = value;
}
lift = () => this._value;
of = (value: T) => new Some(value);
map = functor;
ap = apply;
flatMap = chain;
equals = setoid;
isSome = () => true
isNone = () => false
}
class None implements IMonad<any> {
constructor() {}
lift = () => { throw "Cannot get a value from None" };
of = (value: any) => new None();
map = functor;
ap = apply;
flatMap = chain;
equals = setoid;
isSome = () => false;
isNone = () => true;
}
class MaybeStatic implements IMonadStatic<any> {
of = (value:any) => !isEmpty(value) ? new Some(value) : new None();
}
const maybeStatic = new MaybeStatic();
export { maybeStatic as Maybe, Some, None, MaybeStatic };
运行测试我发现在None
monad的Maybe
情况下,左侧身份规则未通过测试。事实上:
Maybe.of(null).chain(x => Some(x+2)) !== (null + 2)
在这种情况下,似乎Js / Ts允许操作null + 2的事实打破了第一个monadic规则(其他语言可能不允许此操作)。我检查了很多可能monad的javascript和typescript实现,他们似乎完全忽略了这个问题。
所以问题:
x => Some(x+2)
的{{1}}是None
的有效参数,或者链只应接受返回None
的函数吗?NaN
(这可能会将除以零的效率和其他奇怪的情况扩展为undefined + 2
)?修改
Option
类型在 Scala here中实施类似的案例。答案 0 :(得分:1)
Maybe<T>
类型(或任何泛型类型)应避免检查其包含的值,并且应仅基于构造函数定义的结构进行操作(在本例中为None<T>
和{{ 1}}类)。
您的Some<T>
功能会通过检查isSome
和null
来违反此原则。要遵守monad法律,undefined
函数应始终返回of
的实例,而Some
函数应仅检查chain
是this
的实例}或Some
。
在None
函数中,chain
个实例可以无条件地使用其内部值调用continuation函数。 Some
个实例始终返回None<T>
,因此根本不需要包含值。
将这些放在一起,您的实现将类似于:
None
答案 1 :(得分:0)
如果您正在做这个项目以了解monad,那么javascript可能会导致其动态类型更加混乱。如果这样做是因为您想使用Monad编写软件,那么您可能会过分考虑Monad法则。在任何情况下,等效的定义都比javascript的==宽松。这是ts中的一种(不完整的)实现:
class MaybeInstance<T> {
private isSome: boolean;
private val: T;
constructor(isSome: boolean, val: T) {
this.isSome = isSome;
this.val = val;
}
orJust(t: T) {
return this.isSome
? this.val
: t;
}
map<U>(fn: (t: T) => U): Maybe<U> {
return new MaybeInstance<U>(this.isSome,
this.isSome
? fn(this.val)
: null);
}
chain<U>(fn: (t: T) => Maybe<U>): Maybe<U> {
return this.isSome
? fn(this.val)
: new MaybeInstance<U>(false, null);
}
isEquivalent<U>(mU: Maybe<U>) {
return this.isSome === mU.isSome
&& typeof this.val === typeof mU.val;
}
}
export type Maybe<T> = MaybeInstance<T>;
export module Maybe {
export const some: <T>(val: T) => Maybe<T> =
(val) => new MaybeInstance(true, val);
export const none: () => Maybe<any> =
() => new MaybeInstance(false, null);
export const fromUndef: <T>(val: T | undefined) => Maybe<T> =
(val) => !!val && (val as any) !== NaN
? some(val)
: none();
}
const a = 1;
const f = (x: number) => Maybe.fromUndef(100 / x);
const g = (x: number) => Maybe.fromUndef(x).map(String);
const mA = Maybe.some(a);
console.log(
// M.of(a).chain(f) is equivalent to f(a) (left identity)
mA.chain(f).isEquivalent(f(a)),
// m.chain(M.of) is equivalent to m (right identity)
mA.chain(Maybe.some).isEquivalent(mA),
// m.chain(f).chain(g) is equivalent to m.chain(x => f(x).chain(g)) (associativity)
mA.chain(f).chain(g).isEquivalent(mA.chain((x) => f(x).chain(g))),
);
ts编译器不会喜欢带有严格的null检查的代码(但可能不是严格的null检查?),并且缺少很多功能,例如ap,concat等,但它表明在ts很简单。