JavaScript
Curry

Javascript: 関数のCurry化

More than 1 year has passed since last update.

Javascript: 関数のCurry化

Curry me This: Partial Application in JavaScript
https://www.linkedin.com/pulse/curry-me-partial-application-javascript-kevin-greene
カリー化
https://ja.wikipedia.org/wiki/%E3%82%AB%E3%83%AA%E3%83%BC%E5%8C%96
「カリー化 (currying, カリー化された=curried) とは、複数の引数をとる関数を、引数が「もとの関数の最初の引数」で戻り値が「もとの関数の残りの引数を取り結果を返す関数」であるような関数にすること(あるいはその関数のこと)である。」

Code with comment

// curry function turns fn to curried function
function curry(fn, ...args) {
  // arity is the number of arguments which fn takes
  const arity = fn.length

  function curried(...args2) {
    // locals keeps track of the arguments have been passed
    const locals = args.concat(args2)
    // when arguments passed exceeds arity, apply function and return
    if (locals.length >= arity) {
      return fn(...locals)
    // otherwise, return curry(fn, ...locals)
    // -> return ((args.length >= arity) ? curried() : curried)
    } else {
      return curry(fn, ...locals)
    }
  }
  // if already have all arguments, call curried without arguments
  // -> return fn(...locals)
  // otherwise return curried to take more arguments
  return ((args.length >= arity) ? curried() : curried)
}

function threeSum(a, b, c) {
  return a + b + c
}

const addFive = curry(threeSum, 2, 3)

console.log(addFive) // curried function, locals = [2,3]
console.log(addFive(4)) // 9

Curry化の意味

カリー化も部分適用も道具に過ぎず、その真価はより多くのコードを再利用できる事にある。関数を最も普遍的な形で定義し、部分的に適用する事で特定の状況に適した関数を作り出すことができる。関数のサブクラス化というとわかりやすいかもしれない。ほとんどのプログラマーは特定の状況に合わせてサブクラス化される基底クラスという概念に慣れ親しんでいるだろう。それらの言葉での抽象化を考えることで、おそらく、カリー化の価値を理解することができるだろう。

Example

// returns function to takes len and obj
const hasLength = curry((len, obj) => {
  return (obj.length >= len)
})

const arr = [
  'first item',
  'another',
  'an item longer than other items.......'
]

// hasLength(15) is a function to take obj and check length
const arr2 = arr.filter(hasLength(15))
// arr2 = ['an item longer than other items.......']
// verbose, but more clearly illustrates what code is doing

const hasLength = curry((len, obj) => {
  return (obj.length >= len)
})

const arr = [
  'first item',
  'another',
  'an item longer than other items.......'
]

const isFifteenCharsLong = hasLength(15)
const arr2 = arr.filter(isFifteenCharsLong)
// arr2 = ['an item longer than other items.......']
const isFunction = (obj) => typeof obj === 'function'

const matches = curry((selector, element) => {
  // https://developer.mozilla.org/ja/docs/Web/API/Element/matches
  if (isFunction(element.matches)) {
    return element.matches(selector)
  } else {
    const elementList = document.querySelectorAll(selector)
    let i = 0

    while (elementList[i] && elementList[i] !== element) {
      i++
    }

    return (elementList[i] ? true : false)
  }
})

const isListItem = matches('.list-item')
const ul = document.getElementById('some-list')

ul.addEventListener('click', (evt) => {
  if (isListItem(evt.target)) {
    evt.target.classList.add('active')
  }
});

他の方が書いた記事

食べられないほうのカリー化入門