使用高阶函数时出现问题。
假设我有以下不使用它们的代码(而是调用全局函数):
import {db_insert} from 'some-db-lib' // function with side-effect
const save_item = (item) => {
// some logic like validating item data...
db_insert(item) // call db_insert globally
}
const handle_request = (request) => {
// some logic like sanitizing request...
save_item(request.data) // call save_item globally
}
handle_request(some_request)
现在,使用高阶函数(将副作用作为函数参数注入)是同一示例:
import {db_insert} from 'some-db-lib' // function with side-effect
const save_item = (item, insert) => { // inject insert
// some logic like validating item data...
insert(item)
}
const handle_request = (request, save, insert) => { // inject save and insert
// some logic like sanitizing request...
save(request.data, insert)
}
handle_request(some_request, save_item, db_insert)
想象一下,通过更大的函数树相互调用。最后一个示例将使函数相互传递而成为一大堆函数。
这是隔离副作用的正确方法吗?我想念什么吗?
最佳答案
使用高阶函数时出现问题。假设我有以下不使用它们的代码(而是调用全局函数):
c = (x) => console.log(x)
b = (x) => c(x)
a = (x) => b(x)
a('Hello world')
坦白说,但这是一个可怕的起点
c
只是console.log
的eta conversionb
只是c
的eta转换a
只是b
的eta转换换句话说,
a
=== b
=== c
=== console.log
–如果您要了解高阶函数,则需要一个更好的起点常见示例:
map
人们喜欢使用
Array.prototype.map
演示高阶函数const f = x => x + 1
const g = x => x * 2
const xs = [1,2,3]
console.log (xs.map (f)) // [2,3,4]
console.log (xs.map (g)) // [2,4,6]
这里发生了什么事?实际上非常整洁。我们可以采用输入数组
xs
并创建一个新数组,其中每个元素都是使用高阶函数对xs
中的元素进行转换。高阶函数可以是一次性使用lambda,也可以是已在其他位置定义的命名函数。// xs.map(f)
[ f(1), f(2), f(3) ]
[ 2 , 3 , 4 ]
// xs.map(g)
[ g(1), g(2), g(3) ]
[ 2 , 4 , 6 ]
// xs.map(x => x * x)
[ (x => x * x)(1), (x => x * x)(2), (x => x * x)(3) ]
[ 1 , 4 , 9 ]
更大的图景
好的,这是在JavaScript中使用高阶函数的非常实际的示例,但是...
我想念什么吗?
是。高阶函数具有深远而有意义的功能。让我提出另一组问题:
如果没有诸如数组之类的东西怎么办?
我们如何以有意义的方式将价值分组在一起?
如果没有一组价值观,我们当然不能对它们进行
map
吧?如果我告诉您只需要函数即可完成所有操作呢?
// empty
const empty = null
const isEmpty = x => x === empty
// pair
const cons = (x,y) => f => f (x,y)
const car = p => p ((x,y) => x)
const cdr = p => p ((x,y) => y)
// list
const list = (x,...xs) =>
x === undefined ? empty : cons (x, list (...xs))
const map = (f,xs) =>
isEmpty (xs) ? empty : cons (f (car (xs)), map (f, cdr (xs)))
const list2str = (xs, acc = '( ') =>
isEmpty (xs) ? acc + ')' : list2str (cdr (xs), acc + car (xs) + ' ')
// generic functions
const f = x => x + 1
const g = x => x * 2
// your data
const data = list (1, 2, 3)
console.log (list2str (map (f, data))) // '( 2 3 4 )'
console.log (list2str (map (g, data))) // '( 2 4 6 )'
console.log (list2str (map (x => x * x, data))) // '( 1 4 9 )'
我仔细观察,会发现此代码未使用JavaScript提供的任何本机数据结构(除了
Number
例如数据,String
出于输出内容目的)。没有Object
,没有Array
。没有技巧只需Function
s。它是如何做到的?数据在哪里?
简而言之,数据存在于部分应用的函数中。让我们专注于一段特定的代码,以便让我明白我的意思
const cons = (x,y) => f => f (x,y)
const car = p => p ((x,y) => x)
const cdr = p => p ((x,y) => y)
const pair = cons (1,2)
console.log (car (pair)) // 1
console.log (cdr (pair)) // 2
当我们使用
pair
创建cons(1,2)
时,请仔细查看数据如何存储在pair
中。它以什么形式出现? cons
返回一个将x
和y
绑定到值1
和2
的函数。我们将其称为p
的该函数正在等待被另一个函数f
调用,该函数会将f
应用于x
和y
。 car
和cdr
提供该功能(f
)并返回所需的值–在car
的情况下,选择x
。在cdr
的情况下,选择y
。所以让我们重复一遍...
“我想念什么吗?”
是。您刚刚从通用数据结构的起源中亲眼目睹了(高阶)功能。
您的语言是否缺少您可能需要的特定数据结构?您的语言是否提供一流的功能?如果您对这两个答案都回答为“是”,则没有问题,因为您可以使用函数来实现所需的特定数据结构。
那就是高阶函数的力量。
勉强说服
好的,所以也许您认为我在这里花了一些技巧来代替上面的
Array
。我向你保证,没有花招。这是我们将用于新字典类型
dict
的APIdict (key1, value1, key2, value2, ...) --> d
read (d, key1) --> value1
read (d, key2) --> value2
write (d, key3, value3) --> d'
read (d', key3) --> value3
在下文中,我将保证不使用除函数(和用于演示输出的字符串)以外的任何内容,但是这次我将实现一个可以容纳键/值对的不同数据结构。您可以基于键读取值,写入新值和覆盖现有值。
这将加强高阶数据的概念,即作为较低抽象的抽象的数据–即,使用
dict
实现的node
使用通过list
实现的cons
实现的// empty
const empty = null
const isEmpty = x => x === empty
// pair
const cons = (x,y) => f => f (x,y)
const car = p => p ((x,y) => x)
const cdr = p => p ((x,y) => y)
// list
const list = (x,...xs) =>
x === undefined ? empty : cons (x, list (...xs))
const cadr = p => car (cdr (p))
const cddr = p => cdr (cdr (p))
const caddr = p => car (cddr (p))
const cadddr = p => cadr (cddr (p))
// node
const node = (key, value, left = empty, right = empty) =>
list (key, value, left, right)
const key = car
const value = cadr
const left = caddr
const right = cadddr
// dict
const dict = (k,v,...rest) =>
v === undefined ? empty : write (dict (...rest), k, v)
const read = (t = empty, k) =>
isEmpty (t)
? undefined
: k < key (t)
? read (left (t), k)
: k > key (t)
? read (right (t), k)
: value (t)
const write = (t = empty, k, v) =>
isEmpty (t)
? node (k, v)
: k < key (t)
? node (key (t), value (t), write (left (t), k, v), right (t))
: k > key (t)
? node (key (t), value (t), left (t), write (right (t), k, v))
: node (k, v, left (t), right (t))
let d = dict ('a', 1, 'b', 2)
console.log (read (d, 'a')) // 1
console.log (read (d, 'b')) // 2
console.log (read (d, 'c')) // undefined
d = write (d, 'c', 3)
console.log (read (d, 'c')) // 3
现在肯定可以看到高阶函数的功能,对吗? ^ _ ^
关于javascript - 注入(inject)具有副作用的功能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42179513/