Currying in js

10169 ワード

Currying is functional programming technique we can use to write code that is modular, easy to test, and highly reusable. Functional programming is a declarative paradigm that emphasizes immutability and pure functions — meaning the function is side-effect free and for any given input it will always return the same output.

How currying works


Converting regular function into a series of nested functions that will each take a single argument.
This makes the function calls more modular. With curried functions, calling the outer function returns the next function in the chain and so on until the innermost function is called, which then returns a value.
// traditional function
function add(a,b) {
// Could return NaN if either one argument is missing as number.
    return a + b;
}

//Curried
function curried_add(a) {
    // has access to the argument for a
    return function nested_add(b) {
        // has access to the arguments for a and b
        return a + b;
    }
}
 
// creates a local variable a and assigns it the value 1
let add_one = curried_add(1); 
 
// add_one() still has access to the argument from curried_add()
add_one(10);  // 11
The argument from calling curried_add() is available to the nested functions due to closure. A closure means that the nested function retains the scope of parent functions based on where the function is defined, even if the nested function is executed outside of that lexical scope.
A lexical scope in JavaScript means that a variable defined outside a function can be accessible inside another function defined after the variable declaration.

Lexical vs Closure


The lexical scope allows a function scope to access statically the variables from the outer scopes. Finally, a closure is a function that captures variables from its lexical scope.
You may recall that a function can access variables both in its inner and outer scope. That behavior is an example of lexical scoping rules, which means the scoping is based on the structure of the code.
Taking that one step further, when a function is invoked, lexical scoping is retained. So nested functions will continue to have access to any variables that are declared in the outer scope of parent functions. This is true even if that parent function is done executing.
let add_one = curried_add(1); 
 
// add_one() still has access to the argument from curried_add()
add_one(10);  // 11
Overall, that means that when you run the line let add_one = curried_add(1); , add_one() will retain the scope from curried_add() and therefore have access to the variable created for the argument a as 1, which you can see explained in the code snippet line by line:

Arrow function with currying


The same curried_add() function from earlier can be rewritten much more concisely using ES6 arrow function syntax:
let curried_add = a => b => a + b;
  • let curried_add is a variable assignment to the outer arrow function, a => ....
  • Calling curried_add takes an argument a and returns b => a + b.
  • Invoking the second arrow function returns the sum, or a + b.
  • Currying in Context(リンクコンテキスト)


    たとえば、プレイヤーアレイでは、年齢、スポーツ、居住都市、日付に応じてフィルタリングする必要があります.
    const players = [
        { age: 5, sport: "soccer", city: "Chicago", dateJoined: new Date('2021-01-20') },
        { age: 6, sport: "baseball", city: "Boulder", dateJoined: new Date('2019-12-30') },
        { age: 10, sport: "soccer", city: "Chicago", dateJoined: new Date('2020-11-12') },
        { age: 11, sport: "handball", city: "San Francisco", dateJoined: new Date('2020-08-21') },
        { age: 6, sport: "soccer", city: "Chicago", dateJoined: new Date('2021-07-06') },
        { age: 8, sport: "softball", city: "Boulder", dateJoined: new Date('2019-02-27') },
        { age: 7, sport: "tennis", city: "San Francisco", dateJoined: new Date('2019-05-31') },
        { age: 4, sport: "handball", city: "San Francisco", dateJoined: new Date('2018-03-10') }
    ]
    以下の関数は1)パラメータ都市に基づいてプレイヤーアレイをフィルタリングし,年齢値に基づいてソートする.
    const sortPlayersByValueFromCity = (playersArr, city, sortKey) => {
        return playersArr.filter(player => {
            return player.city === city;
        }).sort((a,b) => {
            return a[sortKey] - b[sortKey]
        });
    }
     
    console.log(sortPlayersByValueFromCity(players, "San Francisco", "age"));
    また、次の関数は、プレーヤーアレイに指定されたパラメータ都市にフィルタリングしてから、その都市のkeyvalueプロパティに基づいてフィルタリングすることができます.
    const filterPlayersByValueFromCity = (playersArr, city, filterKey, filterValue) => {
     return playersArr.filter(player => {
       return player.city === city;
     }).filter(playersFromCity => playersFromCity[filterKey] === filterValue)
    }
     
    console.log(filterPlayersByValueFromCity(players, "San Francisco", "sport", "handball"));
    前の2つの例に示すように、この2つの関数は大きくて重複しており、デバッグに多くの時間がかかる場合があります.
    符号化技術を使用すると、より多くの同じ関数を宣言形式に分解することができ、再利用が容易で、読みやすい.
    最も重要なのは、パラメータを関数に入れる順序が混同され、誤ったパラメータを関数に渡すことができるため、エラーが発生しやすいことです.
    const filterPlayersByValueFromCity = (playersArr, city, filterKey, filterValue) => {
     return playersArr.filter(player => {
       return player.city === city;
     }).filter(playersFromCity => playersFromCity[filterKey] === filterValue)
    }
     
    console.log(filterPlayersByValueFromCity(players, "San Francisco", "sport", "handball"));
    
    // *** curried ***
    const setFilter = arr => key => val => arr.filter(obj => obj[key] === obj[val]);
    
    const filterPlayers = setFiler(players);
    const filterPlayersByCity = filterPlayers('city');
    const filterPlayersBySanFrancisco = filterPlayersByCity('San francisco');
    
    // 리사이클링 예 
    const playersInSanFrancisco = setFilter(filterPlayersBySanFrancisco);
    const handballPlayersInSanFran = playersInSanFrancisco('sport')('handball');
    
    実際、コード量は一般的なFilterPlayersByValueFromCity()よりも多く見えますが、関数変数ごとに1つのことしか処理されていないので読みやすく、将来のリサイクルは本当に良いです.
    上記の例のsetFilterを使用すると、異なる属性キー値に基づいてフィルタを繰り返す必要がある場合、フィルタ後の値を異なる変数に指定して再使用できます.
    //SanFrancisco 말고 Chicago 도시로 필터링 하고 싶음 이렇게 한줄만 더 넣음 된다. 인자를 실수로 인해 잘못 넣을일이 없다.
    const playersInChicago = filterPlayersByCity('Chicago');
    With currying, we can write functions that handle one task, and are therefore not only easier to read and understand, but more reusable. For example, we can create a curried function that filters an array of objects by a provided key and value.
    In this post, we took a look at how currying works under the hood thanks to closures in JavaScript, and how you can use different syntax techniques to curry your functions.
    Overall, thanks to the modularity of curried functions, you can now use currying in your code to make your functions have a single purpose and therefore be easier to test, debug, maintain, and read.

    curryingの使用例


    次のコード例はfrontsom.tistoryであり、例を用いて再生をうまく行うことができる.
    let sentence =
        greeting => seperator =>  end => name =>
            console.log(greeting + seperator + name + end);
    
    let introduce = sentence('안녕하세요')('. ')('입니다.');
    introduce('다솜'); // 안녕하세요. 다솜입니다.
    
    let greet = sentence('안녕하세요')('? ')('씨.');
    greet('다솜'); // 안녕하세요? 다솜씨.
    
    let bye = sentence('안녕히가세요')(', ')('씨.');
    bye('다솜'); // 안녕히가세요, 다솜씨.
    前述したように、cantentというcurring関数を使用して様々な関数を定義および使用することもでき、部分的に定義された関数を再定義することでモードを使用することもでき、重複を最小限に抑えることができる.
    伝統的な使い方
    上記ではES 6文法を用いて簡単に表現していますが、従来JavaScriptではカレー関数は基本的にサポートされていません.したがって,矢印関数なしでcurringを実現するには,コードが非常に長くなる.だから伝統的な方法で実施するなら、Function.Prototypeにcurryメソッドを追加することで使用できます.
    Function.prototype.curry = function() {
        var slice = Array.prototype.slice;
        var args = slice.apply(arguments);
        var that = this;
        return function() {
            return that.apply(null, args.concat(slice.apply(arguments)));
        };
    }
    
    var sum1 = sum.curry(1);
    console.log(sum1(5));        // 6
    console.log(sum1(3));        // 4
    The arguments object:
    arguments is an Array-like object accessible inside functions that contains the values of the arguments passed to that function. - でもes 6を使って...rest文法を書きましょう
    Note: "Array-like"means that arguments has a length property and properties indexed from zero, but it doesn't have Array's built-in methods like forEach() or map(). See §Description for details.
    function func1(a, b, c) {
      console.log(arguments[0]);
      // expected output: 1
    
      console.log(arguments[1]);
      // expected output: 2
    
      console.log(arguments[2]);
      // expected output: 3
    }
    
    func1(1, 2, 3);
    
    The apply() method calls a function with a given this value, and arguments provided as an array (or an array-like object).
    apply(thisArg)
    apply(thisArg, argsArray)
    thisArg -> if the method is a function in non-strict mode code, null and undefined will be replaced with the global object,
    ref:
    https://frontsom.tistory.com/m/10#:~:text=%EC%9E%A0%EC%8B%9C%20%EC%99%9C%20%ED%95%A8%EC%88%98%ED%98%95%20JavaScript%EB%A5%BC,%EC%9E%91%EC%84%B1
    and codecademy.com