注入具有副作用的功能 [英] Inject functions with side-effects

本文介绍了注入具有副作用的功能的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

使用高阶函数时遇到问题。
比方说,我有下面的代码不使用它们(而是调用全局函数):

$ $ p $ import { db_insert} from'some-db-lib'//带副作用的函数

const save_item =(item)=> {
//一些逻辑,如验证项目数据...
db_insert(item)//调用全局的db_insert
}
$ b $常量handle_request =(请求)=> ; {
//一些逻辑如清理请求...
save_item(request.data)//全局调用save_item
}

handle_request(some_request)

现在,通过使用高阶函数(注入副作用作为函数参数) / p>

 从'some-db-lib'导入{db_insert} //带副作用的函数

const save_item =(item,insert)=> {//注入插入
//一些逻辑,如验证项目数据...
insert(item)
}

const handle_request =(request,save,insert )=> {//注入save并插入
//一些逻辑,如清理请求...
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转换

  • b 只是 c
  • a 只是 b


换句话说, a === b code> === c === console.log - 如果您要了解更高您需要一个更好的起点




常见示例: map



人们喜欢使用 Array.prototype.map



const f = x => x + 1const g = x => x * 2const 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 b)]
[2],[b] 3 [/ b],[b] 4 [/ b]]

// 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中使用高阶函数的非常实用的例子,但是...

lockquote

我缺少什么?


是的。高阶函数具有非常深刻而有意义的力量。让我提出另一组问题:


  • 如果没有Array这样的东西会怎么样?

  • 我们如何以有意义的方式将值组合在一起?

  • 没有一组值,当然我们不能 map over他们是吧?


    如果我告诉你只有函数需要完成所有操作,那该怎么办?

      // emptyconst empty = nullconst isEmpty = x => x === empty // pairconst cons =(x,y)=> f => f(x,y)const car = p => p((x,y)=> x)const cdr = p => p((x,y)=> y)// listconst 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)+'')//通用functionsconst f = x => x + 1const g = x => x * 2 //您的dataconst数据= list(1,2,3)console.log(list2str(map(f,data)))//'(2 3 4)'console.log(list2str(map(g,数据)))//'(2 4 6)'console.log(list2str(map(x => x * x,data)))//'(1 4 9)' 

    我仔细看看,你会看到这段代码并没有使用任何原生数据结构JavaScript(除了数字为例子数据和字符串用于输出内容以外)。没有 Object s,没有 Array s。没有技巧。只需功能 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))// 1console.log(cdr(pair))// 2  



    当我们创建 pair code> cons(1,2)仔细查看数据如何存储在 pair 中。它是什么形式? cons 返回一个函数,它绑定到 x y 1 2 。这个函数,我们称之为 p ,正在等待被另一个函数 f 调用,它将应用 f x y car cdr 提供该函数( f )并返回在 car x 的情况下选择期望的值。在 cdr 的情况下,选择 y



    让我们重复...






    我错过了什么吗?



    是的。您刚刚目睹了多功能数据结构的起源,除了(高阶)函数之外。

    您的语言是否缺少您可能需要的特定数据结构?你的语言是否提供一流的功能?如果你对这两者都回答'是',那么没有任何问题,因为你可以只使用函数来实现你需要的特定数据结构。



    / em>是高阶函数的力量。




    不情愿的说服



    好的,也许你认为我在那里做了一些技巧,替换上面的 Array 。我向你保证,这里不涉及任何技巧。

    下面是我们将为一个新的字典类型 dict
    $ b

      dict(key1,value1,key2,value2,...) - > d 
    read(d,key1) - > value1
    read(d,key2) - > value2

    write(d,key3,value3) - > d'
    read(d',key3) - > value3

    下面,我将保持不使用除函数之外的任何东西(以及用于演示输出的字符串目的),但是这次我将实现一个可以保存键/值对的不同数据结构。您可以读取值,写入新值,并根据键覆盖现有值。



    这将强化高阶数据的概念 ,即数据是抽象的抽象 - 即,使用节点来实现 dict list 这是使用 cons 等实现的



      // emptyconst empty = nullconst isEmpty = x => x === empty // pairconst cons =(x,y)=> f => f(x,y)const car = p => p((x,y)=> x)const cdr = p => p((x,y)=> y)// listconst 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))// nodeconst node =(key,value,left = empty,right = empty)=> list(key,value,left,right)const key = carconst value = cadrconst left = caddrconst right = cadddr // dictconst dict =(k,v,... rest)=> v ===未定义? empty:write(dict(... rest),k,v)const read =(t = empty,k)=> isEmpty(t)?未定义:k <键(t)?读(left(t),k):k>键(t)?读(right(t),k):value(t)const write =(t = empty,k,v)=> isEmpty(t)?节点(k,v):k <键(t)?节点(key(t),value(t),write(left(t),k,v),right(t)):k>键(t)?节点(key(t),value(t),left(t),write(right(t),k,v)):节点(k,v,left(t),right(t))let d = dict ('a',1,'b',2)console.log(read(d,'a'))// 1console.log(read(d,'b'))// 2console.log(read(d ,'c'))// undefinedd = write(d,'c',3)console.log(read(d,'c'))// 3  



    当然现在你看到高阶函数的力量,对吗? ^ _ ^


    I'm having an issue when using higher-order functions. Let's say I have the following code that doesn't use them (instead call global 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)
    

    And now, the same example, by using higher-order functions (inject side-effects as function arguments):

    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)
    

    Imagine this with a larger tree of functions calling each other. The last example would become a big mess of functions passing functions down to each other.

    Is this the correct way to isolate side-effects? Am I missing something?

    解决方案

    I'm having an issue when using higher-order functions. Let's say I have the following code that doesn't use them (instead call global functions):

    c = (x) => console.log(x)
    b = (x) => c(x)
    a = (x) => b(x)
    a('Hello world')
    

    But this is a terrible starting point, to be honest

    • c is just an eta conversion of console.log
    • b is just an eta conversion of c
    • a is just an eta conversion of b

    In other words, a === b === c === console.log – If you're going to understand higher-order functions, you need a better starting point


    The common example: map

    People love to demonstrate higher-order functions using 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]

    What's happening here? It's actually pretty neat. We can take an input array xs and create a new array where each element is the transformation of an element in xs using a higher-order function. The higher-order function could be a one-time use lambda, or it could be a named function that was already defined elsewhere.

    // 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  ]


    The bigger picture

    OK, so that's a very practical example of using higher-order functions in JavaScript, but ...

    Am I missing something?

    Yes. Higher-order functions have an immensely deep and meaningful power. Let me pose another set of questions:

    • What if there was no such thing as an Array?
    • How would we group values together in a meaningful way?
    • Without a group of values, surely we couldn't map over them, right?

    What if I told you only functions were required to do all of it?

    // 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 )'

    I you look closely, you'll see this code doesn't use any native data structures provided by JavaScript (with the exception of Number for example data and String for purposes of outputting something to see). No Objects, no Arrays. No tricks. Just Functions.

    How does it do that? Where is the data?

    In short, the data exists in partially applied functions. Let's focus on one specific piece of code so I can show you what I mean

    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

    When we create pair using cons(1,2) look carefully how the data gets stored in pair. What form is it in? cons returns a function with x and y bound to the values 1 and 2. This function, we'll call it p, is waiting to be called with another function f, which will apply f to x and y. car and cdr provide that function (f) and return the desired value – in the case of car, x is selected. in the case of cdr, y is selected.

    So let's repeat...


    "Am I missing something?"

    Yes. What you just witnessed with the genesis of a versatile data structure out of nothing but (higher-order) functions.

    Is your language missing a particular data structure you might need? Does your language offer first class functions? If you answered 'yes' to both of those, there is no problem because you can materialize the particular data structure you need using nothing but functions.

    That is the power of higher-order functions.


    Reluctantly convinced

    OK, so maybe you're thinking I pulled some tricks there making a replacement for Array above. I assure you, there are no tricks involved.

    Here's the API we will make for a new dictionary type, dict

    dict (key1, value1, key2, value2, ...) --> d
    read (d, key1) --> value1
    read (d, key2) --> value2
    
    write (d, key3, value3) --> d'
    read (d', key3) --> value3
    

    Below, I will keep the same promise of not using anything except functions (and strings for demo output purposes), but this time I will implement a different data structure that can hold key/value pairs. You can read values, write new values, and overwrite existing value based on a key.

    This will reinforce the concept of higher-order data, that is data that is an abstraction of a lower abstraction – ie, dict is implemented using node which is implemented using list which is implemented using cons, etc

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

    Surely now you see the power of higher-order functions, right ? ^_^

    这篇关于注入具有副作用的功能的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆