这是的高级主题
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? ,但此结构严格仅依赖于功能链。
那么,提取所有值的方法是什么? 我目前的成就只是拔掉了头和尾巴,我不知道该如何解决。
编辑: 我忘了说我想做的事
操作。
因此,如果选择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
答案 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
)
我们可以为列表实现map
和filter
之类的功能
// 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 (...)
来矛盾自己。
这是我的错。为了简化示例,我突破了抽象的障碍。让我们看看nil
和cons
的类型–
// nil : List a
// cons : (a, List a) -> List a
给出一个类型为List a
的列表,我们如何得到类型为a
的值?在上面的示例(下面重复)中,我们通过调用myList
作为函数来作弊。这是内部知识,只有nil
和cons
的实施者应该知道
// 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
nil
和cons
下面的实现完全相同。我只修复了类型注释。最重要的是,我添加了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
模块为我们提供了扩展其功能的地方。我们可以轻松添加上面使用的功能,例如List
,map
,filter
和append
–当您尝试通过可变的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功能链赋予基本功能时,它会展开该链并允许我们减少它。 transducers和lenses背后的想法是相同的。
编辑: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]