Apakah Java mendukung Currying?

90

Saya bertanya-tanya apakah ada cara untuk menariknya di Jawa. Saya pikir itu tidak mungkin tanpa dukungan asli untuk penutupan.

pengguna855
sumber
4
Sebagai catatan, Java 8 sekarang mendukung aplikasi kari dan parsial serta memiliki dukungan asli untuk penutupan. Ini adalah pertanyaan yang sangat ketinggalan jaman.
Robert Fischer

Jawaban:

146

Java 8 (dirilis 18 Maret 2014) mendukung kari. Contoh kode Java yang diposting dalam jawaban oleh missingfaktor dapat ditulis ulang sebagai:

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));
   }
}

... yang cukup bagus. Secara pribadi, dengan Java 8 tersedia, saya melihat sedikit alasan untuk menggunakan bahasa JVM alternatif seperti Scala atau Clojure. Mereka menyediakan fitur bahasa lain, tentu saja, tapi itu tidak cukup untuk membenarkan biaya transisi dan dukungan IDE / tooling / libraries yang lebih lemah, IMO.

Rogério
sumber
11
Saya terkesan dengan Java 8, tetapi Clojure adalah platform yang menarik di luar fitur bahasa fungsional. Clojure menawarkan struktur data yang sangat efisien dan tidak dapat diubah serta teknik konkurensi yang canggih seperti memori transaksional perangkat lunak.
Michael Easter
2
Terima kasih atas solusinya, tidak terlalu banyak untuk penggalian bahasa :) Dukungan IDE yang lebih lemah adalah masalah, tetapi perkakas / perpustakaan tidak jelas - setelah kembali ke Java 8 setelah Clojure, saya benar-benar kehilangan alat, midje, perpustakaan seperti inti .async, dan fitur bahasa seperti makro dan sintaks fungsional yang mudah. Contoh kari di clojure:(def adder5 (partial + 5)) (prn (adder5 4)) (prn adder5 6)
Korny
5
Clojure mungkin bahasa yang bagus, tapi masalahnya adalah bahasa itu terlalu "asing" bagi sebagian besar pengembang Java yang hanya terbiasa dengan sintaks C-style konvensional; sangat sulit untuk melihat migrasi yang signifikan ke Clojure (atau bahasa JVM alternatif lainnya) di masa depan, khususnya mengingat beberapa bahasa seperti itu sudah ada selama bertahun-tahun dan itu belum terjadi (fenomena yang sama terjadi di dunia .NET, dimana bahasa seperti F # tetap marjinal).
Rogério
2
Saya harus downvote, karena ini menunjukkan kasus sederhana. Cobalah salah satu yang dari String membuat kelas Anda sendiri yang kemudian dikonversi ke kelas lain, dan bandingkan jumlah kodenya
M4ks
11
@ M4ks Pertanyaannya hanya apakah Java mendukung kari atau tidak, ini bukan tentang jumlah kodenya jika dibandingkan dengan bahasa lain.
Rogério
67

Aplikasi kari dan parsial benar-benar dimungkinkan di Java, tetapi jumlah kode yang diperlukan mungkin akan mematikan Anda.


Beberapa kode untuk mendemonstrasikan kari dan aplikasi parsial di 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 di sini adalah Haskell yang setara dengan kode Java di atas:

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
missingfaktor
sumber
@OP: Keduanya merupakan cuplikan kode yang dapat dijalankan dan Anda dapat mencobanya di ideone.com.
missingfaktor
16
Jawaban ini sudah usang sejak Java 8 dirilis. Lihat jawaban Rogério untuk cara yang lebih ringkas.
Matthias Braun
15

Ada banyak pilihan untuk Currying dengan Java 8. Jenis fungsi Javaslang dan jOOλ keduanya menawarkan Currying out of the box (menurut saya ini adalah pengawasan di JDK), dan modul Cyclops Functions memiliki seperangkat metode statis untuk Currying JDK Functions dan referensi metode. Misalnya

  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;
 }

'Currying' juga tersedia untuk Konsumen. Misalnya untuk mengembalikan metode dengan 3 parameter, dan 2 di antaranya sudah diterapkan, kami melakukan sesuatu yang mirip dengan ini

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

Javadoc

John McClean
sumber
IMO, inilah yang sebenarnya disebut curryingdalam Curry.currynkode sumber.
Lebecca
13

EDIT : Pada 2014 dan Java 8, pemrograman fungsional di Java sekarang tidak hanya mungkin, tetapi juga tidak jelek (saya berani mengatakan cantik). Lihat misalnya jawaban Rogerio .

Jawaban lama:

Java bukanlah pilihan terbaik, jika Anda akan menggunakan teknik pemrograman fungsional. Seperti yang ditulis missingfaktor, Anda harus menulis kode yang cukup banyak untuk mencapai apa yang Anda inginkan.

Di sisi lain, Anda tidak terbatas pada Java di JVM - Anda dapat menggunakan Scala atau Clojure yang merupakan bahasa fungsional (Scala sebenarnya fungsional dan OO).

Xaerxess
sumber
8

Kari perlu mengembalikan fungsi . Ini tidak mungkin dengan java (tidak ada pointer fungsi) tetapi kita dapat mendefinisikan dan mengembalikan tipe yang berisi metode fungsi:

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

Sekarang mari kita kari pembagian sederhana. Kami membutuhkan Divider :

// 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;
  }
}

dan 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);
  }

Sekarang kita bisa melakukan pembagian kari:

DivideFunction divide = new DivideFunction();
double result = divide.f(2.).f(1.);  // calculates f(1,2) = 0.5
Andreas Dolk
sumber
1
Sekarang setelah saya menyelesaikan contoh saya (dikembangkan dari awal) ternyata, satu-satunya perbedaan jawaban
kode yang hilang
1
@missingfaktor - mea culpa;)
Andreas Dolk
5

Nah, Scala , Clojure atau Haskell (atau bahasa pemrograman fungsional lainnya ...) jelas merupakan bahasa yang digunakan untuk kari dan trik fungsional lainnya.

Karena itu tentu saja mungkin untuk kari dengan Java tanpa jumlah super dari boilerplate yang mungkin diharapkan (yah, meskipun harus eksplisit tentang jenis-jenis itu menyakitkan - lihat saja curriedcontoh ;-)).

Tes melenguh menampilkan kedua, currying a Function3ke 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);
}

serta aplikasi parsial , meskipun tidak benar-benar aman untuk mengetik dalam contoh ini:

@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);
}

Ini diambil dari Bukti Konsep yang baru saja saya implementasikan untuk bersenang-senang sebelum JavaOne besok dalam satu jam "karena saya bosan" ;-) Kode tersedia di sini: https://github.com/ktoso/jcurry

Ide umumnya dapat diperluas ke FunctionN => FunctionM, relatif mudah, meskipun "keamanan jenis nyata" tetap menjadi masalah untuk contoh aplikasi partia dan contoh kari akan membutuhkan banyak kode boilerplaty di jcurry , tetapi itu bisa dilakukan.

Secara keseluruhan, itu bisa dilakukan, namun di Scala itu di luar kotak ;-)

Konrad 'ktoso' Malawski
sumber
5

Seseorang dapat meniru kari dengan 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
    }
}
Thomas Darimont
sumber
5

Ya, lihat contoh kodenya sendiri:

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

    }

}

Ini adalah contoh sederhana dengan curriedAdd menjadi fungsi curried yang mengembalikan fungsi lain, dan ini bisa digunakan untuk aplikasi parsial parameter seperti yang disimpan di curried yang merupakan fungsi itu sendiri. Ini sekarang diterapkan sepenuhnya saat kami mencetaknya di layar.

Selain itu, nanti Anda bisa melihat bagaimana Anda bisa menggunakannya dalam gaya JS seperti

curriedAdd.apply(1).apply(2) //in Java
//is equivalent to 
curriedAdd(1)(2) // in JS
Rishabh Agarwal
sumber
4

Satu lagi kemungkinan 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;

Anda juga dapat menentukan metode utilitas seperti ini:

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

Yang memberi Anda sintaks yang bisa dibilang lebih mudah dibaca:

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

Meng-kari metode selalu memungkinkan di Java, tetapi metode itu tidak mendukungnya dengan cara standar. Mencoba untuk mencapai ini rumit dan membuat kodenya tidak dapat dibaca. Java bukanlah bahasa yang tepat untuk ini.

Jérôme Verstrynge
sumber
3

Pilihan lain ada di sini untuk 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);
}

maka Anda bisa membuat kari dengan cara ini

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();
John Lee
sumber
2

Meskipun Anda dapat melakukan Currying di Java, itu jelek (karena tidak didukung) Di Java, lebih sederhana dan lebih cepat menggunakan loop biasa dan ekspresi sederhana. Jika Anda memposting contoh di mana Anda akan menggunakan kari, kami dapat menyarankan alternatif yang melakukan hal yang sama.

Peter Lawrey
sumber
3
Apa hubungannya kari dengan loop ?! Setidaknya lihat istilah tersebut sebelum Anda menjawab pertanyaan tentangnya.
missingfaktor
@missingFaktor, fungsi kari biasanya diterapkan pada koleksi. misalnya list2 = list.apply (curriedFunction) di mana curriedFunction mungkin 2 * ?Di Jawa Anda akan melakukan ini dengan sebuah loop.
Peter Lawrey
@ Peter: Itu aplikasi parsial, bukan kari. Dan tidak ada yang khusus untuk operasi pengumpulan.
missingfaktor
@missingfaktor, Maksud saya adalah; tidak terpaku pada fitur tertentu tetapi mengambil langkah mundur dan melihat masalah yang lebih luas dan kemungkinan besar ada solusi sederhana.
Peter Lawrey
@ Peter: Jika Anda ingin mempertanyakan inti pertanyaan, Anda harus memposting komentar Anda sebagai komentar, dan bukan sebagai jawaban. (IMHO)
missingfaktor
2

Ini adalah perpustakaan untuk kari dan aplikasi parsial di Java:

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

Ini juga mendukung perusakan Tuple dan Map.Entry ke dalam parameter metode, seperti misalnya meneruskan Map.Entry ke metode yang mengambil 2 parameter, sehingga Entry.getKey () akan pergi ke parameter pertama, dan Entry.getValue () akan memilih parameter kedua

Detail selengkapnya ada di file README

Ahmed Adel Ismail
sumber
2

Keuntungan menggunakan Currying di Java 8 adalah memungkinkan Anda menentukan fungsi urutan tinggi dan kemudian meneruskan fungsi urutan pertama dan argumen fungsi dengan cara yang berantai dan elegan.

Berikut adalah contoh Kalkulus, fungsi turunannya.

  1. Mari kita definisikan pendekatan fungsi turunan sebagai (f (x + h) -f (x)) / h . Ini akan menjadi fungsi orde tinggi
  2. Mari kita hitung turunan dari 2 fungsi berbeda, 1 / x , dan distribusi gaussian standar

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
      }
    }
Jose Luis Soto Posada
sumber
0

Ya, saya setuju dengan @ Jérôme, pengaktifan di Java 8 tidak didukung dengan cara standar seperti di Scala atau bahasa pemrograman fungsional lainnya.

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");
  }
}
Ajeet
sumber