7

Zajrzałem tutaj: Scala currying vs partially applied functions, ale tam odpowiedzi odpowiadają bardziej na temat funkcjonalnych i semantycznych różnic między funkcjami currying, partial application i normalnymi funkcjami Scali.Jakie są cechy wydajności między funkcjami curry, częściowo zastosowanymi i "normalnymi" w Scali?

jestem zainteresowany w nauce, czy istnieją względy wydajności między tymi różnymi technikami, które mogą być stosowane w tych funkcji, a mianowicie ...

Jeśli używamy wydajności normalnej funkcji jako podstawy:

def add3(a: Int, b: Int, c: Int) = a + b + c 
add3(1, 2, 3) 

a następnie porównać do:

// First 
(add3 _).curried(1)(2)(3) 

// Second 
val add2 = add3(1, _: Int, _: Int) 
val add1 = add2(2, _: Int) 
add1(3) 

// Third 
def add3(a: Int)(b: Int)(c: Int) = a + b + c 
add3(1)(2)(3) 

Jakie są rzeczy, może chcę być świadomy jakbym zidentyfikowano pewne słabo wykonywania kodu (zarówno pod względem prędkości, jak i wykorzystania pamięci) i widzę wiele curry lub częściowe zastosowanie dzieje się w tym segmencie kodu?

W Haskell chciałbym na przykład sprawdzić, ile z nich generuje się i kręci. Przypuszczam, że Scala używa podobnego rodzaju metody przekazywania funkcji częściowo stosowanych i curry, a szczegóły, jak Scala radzi sobie z tymi rzeczami, byłyby cenne.

Odpowiedz

5

Kiedyś mój scala-to-java tool konwertować fragmenty Java, a oto wyniki:

  1. Proste wywołanie funkcji jest transpiled podobnym wywołania funkcji:

    def add3(a: Int, b: Int, c: Int) = a + b + c 
    add3(1, 2, 3) 
    

    Wynik:

    public final class _$$anon$1 { 
        private int add3(final int a, final int b, final int c) { 
         return a + b + c; 
        } 
    
        { 
         this.add3(1, 2, 3); 
        } 
    } 
    
  2. "Pierwszy" fragment: (add3 _).curried(1)(2)(3) jest tr w tym zasadniczo:

    ((Function3<Integer, Object, Object, Object>)new _$$anon$1$$anonfun._$$anon$1$$anonfun$1(this)).curried().apply(BoxesRunTime.boxToInteger(1)).apply(BoxesRunTime.boxToInteger(2)).apply$mcII$sp(3); 
    

    Pominąłem blankiet. Co się dzieje tutaj, klasa funkcji opakowania jest tworzona około add3, następnie wywoływana jest metoda curried tej klasy, a następnie apply jest wywoływana trzy razy na wyniku poprzedniej aplikacji. Możesz sprawdzić źródła curriedhere. Co robi, po prostu zwraca kilka funkcji wyższego rzędu (funkcja, która zwraca funkcję).

    Tak więc, w porównaniu do "przypadku 0", kilka dodatkowych owijaczy funkcji jest tworzonych dla pojedynczego połączenia curried.

  3. "drugi":

    // Second 
    val add2 = add3(1, _: Int, _: Int) 
    val add1 = add2(2, _: Int) 
    add1(3) 
    

    nie dostarczy całą stronę transpiled wyjściu. Sprawdź to here, jeśli chcesz. Zasadniczo dzieje się tak, że klasa wrapperów funkcyjnych add2 i add1 jest generowana za pomocą pojedynczej metody, która pobiera odpowiednią liczbę argumentów. Tak więc, wywołując add1(3), wywołuje add2, który wywołuje add3. Generowane wrappery są efektywnymi singletonami, więc narzut ogranicza się do kilku wywołań funkcji.

  4. trzecie:

    def add3(a: Int)(b: Int)(c: Int) = a + b + c 
    add3(1)(2)(3) 
    

    Czy transpiled do prostego wywołania funkcji jeszcze:

    public final class _$$anon$1 { 
        private int add3(final int a, final int b, final int c) { 
         return a + b + c; 
        } 
    
        { 
         this.add3(1, 2, 3); 
        } 
    } 
    

    ale jeśli spróbujesz użyć go w curry sposób, na przykład, jak to val p = add3(1) _, otoki funkcjonalny klasa zostanie wygenerowana dodatkowo.

+0

Co wnoszę z tego jest to, że narzut currying i częściowo stosowania funkcji w Scala jest głównie znikoma, ponieważ tworzy klas singleton dla powracających każdą funkcję wynik, którego kwota praktycznie do prostego wywołania funkcji w przeważającej części . – josiah