Зробіть мені трохи каррі


20

Маючи функцію f, яка приймає аргументи x 1 , x 2 ,…, x n

                                               - тобто.  f: X 1 × X 2 ×… × X n → Y

- currying переосмислює f як функцію, що приймає єдиний аргумент a 1, який відображає ще одну функцію. Цей прийом корисний для часткового застосування, наприклад, із завитою powфункцією, яку ми могли б написати exp = pow(e).

Приклад

Припустимо, що ми маємо таку функцію f, яка бере три аргументи ( f: X 1 × X 2 × X 3 → Y ):

def f(a,b,c):
  return a + b * c

Виконання цієї функції залишає нам f_curry: X 1 → (X 2 → (X 3 → Y)) , якщо ми зараз би двічі викликали цю функцію, f_curry(1)(2)ми отримаємо функцію ( h), еквівалентну наступному поверненому:

def h(c):
   return 1 + 2 * c

Вироблена функція fможе бути записана так (Python 3):

def f_curry(a):
  def g_curry(b):
    def h(c):
      return a + b * c
    return h
  return g_curry

Спробуйте в Інтернеті!

Виклик

Вашим завданням буде стати функцією, описаною вище, ось такі правила:

  • Вхід буде функцією blackbox, яка бере щонайменше 2 аргументи
  • Функція введення завжди матиме фіксовану кількість аргументів (на відміну від printfабо подібних, зверніть увагу: вам потрібно підтримувати функції з будь-якою кількістю аргументів ≥2)
  • Якщо у вашій мові за замовчуванням використовуються вивірені функції (наприклад, Haskell), ви можете очікувати, що функція введення буде визначена через N- пар, замість "функції вищого порядку"
  • Ви можете взяти кількість аргументів як вхідні дані
  • Вихідним показником буде еквівалентний вхід *
  • Ви можете припустити, що функція виводу завжди буде:
    • викликається з меншою або рівною кількістю аргументів, яку приймає функція введення
    • викликається аргументами правильного типу

* Це буде означати для введення fзN аргументами та висновку, hщо для всіх дійсних аргументів a1,…,aNце має місце f(a1,a2,…,aN) == h(a1)(a2)…(aN).



так що вхід є, def f(a,b,c): return a + b * cа вихід є def f_curry(a): def g_curry(b): def h(c): return a + b * c return h return g_curry?
DanielIndie

@DanielIndie: Якщо ви берете цей приклад, вхід буде f(що визначено десь), а вихід повинен бути чимось еквівалентним f_curry. Або вхід буде, lambda a,b,c: a+b*cа вихід - еквівалентною функції f_curry.
ბიმო

Це важко зробити у більшості мов, які набираються статично ... Я думаю, для цього вам потрібні функції типу.
Paŭlo Ebermann

@ PaŭloEbermann: Правда, деякі мови не зможуть вирішити це завдання (зверніть увагу на функціональне програмування тегів ). Однак деякі статично набрані мови можуть бути в змозі використовувати функціональні вказівники, які були б дійсним введенням-виведенням, це головним чином причина, за якою я дозволив приймати кількість аргументів як додатковий вхід.
ბიმო

Відповіді:



9

Ідріс , 204 байти

import Data.HVect
C:(a:Vect n Type)->(HVect a->Type)->Type
C[]T=T[]
C(h::t)T=(x:h)->C t(T .(x::))
c:{a:Vect n Type}->{T:HVect a->Type}->((b:HVect a)->T b)->C a T
c{a=[]}f=f[]
c{a=h::t}f=\v=>c(\u=>f(v::u))

Спробуйте в Інтернеті!

Звучить як робота для залежних типів! Ну, можливо.


C - функція типу currying. Враховуючи вектор типів a = [t 1 , t 2 ,… t n ] та функцію типу T: HVect a → Type , він повертає новий тип:

           (x 1  : t 1 ) → (x 2  : t 2 ) →… → (T [x 1 , x 2 ,… x n ])

Тут HVect - неоднорідний векторний тип із прелюдії Ідріса - тип n- пар, елементи яких мають n різних типів.

c - це функція, яка приймає a і T як неявні аргументи, а потім перетворює uncurried функцію fтипу ((b: HVect a) → T b) у curried тип C A T .

( C просто описує те, що ми хочемо зробити; c насправді це робить. Але ми не можемо відійти від того, щоб не визначити C , оскільки Ідріс вимагає, щоб кожне визначення верхнього рівня було підписом типу.)


Посилання TIO дає приклад використання. Якщо ми визначимо функцію на 3-кортежах (Nat, Nat, String) так:

uncurried : HVect [Nat, Nat, String] -> String
uncurried [a, b, c] = show (a*a + b*b) ++ c

то uncurried [3, 4, "th"]дає такий же результат, як і c uncurried 3 4 "th". Ідріс приводить аргументи a=[Nat, Nat, String]і T=const Stringдля нас, я вважаю.

Я базував цей код на цій суті від timjb.


На мою думку, кортежі в Хаскеллі та Ідрісі насправді повинні бути HVectза замовчуванням HVect- це, по суті, кортеж, який можна розстебнути.
Esolanging Fruit


5

R , 96 байт

y=function(f,n=length(formals(f)),p=list())function(x,u=c(p,x))`if`(n<2,do.call(f,u),y(f,n-1,u))

Спробуйте в Інтернеті!


Попередня версія (97 байт)

-1 байт завдяки @JayCE


Я не бачу, як кардинально це скоротити. Ви можете відіграти три байти, позбувшись дужок і місця в кінці першого рядка. І ще два через умову, що тут не включають ім'я функції в число байтів. TIO
ngm

@ngm Ім'я функції має бути включене, коли вона рекурсивна.
Ørjan Johansen

@ngm: Я поставив оператор if всередині підфункції, заощаджуючи десяту частину байтів :)
digEmAll


3

Python 2 , 60 байт

c=lambda f,n,l=[]:lambda a:n-1and c(f,n-1,l+[a])or f(*l+[a])

Спробуйте в Інтернеті!

Нижній колонтитул - це тестер, який використовує STDIN таким чином за рядком:

  1. Сама функція
  2. Кількість аргументів функції, ≥2
  3. Список аргументів ( [a,b,...])

Зауважте, що, хоча список аргументів подається як вхід у тестер, насправді добутий еквівалент стає попереднім до списку, а список зменшується за допомогою виклику функції.

Подібну 55- байтну версію люб'язно надали ovs :

c=lambda f,n,*l:lambda a:n-1and c(f,n-1,*l,a)or f(*l,a)

Спробуйте в Інтернеті!


2

Цвітна капуста , 84 байт

(:= c(\($f$n(@a))(if$n(\($a)(call c(cat(list$f(-$n 1))@a(list$a))))else(call$f@a))))

Спробуйте в Інтернеті!


1
Ммм, каррі цвітної капусти. Смачний. ^ _ ^
DLosc

@DLosc недостатньо відповідей на це завдання в мовах із назвами, пов’язаними з їжею: P (хоча, мабуть, більшість із них насправді не має функцій)
лише ASCII



1

Приєднати , 5 байт

Curry

Спробуйте в Інтернеті!

Простий вбудований, багато в чому нецікавий. Але ось версія з нуля:

Attache, 35 байт

{If[#__2<#_,Fold[`&:,$'__],_@@__2]}

Пояснення:

{If[#__2<#_,Fold[`&:,$'__],_@@__2]}
{                                 }    lambda
 If[       ,              ,      ]     if
    #__2                                 the number of parameters after the first
        <#_                              is less than the arity of the first
            Fold[   ,    ]             then fold:
                 `&:                     right-function bonding
                     $                     over this function
                      '__                  paired with the rest of the parameters
                          ,            otherwise:
                           _@@           call the first parameter
                              __2        with the rest of them

1

Java 8, 46 + 318 = 364 байти

Це зрізана (га) лямбда, яка приймає функцію та кількість аргументів і повертає функцію вилучення.

import java.lang.reflect.*;import java.util.*;

f->p->new java.util.function.Function(){Object F=f;Method m=F.getClass().getDeclaredMethods()[0];int P=p;List a=new Stack();public Object apply(Object r){a.add(r);try{return a.size()<P?this:m.invoke(F,a.toArray());}catch(Throwable t){t=t.getCause();if(t instanceof Error)throw(Error)t;else throw(RuntimeException)t;}}}

Спробуйте в Інтернеті

Тип подання

Функція введення

Вхід функції - це об'єкт з єдиним методом (за винятком успадкованих методів), що представляє функцію. Зауважте, що стандартний функціональний інтерфейс не може використовуватися як тип введення, оскільки функції (наприклад) 3 параметрів повинні підтримуватися. Також зауважте, що лямбда-вираз, java.util.function.Functionпереданий стандартному типу, може передаватися (єдиний метод є apply).

Перевірені винятки можуть бути оголошені у вхідній функції, але вони не можуть бути кинуті (тобто вони не будуть поширюватися на виклик функції виводу). Це вважається прийнятним, оскільки функціональні інтерфейси Java не дозволяють перевіряти винятки (а їх розповсюдження перешкоджатиме поверненню а Function). Винятки з виконання (віднесені до RuntimeExceptionабоError ) поширюються.

Вихідна функція

Результатом подання є java.util.function.Function<Object, Object>. Я вважав повернення рівнину Objectз applyметодом (наприклад , на вході), а потім відображення необхідно буде посилатися на результат, який , здавалося незручним досить , щоб бути disallowable зокрема, називаючи весь шлях вниз буде вже не можливо в одному вираз.

Використання

Оскільки подання повертає функцію від Objectдо Object, вихід може бути викликаний безпосередньо (з apply), але наступні проміжні значення повернення повинні бути передані відповідному типу (наприклад,java.util.function.Function<Object, Object> ) перед тим, як викликати. Зверніться до TIO для деяких прикладів використання.

Зауважте, що в Java функції (тобто методи) не є першокласними об'єктами. Таким чином, синтаксис, що використовується у вихідній кулі опису виклику, на Java не має сенсу. Швидше, ніж у f(a1, a2, a3)нас f.apply(a1, a2, a3), а не в f(a1)(a2)(a3)нас f.apply(a1).apply(a2).apply(a3).

Обмеження

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

Function<Object, Function<Integer, Function>> submission = ...;
Function c = submission.apply((IntBinaryOperator) (a, b) -> a + b).apply(2);
Function c2 = (Function) c.apply(2);
System.out.println(c2.apply(2));
System.out.println(c2.apply(3));

рядок 4 буде надруковано 4, але рядок 5 провалиться, оскільки до того часу c2вже є аргументи 2і 2(зауважте також, що c2 == c). Це порушує дух каррі, але відповідає конкретній вимозі, заявленій у виклику.

Безумовно

Дивіться ТІО для копії, що не перебуває в комах.



Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.