Co z tą Scalą ?

0

Ja mysle, że do Scali trzeba mieć po prostu doświadczenie z czyms wczesniej. Gorzej jak ktos zaczyna od Scali...

chociaz tez obrazek mnie bawi ;)

title

0

@Krolik: Będę czwarty. Scala jest trudna. Każdy język w którymś istnieje coś takie jak Implicit w dowolnej postaci jest trudniejszy od tego w którym tego nie ma. I tak kotlin czy java są dużo prostsze w czytaniu i pisaniu od scali.

0

Polecam wyguglować hasło "scala fall" w obrazkach... ;-)

1
krzysiek050 napisał(a):

@Krolik: Będę czwarty. Scala jest trudna. Każdy język w którymś istnieje coś takie jak Implicit w dowolnej postaci jest trudniejszy od tego w którym tego nie ma.

"implicit w dowolnej postaci" - musisz w takim razie lepiej zdefiniować co rozumiesz przez implicit, bo traktujac to dosłownie, w Javie i Kotlinie też jest mnóstwo rzeczy, które są "implicit" czyli niejawne / domniemane / ukryte. Nie mam na myśli tylko konwersji, ale wszelkie przejawy domniemywania różnych rzeczy przez kompilator. Nawet głupie this w obu jest implicit (czyli można nie podać jawnie i kompilator sobie sam "domniema" że chodziło o this; w przeciwieństwei do Pythona). Albo parametry domyślne w Kotlin - można nie podawać ich jawnie, czyli znowu implicit a nie explicit. I niby dlaczego to miałoby być złe? Ktoś uważa, że wartości domyślne są trudne?

Poza tym, przez to że w Kotlin / Java nie ma takiego mechanizmu jak implicit objects Scali, ani żadnego mechanizmu równoważnego, w przeciwieństwie do języków takich jak Rust czy Haskell, to brak ekspresywności trzeba czymś uzupełnić. I kończy się to zwykle użyciem refleksji, instanceof i dynamicznego rzutowania, albo nawet ręczną generacją kodu. Zobacz sobie w ilu projektach np. ORMach / DI Javowych jest używane coś takiego jak ASM lub CGLIB. W Scali takie coś jest zupełnie niepotrzebne. Naprawdę uważasz, że generacja kodu w locie i refleksja są prosztsze w zrozumieniu (i bezpieczniejesze, hahaha) od implicitów i np. scala.meta?

I tak kotlin czy java są dużo prostsze w czytaniu i pisaniu od scali.

Scala ma wszystko, co ma Java i Kotlin, więc można pisać w Scali kod niemal identyczny jak w Kotlinie, z niewielkimi różnicami kosmetycznymi w składni (typu def zamiast fun). W drugą stronę niestety już tak nie jest. Np. w Javie ani Kotlinie nie napszesz czegoś takiego:

case class Address(street: String, number: Int, city: String)
case class User(login: String, firstName: String, lastName: String, address: Address)
...

val user = User("jk", "Jan", "Kowalski", Address("Lipowa", 17, "Szczecin"))
val jsonStr = user.toJson.prettyPrint  // tego nie zrobisz w Kotlin
val backToObject = jsonStr.parseJson.convertTo[User]    // tego też nie zrobisz
0

Króliku, możesz dać jakieś informacje(źródła) na temat Twoim problemów w zakresie braku implicit objects w Javie.
Po drugie co rozumiesz jako dynamiczne rzutowanie w Javie? Nie spotkałem się z takim określeniem. Może chodzi Ci o adapter pattern [0].

[0 ]https://en.wikipedia.org/wiki/Adapter_pattern

0

Ok, generalnie pomińmy odwołania do this, publiczne metody w konstruktorach czy nawet import z wildcard, bo to jest zupełnie inny poziom implicit'a. Argumenty domyślne w kotlinie też nie podchodzą, bo w przypadku braku ich podania, ich wartość zawsze będzie taka sama. Ten sam mechanizm jest zresztą w scali i o niego się nie czepiam.
To co faktycznie sprawia problem, to miejsca gdzie używasz właśnie słowa kluczowego implicit.
m()(implicit ...)
i wywołanie
m()
zależy od tego co zostało zdefiniowane wcześniej, lub nawet zaimportowane! Inny przykład: metoda przyjmuje typ A, Ty wrzucasz typ B i następuje niejawna konwersja. Jak? A to już zależy od twoich importów.
IDE sobie nie radzi z sugerowaniem importów. Wiem, w teorii nie wina języka, ale jednak pisze się przez to dużo trudniej.

to brak ekspresywności trzeba czymś uzupełnić

Nie jestem pewny do czego konkretnie pijesz. Instanceof raczej nie powinno się używać. W javie nie ma patternmatchingu i koniec.
Twierdzenie że A nie jest złe, bo B jest gorsze to błąd argumentacyjny. Nie twierdzę że CGLIB czy jakiekolwiek dynamiczne proxy jest lepsze (od czego?). Wiem natomiast, że istnieją lepsze sposoby dekoracji obiektów, chociażby ręcznie przez wzorzec dekoratora. Albo lepiej, wspomagany przez język jak w pythonie. Patologia która powstała w javie jest spowodowana chęcią upchnięcia magii wszędzie w tym ekosystemie. Nigdzie nie napisałem że to jest lepsze od innych złych rozwiązań.

Podsumowując, wolę napisać 20 znaków więcej i mieć czarno na białym co się dzieje, niż je oszczędzić, a potem czytać importy żeby wiedzieć o co chodzi w kodzie.

0

Rozumiem Java nie jest zbytnio ekspresywna.
W Twoim przykładzie parsowałeś object do jsona.
W Javie napisałbym taki parser, albo pewniej użył gotowej biblioteki, np Jackson.

0

@Krolik:
Da się to zrobić na 2 sposoby.

  1. Jak wspomniał @Koziołek - Extension methods (ale to też podchodzi pod implicit bo wg. importu zależy działanie metody)
  2. Dodanie metody toJson oraz parseJson do User - co jest złego w tym rozwiązaniu?
0

zależy od tego co zostało zdefiniowane wcześniej, lub nawet zaimportowane!

No, eureka, znaczenie kodu zalezy od tego, co jest w zasięgu widoczności i co było wcześniej.
Znaczenie wywołania:

a.b();

praktycznie w każdym języku (czy to Kotlin, Java czy C++) też zależy od tego co było wcześniej i od importów. Jakiej klasy jest a. I która klasa została zaimportowana. To jest podstawowa cecha programowania. Gdzie tu jest problem? Ba, nawet gorzej, jak masz wywołanie polimorficzne, to nawet w tym samym pliku źródłowym możesz nie widzieć, która klasa będzie wywołana! I to w zwykłej Javie! Java to musi być strasznie trudny język.

Inny przykład: metoda przyjmuje typ A, Ty wrzucasz typ B i następuje niejawna konwersja. Jak? A to już zależy od twoich importów.

Używanie implicitów do niejawnych konwersji to anti-pattern. Chyba tylko bardzo początkujący się na to łapią. Implicity nie zostały stworzone do konwersji między typami - to że przez przypadek tak działają, i niektórzy myślą, że to dobry pomysł, to jest ich problem. Obecnie kompilator / IDE ostrzega przed takim użyciem implicitów.

  1. Dodanie metody toJson oraz parseJson do User - co jest złego w tym rozwiązaniu?

To, że ktoś tu wcześniej twierdził, że w Kotlin łatwiej się pisze kod i jest ponoć czytelniejszy. Otóż ten przykłąd pokazuje, że się łatwiej nie pisze. Jeśli miałbym definiować toJson i parseJson ręcznie dla każdego typu, to by mnie coś trafiło. Problemem nie jest to, że w Kotlin nie można dodać takiej metody do własnego typu. Można dodać. Problem jest taki, że ja nie chcę powielać całej definicji parseJson / toJson dla każdego obiektu. Co najwyżej mogę powiedzieć jaką biblioteką chcę serializować / deserializować które obiekty (ok, jeden import, jeden implicit), ale nie będę pisał tej implementacji sam. Chcę mieć wspólną implementację dla wszystkich typów, a dla każdego typu chcę zakodować tylko absolutne minimum konieczne do obsługi mojego typu (w większości przypadków: nic).

Jedyny sposób aby to sensownie zrobić w Javie / Kotlinie, tak aby nie męczyć programisty, to przez bibliotekę korzystającą masowo z refleksji, sprawdzającą typy w runtime i rejestrującą jakieś runtime'owe konwertery. Czyli to o czym pisałem - system typów nie wyrabia i bardziej zaawansowane rzeczy trzeba robić w runtime, co wcale nie jest prostsze (a na pewno nie wydajniejsze).

0

Różnica jest spora.
W javie możesz ustawić autoimport i raz na jakiś czas zostaniesz powiadomiony że znalaziono XXX w pakiecie a i b. Wybierz który. Z czego w większości sytuacji wiesz dokładnie że chodzi a.XXX a nie o b.XXX. De facto pisząc w javie nie patrzysz na tą sekcję prawie wcale. Jedynie jeżeli z jakiegoś powodu klasa się nie kompiluje.
W scali w importach możesz wybrać chociażby excecution context do uruchamiania future.

0

W Scali może być tylko jeden pasujący implicit. Jak coś poknocisz, to kompilator będzie krzyczał. Poza tym jednym kliknięciem dowiesz się, jakie implicitysą w zasięgu. To jest tak samo jak z metodami / klasami, tylko po prostu do klas i metod wszyscy się przyzwyczaili, więc wydaje się to natiralne. A implicity są nowe, więc wydają się trudniejsze.

Ok, chodzi mi właśnie o to że musisz kliknąć, a przede wszystkim musisz wiedzieć że musisz kliknąć.
a.b(c)
W większości języków jakie spotkałem, oznacza to mniej więcej w zakresie a wywołaj metodę b z parametrem c. W scali może być jeszcze ukryte d, e i f. Żeby wiedzieć czy coś jest tam ukryte, musisz zobaczyć jak zadeklarowana jest metoda. Pytanie po co tak? Dlaczego po prostu jawnie nie można dodać tych argumentów? Oszczędność miejsca, ok ale nie takim kosztem.

Scala to swego rodzaju poligon doświadczalny. Jeżeli ktoś wymyślił jakiś mechanizm, to albo już w scali jest, albo będzie. Nie jestem osobą która może ocenić czy na tym zyskuje czy traci bo zbyt mało wiem. Widziałem jednak już trochę języków i IMHO Scala jest jednym z tych trudniejszych.

0

W większości języków jakie spotkałem, oznacza to mniej więcej w zakresie a wywołaj metodę b z parametrem c. W scali może być jeszcze ukryte d, e i f. Żeby wiedzieć czy coś jest tam ukryte, musisz zobaczyć jak zadeklarowana jest metoda.

W Kotlinie też może być d, e, i f, bo mogą być domyślne. Twój argument przeciw implicitom działa również przeciw wartościom domyślnym.

Pytanie po co tak? Dlaczego po prostu jawnie nie można dodać tych argumentów? Oszczędność miejsca, ok ale nie takim kosztem.

Jeżeli myślisz, że implicity są dla oszczędności miejsca, to znaczy, że ich nie rozumiesz. To tak jakbyś napisał, że dziedziczenie i polimorfizm są po to aby nie musieć pamiętać, którą podklasę wywołać.

Implicity są mechanizmem silniejszym od dziedziczenia (nominal subtyping) i umożliwają rozwiązanie problemów z zakresu inżynierii oprogramowania, których dziedziczenie i zwykłe OOP na poziomie Javy nie rozwiązuje. Np. wspomniany wyżej https://en.wikipedia.org/wiki/Expression_problem.

0

Być może nie rozumiem. W wielu miejscach gdy czytałem o scali i pisało o implicit to były jedynie przykłady bez sugerowanych problemów do rozwiązania. Wyglądało to tak jakby chodziło o ominięcie części która w 90% jest taka sama. I również sporo exampli było odnośnie implicit conversion. Widocznie brakuje czegoś w stylu Effective Scala i osoby która mogłaby to napisać.

Mimo wszystko, to tylko kolejny klocek do mojej teorii że scala jest trudna.

2

Jeśli implicity są takie trudne to w takim razie wzbogacanie obiektów o metody w czasie trwania ich życia jest o rząd wielkości trudniejsze. W każdym przypadku, gdy w języku skryptowym dodajemy lub zmieniamy w obiekcie metody (oraz ilość i rodzaj pól) to robimy coś znacznie bardziej ukrytego niż implicity. Implicity w Scali można sobie podejrzeć za pomocą jednego kliknięcia. Dynamiczne wzbogacanie obiektów w językach kaczo typowanych jest za to niemożliwe do prześledzenia przez IDE, przez co w ogólności podpowiadania nie da się zrobić (oczywiście można zrobić explicite wsparcie dla jakiegoś tam frameworka, o którym wiadomo, że w taki i taki sposób wzbogaca obiekty i takich i takich nazwach, ale takie coś to nie jest wnioskowanie z kodu tylko ze sztywnych, ręcznie stworzonych konfiguracji).

Dynamiczne typowanie idzie często nawet o krok dalej i nawet nie trzeba dynamicznie wzbogacać obiektu przed wywołaniem metody, której nie było w obiekcie podczas jego tworzenia. Można to zrobić nawet w trakcie wywołania: https://rosettacode.org/wiki/Respond_to_an_unknown_method_call
Z tego co słyszałem to method_missing to często wykorzystywane narzędzie w Ruby, zwłaszcza we frameworku Ruby on Rails

Jak w takim razie wypada porównanie trudności Ruby'ego w stosunku do Scali?

Ponadto polecam poczytać o konceptach "open class" i "monkey patching" w kontekście języków kaczo typowanych, jak np wspomnianego już Ruby, ale też np w JavaScripcie też takie coś się chyba dość często stosuje. Moim zdaniem konwersje implicit są zdecydowanie lepsze od mechanizmów z języków kaczo typowanych ponieważ konwersję implicit w Scali można zawsze łatwo podejrzeć w IDE, a mimo samej konwersji implicit nie tracimy silnego typowania.

0

Nie wiem jak w Ruby, ale wiem jak w JS (w typ pisałem). I tak, to jest złe i trudne. Ciężko się to czyta i ciężko się to zmienia. Da się z tym jako tako pracować pod warunkiem że tworzysz obiekt jakąś funkcją konstruującą i dopiero potem używasz. Teraz nie piszę w JS i nie chcę do tego wracać.
Uważam więc że 2 różne elementy z 2 różnych języków są złe. Niech będzie że duck typing gorszy. Pytanie, co chcesz usłyszeć? Że implicit to nie koniec świata? Że są gorsze mechanizmy?

0

Nie. Chcę usłyszeć porównanie praktycznej trudności języków. Dobrze wiedzieć, że uważasz języki kaczo typowane za trudniejsze od Scali.

Jeszcze wracając do parametrów implicit. Parametry implicit są mechanizmem sporo lepszym niż np wstrzykiwanie zależności za pomocą magicznych kontenerów DI korzystających z refleksji. Mając parametry implicit mam znowu możliwość bardzo łatwego (jedno kliknięcie) podejrzenia co się właściwie wrzuca w miejsce implicita. W przypadku wstrzykiwania za pomocą kontenera DI jak np Spring czy Google Guice muszę analizować konfigurację dla owego kontenera DI. Czasami jest to nietrywialne jeśli jest wiele różnych konfiguracji dla wstrzykiwacza i trzeba się zorientować która z nich była użyta przy tworzeniu obiektu, który analizujemy.

0

Praktycznie Scala i JS są trudne. JS w dodatku na pierwszy rzut oka wydaje się łatwy, a wiele rzeczy wychodzi w trakcie.

Nie wiem dlaczego uważasz że implicit jest lepszą alternatywą dla DI. Nie wiem jak mógłbyś go użyć żeby go zastąpić. Z jakiegoś powodu Guice został włączony do PlayFramework, mimo że wcześniej go nie było.
Z drugiej strony DI to przecież magia bibliotek. W czystej javie tego nie ma, w przeciwieństwie do czystej scali i implicit.

1
Wibowit napisał(a):

Ponadto polecam poczytać o konceptach "open class" i "monkey patching" w kontekście języków kaczo typowanych, jak np wspomnianego już Ruby, ale też np w JavaScripcie też takie coś się chyba dość często stosuje.

Nie wiem jak w Ruby, ale przynajmniej w środowisku JS monkey patching jest uznawany za anti-pattern.
W sumie stosuje się go w jednym przypadku - do polyfilli, co umożliwia korzystanie z nowych metod wbudowanych w starych wersjach javascriptu (co jest cool ;) ).
A już modyfikacji własnych obiektów, tym bardziej w runtime, w dobrym kodzie raczej nie uświadczysz (poza bardzo rzadkimi przypadkami, gdzie to rzeczywiście jest potrzebne).

Zgoda, że jest to niczytelne - tyle, że podajesz anti-pattern w JS jako kontrargument do patternu w Scali (kompletnie abstrachując od tego, czy implicit w Scali jest złe czy nie).

0

Że implicit to nie koniec świata? Że są gorsze mechanizmy?

Implicity są bardzo ogólnym i bardzo potężnym mechanizmem abstrakcji. Silniejszym niż dziedziczenie i polimorfizm. To powoduje, że są trudne do zrozumienia dla ludzi, którzy nigdy wcześniej nie mieli do czynienia z podobnym mechanizmem, mimo w swojej budowie jest on prosty jak konstrukcja cepa - masz dostarczyć dokładnie jeden obiekt pasujący typem do parametru i kompilator dostarczy taki obiekt, jeśli jest w zasięgu. Jakie obiekty są w zasięgu - kontrolujesz przez importy i umiejscowienie definicji implicitów. Reszta to szczegóły. Po prostu implicity nie pasują do niczego, co przeciętny programista widział wcześniej i automatycznie wpadają do worka z etykietą "trudne".

Jak popatrzymy na dziedziczenie, interfejsy i polimorfizm, to jest to również element języków obiektowych, który sprawia początkującym bardzo dużo problemów. Też łatwo tego mechanizmu używać niewłaściwie. To, że ktoś gdzieś napisze class Rybka extends Akwarium nie oznacza, że mechanizm abstrakcji jest zły albo nieprzydatny.

Z mechanizmami ogólnymi jest jeszcze ten problem, że ciężko znaleźć dobre przykłady. Nawet z klasycznym OOP. Podejrzewam, że większość z nas kiedyś próbowała komuś tłumaczyć arkana OOP, żeby ostatecznie spotkać się ze stwierdzeniem "ale po co to, jak mogę zrobić to na switchach?"

Implicity są mechanzmem zaawansowanym, służącym, podobnie jak makra, do pisania kodu bibliotecznego i do rozwiązywania trudnych problemów projektowych. Dzięki implicitom można tworzyć łatwe w użyciu biblioteki (ale niekoniecznie łatwe do napisania czy zrozumienia od środka). Krytyka oparta na tym, że "Kotlin jest lepszy, bo nie ma implicitów, a implicity są trudne w zrozumieniu, więc Kotlin musi być łatwiejszy" jest totalnie bez sensu. Po pierwsze: przeciętny programista nie musi używać ani rozumieć implicitów aby używać języka i jego bibliotek. Po drugie brak implicitów powoduje, że to co w Scali jest trudne do rozwiązania implicitami i makrami, w Kotlinie i Javie jest całkowicie niemożliwe do rozwiązania.

Jak niby zastąpienie czegoś trudnego czymś niemożliwym jest uproszczeniem?

Krytyka implicitów miałaby sens, gdyby Kotlin (lub inny konkurencyjny język) oferował coś w zamian, co byłoby równocześnie prostsze i tak samo potężne w sile ekspresji. Niestety obecnie jedyne w miarę popularne języki, które mogą konkurować systemem typów ze Scalą to Haskell, Idris i Coq. Żaden z nich nie jest w praktyce prostszy od Scali, zwłaszcza dla przeciętnego programisty. Żaden też nie jest tak popularny.

0

... a implicity są trudne w zrozumieniu, więc Kotlin musi być łatwiejszy" jest totalnie bez sensu

Jeżeli język nie posiada czegoś, co jest trudne w zrozumieniu, to jest łatwiejszy. To nie jest bez sensu.

Po drugie brak implicitów powoduje, że to co w Scali jest trudne do rozwiązania implicitami, w Kotlinie i Javie jest całkowicie niemożliwe do rozwiązania.
Jak niby zastąpienie czegoś trudnego czymś niemożliwym jest uproszczeniem?

Jest niemożliwe jeżeli założysz że musisz zrobić coś w jeden konkretny sposób. Nie wszystko w javie/kotlinie się da i co z tego? Nie da się napisać w nich ładnych i wydajnych aplikacji? Będą jakieś wybrakowane? Nie, da się zrobić w nich wszystko co chcesz. Wszystko, bez dodania trudnego w czytaniu konstruktu. A skoro tak się da, to jest uproszczony.

Po prostu implicity nie pasują do niczego, co przeciętny programista widział wcześniej i automatycznie wpadają do worka z etykietą "trudne".

Poznając scalę poznałem streamy/view/list(fp) czy pattern matching. Mechanizm dużo dziwniejszy na pierwszy rzut oka niż implicit, a jednak uważam je za fajne i przystępne, w porównaniu.

2

Właściwie to cały problem sprowadza się do jednego pytania
czy chcesz, żeby wywaliło się na produkcji / testach ?
czy w czasie kompilacji ?

Języki skryptowe, (stringly typed), gdzie wywala się dopiero w Runtimie mają tą siłę, że łatwo zrobić hello world i coś dodawać/ łatać i poprawiać, szybki feedback i to działa
Scala czy Haskell to przykład gdzie ciężko skompilować, ale jak się skompiluje to już będzie działać.

Coś za coś - jak piszesz w perspektywie tygodnia to taki BASIC się sprawdza, dla początkujących super.
Siłę Scali widać dopiero wtedy, kiedy po roku rozgrzebujesz stary projekt, robisz mega refaktoring (nowa Akka, nowa Scala 2.12 itp. do tego odkrycie, że design był głupi :-) ) i po tygodniu walki z błędami kompilacji w końcu odpalasz -> skubaniec od razu działa (chociaż dopiero po następnym tygodniu wiesz dlaczego :-( )

Java z kontenerem (Spring/JavaEE) to zresztą przykład jak z języka prawie typowanego :-) zrobić BASIC, nagle zależymy od mnóstwa globalnych/runtimowych ustawień i nagle to że kod się kompiluje i nawet przechodzi testy nie gwarantuje, że gdzieś na czwartej stronie aplikacji nie walnie NullPointerExceptuion bo kontener ma zły dzień.
Za to można proste - standardowe programiki nieco szybciej ulepić. Copy paste i do przodu.

Implicity to właśnie mechanizm, który pozawala załatwić w kompilatorze wiele konceptów inaczej łatanych w runtime (choćby automagiczne DI). Zresztą implicity to pikuś - ani nie są bardzo trudne, ani nie psują krwi bardzo.

Problem Scali to są na przykład makra (nic wspólnego z makrami w C nie mają). Trudno jest być z nich w 100 % zadowolonym. Z drugiej strony makra pozwalają na rozwiązanie problemów, które w javie rozwiązuje się refleksją. Runtimowa refleksja (javowy standard rozwiązywania problemów) to jest bagno - bo jak się wywali to na produkcji (no dobra -> testach) i cały ten kompilator javy można sobie wsadzić :-). Scalowe makra przenoszą te problemy do kompilacji - super , ale z drugiej strony jak mi jakieś makro nie działa to spędzam naprawdę dużo czasu na stwierdzeniu dlaczego.

0

Jeżeli język nie posiada czegoś, co jest trudne w zrozumieniu, to jest łatwiejszy.

Assembler MIPS 32 jest banalny w zrozumieniu, a specyfikacja całego języka mieści się na raptem kilku stronach, ale nie oznacza to, że pisanie w nim jest łatwiejsze niż pisanie w języku wysokiego poziomu.

Twój błąd polega na tym, że zakładasz, że trzeba znać implicity aby pisać efektywnie w Scali. Nie trzeba znać implicitów, tak jak i nie trzeba znać makr. Można pisać bez implicitów, w ogóle można pójść jeszcze dalej i zdegenerować Scalę do poziomu na jakim jest Kotlin (czyli Java++) i poza drobiazgami składniowymi nie będzie widać większych różnic.

Jest niemożliwe jeżeli założysz że musisz zrobić coś w jeden konkretny sposób. Nie wszystko w javie/kotlinie się da i co z tego? Nie da się napisać w nich ładnych i wydajnych aplikacji? Będą jakieś wybrakowane?

Stosując tę argumentację zaraz dojdziemy do wniosku, że wszystko da się napisać w assemblerze. Tylko czy jest sens?

Problem Scali to są na przykład makra

Makra są trudne. Ale gdzie nie są? Należy zauważyć, że makra w Scali mogą zrobić znacznie więcej od szablonów C++. Mało jest popularnych języków, które mają takie coś. Makro może połączyć się z bazą danych w czasie kompilacji, ściągnąć jej schemat, przetłumaczyć na kod i dać do skompilowania.

0

Jeżeli język nie posiada czegoś, co jest trudne w zrozumieniu, to jest łatwiejszy. To nie jest bez sensu.

Przy takim podejściu to Brainfuck jest banalny. Jego specyfikacja to kilka zdań i jedna tabelka.
Napisałem kiedyś interpreter BF z kompilatorem (JIT) do kodu wykonywalnego. Działał i był nawet szybki.
Tylko że... w życiu nie napisałem ani jednego programu w BF. Może i potrafię, ale uznałem to za niewarte nauki.
Tak, uważam Kotlin za łatwiejszy język od BF w programowaniu, mimo że jest ogromnie bardziej skomplikowany i z pewnością są w nim rzeczy trudniejsze w zrozumieniu.

1

Nie wiem dlaczego uważasz że implicit jest lepszą alternatywą dla DI. Nie wiem jak mógłbyś go użyć żeby go zastąpić.

Parametr implicit sam w sobie jest wstrzykiwaniem zależności. Parametr explicit też. Tutaj wracamy do dyskusji co jest wstrzykiwaniem zależności (i jest to po prostu podawanie ich z zewnątrz zamiast tworzenia w miejscu użycia).

Mi chodziło jednak o porównanie wstrzykiwania przez implicity kontra wstrzykiwania przez refleksję.

Załóżmy, że mamy klasy:

class A(b: B, c: C)
class B
class C

Wstrzykiwanie mogę zrobić za pomocą refleksji (składnia ad-hoc na potrzeby przykładu):

class GuiceModule {
  @Provide
  @Singleton
  def provideB: B = ???

  @Provide
  @Singleton
  def provideC: C = ???
}

...

val injector = new Injector(new GuiceModule)
val a = injector.inject[A]

Alternatywa za pomocą implicitów:

// companion object dla A
object A {
  def inject(implicit b: B, c: C): A =
    new A(b, c)
}

class WiringSetup {
  implicit val b: B = ???
  implicit val c: C = ???
}


val setup = new WiringSetup()
import setup._ // tutajimportujemy zależności jako implicity
val a = A.inject // by tutaj je wstrzyknąć automatycznie

W miejscu wywołania inject mogę ustawić kursor, nacisnąć Ctrl+Alt+P i zobaczyć skąd biorą się implicite zależności.

Ewentualnie można zrobić (chyba nawet lepiej):

//brak companion objecta

class WiringSetup {
  implicit val b: B = ???
  implicit val c: C = ???
}


val setup = new WiringSetup()
import setup._ // tutajimportujemy zależności jako implicity
val a = new A(implicitly[B], implicitly[C]) // by tutaj je wstrzyknąć automatycznie

Z drugiej strony DI to przecież magia bibliotek. W czystej javie tego nie ma, w przeciwieństwie do czystej scali i implicit.

Jeszcze raz powtarzam, że DI samo w sobie to tylko podawanie zależności z zewnątrz i nie wymaga żadnej refleksji.

Z jakiegoś powodu Guice został włączony do PlayFramework, mimo że wcześniej go nie było.

Pewnie po to by być bardziej "pro". Z jednej strony pchają się w jakieś refleksyjne wstrzykiwanie zależności, a z drugiej dalej mają mutowalne globale. Mało tego, w Javowym API używają ThreadLocali jako mechanizmu na przesyłanie parametrów:

Java code in Play uses a ThreadLocal to find out about contextual information such as the current HTTP request. Scala code doesn’t need to use ThreadLocals because it can use implicit parameters to pass context instead. ThreadLocals are used in Java so that Java code can access contextual information without needing to pass context parameters everywhere.

Dobra wiadomość jest przynajmniej taka, że w Scalowym API używają implicitów do przesyłania aktualnego żądania.

0

Dopiszę jeszcze jedną rzecz o implicitach. Implicity w Scali służą też do emulowania typeclasses znanych z Haskella lub Rusta.

Czytając forum natknąłem się na link do artykułu: Fluent Interfaces are Evil
Z częścią rzeczy się zgadzam, z częścią nie, ale chciałbym się skupić na fragmencie Fluent Interfaces break Decorators (and Composition) bo tam padły dość konkretne argumenty.

Scala, dzięki implicitom, pozwala jednak na skuteczną kompozycję fluent interfaces. Pierwszym krokiem ku temu jest oddzielenie danych od operacji, a więc zamiana podejścia obiektowego na funkcyjne. Zostajemy z typem algebraicznym, klasą z zestawem funkcji operujących na tym typie oraz konwersją implicit z typu algebraicznego na klasę z zestawem funkcji (type class).

Oto przykład skutecznej kompozycji płynnych interfesjów: http://ideone.com/GSIJ89

import scala.language.implicitConversions

trait Counter {
  def value: Int
}

class MutableCounter(var value: Int) extends Counter

case class ImmutableCounter(value: Int) extends Counter

trait CounterOps[C <: Counter] {
  def count(): C // tylko jedna metoda w płynnym interfejsie, jak chcecie to doróbcie więcej
}

class MutableCounterOps(counter: MutableCounter)
    extends CounterOps[MutableCounter] {
  override def count(): MutableCounter = {
    counter.value += 1
    counter
  }
}

object MutableCounterOps {
  implicit def counterToOps(counter: MutableCounter): MutableCounterOps =
    new MutableCounterOps(counter)
}

class ImmutableCounterOps(counter: ImmutableCounter)
    extends CounterOps[ImmutableCounter] {
  override def count(): ImmutableCounter =
    ImmutableCounter(counter.value + 1)
}

object ImmutableCounterOps {
  implicit def counterToOps(counter: ImmutableCounter): ImmutableCounterOps =
    new ImmutableCounterOps(counter)
}

class EchoingCounterOps[C <: Counter](
    baseCounterOpsProvider: C => CounterOps[C],
    counter: C)
    extends CounterOps[C] {
  override def count(): C = {
    println("Counting from: " + counter.value)
    baseCounterOpsProvider(counter).count()
  }
}

object EchoingCounterOps {
  trait EchoingCounterOpsProvider[C <: Counter] {
    implicit def counterToOps(counter: C): CounterOps[C]
  }

  def compose[C <: Counter, CO <: CounterOps[C]](
      baseCounterOpsProvider: C => CO): EchoingCounterOpsProvider[C] = {
    new EchoingCounterOpsProvider[C] {
      override implicit def counterToOps(counter: C): CounterOps[C] =
        new EchoingCounterOps(baseCounterOpsProvider, counter)
    }
  }
}

object Main {
  def main(args: Array[String]): Unit = {
    println("Echoing mutable...")
    echoingMutable()
    println("Echoing immutable...")
    echoingImmutable()
  }

  def echoingMutable(): Unit = {
    val composed = EchoingCounterOps.compose(MutableCounterOps.counterToOps)
    import composed.counterToOps

    val counter = new MutableCounter(3)
    println("New value: " + counter.count().count().count().value)
    println("Retained value: " + counter.value)
  }

  def echoingImmutable(): Unit = {
    val composed = EchoingCounterOps.compose(ImmutableCounterOps.counterToOps)
    import composed.counterToOps

    val counter = ImmutableCounter(3)
    println("New value: " + counter.count().count().count().value)
    println("Retained value: " + counter.value)
  }
}
Echoing mutable...
Counting from: 3
Counting from: 4
Counting from: 5
New value: 6
Retained value: 6
Echoing immutable...
Counting from: 3
Counting from: 4
Counting from: 5
New value: 6
Retained value: 3

Główną różnicą, która powoduje to, że kompozycja płynnych interfejsów w Scali działa bezproblemowo, a w PHP się wykrzacza jest to, że konwersje implicit są wykonywane w każdym kroku, tzn w zapisie:

counter.count().count().count().value

są 3 konwersje implicit w kodzie i po kompilacji będą 3 konwersje z Countera na CounterOpsy. Inaczej mówiąc podczas kompilacji ten kod jest rozwijany do:

val ops1 = counterToOps(counter)
val counter1 = ops1.count()
val ops2 = counterToOps(counter1)
val counter2 = ops2.count()
val ops3 = counterToOps(counter2)
ops3.count().value

Język PHP nie daje takich udogodnień, więc w dekoratorach dla płynnych interfejsów trzeba hardkodować zachowanie dla kontraktów konkretnych implementacji tych płynnych interfejsów. To sprawia, że próby tworzenia generycznych dekoratorów dla płynnych interfejsów jest jak chodzenie po polu minowym, co zostało już opisane w artykule, który w tym poście podlinkowałem.

W PHP alternatywą dla problematycznych w nim płynnych interfejsów są mutowalne obiekty i metody, które nie zwracają obiektów o typie takim samym jak obiekt na którym została wywołana metoda (a takie coś jest w płynnym interfejsie, bo tam metody zwracają this/ self).

2

Coś dla fanów Scali (mnie jeszcze nie wciągnęła):
Scala High Performance Programming
Tylko dziś za free na Packt.

0

Biorą w tej Scali na jakieś staże/praktyki? Czy droga do Scali to Java?

0

Podbijam wątek, co uważacie o scali vs kotlin & whatever na czas AD 2017/18? Scala.js wymarła na rzecz typescriptu i innych czy też dalej to w miarę świerze mięso na wejście w rynek?

0

Scala jest świeża, ale Kotlin świeższy. Ale nie lubię Kotlina, bo ruscy wymordowali całą Polską inteligencję i nie będę wspierał ich języka programowania i ich IDE. Wybieram Scala i Eclipse/NetBeans.

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