漏斗或单体可以分别只用高阶函数来表达吗?

问题描述 投票:3回答:1

我试图在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 取一个值和一个纯函数, 把函数提升到它的上下文中, 把它应用到值上 然后在同样的上下文中返回结果. 我想它是一个漏斗器。然而,它并没有遵循Javascript中的漏斗prototcol,即每个漏斗必须在其原型上拥有一个映射函数。

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

Provided option 实际上是一个漏斗,我的问题是:是否可以只用(纯)高阶函数来表达每一个漏斗,其中上下文(或效果)计算的结果被保存在调用栈中?

javascript functional-programming monads functor higher-order-functions
1个回答
2
投票

当然了。除了几个JS基元(*, +, NumberString)来演示功能,下面你将只看到。

  1. 变量
  2. lambda抽象
  3. 应用

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

我们可以跳过一些中间的赋值,只用单参数的lambdas来写它--。

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

也许这能帮助我们看到模式 m(_ => _ => ... => ?)join, map, bind, ap, 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))

这可以被抽象为一个通用函数。arity(len) 返回一个函数。arg(n),该函数返回一个带有 len 参数,返回 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

现在,我们可以很容易地把公共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
© www.soinside.com 2019 - 2024. All rights reserved.