从没有数组的功能链中提取数据

时间:2018-07-19 08:48:55

标签: javascript arrays list functional-programming

这是的高级主题

How to store data of a functional chain of Monoidal List?

我非常确定我们可以以某种方式从功能链中提取数据,而无需使用存储数据的数组。 基本结构是:

L = a => L

非常简单,但是此结构会生成一个列表:

L(1)(2)(3)(4)(5)(6)()

这可能与 What is a DList? ,但此结构严格仅依赖于功能链。

那么,提取所有值的方法是什么? 我目前的成就只是拔掉了头和尾巴,我不知道该如何解决。

编辑: 我忘了说我想做的事

List.fold(f)/ reduce(f)

操作。

因此,如果选择f作为Array.concat,则意味着您可以将数据提取为数组,但是折叠并不仅仅限于数组串联。和f可以加/加等。

因此,到目前为止,从某种意义上讲,到目前为止,我可以将内部行为可视化,我将log写为f

EDIT2

我必须澄清更多。该规范可以表示为:

const f = (a) => (b) => a + b;//binary operation

A(a)(b)(f) = f(a)(b)  // a + b

A(a)(b)(c)(f) = f(f(a)(b))(c) // a + b + c

这完全是

(a b c).reduce(f)

事物以及何时

f = (a) => (b) => a.concat(b)

结果将为[a, b, c]

Array.concat只是广义二进制操作f的成员。

起初,对于我来说,这项挑战很容易,但是结果却很难,并且觉得最好问一个更聪明的编码器。

谢谢。

const A = a => {

    const B = b => (b === undefined)
        ? (() => {
            log("a " + a);
            return A();
        })()
        : c => (c === undefined)
            ? (() => {
                log("b " + b);
                return B()();
            })()
            : B;

    return B;

};
 

A(1)(2)(3)(4)(5)(6)()

function log(m)  {
    console.log((m)); //IO
    return m;
};

结果:

b 6
a 1
a undefined

5 个答案:

答案 0 :(得分:2)

您在这里有一系列问题。这是我的看法:

我们从构造列表开始

  • nil是一个常量,代表空列表
  • cons (x, list)构造一个新列表,其中x前添加了list

// nil : List a
const nil =
  (c, n) => n

// cons : (a, List a) -> List a
const cons = (x, y = nil) =>
  (c, n) => c (y (c, n), x)

// list : List Number
const myList = 
  cons (1, cons (2, cons (3, cons (4, nil))))

console.log (myList ((x, y) => x + y, 0))
// 10

为满足您的高尔夫风格的咖喱界面,这里是autoCons

const autoCons = (init, n) => 
{ const loop = acc => (x, n) =>
    isFunction (x)
      ? acc (x, n)
      : loop (cons (x, acc))
  return loop (nil) (init, n)
}

const isFunction = f =>
  f != null && f.constructor === Function && f.length === 2

const nil =
  (c, n) => n

const cons = (x, y = nil) =>
  (c, n) => c (y (c, n), x)

console.log
  ( autoCons (1) ((x,y) => x + y, 0)             // 1
  , autoCons (1) (2) ((x,y) => x + y, 0)         // 3
  , autoCons (1) (2) (3) ((x,y) => x + y, 0)     // 6
  , autoCons (1) (2) (3) (4) ((x,y) => x + y, 0) // 10
  )

我们的编码使编写其他通用列表函数成为可能,例如isNil

// isNil : List a -> Bool
const isNil = l =>
  l ((acc, _) => false, true)

console.log
  ( isNil (autoCons (1))     // false
  , isNil (autoCons (1) (2)) // false
  , isNil (nil)              // true
  )

或者像length

// length : List a -> Int
const length = l =>
  l ((acc, _) => acc + 1, 0)

console.log
  ( length (nil)                  // 0
  , length (autoCons (1))         // 1
  , length (autoCons (1) (2))     // 2
  , length (autoCons (1) (2) (3)) // 3
  )

nth提取列表中的第 n 个项目

// nth : Int -> List a -> a
const nth = n => l =>
  l ( ([ i, res ], x) =>
        i === n
          ? [ i + 1, x ]
          : [ i + 1, res]
    , [ 0, undefined ]
    ) [1]

console.log
  ( nth (0) (autoCons ("A") ("B") ("C")) // "A"
  , nth (1) (autoCons ("A") ("B") ("C")) // "B"
  , nth (2) (autoCons ("A") ("B") ("C")) // "C"
  , nth (3) (autoCons ("A") ("B") ("C")) // undefined
  )

我们可以为列表实现mapfilter之类的功能

// map : (a -> b) -> List a -> List b
const map = f => l =>
  l ( (acc, x) => cons (f (x), acc)
    , nil
    )

// filter : (a -> Bool) -> List a -> List a
const filter = f => l =>
  l ( (acc, x) => f (x) ? cons (x, acc) : acc
    , nil
    )

我们甚至可以使用列表作为参数来编写程序

// rcomp : (a -> b) -> (b -> c) -> a -> c
const rcomp = (f, g) =>
  x => g (f (x))

// main : List String -> String   
const main = letters =>
  autoCons (map (x => x + x))
           (filter (x => x !== "dd"))
           (map (x => x.toUpperCase()))
           (rcomp, x => x)
           (letters)
           ((x, y) => x + y, "")

main (autoCons ("a") ("b") ("c") ("d") ("e"))
// AABBCCEE

在下面的浏览器中运行程序

const nil =
  (c, n) => n

const cons = (x, y = nil) =>
  (c, n) => c (y (c, n), x)

const isFunction = f =>
  f != null && f.constructor === Function && f.length === 2

const autoCons = (init, n) => 
{ const loop = acc => (x, n) =>
    isFunction (x)
      ? acc (x, n)
      : loop (cons (x, acc))
  return loop (nil) (init, n)
}

const map = f => l =>
  l ( (acc, x) => cons (f (x), acc)
    , nil
    )

const filter = f => l =>
  l ( (acc, x) => f (x) ? cons (x, acc) : acc
    , nil
    )

const rcomp = (f, g) =>
  x => g (f (x))

const main = letters =>
  autoCons (map (x => x + x))
           (filter (x => x !== "dd"))
           (map (x => x.toUpperCase()))
           (rcomp, x => x)
           (letters)
           ((x, y) => x + y, "")

console.log (main (autoCons ("a") ("b") ("c") ("d") ("e")))
// AABBCCEE


对不起,我不好

倒退,看看我们最初的List示例

// list : List Number
const myList = 
  cons (1, cons (2, cons (3, cons (4, nil))))

console.log
  ( myList ((x, y) => x + y, 0) // 10
  )

我们将myList概念化为数字列表,但是我们像函数一样调用myList (...)来矛盾自己。

这是我的错。为了简化示例,我突破了抽象的障碍。让我们看看nilcons的类型–

// nil : List a
// cons : (a, List a) -> List a

给出一个类型为List a的列表,我们如何得到类型为a的值?在上面的示例(下面重复)中,我们通过调用myList作为函数来作弊。这是内部知识,只有nilcons的实施者应该知道

// myList is a list, not a function... this is confusing...
console.log
  ( myList ((x, y) => x + y, 0) // 10
  )

如果您回顾我们对List的原始实现,

// nil : List a
const nil =
  (c, n) => n

// cons : (a, List a) -> List a
const cons = (x, y = nil) =>
  (c, n) => c (y (c, n), x)

我还欺骗了您,提供了List a之类的简化类型注释。到底是什么List

我们将解决所有这些问题,并从我们实现List

开始

List,取2

nilcons下面的实现完全相同。我只修复了类型注释。最重要的是,我添加了reduce,它提供了一种从列表容器中“取出”值的方法。

List的类型注释已更新为List a r –这可以理解为“包含类型为a的值的列表,当其减少时,其值将为输入r。”

// type List a r = (r, a) -> r

// nil : List a r
const nil =
  (c, n) => n

// cons : (a, List a r) -> List a r
const cons = (x, y = nil) =>
  (c, n) => c (y (c, n), x)

// reduce : ((r, a) -> r, r) -> List a -> r
const reduce = (f, init) => l =>
  l (f, init)

现在,我们可以将List保持为理智的类型,并将所需的所有不可靠行为推送到autoCons函数中。下面我们使用新的autoCons函数更新acc以使用列表reduce

const autoCons = (init, n) => 
{ const loop = acc => (x, n) =>
    isFunction (x)
      // don't break the abstraction barrier
      ? acc (x, n)
      // extract the value using our appropriate list module function
      ? reduce (x, n) (acc)
      : loop (cons (x, acc))
  return loop (nil) (init, n)
}

所以说到类型,让我们检查一下autoCons的类型–

autoCons (1)                  // "lambda (x,n) => isFunction (x) ...
autoCons (1) (2)              // "lambda (x,n) => isFunction (x) ...
autoCons (1) (2) (3)          // "lambda (x,n) => isFunction (x) ...
autoCons (1) (2) (3) (add, 0) // 6

autoCons总是返回一个lambda,但是该lambda具有我们无法确定的类型-有时它返回另一个相同类型的lambda,有时返回一个完全不同的结果;在这种情况下,6

因此,我们无法轻松地将autoCons表达式与程序的其他部分混合和组合。如果您丢掉这个错误的驱动器来创建可变参数的咖喱界面,则可以制作一个autoCons可输入类型的

// autoCons : (...a) -> List a r
const autoCons = (...xs) =>
{ const loop = (acc, x = nil, ...xs) =>
    x === nil
      ? acc
      : loop (cons (x, acc), ...xs)
  return loop (nil, ...xs)
}

由于autoCons现在返回了一个已知的List(而不是可变变数引起的神秘未知类型),因此我们可以将autoCons列表插入到{ {1}}模块。

List

const c = autoCons (1, 2, 3) const d = autoCons (4, 5, 6) console.log ( toArray (c) // [ 1, 2, 3 ] , toArray (map (x => x * x) (d)) // [ 16, 25, 36 ] , toArray (filter (x => x != 5) (d)) // [ 4, 6 ] , toArray (append (c, d)) // [ 1, 2, 3, 4, 5, 6 ] ) 返回一个我们不能依靠的类型时,这种混合组合表达式是不可能的。注意的另一重要事项是autoCons模块为我们提供了扩展其功能的地方。我们可以轻松添加上面使用的功能,例如Listmapfilterappend –当您尝试通过可变的Curried界面推送所有内容时,就会失去这种灵活性

现在让我们看一下toArray模块中的那些新增功能–如您所见,每个函数的类型都很好,并且具有我们可以依靠的行为

List

// type List a r = (r, a) -> r // nil : List a r // cons : (a, List a r) -> List a r // reduce : ((r, a) -> r, r) -> List a r -> r // length : List a r -> Int const length = reduce ( (acc, _) => acc + 1 , 0 ) // map : (a -> b) -> List a r -> List b r const map = f => reduce ( (acc, x) => cons (f (x), acc) , nil ) // filter : (a -> Bool) -> List a r -> List a r const filter = f => reduce ( (acc,x) => f (x) ? cons (x, acc) : acc , nil ) // append : (List a r, List a r) -> List a r const append = (l1, l2) => (c, n) => l2 (c, l1 (c, n)) // toArray : List a r -> Array a const toArray = reduce ( (acc, x) => [ ...acc, x ] , [] ) 现在已经成为我们模块的一部分了

autoCons

将其他功能添加到// autoCons : (...a) -> List a r const autoCons = (...xs) => { const loop = (acc, x = nil, ...xs) => x === nil ? acc : loop (cons (x, acc), ...xs) return loop (nil, ...xs) } 模块

List

答案 1 :(得分:1)

我不得不承认我没有阅读过您所链接的问题,而我主要是在这里找一个有趣的难题...但这有什么帮助吗?

我想您要区分添加元素(使用新值调用)和在列表上运行函数(使用函数调用)之间的区别。由于必须以某种方式传递函数才能运行,所以无法使(1)()语法正常工作。

这使用一个接口,该接口返回带有concat的对象以扩展列表,并返回fold以便在列表上运行化简器。同样,不确定这是否是完整的答案,但这可能会帮助您探索其他方向。

const Empty = Symbol();

const L = (x, y = Empty) => ({
  concat: z => L(z, L(x, y)),
  fold: (f, seed) => f(x, y === Empty ? seed : y.fold(f, seed))
});

const sum = (a, b) => a + b;


console.log(
  L(1)
    .concat(2).concat(3).concat(4).concat(5).concat(6)
    .fold(sum, 0)
)

答案 2 :(得分:1)

给出一个类似A(a)(b)(f)的表达式,其中f是一个函数,就无法知道是否应该将f添加到列表中或者它是否是归约函数。因此,我将描述如何编写与A(a)(b)(f, x)等价的[a, b].reduce(f, x)之类的表达式。这使我们可以根据提供的参数来区分列表何时结束:

const L = g => function (x, a) {
    switch (arguments.length) {
    case 1: return L(k => g((f, a) => k(f, f(a, x))));
    case 2: return g((f, a) => a)(x, a);
    }
};

const A = L(x => x);

const xs = A(1)(2)(3)(4)(5);

console.log(xs((x, y) => x + y, 0));        // 15
console.log(xs((x, y) => x * y, 1));        // 120
console.log(xs((a, x) => a.concat(x), [])); // [1,2,3,4,5]

它可以继续工作。每次添加新元素时,我们都会累积CPS函数。每个CPS功能都调用先前的CPS功能,从而创建一个CPS功能链。当我们将此CPS功能链赋予基本功能时,它会展开该链并允许我们减少它。 transducerslenses背后的想法是相同的。


编辑user633183's solution非常出色。它使用Church encoding of lists using right folds减轻了继续操作的需要,从而使代码更简单,易于理解。这是她的解决方案,经过修改以使foldr看起来像foldl

const L = g => function (x, a) {
    switch (arguments.length) {
    case 1: return L((f, a) => f(g(f, a), x));
    case 2: return g(x, a);
    }
};

const A = L((f, a) => a);

const xs = A(1)(2)(3)(4)(5);

console.log(xs((x, y) => x + y, 0));        // 15
console.log(xs((x, y) => x * y, 1));        // 120
console.log(xs((a, x) => a.concat(x), [])); // [1,2,3,4,5]

这里g是到目前为止累积的教会编码列表。最初是空列表。调用g会将其从右侧折叠。但是,我们也从右侧构建列表。因此,由于我们编写列表的方式,似乎我们正在构建列表并将其从左侧折叠。


如果所有这些功能使您感到困惑,那么user633183真正在做什么:

const L = g => function (x, a) {
    switch (arguments.length) {
    case 1: return L([x].concat(g));
    case 2: return g.reduceRight(x, a);
    }
};

const A = L([]);

const xs = A(1)(2)(3)(4)(5);

console.log(xs((x, y) => x + y, 0));        // 15
console.log(xs((x, y) => x * y, 1));        // 120
console.log(xs((a, x) => a.concat(x), [])); // [1,2,3,4,5]

如您所见,她正在向后构建列表,然后使用reduceRight向后折叠向后列表。因此,看起来您正在构建列表并将其向前折叠。

答案 3 :(得分:0)

正在进行中

由于@ user3297291的惊人贡献,我可以某种方式重构代码以使其符合我的规范,但由于我在实现过程中迷失了这个概念而无法正常工作:(

重点是必须整件事,不涉及object.method。

任何人都可以“调试”:)

初始值设置为第一个元素,在本示例中为1

我认为这已经完成了。

const isFunction = f => (typeof f === 'function');

const Empty = Symbol();

const L = (x = Empty) => (y = Empty) => z => isFunction(z)
    ? (() => {
        const fold = f => seed => f(x)(y) === Empty
            ? seed
            : (L)(y)(f);
        return fold(z)(x);
    })()
    : L(z)(L(x)(y));


const sum = a => b => a + b;


console.log(
    L(1)(2)(3)(4)(5)(6)(sum)
);

输出

 z => isFunction(z)
    ? (() => {
        const fold = f => seed => f(x)(y) === Empty
            ? seed
            : (L)(y)(f);
        return fold(z)(x);
    })()
    : L(z)(L(x)(y))

答案 4 :(得分:0)

我已经解决了您遇到的各种问题,但是我仍然不确定我是否完全了解您的要求。如果您只是想表示一个链表,这是一个“哑”表示,它不使用诸如重载参数或默认参数值之类的巧妙技巧:

const List = (() => {
  const nil = Symbol()

  // ADT
  const Nil = nil
  const Cons = x => xs => ({ x, xs })
  const match = ({ Nil, Cons }) => l => l === nil ? Nil : Cons(l.x)(l.xs)

  // Functor
  const map = f => match({
    Nil,
    Cons: x => xs => Cons(f(x))(map(f)(xs))
  })

  // Foldable
  const foldr = f => z => match({
    Nil: z,
    Cons: x => xs => f(x)(foldr(f)(z)(xs)) // danger of stack overflow!
                                           // https://wiki.haskell.org/Foldr_Foldl_Foldl%27
  })

  return { Nil, Cons, match, map, foldr }
})()

const { Nil, Cons, match, map, foldr } = List
const toArray = foldr(x => xs => [x, ...xs])([])

const l = Cons(1)(Cons(2)(Cons(3)(Nil)))

const l2 = map(x => x * 2)(l)
const l3 = map(x => x * 3)(l2)

const a = toArray(l3)
console.log(a) // => [6, 12, 18]