我正在尝试通过egghead学习传感器,我想我得到它直到我们尝试构建对象变换。我在下面有一个不起作用的例子
const flip = map(([k,v]) => ({[v]: k}));
const double = map(([k,v]) => ({[k]: v + v}));
seq(flip, {one: 1, two: 2}); /*?*/ {1: 'one', 2: 'two'}
seq(double, {one: 1, two: 2}); /*?*/ {'one': 2, 'two: 4}
但如果我写作失败:
seq(compose(flip, double), {one: 1, two: 2}); /*?*/ {undefined: NaN}
seq(compose(double, flip), {one: 1, two: 2}); /*?*/ {undefined: undefined}
如何使用具有fp组成的传感器处理对象?
有相当多的锅炉板,所以我真的建议查看实时代码示例来查看compose,seq等工具。
答案 0 :(得分:2)
您有任何限制
其他人指出你在使用这些类型时犯了错误。您的每个函数都需要[k,v]
输入,但它们都不会输出该格式 - 在这种情况下compose(f,g)
或compose(g,f)
都不起作用
无论如何,传感器是通用的,不需要知道它们处理的数据类型
const flip = ([ key, value ]) =>
[ value, key ]
const double = ([ key, value ]) =>
[ key, value * 2 ]
const pairToObject = ([ key, value ]) =>
({ [key]: value })
const entriesToObject = (iterable) =>
Transducer ()
.log ('begin:')
.map (double)
.log ('double:')
.map (flip)
.log ('flip:')
.map (pairToObject)
.log ('obj:')
.reduce (Object.assign, {}, Object.entries (iterable))
console.log (entriesToObject ({one: 1, two: 2}))
// begin: [ 'one', 1 ]
// double: [ 'one', 2 ]
// flip: [ 2, 'one' ]
// obj: { 2: 'one' }
// begin: [ 'two', 2 ]
// double: [ 'two', 4 ]
// flip: [ 4, 'two' ]
// obj: { 4: 'two' }
// => { 2: 'one', 4: 'two' }
当然,我们有标准无聊的数字数组,并且还有一些无聊的数字作为可能性
const main = nums =>
Transducer ()
.log ('begin:')
.filter (x => x > 2)
.log ('greater than 2:')
.map (x => x * x)
.log ('square:')
.filter (x => x < 30)
.log ('less than 30:')
.reduce ((acc, x) => [...acc, x], [], nums)
console.log (main ([ 1, 2, 3, 4, 5, 6, 7 ]))
// begin: 1
// begin: 2
// begin: 3
// greater than 2: 3
// square: 9
// less than 30: 9
// begin: 4
// greater than 2: 4
// square: 16
// less than 30: 16
// begin: 5
// greater than 2: 5
// square: 25
// less than 30: 25
// begin: 6
// greater than 2: 6
// square: 36
// begin: 7
// greater than 2: 7
// square: 49
// [ 9, 16, 25 ]
更有趣的是,我们可以输入一个对象数组并返回一组
const main2 = (people = []) =>
Transducer ()
.log ('begin:')
.filter (p => p.age > 13)
.log ('age over 13:')
.map (p => p.name)
.log ('name:')
.filter (name => name.length > 3)
.log ('name is long enough:')
.reduce ((acc, x) => acc.add (x), new Set, people)
const data =
[ { name: "alice", age: 55 }
, { name: "bob", age: 16 }
, { name: "alice", age: 12 }
, { name: "margaret", age: 66 }
, { name: "alice", age: 91 }
]
console.log (main2 (data))
// begin: { name: 'alice', age: 55 }
// age over 13: { name: 'alice', age: 55 }
// name: alice
// name is long enough: alice
// begin: { name: 'bob', age: 16 }
// age over 13: { name: 'bob', age: 16 }
// name: bob
// begin: { name: 'alice', age: 12 }
// begin: { name: 'margaret', age: 66 }
// age over 13: { name: 'margaret', age: 66 }
// name: margaret
// name is long enough: margaret
// begin: { name: 'alice', age: 91 }
// age over 13: { name: 'alice', age: 91 }
// name: alice
// name is long enough: alice
// => Set { 'alice', 'margaret' }
请参阅?我们可以执行您想要的任何类型的转换。您只需要符合条件的Transducer
const identity = x =>
x
const Transducer = (t = identity) => ({
map: (f = identity) =>
Transducer (k =>
t ((acc, x) => k (acc, f (x))))
, filter: (f = identity) =>
Transducer (k =>
t ((acc, x) => f (x) ? k (acc, x) : acc))
, tap: (f = () => undefined) =>
Transducer (k =>
t ((acc, x) => (f (x), k (acc, x))))
, log: (s = "") =>
Transducer (t) .tap (x => console.log (s, x))
, reduce: (f = (a,b) => a, acc = null, xs = []) =>
xs.reduce (t (f), acc)
})
完整的程序演示 - .log
已添加,以便您可以按正确的顺序查看事件
const identity = x =>
x
const flip = ([ key, value ]) =>
[ value, key ]
const double = ([ key, value ]) =>
[ key, value * 2 ]
const pairToObject = ([ key, value ]) =>
({ [key]: value })
const Transducer = (t = identity) => ({
map: (f = identity) =>
Transducer (k =>
t ((acc, x) => k (acc, f (x))))
, filter: (f = identity) =>
Transducer (k =>
t ((acc, x) => f (x) ? k (acc, x) : acc))
, tap: (f = () => undefined) =>
Transducer (k =>
t ((acc, x) => (f (x), k (acc, x))))
, log: (s = "") =>
Transducer (t) .tap (x => console.log (s, x))
, reduce: (f = (a,b) => a, acc = null, xs = []) =>
xs.reduce (t (f), acc)
})
const entriesToObject = (iterable) =>
Transducer ()
.log ('begin:')
.map (double)
.log ('double:')
.map (flip)
.log ('flip:')
.map (pairToObject)
.log ('obj:')
.reduce (Object.assign, {}, Object.entries (iterable))
console.log (entriesToObject ({one: 1, two: 2}))
// begin: [ 'one', 1 ]
// double: [ 'one', 2 ]
// flip: [ 2, 'one' ]
// obj: { 2: 'one' }
// begin: [ 'two', 2 ]
// double: [ 'two', 4 ]
// flip: [ 4, 'two' ]
// obj: { 4: 'two' }
// => { 2: 'one', 4: 'two' }
功能性编程与功能性程序
JavaScript不包括map
,filter
或reduce
等功能实用程序,用于其他迭代项,如Generator,Map或Set。在编写启用函数式编程的函数时,我们可以通过多种方式实现这一功能 - 考虑reduce
的不同实现
// possible implementation 1
const reduce = (f = (a,b) => a, acc = null, xs = []) =>
xs.reduce (f, acc)
// possible implementation 2
const reduce = (f = (a,b) => a, acc = null, [ x = Empty, ...xs ]) =>
isEmpty (x)
? acc
: reduce (f, f (acc, x) xs)
// possible implementation 3
const reduce = (f = (a,b) => a, acc = null, xs = []) =>
{
for (const x of xs)
acc = f (acc, x)
return acc
}
上面reduce
的每个实现都可以实现编程;但是,只有一个实现本身就是一个功能程序
这只是原生Array.prototype.reduce
的包装器。它与Array.prototype.reduce
具有相同的缺点,因为它仅适用于数组。在这里,我们很高兴我们现在可以使用普通函数编写reduce表达式,并且创建包装器很容易。但是,如果我们调用reduce (add, 0, new Set ([ 1, 2, 3 ]))
,则会失败,因为集合没有reduce
方法,这让我们感到难过。
这适用于任何可迭代的,但递归定义意味着如果xs
非常大,它将溢出堆栈 - 至少在JavaScript解释器添加对尾部调用消除的支持之前。在这里,我们对reduce
的代表性感到高兴,但无论我们在哪里使用它,我们的程序都让我们为它的致命弱点而悲伤
这适用于任何迭代,就像#2一样,但是我们必须交换优雅的递归表达式,以确保堆栈安全的命令式for
循环。丑陋的细节让我们为reduce
感到难过,但无论我们在程序中使用它,它都会让我们感到高兴。
为什么这很重要?好吧,在我分享的Transducer
中,我包含的reduce
方法是:
const Transducer (t = identity) =>
({ ...
, reduce: (f = (a,b) => a, acc = null, xs = []) =>
xs.reduce (t (f), acc)
})
这个特定的实现最接近我们上面的reduce
#1 - 它是Array.prototype.reduce
周围快速而又脏的包装器。当然,我们的Transducer
可以对包含任何类型值的数组执行转换,但这意味着我们的传感器只能接受数组作为输入。我们交易灵活性以便于实施。
我们可以将它写得更接近样式#2,但是当我们在大数据集上使用我们的传感器模块时,我们就会继承堆栈漏洞 - 这就是传感器首先要擅长的地方。接近#3的实现本身不是一个功能程序,但它启用函数式编程 -
结果是一个模块必然利用JavaScript的一些命令式风格,以使用户能够以无负担的方式编写功能风格的程序
const Transducer (t = identity) =>
({ ...
, reduce: (f = (a,b) => a, acc = null, xs = []) =>
{
const reducer = t (f)
for (const x of xs)
acc = reducer (acc, x)
return acc
}
})
这里的想法是你编写自己的Transducer
模块并发明任何其他数据类型和实用程序来支持它。熟悉权衡取舍,你可以选择最适合 计划的内容。
本节介绍的“问题”有很多种方法。那么,如果我们不得不在程序的各个部分中不断恢复命令式的风格,那怎么能真正用JavaScript编写函数式程序呢?没有银弹答案,但我花了相当多的时间探索各种解决方案。如果你对这篇帖子很感兴趣并感兴趣,我会分享一些工作here
可能性#4
是的,您可以利用Array.from
将任何可迭代转换为数组,这样我们就可以直接插入Array.prototype.reduce
。现在换能器接受任何可迭代输入,功能样式,和一个简单的实现 -
这种方法的一个缺点是,它创建了一个中间值的数组(浪费的内存),而不是在它们从迭代中出来时一次一个地处理这些值。注意,即使是解决方案#2也存在非常重要的缺陷
const Transducer (t = identity) =>
({ ...
, reduce: (f = (a,b) => a, acc = null, xs = []) =>
Array.from (xs)
.reduce (t (f), acc)
})
答案 1 :(得分:1)
首先感谢您参加课程。 您在编写时遇到问题,因为我们在预期的输入和输出之间存在冲突的数据类型。
在组合flip和double时,seq
帮助器调用transduce
辅助函数,该函数将输入对象转换为[k,v]
条目数组,以便它可以迭代它。它还使用objectReducer
辅助函数调用您的组合变换作为内部缩减器,它只需要Object.assign
来构建累积。
然后遍历[k,v]
条目,将它们传递给组合的缩减器,但是由您来决定是否保持变换之间的数据类型兼容。
在您的示例中,double
将获得flip
的返回值,但double
需要[k,v]
数组,而翻转会返回一个对象。
所以你必须做这样的事情:
const entriesToObject = map(([k,v]) => {
return {[k]:v};
});
const flipAndDouble = compose(
map(([k,v]) => {
return [k,v+v];
}),
map(([k,v]) => {
return [v,k];
}),
entriesToObject,
);
//{ '2': 'one', '4': 'two', '6': 'three' }
这有点令人困惑,因为您必须确保最后一步返回一个对象而不是[k,v]
数组。这样,执行objReducer
的{{1}}将正常工作,因为它期望将对象作为值。
这就是我在Object.assign
添加的原因。
如果entriesToObject
已更新为处理objReducer
数组和对象
作为值,您可以继续从最后一步返回[k,v]
数组,这是一个更好的方法
您可以在此处看到如何重写objReducer的示例: https://github.com/jlongster/transducers.js/blob/master/transducers.js#L766
对于生产用途,如果您使用该传感器库,您可以将输入和输出视为[k,v]数组,这是一种更好的方法。为了您自己的学习,您可以尝试根据该链接修改[k,v]
,然后您应该可以从上面的合成中删除objReducer
。
希望有所帮助!