javascript - 注入(inject)具有副作用的功能

标签 javascript dependency-injection functional-programming inversion-of-control higher-order-functions

使用高阶函数时出现问题。
假设我有以下不使用它们的代码(而是调用全局函数):

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.logeta conversion
b只是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返回一个将xy绑定到值12的函数。我们将其称为p的该函数正在等待被另一个函数f调用,该函数会将f应用于xycarcdr提供该功能(f)并返回所需的值–在car的情况下,选择x。在cdr的情况下,选择y

所以让我们重复一遍...



“我想念什么吗?”

是。您刚刚从通用数据结构的起源中亲眼目睹了(高阶)功能。

您的语言是否缺少您可能需要的特定数据结构?您的语言是否提供一流的功能?如果您对这两个答案都回答为“是”,则没有问题,因为您可以使用函数来实现所需的特定数据结构。

那就是高阶函数的力量。



勉强说服

好的,所以也许您认为我在这里花了一些技巧来代替上面的Array。我向你保证,没有花招。

这是我们将用于新字典类型dict的API

dict (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/

相关文章:

javascript - 重复值 VS document.querySelector

c# - 什么时候使用闭包?

JavaScript 当用户结束事件时调用函数

javascript - Duktape 有在 Linux 中执行外部程序的功能吗?

javascript - 如何修复此(不是函数)错误?

c# - 使用 IOptions 注入(inject)问题初始化类

c# - 在 ASP.NET Core 中通过依赖注入(inject)注册后,如何修改选项对象(保存配置设置)中的值?

java - 当我有很多 SpringBootTest 类时,如何有效地使用嵌套配置类来注入(inject)依赖项

haskell - Haskell 初学者指南?

functional-programming - 在哪里可以获得 Erlang BEAM VM 的最新规范?