函子或单子能否分别单独用高阶函数表达?
Can functors or monads respectively be expressed solely with higher order functions?
我正在尝试在 Javascript 中实现仿函数而不使用容器类型 ([]
/{}
)。因此,我只使用纯高阶函数来构造它们:
const option = x => f => isAssigned(x) ? option(f(x)) : none;
const none = option(null);
const isAssigned = x => x !== null && x !== undefined;
const inc = x => x + 1;
const sqr = x => x * x;
const head = xs => xs[0]
const log = x => (console.log(x), x);
option(head([4])) (inc) (sqr) (log); // 25
option(head([])) (inc) (sqr) (log); // not executed
option
接受一个值和一个纯函数,将函数提升到它的上下文中,将它应用于值和 returns 同一上下文中的结果。我想这是一个函子。但是,它不遵循 Javascript 中的仿函数协议,即每个仿函数必须在其原型上拥有一个映射函数。
option
显然可以扩展为类似 monad 的类型(至少它的行为与我的示例中的类似):
const option = x => f => isAssigned(x) ? option(f(x)) : none;
const option_ = x => f => isAssigned(x) ? flatten(option(f(x))) : none;
const none = option(null);
const of = x => option(x); // return
const flatten = F => { // it gets a bit ugly here
let y;
F(z => (y = z, z));
return y;
};
// auxiliary functions
const compn = (...fs) => x => fs.reduceRight((acc, f) => f(acc), x);
const getOrElse = x => F => {
let y;
F(z => (y = z, z));
return isAssigned(y) ? y : x;
};
const isAssigned = x => x !== null && x !== undefined;
const log = prefix => x => (console.log(prefix, x), x);
const head = xs => xs[0];
const head_ = xs => option(xs[0]);
const sqr = x => x * x;
// some data
const xs = [5],
ys = [];
// run
const w = option(xs) (head),
x = option(ys) (head),
y = option_(xs) (head_),
z = option_(ys) (head_);
log("square head of xs:") (compn(sqr, getOrElse(1)) (w)); // 25
log("square head of ys:") (compn(sqr, getOrElse(0)) (x)); // 0
log("square head_ of xs:") (compn(sqr, getOrElse(0)) (y)); // 25
log("square head_ of ys:") (compn(sqr, getOrElse(0)) (z)); // 0
假设 option
实际上是一个函子 我的问题是:是否有可能仅用(纯)高阶函数来表达每个 functor/monad,其中上下文(或有效)计算的结果是保存在调用堆栈中?
当然可以。除了一些用于演示功能的 JS 原语(*
、+
、Number
和 String
)之外,您只会在下面看到:
- 变量
- lambda 抽象
- 应用程序
const identity = x => x
const fromNullable = x => x == null ? None : Option(x)
const Option = (value) => k => {
const join = () => value
const map = f => Option(f(value))
const bind = f => f(value)
const ap = m => optionMap(value)(m)
const fold = f => f(value)
return k (value, join, map, bind, ap, fold)
}
const None = () => k => {
const join = identity
const map = f => None()
const bind = f => None()
const ap = m => None()
const fold = f => f(null)
return k (null, join, map, bind, ap, fold)
}
const optionJoin = m => m((value, join, map, bind, ap, fold) => join())
const optionMap = f => m => m((value, join, map, bind, ap, fold) => map(f))
const optionBind = f => m => m((value, join, map, bind, ap, fold) => bind(f))
const optionAp = n => m => m((value, join, map, bind, ap, fold) => ap(n))
const optionFold = f => m => m((value, join, map, bind, ap, fold) => fold(f))
optionFold (console.log) (Option(5)) // 5
optionFold (console.log) (None()) // null
optionFold (console.log) (optionMap (x => x * 2) (Option(5))) // 10
optionFold (console.log) (optionMap (x => x * 2) (None()))// null
optionFold (console.log) (optionAp(Option(3)) (Option(x => x + 4))) // 7
optionFold (console.log) (optionAp(Option(3)) (None())) // null
optionFold (console.log) (optionBind(x => Option(x * x)) (Option(16))) // 256
optionFold (console.log) (optionBind(x => Option(x * x)) (None())) // null
optionFold (console.log) (optionJoin (Option(Option('hi')))) // 'hi'
optionFold (console.log) (optionJoin (Option(None())))// null
我们可以跳过一些中间赋值,只使用单参数 lambda 来编写它 -
const Option = value => k => // unit
k (_ => value) // join
(f => Option(f(value))) // map
(f => f(value)) // bind
(m => map(value)(m)) // ap
(f => f(value)) // fold
const None = k => // unit
k (_ => None) // join
(_ => None) // map
(_ => None) // bind
(_ => None) // ap
(f => f(null)) // fold
const join = m =>
m(v => _ => _ => _ => _ => v())
const map = f => m =>
m(_ => v => _ => _ => _ => v(f))
const bind = f => m =>
m(_ => _ => v => _ => _ => v(f))
const ap = f => m =>
f(_ => _ => _ => v => _ => v(m))
const fold = f => m =>
m(_ => _ => _ => _ => v => v(f))
const log =
fold(console.log)
log(Option(5)) // 5
log(None) // null
log(map (x => x * 2)(Option(5))) // 10
log(map (x => x * 2)(None)) // null
log(ap(Option(x => x + 4))(Option(3))) // 7
log(ap(Option(x => x + 4))(None)) // null
log(ap(None)(Option(3))) // null
log(bind(x => Option(x * x))(Option(16))) // 256
log(bind(x => Option(x * x))(None)) // null
log(join(Option(Option('hi')))) // 'hi'
log(join(Option(None))) // null
也许这有助于我们在 join
、map
、bind
、ap
、fold
函数中看到模式 m(_ => _ => ... => ?)
-
const join = m =>
m(v => _ => _ => _ => _ => v())
const map = f => m =>
m(_ => v => _ => _ => _ => v(f))
const bind = f => m =>
m(_ => _ => v => _ => _ => v(f))
const ap = f => m =>
f(_ => _ => _ => v => _ => v(m))
const fold = f => m =>
m(_ => _ => _ => _ => v => v(f))
这可以抽象为泛型函数。 arity(len)
returns 一个函数,arg(n)
,returns 一个带 len
个参数的柯里化函数,returns 第 n
个参数.这在代码中更清楚地展示了 -
arity(3)(0)
// (x => _ => _ => x)
arity(4)(0)
// (x => _ => _ => _ => x)
arity(4)(1)
// (_ => x => _ => _ => x)
arity(4)(2)
// (_ => _ => x => _ => x)
arity(5)(3)
// (_ => _ => _ => x => _ => x)
我们可以这样实现arity
-
const arity = (len = 1) => (n = 0) =>
len <= 1
? id
: n <= 0
? comp(T)(arity(len - 1)(0))
: T(arity(len - 1)(n - 1))
const id = x => x // identity
const T = x => _ => x // true
const F = _ => x => x // false
const comp = f => g => x => f(g(x)) // function composition
现在我们可以轻松地将 public api 函数编写为简单的参数选择器 -
const Option = value => k => // unit
k (value) // join
(f => Option(f(value))) // map
(f => f(value)) // bind
(m => map(m)(value)) // ap
(f => f(value)) // fold
const None = k => // unit
k (None) // join
(_ => None) // map
(_ => None) // bind
(_ => None) // ap
(f => f(null)) // fold
const arg = arity(5)
const join = m => m(arg(0))
const map = m => m(arg(1))
const bind = m => m(arg(2))
const ap = m => m(arg(3))
const fold = m => m(arg(4))
仅此而已 :D 展开下面的代码片段以在您自己的浏览器中查看验证结果 -
const id = x => x // identity
const T = x => _ => x // true
const F = _ => x => x // false
const comp = f => g => x => f(g(x)) // function composition
const arity = (len = 1) => (n = 0) =>
len <= 1
? id
: n <= 0
? comp(T)(arity(len - 1)(0))
: T(arity(len - 1)(n - 1))
const arg = arity(5)
const Option = value => k => // unit
k (value) // join
(f => Option(f(value))) // map
(f => f(value)) // bind
(m => map(m)(value)) // ap
(f => f(value)) // fold
const None = k => // unit
k (None) // join
(_ => None) // map
(_ => None) // bind
(_ => None) // ap
(f => f(null)) // fold
const join = m => m(arg(0))
const map = m => m(arg(1))
const bind = m => m(arg(2))
const ap = m => m(arg(3))
const fold = m => m(arg(4))
const log = x =>
fold(x)(console.log)
log(Option(5)) // 5
log(None) // null
log(map(Option(5))(x => x * 2)) // 10
log(map(None)(x => x * 2)) // null
log(ap(Option(x => x + 4))(Option(3))) // 7
log(ap(Option(x => x + 4))(None)) // null
log(ap(None)(Option(3))) // null
log(bind(Option(16))(x => Option(x * x))) // 256
log(bind(None)(x => Option(x * x))) // null
log(join(Option(Option('hi')))) // 'hi'
log(join(Option(None))) // null
我正在尝试在 Javascript 中实现仿函数而不使用容器类型 ([]
/{}
)。因此,我只使用纯高阶函数来构造它们:
const option = x => f => isAssigned(x) ? option(f(x)) : none;
const none = option(null);
const isAssigned = x => x !== null && x !== undefined;
const inc = x => x + 1;
const sqr = x => x * x;
const head = xs => xs[0]
const log = x => (console.log(x), x);
option(head([4])) (inc) (sqr) (log); // 25
option(head([])) (inc) (sqr) (log); // not executed
option
接受一个值和一个纯函数,将函数提升到它的上下文中,将它应用于值和 returns 同一上下文中的结果。我想这是一个函子。但是,它不遵循 Javascript 中的仿函数协议,即每个仿函数必须在其原型上拥有一个映射函数。
option
显然可以扩展为类似 monad 的类型(至少它的行为与我的示例中的类似):
const option = x => f => isAssigned(x) ? option(f(x)) : none;
const option_ = x => f => isAssigned(x) ? flatten(option(f(x))) : none;
const none = option(null);
const of = x => option(x); // return
const flatten = F => { // it gets a bit ugly here
let y;
F(z => (y = z, z));
return y;
};
// auxiliary functions
const compn = (...fs) => x => fs.reduceRight((acc, f) => f(acc), x);
const getOrElse = x => F => {
let y;
F(z => (y = z, z));
return isAssigned(y) ? y : x;
};
const isAssigned = x => x !== null && x !== undefined;
const log = prefix => x => (console.log(prefix, x), x);
const head = xs => xs[0];
const head_ = xs => option(xs[0]);
const sqr = x => x * x;
// some data
const xs = [5],
ys = [];
// run
const w = option(xs) (head),
x = option(ys) (head),
y = option_(xs) (head_),
z = option_(ys) (head_);
log("square head of xs:") (compn(sqr, getOrElse(1)) (w)); // 25
log("square head of ys:") (compn(sqr, getOrElse(0)) (x)); // 0
log("square head_ of xs:") (compn(sqr, getOrElse(0)) (y)); // 25
log("square head_ of ys:") (compn(sqr, getOrElse(0)) (z)); // 0
假设 option
实际上是一个函子 我的问题是:是否有可能仅用(纯)高阶函数来表达每个 functor/monad,其中上下文(或有效)计算的结果是保存在调用堆栈中?
当然可以。除了一些用于演示功能的 JS 原语(*
、+
、Number
和 String
)之外,您只会在下面看到:
- 变量
- lambda 抽象
- 应用程序
const identity = x => x
const fromNullable = x => x == null ? None : Option(x)
const Option = (value) => k => {
const join = () => value
const map = f => Option(f(value))
const bind = f => f(value)
const ap = m => optionMap(value)(m)
const fold = f => f(value)
return k (value, join, map, bind, ap, fold)
}
const None = () => k => {
const join = identity
const map = f => None()
const bind = f => None()
const ap = m => None()
const fold = f => f(null)
return k (null, join, map, bind, ap, fold)
}
const optionJoin = m => m((value, join, map, bind, ap, fold) => join())
const optionMap = f => m => m((value, join, map, bind, ap, fold) => map(f))
const optionBind = f => m => m((value, join, map, bind, ap, fold) => bind(f))
const optionAp = n => m => m((value, join, map, bind, ap, fold) => ap(n))
const optionFold = f => m => m((value, join, map, bind, ap, fold) => fold(f))
optionFold (console.log) (Option(5)) // 5
optionFold (console.log) (None()) // null
optionFold (console.log) (optionMap (x => x * 2) (Option(5))) // 10
optionFold (console.log) (optionMap (x => x * 2) (None()))// null
optionFold (console.log) (optionAp(Option(3)) (Option(x => x + 4))) // 7
optionFold (console.log) (optionAp(Option(3)) (None())) // null
optionFold (console.log) (optionBind(x => Option(x * x)) (Option(16))) // 256
optionFold (console.log) (optionBind(x => Option(x * x)) (None())) // null
optionFold (console.log) (optionJoin (Option(Option('hi')))) // 'hi'
optionFold (console.log) (optionJoin (Option(None())))// null
我们可以跳过一些中间赋值,只使用单参数 lambda 来编写它 -
const Option = value => k => // unit
k (_ => value) // join
(f => Option(f(value))) // map
(f => f(value)) // bind
(m => map(value)(m)) // ap
(f => f(value)) // fold
const None = k => // unit
k (_ => None) // join
(_ => None) // map
(_ => None) // bind
(_ => None) // ap
(f => f(null)) // fold
const join = m =>
m(v => _ => _ => _ => _ => v())
const map = f => m =>
m(_ => v => _ => _ => _ => v(f))
const bind = f => m =>
m(_ => _ => v => _ => _ => v(f))
const ap = f => m =>
f(_ => _ => _ => v => _ => v(m))
const fold = f => m =>
m(_ => _ => _ => _ => v => v(f))
const log =
fold(console.log)
log(Option(5)) // 5
log(None) // null
log(map (x => x * 2)(Option(5))) // 10
log(map (x => x * 2)(None)) // null
log(ap(Option(x => x + 4))(Option(3))) // 7
log(ap(Option(x => x + 4))(None)) // null
log(ap(None)(Option(3))) // null
log(bind(x => Option(x * x))(Option(16))) // 256
log(bind(x => Option(x * x))(None)) // null
log(join(Option(Option('hi')))) // 'hi'
log(join(Option(None))) // null
也许这有助于我们在 join
、map
、bind
、ap
、fold
函数中看到模式 m(_ => _ => ... => ?)
-
const join = m =>
m(v => _ => _ => _ => _ => v())
const map = f => m =>
m(_ => v => _ => _ => _ => v(f))
const bind = f => m =>
m(_ => _ => v => _ => _ => v(f))
const ap = f => m =>
f(_ => _ => _ => v => _ => v(m))
const fold = f => m =>
m(_ => _ => _ => _ => v => v(f))
这可以抽象为泛型函数。 arity(len)
returns 一个函数,arg(n)
,returns 一个带 len
个参数的柯里化函数,returns 第 n
个参数.这在代码中更清楚地展示了 -
arity(3)(0)
// (x => _ => _ => x)
arity(4)(0)
// (x => _ => _ => _ => x)
arity(4)(1)
// (_ => x => _ => _ => x)
arity(4)(2)
// (_ => _ => x => _ => x)
arity(5)(3)
// (_ => _ => _ => x => _ => x)
我们可以这样实现arity
-
const arity = (len = 1) => (n = 0) =>
len <= 1
? id
: n <= 0
? comp(T)(arity(len - 1)(0))
: T(arity(len - 1)(n - 1))
const id = x => x // identity
const T = x => _ => x // true
const F = _ => x => x // false
const comp = f => g => x => f(g(x)) // function composition
现在我们可以轻松地将 public api 函数编写为简单的参数选择器 -
const Option = value => k => // unit
k (value) // join
(f => Option(f(value))) // map
(f => f(value)) // bind
(m => map(m)(value)) // ap
(f => f(value)) // fold
const None = k => // unit
k (None) // join
(_ => None) // map
(_ => None) // bind
(_ => None) // ap
(f => f(null)) // fold
const arg = arity(5)
const join = m => m(arg(0))
const map = m => m(arg(1))
const bind = m => m(arg(2))
const ap = m => m(arg(3))
const fold = m => m(arg(4))
仅此而已 :D 展开下面的代码片段以在您自己的浏览器中查看验证结果 -
const id = x => x // identity
const T = x => _ => x // true
const F = _ => x => x // false
const comp = f => g => x => f(g(x)) // function composition
const arity = (len = 1) => (n = 0) =>
len <= 1
? id
: n <= 0
? comp(T)(arity(len - 1)(0))
: T(arity(len - 1)(n - 1))
const arg = arity(5)
const Option = value => k => // unit
k (value) // join
(f => Option(f(value))) // map
(f => f(value)) // bind
(m => map(m)(value)) // ap
(f => f(value)) // fold
const None = k => // unit
k (None) // join
(_ => None) // map
(_ => None) // bind
(_ => None) // ap
(f => f(null)) // fold
const join = m => m(arg(0))
const map = m => m(arg(1))
const bind = m => m(arg(2))
const ap = m => m(arg(3))
const fold = m => m(arg(4))
const log = x =>
fold(x)(console.log)
log(Option(5)) // 5
log(None) // null
log(map(Option(5))(x => x * 2)) // 10
log(map(None)(x => x * 2)) // null
log(ap(Option(x => x + 4))(Option(3))) // 7
log(ap(Option(x => x + 4))(None)) // null
log(ap(None)(Option(3))) // null
log(bind(Option(16))(x => Option(x * x))) // 256
log(bind(None)(x => Option(x * x))) // null
log(join(Option(Option('hi')))) // 'hi'
log(join(Option(None))) // null