08 sierpnia 2013

Metoda a funkcja oraz stan w obiekcie i funkcji w Scali

Trudno byłoby oczekiwać, że poniedziałek, świeżo po wakacjach, mógłby należeć do najmilszych dni, bo w końcu to koniec letniej laby i to nie byle jakiej. Jeszcze posiedziałbym w Niechorzu. Chociaż tydzień dłużej.

Z pewną obawą wracałem do pracy, ale już w biurze czekał na mnie +Grzegorz Balcerek oraz Marcin Jaskólski. Po lekturze kolejnego rozdziału Scala in Action natchnęło mnie na dyskusję o różnicy między metodami a funkcjami w Scali. Ciekawym, ilu z Was zauważa różnicę.

Niech ja zacznę z przedstawieniem swojego zrozumienia.

Metoda jest wyłącznie składową instancji typu (świadomie nie piszę obiektu, aby nie mylić ze scalowym object), podczas gdy funkcja jest bytem niezależnym, żyjącym samodzielnie. Scala oferuje możliwość przekształcenia metody w funkcję za pomocą podkreślnika (dla każdego brakującego parametru).
scala> class A { def f(n: Int) = 5 }
defined class A

scala> new A().f _
res0: Int => Int = <function1>
Od tej pory res0 staje się funkcją jednego parametru typu Int, która zwraca wartość typu Int. Jak widać, funkcja jest więc wartością pewnego typu. Metoda takiej cechy nie posiada.

W Scali sprawa się trochę komplikuje ponieważ każda funkcja jest reprezentowana przez obiekt typu FunctionX, gdzie X oznacza liczbę akceptowanych parametrów wejściowych. Stąd też możnaby powiedzieć, że nawet dla bytów określanych mianem funkcji w Scali istnieje instancja typu scala.FunctionX, co przeczy, że funkcja jest bytem samodzielnym. Czy, aby na pewno?

A co Ty o tym sądzisz? Chętnie wysłucham Twojej wersji, która wyprowadzi mnie z błędu lub uzupełni braki, gdzie potrzeba. Z góry dziękuję.

Nie długo trwało, aby w biurze pojawił się +Paweł Cesar Sanjuan Szklarz! Czyż można byłoby wyobrazić sobie lepsze rozpoczęcie dnia?! Na pewno nie!

Zaintrygowany Paweł poruszył inny temat odnośnie porównania istoty funkcji i obiektu. Dla mnie oba domykają pewien stan i wykonanie obu jest wręcz identyczne. Paweł obiecał pogłówkować nad tym jeszcze, bo nie mógł się zgodzić z tym, że musi się ze mną zgodzić :-) Czekam z niecierpliwością.

Cudnie móc tak rozpocząć pracę po wspaniałym urlopie. Każdemu życzę tak mocnego zespołu merytorycznie, z którym można poruszyć niejeden nietrywialny temat. Bezcenne.

p.s. Są jeszcze wakaty w moim zespole w Citi. Zainteresowanych zapraszam do kontaktu na priv, aby uruchomić proces rekrutacyjny.

8 komentarzy:

  1. Scala na bok, metoda obiektu to funkcja, która przyjmuje automatycznie i przezroczyście referencję "this". Innymi słowy metoda obj.fun(x, y) jest w rzeczywistości funkcją fun(obj, x, y). Metoda to składniowy cukier łączący struktury (struct w C) z funkcjami.

    Wracając do Scali - metoda w Scali jest równoważna metodzie w Javie. Nie ma tu żadnej różnicy. Natomiast Scala wprowadza funkcje, czyli poniekąd metody "oderwane" od obiektu (czyli po prostu go nie wymagające). Tak jak w języku C.

    Czasem chcemy użyć metody tam, gdzie wymagana jest funkcja (zauważ, że Scala ma funkcje wyższego rzędu, nie metody). Wtedy kompilator wykonuje za nas proces zwany eta-expansion. Na przykładzie: z obj.foo(x) robi się foo(x), ale parametr this (obj) nie zniknął, referencja nań jest ukryta wewnątrz funkcji. Porównaj to z "naiwnymi" funkcjami i metodami w JavaScript (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind - Examples)

    To, że w Scali są typy FunctionX i że funkcja jest jednak obiektem nazwałbym szczegółem implementacyjnym. Wszak zamiast "fun: Function1[Int, String]" możesz napisać "fun: (Int => String)" - co nawiasem mówiąc może być czytelniejsze.

    I ciekawostka z Javy 8. Tam zamiast dajmy na to "forEach(x -> obj.fun(x))" można napisać "forEach(obj::fun)" - czyli wypisz-wymaluj konwersja metody na funkcję.

    Jeśli interesuje Cię implementacja eta-expansion, zerknij na Jak działa (i czym jest) eta expansion?

    OdpowiedzUsuń
  2. @Tomasz, jeżeli mam się opowiadać za czymś to za Twoją wersją :)

    OdpowiedzUsuń
  3. Po pierwsze, jak zauważył Tomek, metoda różni się od funkcji jedynie tym, że otrzymuje dodatkowy parametr zawierający instancję obiektu (this). Jest to bardzo dobrze widoczne w Pythonie, gdzie każda definicja metody musi mieć pierwszy parametr self (odpowiednik this w Pythonie).

    Spróbowałem przetestować jak działa konwersja metody na funkcję w Scali i przeprowadziłem prosty test:

    package pl.marpiec

    class Functions {
    def a(param: String):Int = {
    println(this)
    param.length
    }
    }

    object FunctionsTest extends App {
    new Functions().a("aa")
    val functionA = new Functions().a(_)
    functionA("aa")
    }

    I niespodziewany wynik działania był taki:

    pl.marpiec.Functions@633589
    pl.marpiec.Functions@235b5d

    Okazuje się, że po stworzeniu referencji functionA, ta funkcja (czy aby na pewno?) cały czas jest powiązana z obiektem z którego pochodzi. Czyli jednak nie stała się funkcją, ale jest cały czas metodą obiektu klasy Functions. A functionA jest jedynie referencją do tej metody.

    Dlatego chyba należałoby zmodyfikować sposób w jaki rozróżniamy funcję i metodę. Metoda to po prostu taka funkcja, która wykorzystuje instancję obiektu macierzystego.


    Warto też zauważyć, że Scala w większości przypadków jest tłumaczona w trakcie kompilacji na Jave, dlatego, aby móc stworzyć samodzielną funkcję wykorzystano możliwość umieszczenia jej w klasie Function1, której javowy interfejs może wyglądać tak:

    interface Function1 {
    T function(E param1);
    }

    Jest to standardowe podejście w Javie do przekazywania funkcji, np. w przypadku Handlerów w Swingu.

    Z tego wynika, że pod maską Scali konwersja metody na funkcję polega na stworzeniu klasy FunctionX, którego metoda _wywołuje_ metodę z klasy obiektu oryginalnego. Jest to szczególny sposób implementacji funkcji wynikający z wykorzystania JVM i wydaje się, że z punktu widzenia programisty Scali można to pominąć i faktycznie traktować funkcję jako samodzielny byt. Być może w przyszłości Scala będzie działać na innej wirtualnej maszynie, która nie będzie wymagała takiej gimnastyki ze strony kompilatora.

    OdpowiedzUsuń
  4. Dodam jeszcze, że w takim razie:

    scala> new A().f _
    res0: Int => Int =

    Tworzy tak jakby referencję do wywołania metody f w obiekcie stworzonym przez new A(), a nie nową funkcję.

    Zastanawia mnie jeszcze styl zapisu:
    new A().f _
    Moim zdaniem lepszym rozwiązaniem byłoby użycie nawiasów:
    new A().f(_)
    Które i tak byłyby wymagane w przypadku większej liczby parametrów:
    new B().f(_, _)

    OdpowiedzUsuń
  5. Zwróćcie też uwagę na definicje traitów FunctionX:

    trait Function3[-T1, -T2, -T3, +R] extends AnyRef

    Konkretnie mam na myśli adnotacje wariancji - wynika z nich, że funkcje zawsze są 'polimorficzne'..

    Druga różnica jaka przychodzi na myśl - metody nie mają ograniczenia ilości argumentów.

    OdpowiedzUsuń
  6. Witam,
    czy jeśli będziesz dysponował wolnym czasem i chęciami to czy mógłbyś napisać tutorial odnośnie prostej aplikacji webowej w technologiach spring i hibernate?

    OdpowiedzUsuń
    Odpowiedzi
    1. Cześć,

      Proponuję zajrzeć do http://jaceklaskowski.pl/wiki/Apache_Wicket_z_JPA_z_pomoc%C4%85_Spring_Framework_i_Apache_Maven_2 oraz http://jaceklaskowski.pl/wiki/Tworzenie_samodzielnej_aplikacji_ze_Spring_Framework_i_Hibernate_w_NetBeans_IDE_6.9. Trochę stare, ale biorąc pod uwagę, że oddałem się Scali pewnie nie prędko znajdę czas na Spring Framework oraz Hibernate. Na pewno nie w najbliższych kilku m-cach.

      Jacek

      Usuń