Java, Currying'i destekliyor mu?


90

Bunu Java'da çekmenin bir yolu olup olmadığını merak ediyordum. Kapanışlar için yerel destek olmadan bunun mümkün olmadığını düşünüyorum.


4
Kayıt için, Java 8 artık körlemeyi ve kısmi uygulamayı destekliyor ve kapatma için yerel desteğe sahip. Bu çılgınca modası geçmiş bir sorudur.
Robert Fischer

Yanıtlar:


146

Java 8 (18 Mart 2014'te piyasaya sürüldü) körlemeyi destekliyor. Missfaktor tarafından cevaba gönderilen örnek Java kodu şu şekilde yeniden yazılabilir:

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

... ki bu oldukça hoş. Kişisel olarak, Java 8 ile Scala veya Clojure gibi alternatif bir JVM dili kullanmak için çok az neden görüyorum. Elbette başka dil özellikleri sağlarlar, ancak bu, geçiş maliyetini ve daha zayıf IDE / araç / kitaplık desteği, IMO'yu haklı çıkarmak için yeterli değildir.


11
Java 8'den etkilendim, ancak Clojure, işlevsel dil özelliklerinin ötesinde zorlayıcı bir platform. Clojure, yüksek verimli, değişmez veri yapıları ve yazılım-işlem belleği gibi sofistike eşzamanlılık teknikleri sunar.
Michael Easter

2
Çözüm için teşekkürler, dil kazmak için çok değil :) Daha zayıf IDE desteği bir sorundur, ancak araçlar / kitaplıklar net bir kesim değildir - Clojure'den sonra Java 8'e geri döndüğümde, gerçekten araçları özlüyorum midje, core gibi kitaplıklar .async ve makrolar ve kolay işlevsel sözdizimi gibi dil özellikleri. (def adder5 (partial + 5)) (prn (adder5 4)) (prn adder5 6)
Clojure'de körleme

5
Clojure harika bir dil olabilir, ancak sorun şu ki, yalnızca geleneksel C-stili sözdizimine alışkın olan Java geliştiricilerinin çoğu için çok "yabancı"; Gelecekte Clojure'a (veya başka bir alternatif JVM diline) önemli bir geçiş görmek çok zordur, özellikle bu tür birkaç dilin yıllardır zaten var olduğu ve gerçekleşmediği düşünüldüğünde (aynı fenomen .NET dünyasında da görülür, F # gibi diller marjinal kalır).
Rogério

2
Olumsuz oy vermem gerekiyor çünkü basit bir durumu gösteriyor. String'den kendi sınıfınızı oluşturan ve daha sonra başka bir sınıfa dönüşen birini deneyin ve kod miktarını karşılaştırın
M4ks

11
@ M4ks Soru sadece Java'nın currying'i destekleyip desteklemediğidir, diğer dillerle karşılaştırıldığında kod miktarı ile ilgili değildir.
Rogério

67

Java'da curing ve kısmi uygulama kesinlikle mümkündür, ancak gereken kod miktarı muhtemelen sizi kapatacaktır.


Java'da körili ve kısmi uygulamayı gösteren bazı kodlar:

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, yukarıdaki Java kodunun Haskell eşdeğeridir:

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: Her ikisi de çalıştırılabilir kod parçacıklarıdır ve bunları ideone.com'da deneyebilirsiniz.
missingfaktor

16
Bu yanıt, Java 8'in yayımlanmasından bu yana geçerliliğini yitirmiştir. Daha kısa bir yol için Rogério'nun yanıtına bakın.
Matthias Braun

15

Java 8 ile Currying için birçok seçenek vardır. Her ikisi de kutudan Currying'i sunan Javaslang ve jOOλ işlev türü (bunun JDK'da bir gözetim olduğunu düşünüyorum) ve Cyclops İşlevleri modülünde Currying JDK İşlevleri için bir dizi statik yöntem vardır ve yöntem referansları. Örneğin

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

Tüketiciler için "Currying" de mevcuttur. Örneğin, 3 parametresi olan bir yöntemi döndürmek için ve önceden uygulanmış olanlardan 2'si buna benzer bir şey yaparız

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

Javadoc


IMO, bu gerçekten dediği curryingde Curry.currynkaynak kodu.
Lebecca

13

DÜZENLEME : 2014 ve Java 8 itibariyle, Java'da işlevsel programlama artık sadece mümkün değil, aynı zamanda çirkin de değil (güzel demeye cesaret ediyorum). Örneğin Rogerio'nun cevabına bakınız .

Eski cevap:

Fonksiyonel programlama tekniklerini kullanacaksanız, Java en iyi seçenek değildir. Missfaktor'un yazdığı gibi, istediğinizi elde etmek için oldukça büyük miktarda kod yazmanız gerekecek.

Öte yandan, JVM'de Java ile sınırlı değilsiniz - işlevsel diller olan Scala veya Clojure'u kullanabilirsiniz (Scala aslında hem işlevsel hem de OO'dur).


8

Currying , bir işlev döndürmeyi gerektirir . Java ile bu mümkün değildir (işlev işaretçileri yoktur) ancak bir işlev yöntemi içeren bir türü tanımlayabilir ve döndürebiliriz:

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

Şimdi let köri basit bölme. Bir Bölücüye ihtiyacımız var :

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

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

Şimdi körili bir bölme yapabiliriz:

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

1
Örneğimi (sıfırdan geliştirdiğim) bitirdiğime göre, eksik kod cevabındaki tek farkın anonim sınıfları kullanmamam olduğu ortaya çıktı;)
Andreas Dolk

1
@missingfaktor - mea culpa;)
Andreas Dolk

5

Pekala, Scala , Clojure veya Haskell (veya başka herhangi bir işlevsel programlama dili ...) kesinlikle curry ve diğer işlevsel hileler için kullanılacak dillerdir .

Bunu söyleyince, Java ile beklenebilecek süper miktarlarda standart şablon olmadan curry yapmak kesinlikle mümkündür (pekala, türler konusunda açık olmak çok acı verir - sadece curriedörneğe bir göz atın ;-)).

Testler, hem vitrin feryat currying bir Function3içine 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);
}

Bu örnekte gerçekten tip güvenli olmasa da, kısmi uygulamanın yanı sıra :

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

Bu, JavaOne'dan önce yarın "sıkıldığım için" ;-) eğlence için uyguladığım bir Proof of Concept'ten alınmıştır ;-) Koda buradan ulaşabilirsiniz: https://github.com/ktoso/jcurry

Genel fikir, nispeten kolay bir şekilde FunctionN => FunctionM'ye genişletilebilir, ancak "gerçek tip güvenlik" partia uygulama örneği için bir sorun olmaya devam ediyor ve currying örneği , jcurry'de çok fazla kazanplaty koduna ihtiyaç duyacak , ancak yapılabilir.

Sonuç olarak, yapılabilir, ancak Scala'da kutunun dışında ;-)


5

Java 7 MethodHandles ile körleme taklit edilebilir: 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

Evet, kendiniz için kod örneğine bakın:

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

    }

}

Bu basit bir örnek curriedAdd başka bir işlevi döndüren bir curried fonksiyonudur, ve bunun için de kullanılabilir parametrelerin kısmi uygulama saklanan olarak curried kendi içinde bir fonksiyonu olan. Bu şimdi daha sonra ekrana yazdırdığımızda tam olarak uygulanıyor.

Dahası, daha sonra nasıl JS tarzında kullanabileceğinizi görebilirsiniz.

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

4

Java 8 olasılıklarını bir kez daha ele alın:

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

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

Ayrıca bunun gibi yardımcı yöntemler de tanımlayabilirsiniz:

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

Bu size tartışmaya açık bir şekilde daha okunabilir bir sözdizimi verir:

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

3

Java'da bir yöntemi köreltmek her zaman mümkündür, ancak standart bir şekilde desteklemez. Bunu başarmaya çalışmak karmaşıktır ve kodu oldukça okunamaz hale getirir. Java bunun için uygun dil değil.


3

Java 6+ için başka bir seçenek daha var

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

o zaman bu yolla körileştirebilirsin

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'i Java'da yapabilseniz de çirkin (çünkü desteklenmiyor) Java'da düz döngüleri ve basit ifadeleri kullanmak daha basit ve daha hızlıdır. Köriyi nerede kullanacağınıza dair bir örnek yayınlarsanız, aynı şeyi yapan alternatifler önerebiliriz.


3
Körlemenin döngülerle ne ilgisi var ?! En azından onunla ilgili bir soruyu cevaplamadan önce terime bakın.
missingfaktor

@missingFaktor, curried işlevler genellikle koleksiyonlara uygulanır. örneğin list2 = list.apply (curriedFunction) curriedFunction olabilir burada 2 * ?Java'da bunu bir döngü ile yaparsınız.
Peter Lawrey

@Peter: Bu kısmi bir uygulama, körleme değil. Ve hiçbiri toplama işlemlerine özgü değildir.
missingfaktor

@missingfaktor, Benim açımdan; belirli bir özelliğe takılıp kalmamak, ancak bir adım geri çekilip daha geniş soruna bakmak ve basit bir çözüm olması çok muhtemeldir.
Peter Lawrey

@Peter: Sorunun amacını sorgulamak istiyorsanız, yorumunuzu bir cevap olarak değil, bir yorum olarak göndermelisiniz. (IMHO)
lostfaktor

2

Bu, Java'da körleme ve kısmi uygulama için bir kitaplıktır:

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

Ayrıca, Tuples ve Map.Entry yöntem parametrelerine, örneğin 2 parametre alan bir yönteme bir Map.Entry geçirmek gibi, Entry.getKey () ilk parametreye ve Entry.getValue () öğeye gidecektir. ikinci parametre için gidecek

README dosyasında daha fazla ayrıntı


2

Currying'i Java 8'de kullanmanın avantajı, yüksek dereceli işlevleri tanımlamanıza ve ardından birinci dereceden bir işlevi ve işlev bağımsız değişkenlerini zincirleme, zarif bir şekilde iletmenize olanak vermesidir.

İşte Calculus için bir türev fonksiyonu örneği.

  1. Türev fonksiyon yaklaşımını (f (x + h) -f (x)) / h olarak tanımlayalım . Bu yüksek dereceli fonksiyon
  2. 2 farklı fonksiyonun türevini, 1 / x ve standartlaştırılmış gauss dağılımını hesaplayalım

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

Evet, @ Jérôme ile aynı fikirdeyim, Java 8'de akma Scala veya diğer işlevsel programlama dillerinde olduğu gibi standart bir şekilde desteklenmiyor.

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

Diğer tüm cevaplar belirli örneklere odaklanırken, ben yine de ikili fonksiyonları curried fonksiyonlara dönüştürmek için genel bir çözüm sağlamak istedim.

private static <A, B, C> Function<A, Function<B, C>> Curry(BiFunction<A, B, C> f) {
    return a -> b -> f.apply(a, b);
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.