Scala - parametry domniemane - motywacja

Odpowiedz Nowy wątek
2018-08-10 07:52
0

Po dłuższej przerwie wracam do nauki Scali. Może pytanie, które zadam jest po prostu jest głupie i równoważne z tym "po co zostały wprowadzone pętle", ale zastanawia mnie po co (jaka motywacja za tym stoi) wprowadzone zostały "parametry domniemane" (implicit parameters)?

Z przykładów praktycznych to przewija się automatyczna konwersja typów, wstrzykiwanie metod czy zależności. Natomiast nie wiem czy przykłady wynikają z tego, że najpierw pojawiły się parametry domniemane i przykłady są konsekwencją, czy może odwrotnie, najpierw pojawiły się problemy (będziemy potrzebować automatycznej konwersji typów) i wymyślono ten mechanizm, który ma również inne zastosowania.

edytowany 2x, ostatnio: yarel, 2018-08-10 07:54

Pozostało 580 znaków

2018-08-10 12:37
1

Hej,
ja myślę, że to trudne pytanie... Generalnie twórca Scali (Martin Odersky)... wprowadził pewne techniki umożliwiające domyślne działania funkcji lub jej konwersję w zależności od kontekstu... Między innymi umożliwia mu to korzystanie ze wszystkich dobrodziejstw Javy, ale można tworzyć też własne Scalowe odpowiedniki... Myślę, że także dzięki tej elastyczności można pracować na objektach Sparka jak na zwykłych objektach Scalowych, wykorzystując też przy tym Scalowe funkcje, np. filter, itp... Ale w Pythonie "konwersje" chyba robi się łatwiej:

def f(x):
    try:
       if type(x[0])=='int':
            return sum(x)
       else:
            return list(x)
    except:
        print("think a little about it")

Poczytaj sobie może to: http://www.lihaoyi.com/post/ImplicitDesignPatternsinScala.html Może ciut trywializuję problem... Pozdrawiam... :)

edytowany 7x, ostatnio: hurgadion, 2018-08-10 12:47

Pozostało 580 znaków

2018-08-10 13:33
2

Parametry implicit nie mają nic wspólnego z konwersjami typów. Są uogólnieniem wzorca type-classes z Haskella.
Są zaawansowaną formą programowania generycznego. Umożliwiają rozwiązanie choćby tzw. expression problem (https://en.wikipedia.org/wiki/Expression_problem).
W praktyce przekłada się to na możliwość pisania kodu, który łatwo rozszerzać o obsługę nowych typów bez konieczności modyfikacji istniejącego kodu.

Przykład: biblioteka standardowa wie jak posortować Stringi i wie jak posortować Inty. Jednak Ty chcesz wprowadzić własny typ Z, który też chcesz aby był sortowalny. Dzięki implicitom możesz to zrobić w taki sposób, że Twój typ będzie akceptowany we wbudowanych metodach sortBy, min, max, minBy wymagających porównywania elementów itp. Co więcej, dzieki implicitom np. krotka (Int, Int, Z) też będzie wtedy sortowalna. W słabszych językach (Java, Kotlin, C#) takie coś też da się do pewnego stopnia uzyskać korzystając z dziedziczenia i interfejsów, ale jest mocno ułomne.

A konwersje typów to raczej jakiś niezamierzony efekt uboczny implicitów. Nie należy tego tak używać.

edytowany 2x, ostatnio: Krolik, 2018-08-10 13:35

Pozostało 580 znaków

2018-08-10 13:50
0

Dzięki za linki, które sporo wyjaśniają i co dla mnie istotne, dają nowe spojrzenie.

Zastanawia mnie jeszcze jedno, czy w takim razie mixiny nie są w pewien sposób redundantne jeśli dysponujemy funkcjonalnością parametrów domniemanych?

Pozostało 580 znaków

2018-08-10 15:50
1

Może pytanie, które zadam jest po prostu jest głupie i równoważne z tym "po co zostały wprowadzone pętle", ale zastanawia mnie po co (jaka motywacja za tym stoi) wprowadzone zostały "parametry domniemane" (implicit parameters)?

Parametry implicit pojawiły się w którejś wersji Scali przy okazji przerabiania hierarchii kolekcji. Wprowadzono wtedy domniemany parametr CanBuildFrom, który służył do wielu rzeczy związanych z kolekcjami. Potem zauważono, że parametry implicit mają wiele innych zastosowań i można nimi emulować wiele konstrukcji znanych z innych języków (jak np wspomniane tutaj typeclasses z Haskella).

Zastanawia mnie jeszcze jedno, czy w takim razie mixiny nie są w pewien sposób redundantne jeśli dysponujemy funkcjonalnością parametrów domniemanych?

Trait ma dostęp do parametrów o widoczności protected (i może takie zawierać), obsługuje wielodziedziczenie poprzez linearyzację traitów (to rozwiązuje diamond problem) i może mieć składowe abstrakcyjne (niezaimplementowane). Zależnie od stylu programowania bardziej przydatne mogą być albo mixiny albo implicit parametry.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.

Pozostało 580 znaków

2018-08-10 16:42
0

Patrząc teraz od strony obiektowej, to wydaje mi się że jest to podobne do wyrzucenia na zewnątrz klasy pewnej logiki:

Pseudo-java-kod:

interface ExternalLogic<T> {
    boolean evaluateFoo(T x);
}
...
MyClass(Param param, ExternalLogic<T> externalLogic)    {
    this.externalLogic = externalLogic;
    this.param = param;
}

// Uzywa externalLogic do podjecia decyzji 
Object doSth()  {
    ...
}
...

Gdyby w Scali nie było "implicit", to nadal można by korzystać z tego wzorca, tylko (albo "aż") ręcznie uzupełniać "implicitParam" (tak, wiem, wtedy byłby "explicit" ;-) ), ewentualnie stworzyć sobie mechanizm, który nas od tego uwolni.

Jeśli dobrze zrozumiałem, to w Scali jest "bogatsza" realizacja takiego wzorca, gdyż:

  • błędy zostaną wykryte na etapie kompilacji
  • parametr domniemany zostanie "wstrzyknięty" (czyli tak jakbyśmy mieli funkcjonalność wstrzykiwania zależności)
  • kod będzie zwięzły
  • mechanizm znajduje się w standardzie języka

Pozostało 580 znaków

2018-08-10 17:05
0

Wstrzykiwanie będziesz miał jak wstawisz słówko implicit obok parametru konstruktora. Zwykle robi się wtedy dwie listy parametrów w konstruktorze, np:

class Klaska(parametrJawny: A)(implicit parametrDomniemany: B) {
 // (...)
}

Parametry implicit skracają kod. Czasami mocno, bo argumentem implicit może być wynik metody, która sama ma parametry implicit.

Dotty, czyli rozwojowa wersja Scali 3 idzie z implicitami jeszcze dalej: http://dotty.epfl.ch/docs/ref[...]/implicit-function-types.html


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.

Pozostało 580 znaków

2018-08-16 10:22
1

Gdyby w Scali nie było "implicit", to nadal można by korzystać z tego wzorca, tylko (albo "aż") ręcznie uzupełniać "implicitParam"

Cała magia implicitów polega na tym, że to automatyczne uzupełnianie odbywa się na podstawie typu, a sam proces dopasowywania obiektu do typu może przebiegać wg dowolnego algorytmu w trakcie kompilacji (implicity są kompletne w sensie turninga, a ponadto mogą korzystać z makr). Innymi słowy, nie ma praktycznie ograniczenia na to, jak skomplikowany będzie proces znajdowania odpowiedniego obiektu. Możesz napisać sobie makro dostarczające implicit a w tym makrze 10k linii. W efekcie to "tylko" ręczne wstrzykiwanie to może być jednak bardzo duża niedogodność w porównaniu z automatycznym.

Dzięki implicitom możesz robić takie fajne rzeczy jak np. transformowanie obiektów do/z JSON bez używania refleksji.

Implicitów używa się zresztą nie tylko do wstrzykiwania, ale też do nakładania złożonych ograniczeń na typ. Przykładowo, można w Scali napisać metodę, którą da się wywołać (na etapie kompilacji) tylko dla obiektu, który nie jest określonego typu. Z każdym typem będzie działać, a z jednym wybranym nie. Zrobisz takie coś w OOP i zwykych generykach Java/C#? ;) Może przykład trochę zbyt teoretyczny, ale ogólnie mechanizm ten umiejętnie wykorzystany, pozwala programować w sposób znacznie bezpieczniejszy niż zwykłe OOP. W wielu sytuacjach bez implicitów musiałbym rzucać wyjątkami. Błąd na etapie kompilacji jest lepszy niż błąd w trakcie działania.

Kolejnym sztandarowym wręcz zastosowaniem implicitów (wraz z typami wyższych rzędów) jest realizacja metod zwracających obiekt, którego typ jest zależny od typu parametrów wejściowych. Np. transformujesz kolekcję i w wyniku chcesz dostać kolekcję tego samego typu. Spróbuj w klasycznym programowaniu obiektowym napisać uniwersalną metodę, która tworzy nową kolekcję tego samego typu, ale z inną zawartością. W praktyce mógłbyś:

  • powielać implementację dla każdego typu osobno (np. osobna implementacja dla zbiorów, osobna dla list), wykorzystując przeciążanie / różne klasy
  • dorzucić ręcznie jakiś parametr realizujący wzorzec "builder", ale wtedy nie wymusiłbyś na użytkowniku, że typ buildera musi odpowiadać typowi kolekcji wejściowej (tj. zapewne wymusiłbyś w runtime i sygnalizował jakimś wyjątkiem - paskudztwo).
edytowany 3x, ostatnio: Krolik, 2018-08-16 10:37

Pozostało 580 znaków

2018-08-16 10:33
0

Na zwykłych generykach pewnie nie, ale korzystając z refleksji myślę, że się da. Na szybko nie przychodzi mi jednak jakieś praktyczne zastosowanie do głowy. Sam mechanizm wygląda na potężny, ale jak to mówią "with great power comes great responsibility", pole dla antwzoróców wydaje się duże :)

Póki co jestem na etapie rozpoznawania mechanizmów w scali, ale myślę, że za jakiś czas docenię dobrodziejstwa z tego płynące.

Nie musisz używać jawnie implicitów aby korzystać z ich dobrodziejstw. One umożliwiają działanie wielu fajnych bibliotek, które bez nich nie byłyby tak łatwe / elastyczne w użyciu. - Krolik 2018-08-16 10:36

Pozostało 580 znaków

2018-08-16 11:00
0

Trochę pobocznie do głównego tematu, bo takie gdybanie, a nie pytanie jak jest :)

Twórca Erlanga pisał kiedyś o językach OO: "The problem with object-oriented languages is they’ve got all this implicit environment that they carry around with them. You wanted a banana but what you got was a gorilla holding the banana and the entire jungle."

Czy nie jest to jednak takie zatoczenie koła przez twórców Scali, która wydaje mi się, że dodaje to co było/jest mankamentem (wg Joe Armstronga) OO? Owszem, jest "banan, ale potrzebuje mimo wszystko jakiejś dżungli, która pojawi się z kapelusza".

W obliczu braku zgodności wstecznej w kolejnych wersjach może taka rzecz jak implicite zniknąć, czy już za późno? :-)

Mechanizm implicit nie zniknie, bo jest głównym mechanizmem odrozniajacym Scalę od np. Kotlina lub Swifta. To trochę tak jakbyś pytał, czy są szanse, że w kolejnej wersji C nie będzie wskaźników, a w kolejnej wersji C++ znikną szablony. - Krolik 2018-08-16 11:20
Dzięki za rozwianie wątpliwości :) - yarel 2018-08-16 11:28

Pozostało 580 znaków

2018-08-16 11:02
0

Sam mechanizm wygląda na potężny, ale jak to mówią "with great power comes great responsibility", pole dla antwzoróców wydaje się duże :)

W praktyce rzadko spotykam się z tym by koledzy wrzucali parametry implicit gdzie popadnie. Zdecydowanie częściej dało się uświadczyć dziwaczne funkcje (wyższych rzędów lub nie). (nierealistyczny) Przykład na pokręcone lambdy:

Twórca Erlanga pisał kiedyś o językach OO: "The problem with object-oriented languages is they’ve got all this implicit environment that they carry around with them. You wanted a banana but what you got was a gorilla holding the banana and the entire jungle."

Czy nie jest to jednak takie zatoczenie koła przez twórców Scali, która wydaje mi się, że dodaje to co było/jest mankamentem (wg Joe Armstronga) OO? Owszem, jest "banan, ale potrzebuje mimo wszystko jakiejś dżungli, która pojawi się z kapelusza".

W obliczu braku zgodności wstecznej w kolejnych wersjach może taka rzecz jak implicite zniknąć, czy już za późno? :-)

Trzeba się zorientować o co chodziło Armstrongowi. Być może chodzi np o encje Hibernate'owe gdzie otrzymując obiekt bazodanowy otrzymujesz także persistence managera, połączenie do bazy, dirty state i cuda nie widy. W Scali generalnie nie ma takich dziwactw. W Scali implicity służą raczej do ad-hoc polymorphism, chociaż czasami też do przesyłania kontekstu. Tyle, że jeśli masz implicit Clock czy ExecutionContext (pulę wątków) to tak naprawdę jest lepiej niż gdybyś miał domyślny kontekst zaszyty w Thread Local Storage albo wstrzykiwany magiczną biblioteką. Innymi słowy - parametry implicit to dodatkowe banany, a nie dodatkowe goryle :] Pochodzenie implicitów możesz też sprawdzić nawet przed kompilacją (w IntelliJu skrót Ctrl+Alt+P i Ctrl+Alt+Q), natomiast obecność goryla z Hibernate'a czy magicznego kontenera DI sprawdzisz dopiero w czasie wykonania.

Implicity nie znikają, raczej idzie to w drugą stronę. W Dotty (który ma być docelowo Scalą 3) dochodzą implicit function types - pisałem o tym w tym wątku.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
edytowany 3x, ostatnio: Wibowit, 2018-08-16 11:16
Erlang to końcówka lat 80-tych, więc pewnie chodziłu mu o ogólnie o graf obiektów. Przykład z Hibernate ilustruje w czym rzecz. Sama koncepcja Dotty i fakt, że można eksperymentować na żywym organizmie, podoba mi się. Tyle, że dla mnie, to pieśń przyszłości :) Najpierw trzeba ogarnąć podstawy Scali. - yarel 2018-08-16 12:03

Pozostało 580 znaków

Odpowiedz
Liczba odpowiedzi na stronę

1 użytkowników online, w tym zalogowanych: 0, gości: 1, botów: 0