Составная функция без точек с расширенными аргументами

Я пытаюсь выяснить, существует ли шаблон для написания бесточечной составной функции, когда аргументы должны распространяться в каррированных составных функциях
, т.е. (с Ramda):

add_1_and_multiply = (add, mul) => R.compose(R.multiply(mul), R.add(1))(add)
add_1_and_multiply(3, 5)  // 20

Как написать add_1_and_multiply в бесточечном стиле?


person aleclofabbro    schedule 10.10.2017    source источник
comment
Попробуйте использовать композицию для multiply(add(1, x), y) вместо multiply(y, add(1, x)): R.compose(R.multiply, R.add(1))   -  person Bergi    schedule 10.10.2017
comment
@Bergi Берги, это именно то, к чему я пришел в своем ответе.   -  person Are    schedule 10.10.2017


Ответы (1)


Я не уверен, что вы можете легко комбинировать бесточечный стиль и неунарную арность. Сначала подумайте, каким должен быть тип результирующих и составных функций:

// Compose:         (B  ->  C) -> (A  ->  B) ->  A  ->  C
const compose = f => g => x => f(g(x))
// Add:              A  ->  A  ->  A
const add = x => y => x + y
// Mul:              A  ->  A  ->  A
const mul = x => y => x * y

// Add1:             A  ->  A
const add1 = add(1)

// Add1AndMul:       A  ->         A  ->  A
//   because:
//     Add1:         A  ->  A
//     Mul:                 A  ->  A  ->  A
const add_1_and_mul = compose(mul)(add1)

// Mul4:             A  ->  A
const mul_4 = add_1_and_mul(3)
const result = mul_4(5) //> 20

У Ramda есть uncurryN, так что вы можете обернуть его вокруг compose и избавиться от каррирования результирующей функции.

const add_1_and_multiply = R.uncurryN(2, R.compose(R.multiply, R.add(1)))
let result2 = add_1_and_multiply(3, 5) //> 20

Чтобы добавить еще одну функцию в «цепочку», вам нужно скомпоновать ее с предыдущей функцией.

// Add1AndMul:          A -> A -> A
const add1_mul = compose(mul)(add1)

Это наша желаемая подпись.

//                      1         2         3
// Add1AndMulAndAdd:    A ->      A ->      A -> A
//  which is:           |         |         |
//      Add1:           A -> A    |         |
//      Mul:                 A -> A -> A    |
//      Add:                           A -> A -> A

Так что каким-то образом мы должны пройти эти A2 и A3 без каких-либо «очков». Давайте попробуем простую композицию и проанализируем ее:

let add1_mul_add = compose(add)(add1_mul)

Запомните подпись композиции: (E -> F) -> (D -> E) -> D -> F! Разбираем по шагам:

  1. Мы поставляем нашу сигнатуру функции add вместо (E -> F)

     (E -> F     )
     (A -> A -> A)
    

    Мы заключаем, что

     E = A
     F = A -> A
    
  2. То же самое делаем с (D -> E) и add1_mul

     (D -> E     )
     (A -> A -> A)
    

    Мы заключаем, что

     D = A
     E = A -> A
    

Но мы уже видим здесь противоречие! Вывод на шаге 2 противоречит выводу на шаге 1: E не может быть A и A -> A одновременно.

Поэтому мы не можем составить add и add1_mul и наш add1_mul_add выдаст ошибку.

Давайте попробуем обойти проблему и исправить ее, нарушив наше обещание бесточечного стиля.

const add1_mul_add = x => compose(add)(add1_mul(x))

Я собираюсь нарушить некоторые правила и смешать подписи с кодом, чтобы проиллюстрировать свою точку зрения:

x -> (A -> A -> A) -> (x -> A -> A) -> A -> A -> A
                       ||
                       \/
x -> (A -> A -> A) -> (A -> A) -> A -> A -> A
     (E -> F     ) -> (D -> E) -> D -> F

Итак, мы получили нашу правильную подпись! Как избавиться от переменной x, чтобы вернуться к pointfree? Мы можем попытаться найти очевидные закономерности, например... нашу старую композицию функций!

f(g(x)) => compose(f)(g)

И мы находим эту закономерность в нашем новом add1_mul_add -

f = compose(add)
g = add1_mul
f(g(x)) = compose(add)(add1_mul(x))

И сводим его к бесточечному и получаем нашу новую функцию add1_mul_add:

const add1_mul_add = compose(compose(add))(add1_mul)

Но эй - мы можем уменьшить его еще больше!

const add1_mul_add = compose(compose)(compose)(add)(add1_mul)

И там мы нашли то, что уже существует в haskell под названием The Owl.

Мы можем определить его в Javascript просто как:

const owl = compose(compose)(compose)

Но теперь с каждой новой функцией в цепочке вам придется создавать оператор совы более высокого порядка.

const owl2 = compose(compose)(owl)
const add1_mul_add_mul = owl2(mul)(add1_mul_add)

const owl3 = compose(compose)(owl2)
const add1_mul_add_mul_add = owl3(add)(add1_mul_add_mul)

Поэтому я действительно рекомендую, чтобы ваши функции были унарными в бесточечном стиле. Или используйте другие конструкции, такие как списки:

const actions = [ add, mul, add, mul ]
const values  = [ 1,   2,   3,   4   ]
const add_mul_add_mul = (...values) => zip(actions, values).reduce((acc, [action, value]) => action(acc, value), 0)
person Are    schedule 10.10.2017
comment
вау, это просто работает ... но он работает только с двумя функциями для составления, если для составления того же шаблона необходимы дополнительные функции, они должны быть вложены, например: add_1_and_multiply_and_add = R.uncurryN(2, R.compose(R.add, R.uncurryN(2, R.compose( R.multiply, R.add(1))))) -> add_1_and_multiply_and_add(3, 5, 6) //> 26, кажется возможным реализовать функцию spread_compose, которая выполняет работу для любого количество функций - person aleclofabbro; 18.10.2017
comment
Он работает только с 2 функциями, потому что теоретически compose является бинарной функцией. Поэтому, если вы хотите добавить еще одну функцию в цепочку, вы должны скомпоновать ее с уже составленной функцией. Я обновлю ответ соответственно. - person Are; 18.10.2017