Java รองรับ Currying หรือไม่


90

ฉันสงสัยว่ามีวิธีใดในการดึงสิ่งนั้นใน Java ฉันคิดว่าเป็นไปไม่ได้หากไม่มีการสนับสนุนจากเจ้าของภาษา


4
สำหรับบันทึกตอนนี้ Java 8 รองรับการแกงและแอปพลิเคชันบางส่วนและมีการรองรับการปิดแบบดั้งเดิม นี่เป็นคำถามที่ล้าสมัยอย่างมาก
Robert Fischer

คำตอบ:


146

Java 8 (เผยแพร่ 18 มีนาคม 2014) รองรับการแกงกะหรี่ โค้ด Java ตัวอย่างที่โพสต์ในคำตอบโดย missingfaktorสามารถเขียนใหม่ได้ดังนี้:

import java.util.function.*;
import static java.lang.System.out;

// Tested with JDK 1.8.0-ea-b75
public class CurryingAndPartialFunctionApplication
{
   public static void main(String[] args)
   {
      IntBinaryOperator simpleAdd = (a, b) -> a + b;
      IntFunction<IntUnaryOperator> curriedAdd = a -> b -> a + b;

      // Demonstrating simple add:
      out.println(simpleAdd.applyAsInt(4, 5));

      // Demonstrating curried add:
      out.println(curriedAdd.apply(4).applyAsInt(5));

      // Curried version lets you perform partial application:
      IntUnaryOperator adder5 = curriedAdd.apply(5);
      out.println(adder5.applyAsInt(4));
      out.println(adder5.applyAsInt(6));
   }
}

... ซึ่งค่อนข้างดี โดยส่วนตัวแล้วเมื่อใช้ Java 8 ฉันเห็นเหตุผลเล็กน้อยที่จะใช้ภาษา JVM ทางเลือกเช่น Scala หรือ Clojure แน่นอนว่าพวกเขามีคุณสมบัติภาษาอื่น ๆ แต่นั่นไม่เพียงพอที่จะพิสูจน์ต้นทุนการเปลี่ยนแปลงและการสนับสนุน IDE / tooling / libraries ที่อ่อนแอกว่า IMO


11
ฉันประทับใจกับ Java 8 แต่ Clojure เป็นแพลตฟอร์มที่น่าสนใจนอกเหนือจากคุณสมบัติภาษาที่ใช้งานได้ Clojure นำเสนอโครงสร้างข้อมูลที่มีประสิทธิภาพสูงไม่เปลี่ยนรูปและเทคนิคการทำงานพร้อมกันที่ซับซ้อนเช่นหน่วยความจำซอฟต์แวร์ - ธุรกรรม
Michael Easter

2
ขอบคุณสำหรับการแก้ปัญหาไม่มากสำหรับการขุดภาษา :) การสนับสนุน IDE ที่อ่อนแอกว่าเป็นปัญหา แต่การใช้เครื่องมือ / ไลบรารีไม่ชัดเจน - เมื่อกลับไปที่ Java 8 หลังจาก Clojure ฉันขาดเครื่องมือ midje จริงๆไลบรารีเช่น core .async และคุณสมบัติภาษาเช่นมาโครและไวยากรณ์ที่ใช้งานง่าย ตัวอย่าง(def adder5 (partial + 5)) (prn (adder5 4)) (prn adder5 6)
แกง

5
Clojure อาจเป็นภาษาที่ยอดเยี่ยม แต่ปัญหาคือมันเป็นเพียง "คนต่างด้าว" เกินไปสำหรับนักพัฒนา Java ส่วนใหญ่ที่ใช้เฉพาะกับไวยากรณ์รูปแบบ C แบบเดิมเท่านั้น เป็นเรื่องยากมากที่จะเห็นการโยกย้ายไปยัง Clojure (หรือภาษา JVM ทางเลือกอื่น ๆ ) ในอนาคตโดยเฉพาะเมื่อพิจารณาว่าภาษาดังกล่าวมีอยู่หลายปีแล้วและยังไม่เกิดขึ้น (ปรากฏการณ์เดียวกันนี้เกิดขึ้นในโลก. NET โดยที่ภาษาเช่น F # ยังคงด้อย)
Rogério

2
ฉันต้องลงคะแนนเพราะมันแสดงให้เห็นถึงกรณีที่เรียบง่าย ลองใช้อัน
ไหน

11
@ M4ks คำถามมีเพียงว่า Java รองรับ Currying หรือไม่ไม่เกี่ยวกับจำนวนโค้ดเมื่อเทียบกับภาษาอื่น
Rogério

67

Currying และแอปพลิเคชันบางส่วนเป็นไปได้อย่างแน่นอนใน Java แต่จำนวนรหัสที่ต้องการอาจทำให้คุณปิดได้


รหัสบางส่วนเพื่อสาธิตการแกงและการใช้งานบางส่วนใน Java:

interface Function1<A, B> {
  public B apply(final A a);
}

interface Function2<A, B, C> {
  public C apply(final A a, final B b);
}

class Main {
  public static Function2<Integer, Integer, Integer> simpleAdd = 
    new Function2<Integer, Integer, Integer>() {
      public Integer apply(final Integer a, final Integer b) {
        return a + b;
      }
    };  

  public static Function1<Integer, Function1<Integer, Integer>> curriedAdd = 
    new Function1<Integer, Function1<Integer, Integer>>() {
      public Function1<Integer, Integer> apply(final Integer a) {
        return new Function1<Integer, Integer>() {
          public Integer apply(final Integer b) {
            return a + b;
          }
        };
      }
    };

  public static void main(String[] args) {
    // Demonstrating simple `add`
    System.out.println(simpleAdd.apply(4, 5));

    // Demonstrating curried `add`
    System.out.println(curriedAdd.apply(4).apply(5));

    // Curried version lets you perform partial application 
    // as demonstrated below.
    Function1<Integer, Integer> adder5 = curriedAdd.apply(5);
    System.out.println(adder5.apply(4));
    System.out.println(adder5.apply(6));
  }
}

FWIW นี่คือ Haskell เทียบเท่ากับโค้ด Java ด้านบน:

simpleAdd :: (Int, Int) -> Int
simpleAdd (a, b) = a + b

curriedAdd :: Int -> Int -> Int
curriedAdd a b = a + b

main = do
  -- Demonstrating simpleAdd
  print $ simpleAdd (5, 4)

  -- Demonstrating curriedAdd
  print $ curriedAdd 5 4

  -- Demostrating partial application
  let adder5 = curriedAdd 5 in do
    print $ adder5 6
    print $ adder5 9

@OP: ทั้งสองอย่างเป็นข้อมูลโค้ดที่เรียกใช้งานได้และคุณสามารถทดลองใช้งานได้ที่ ideone.com
missingfaktor

16
คำตอบนี้ล้าสมัยไปแล้วตั้งแต่การเปิดตัว Java 8 ดูคำตอบของRogérioสำหรับวิธีที่กระชับยิ่งขึ้น
Matthias Braun

15

มีตัวเลือกมากมายสำหรับ Currying ด้วย Java 8 ประเภทฟังก์ชัน Javaslang และjOOλทั้งเสนอ Currying นอกกรอบ (ฉันคิดว่านี่เป็นการกำกับดูแลใน JDK) และโมดูลฟังก์ชันCyclops มีชุดวิธีการคงที่สำหรับฟังก์ชัน Currying JDK และการอ้างอิงวิธีการ เช่น

  Curry.curry4(this::four).apply(3).apply(2).apply("three").apply("4");

  public String four(Integer a,Integer b,String name,String postfix){
    return name + (a*b) + postfix;
 }

นอกจากนี้ยังมี 'แกงกะหรี่' สำหรับผู้บริโภค เช่นส่งคืนเมธอดที่มี 3 พารามิเตอร์และ 2 ในนั้นใช้ไปแล้วเราก็ทำคล้าย ๆ กัน

 return CurryConsumer.curryC3(this::methodForSideEffects).apply(2).apply(2);

Javadoc


IMO นี่คือสิ่งที่เรียกจริงๆcurryingในCurry.currynซอร์สโค้ด
Lebecca

13

แก้ไข : ในปี 2014 และ Java 8 การเขียนโปรแกรมเชิงฟังก์ชันใน Java ไม่เพียง แต่เป็นไปได้แล้ว แต่ยังไม่น่าเกลียด (ฉันกล้าพูดว่าสวย) ดูตัวอย่างคำตอบของ Rogerioคำตอบของ

คำตอบเก่า:

Java ไม่ใช่ทางเลือกที่ดีที่สุดหากคุณจะใช้เทคนิคการเขียนโปรแกรมเชิงฟังก์ชัน ตามที่ missfaktor เขียนไว้คุณจะต้องเขียนโค้ดจำนวนมากเพื่อให้ได้สิ่งที่คุณต้องการ

ในทางกลับกันคุณไม่ได้ จำกัด เฉพาะ Java บน JVM - คุณสามารถใช้ScalaหรือClojureซึ่งเป็นภาษาที่ใช้งานได้ (ในความเป็นจริงแล้ว Scala นั้นมีทั้งฟังก์ชันและ OO)


8

ความดีความชอบต้องกลับมาเป็นฟังก์ชั่น สิ่งนี้ไม่สามารถทำได้ด้วย java (ไม่มีตัวชี้ฟังก์ชัน) แต่เราสามารถกำหนดและส่งคืนชนิดที่มีฟังก์ชันวิธีการ:

public interface Function<X,Z> {  // intention: f(X) -> Z
   public Z f(X x);
}

ตอนนี้ขอแกงส่วนที่เรียบง่าย เราต้องการตัวแบ่ง :

// f(X) -> Z
public class Divider implements Function<Double, Double> {
  private double divisor;
  public Divider(double divisor) {this.divisor = divisor;}

  @Override
  public Double f(Double x) {
    return x/divisor;
  }
}

และDivideFunction :

// f(x) -> g
public class DivideFunction implements Function<Double, Function<Double, Double>> {
  @Override
  public function<Double, Double> f(Double x) {
    return new Divider(x);
  }

ตอนนี้เราสามารถแบ่งส่วนกะหรี่ได้:

DivideFunction divide = new DivideFunction();
double result = divide.f(2.).f(1.);  // calculates f(1,2) = 0.5

1
ตอนนี้ฉันทำตัวอย่างเสร็จแล้ว (พัฒนาตั้งแต่เริ่มต้น) ปรากฎว่าความแตกต่างเพียงอย่างเดียวของคำตอบรหัสที่ขาดหายไปคือฉันไม่ได้ใช้คลาสที่ไม่ระบุชื่อ;)
Andreas Dolk

1
@missingfaktor - mea culpa;)
Andreas Dolk

5

ดีScala , Clojure หรือ Haskell (หรือภาษาเขียนโปรแกรมการทำงานอื่น ๆ ... ) เป็นมั่นเหมาะภาษาเพื่อการใช้งานสำหรับความดีความชอบและเทคนิคการทำงานอื่น ๆ

ต้องบอกว่าเป็นไปได้อย่างแน่นอนที่จะแกงกับ Java โดยไม่ต้องมีหม้อไอน้ำจำนวนมากที่อาจคาดหวังได้ (ดีที่ต้องมีความชัดเจนเกี่ยวกับประเภทที่เจ็บมาก - เพียงแค่ดูที่ curriedตัวอย่าง ;-))

การทดสอบตะโกนแสดงทั้งความดีความชอบFunction3เข้าFunction1 => Function1 => Function1:

@Test
public void shouldCurryFunction() throws Exception {
  // given
  Function3<Integer, Integer, Integer, Integer> func = (a, b, c) -> a + b + c;

  // when
  Function<Integer, Function<Integer, Function<Integer, Integer>>> cur = curried(func);

  // then
  Function<Integer, Function<Integer, Integer>> step1 = cur.apply(1);
  Function<Integer, Integer> step2 = step1.apply(2);
  Integer result = step2.apply(3);

  assertThat(result).isEqualTo(6);
}

เช่นเดียวกับแอปพลิเคชันบางส่วนแม้ว่าจะไม่ใช่ประเภทที่ปลอดภัยในตัวอย่างนี้:

@Test
public void shouldCurryOneArgument() throws Exception {
  // given
  Function3<Integer, Integer, Integer, Integer> adding = (a, b, c) -> a + b + c;

  // when
  Function2<Integer, Integer, Integer> curried = applyPartial(adding, _, _, put(1));

  // then
  Integer got = curried.apply(0, 0);
  assertThat(got).isEqualTo(1);
}

สิ่งนี้นำมาจาก Proof Of Concept ที่ฉันเพิ่งใช้เพื่อความสนุกก่อน JavaOne ในวันพรุ่งนี้ในอีกหนึ่งชั่วโมง "เพราะฉันเบื่อ" ;-) โค้ดอยู่ที่นี่: https://github.com/ktoso/jcurry

แนวคิดทั่วไปสามารถขยายเป็น FunctionN => FunctionM ได้ค่อนข้างง่ายแม้ว่า "ความปลอดภัยที่แท้จริง" จะยังคงเป็นปัญหาสำหรับตัวอย่างแอปพลิเคชัน partia และตัวอย่างแกงจะต้องใช้รหัสหม้อไอน้ำจำนวนมากในjcurryแต่ก็ทำได้

สรุปแล้วมันทำได้ แต่ใน Scala มันนอกกรอบ ;-)


5

เราสามารถเลียนแบบแกงด้วย Java 7 MethodHandles: http://www.tutorials.de/threads/java-7-currying-mit-methodhandles.392397/

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;

public class MethodHandleCurryingExample {
    public static void main(String[] args) throws Throwable {
        MethodHandles.Lookup lookup = MethodHandles.lookup();
        MethodHandle sum = lookup.findStatic(Integer.class, "sum", MethodType.methodType(int.class, new Class[]{int.class, int.class}));
        //Currying
        MethodHandle plus1 = MethodHandles.insertArguments(sum,0,1);
        int result = (int) plus1.invokeExact(2);
        System.out.println(result); // Output: 3
    }
}

5

ใช่ดูตัวอย่างโค้ดด้วยตัวคุณเอง:

import java.util.function.Function;

public class Currying {

    private static Function<Integer, Function<Integer,Integer>> curriedAdd = a -> b -> a+b ;

    public static void main(String[] args) {

        //see partial application of parameters
        Function<Integer,Integer> curried = curriedAdd.apply(5);
        //This partial applied function can be later used as
        System.out.println("ans of curried add by partial application: "+ curried.apply(6));
        // ans is 11

        //JS example of curriedAdd(1)(3)
        System.out.println("ans of curried add: "+ curriedAdd.apply(1).apply(3));
        // ans is 4

    }

}

นี่เป็นตัวอย่างง่ายๆที่มีcurriedAddเป็นฟังก์ชัน curried ซึ่งส่งคืนฟังก์ชันอื่นและสามารถใช้สำหรับการใช้พารามิเตอร์บางส่วนตามที่เก็บไว้ในcurriedซึ่งเป็นฟังก์ชันในตัวเอง ตอนนี้จะถูกนำไปใช้อย่างสมบูรณ์ในภายหลังเมื่อเราพิมพ์บนหน้าจอ

ยิ่งไปกว่านั้นในภายหลังคุณจะเห็นว่าคุณสามารถใช้มันในรูปแบบ JS ได้อย่างไร

curriedAdd.apply(1).apply(2) //in Java
//is equivalent to 
curriedAdd(1)(2) // in JS

4

อีกหนึ่งความเป็นไปได้ของ Java 8:

BiFunction<Integer, Integer, Integer> add = (x, y) -> x + y;

Function<Integer, Integer> increment = y -> add.apply(1, y);
assert increment.apply(5) == 6;

คุณยังสามารถกำหนดวิธีการยูทิลิตี้เช่นนี้:

static <A1, A2, R> Function<A2, R> curry(BiFunction<A1, A2, R> f, A1 a1) {
    return a2 -> f.apply(a1, a2);
}

ซึ่งช่วยให้คุณมีไวยากรณ์ที่อ่านง่ายขึ้น:

Function<Integer, Integer> increment = curry(add, 1);
assert increment.apply(5) == 6;

3

วิธีการแกงกะหรี่เป็นไปได้เสมอใน Java แต่ไม่รองรับวิธีมาตรฐาน การพยายามบรรลุสิ่งนี้มีความซับซ้อนและทำให้โค้ดอ่านไม่ออก Java ไม่ใช่ภาษาที่เหมาะสมสำหรับสิ่งนี้


3

อีกทางเลือกหนึ่งสำหรับ Java 6+

abstract class CurFun<Out> {

    private Out result;
    private boolean ready = false;

    public boolean isReady() {
        return ready;
    }

    public Out getResult() {
        return result;
    }

    protected void setResult(Out result) {
        if (isReady()) {
            return;
        }

        ready = true;
        this.result = result;
    }

    protected CurFun<Out> getReadyCurFun() {
        final Out finalResult = getResult();
        return new CurFun<Out>() {
            @Override
            public boolean isReady() {
                return true;
            }
            @Override
            protected CurFun<Out> apply(Object value) {
                return getReadyCurFun();
            }
            @Override
            public Out getResult() {
                return finalResult;
            }
        };
    }

    protected abstract CurFun<Out> apply(final Object value);
}

แล้วคุณก็สามารถทำแกงกะหรี่ได้ด้วยวิธีนี้

CurFun<String> curFun = new CurFun<String>() {
    @Override
    protected CurFun<String> apply(final Object value1) {
        return new CurFun<String>() {
            @Override
            protected CurFun<String> apply(final Object value2) {
                return new CurFun<String>() {
                    @Override
                    protected CurFun<String> apply(Object value3) {
                        setResult(String.format("%s%s%s", value1, value2, value3));
//                        return null;
                        return getReadyCurFun();
                    }
                };
            }
        };
    }
};

CurFun<String> recur = curFun.apply("1");
CurFun<String> next = recur;
int i = 2;
while(next != null && (! next.isReady())) {
    recur = next;
    next = recur.apply(""+i);
    i++;
}

// The result would be "123"
String result = recur.getResult();

2

แม้ว่าคุณจะทำ Currying ใน Java ได้ แต่มันก็น่าเกลียด (เพราะไม่รองรับ) ใน Java นั้นง่ายกว่าและเร็วกว่าในการใช้ลูปธรรมดาและนิพจน์ทั่วไป หากคุณโพสต์ตัวอย่างสถานที่ที่คุณจะใช้แกงกะหรี่เราสามารถแนะนำทางเลือกอื่นที่ทำในลักษณะเดียวกันได้


3
การกะหรี่เกี่ยวข้องกับลูปอย่างไร! อย่างน้อยก็ค้นหาคำศัพท์ก่อนที่คุณจะตอบคำถามเกี่ยวกับมัน
missingfaktor

@missingFaktor ฟังก์ชัน curried มักจะใช้กับคอลเล็กชัน เช่น list2 = list.apply (curriedFunction) โดยที่ curriedFunction อาจอยู่2 * ?ใน Java คุณจะทำสิ่งนี้ด้วยการวนซ้ำ
Peter Lawrey

@ ปีเตอร์: นั่นคือการใช้งานบางส่วนไม่ใช่การกะหรี่ และไม่เฉพาะเจาะจงสำหรับการดำเนินการรวบรวม
missingfaktor

@missingfaktor ประเด็นของฉันคือ; อย่าไปยุ่งเกี่ยวกับคุณลักษณะเฉพาะ แต่ลองย้อนกลับไปดูปัญหาที่กว้างขึ้นและมีโอกาสมากที่จะเป็นวิธีง่ายๆ
Peter Lawrey

@Peter: หากคุณต้องการตั้งคำถามในประเด็นคุณควรโพสต์ความคิดเห็นของคุณเป็นความคิดเห็นไม่ใช่คำตอบ (IMHO)
missingfaktor

2

นี่คือไลบรารีสำหรับแกงกะหรี่และแอปพลิเคชันบางส่วนใน Java:

https://github.com/Ahmed-Adel-Ismail/J-Curry

นอกจากนี้ยังสนับสนุนการทำลาย Tuples และ Map ป้อนพารามิเตอร์เมธอดเช่นตัวอย่างการส่ง Map ป้อนเมธอดที่ใช้พารามิเตอร์ 2 ตัวดังนั้น Entry.getKey () จะไปที่พารามิเตอร์แรกและ Entry.getValue () จะไปที่พารามิเตอร์ที่สอง

รายละเอียดเพิ่มเติมในไฟล์ README


2

ข้อได้เปรียบของการใช้ Currying ใน Java 8 คือช่วยให้คุณกำหนดฟังก์ชันลำดับสูงจากนั้นส่งผ่านฟังก์ชันลำดับแรกและอาร์กิวเมนต์ของฟังก์ชันในลักษณะที่ถูกผูกมัดและสง่างาม

นี่คือตัวอย่างของ Calculus ซึ่งเป็นฟังก์ชันอนุพันธ์

  1. ให้กำหนดฟังก์ชันอนุพันธ์โดยประมาณเป็น(f (x + h) -f (x)) / hชั่วโมง นี่จะเป็นฟังก์ชันลำดับสูง
  2. ลองคำนวณอนุพันธ์ของ 2 ฟังก์ชันที่แตกต่างกัน1 / xและการแจกแจงแบบเกาส์ที่เป็นมาตรฐาน

1

    package math;

    import static java.lang.Math.*;
    import java.util.Optional;
    import java.util.function.*;

    public class UnivarDerivative
    {
      interface Approximation extends Function<Function<Double,Double>, 
      Function<Double,UnaryOperator<Double>>> {}
      public static void main(String[] args)
      {
        Approximation derivative = f->h->x->(f.apply(x+h)-f.apply(x))/h;
        double h=0.00001f;
        Optional<Double> d1=Optional.of(derivative.apply(x->1/x).apply(h).apply(1.0)); 
        Optional<Double> d2=Optional.of(
        derivative.apply(x->(1/sqrt(2*PI))*exp(-0.5*pow(x,2))).apply(h).apply(-0.00001));
        d1.ifPresent(System.out::println); //prints -0.9999900000988401
        d2.ifPresent(System.out::println); //prints 1.994710003159016E-6
      }
    }

0

ใช่ฉันเห็นด้วยกับ @ Jérômeการ curring ใน Java 8 ไม่ได้รับการสนับสนุนในรูปแบบมาตรฐานเช่นใน Scala หรือภาษาโปรแกรมที่ใช้งานได้อื่น ๆ

public final class Currying {
  private static final Function<String, Consumer<String>> MAILER = (String ipAddress) -> (String message) -> {
    System.out.println(message + ":" + ipAddress );
  };
  //Currying
  private static final Consumer<String> LOCAL_MAILER =  MAILER.apply("127.0.0.1");

  public static void main(String[] args) {
      MAILER.apply("127.1.1.2").accept("Hello !!!!");
      LOCAL_MAILER.accept("Hello");
  }
}

0

ในขณะที่คำตอบอื่น ๆ ทั้งหมดมุ่งเน้นไปที่ตัวอย่างที่เฉพาะเจาะจง แต่ฉันก็ยังต้องการให้วิธีแก้ปัญหาทั่วไปในการเปลี่ยนฟังก์ชันไบนารีให้เป็นคำตอบ

private static <A, B, C> Function<A, Function<B, C>> Curry(BiFunction<A, B, C> f) {
    return a -> b -> f.apply(a, b);
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.