images/logo.png
SHWB na blogspot  | Uživatel: Nepřihlášen

O programování 10 - Java a inspirace v Ruby

Na závěr předchozího dílu jsem slíbil, že teď bude čas na něco úplně jiného ... a nebude to Python, ale těsně vedle - Ruby.

V předchozích dílech jsem uvedl některé možnosti funkcionálního přístupu v Java 8 a důvody, proč se mi použitý zápis nelíbí, i když jsem byl nakonec nucen uznat jistou logiku a svoje původní názory revidovat. Občas jsem zmínil, že by se mi více líbil jiný zápis, podobný například tomu používanému v Ruby.

I v Ruby se jedná o volání API, nikoliv o rys jazyka, ale přijde mi, že funkce jsou pojmenovány logičtěji. V této kapitole proto uvedu zápis některých dříve uvedených fragmentů v Ruby a to konkrétně ve verzi JRuby 1.7.3. Novější verze mají sice i další a lepší možnosti zápisu, mně se však nechtělo instalovat Ruby jen kvůli jednomu článku.

BTW, kdysi jsem uvažoval o větším programování v Ruby, některé jeho rysy jsou fajn a jiné zase ne; k tomu se vrátím v závěru článku.

Kopírování pole

Prvním příkladem je kopírování jednoho pole do druhého s modifikací jeho hodnot, konkrétně přičtení jedné. To lze napsat v Ruby jednoduše na jeden řádek s jasnou syntaxí:



list_out = list_in.map {|row| row + 1};

V Javě jsem použil a zkoumal různé varianty následujícího typu cyklu:



inputList.stream().forEach((integer) -> { outputList.add(++integer); });

Podle mě jde o principiální rozdíl ve způsobu řešení. Zápis v Ruby říká, že se má na každý prvek pole aplikovat (mapovat) funkce, což je funkcionální přístup. Ale abych Javě nekřivdil, stejný přístup mohu použít také:



outputList = inputList.stream().map(e -> ++e).collect(Collectors.toList());

Super, to je fakt pěkný zápis, jenom drobnost, nešlo by to bez toho collect()? A ideálně ve formě:



outputList = inputList.map(e -> ++e);

Takže bychom měli celkem elegantní zápis (i v Javě), ještě mě napadá otázka, zda je garantováno pořadí, v němž jsou prvky zpracovány? Podle mě obecně nemusí být, což je další podstatný rozdíl.

Druhá otázka je, jak je tento kód výkonný? Naváži na druhý díl a doplním tam publikovanou tabulku o třetí řádek, viz tabulka.

WARMUP = 10

N100100010000100000
standard forEachavg: 0,017
min: 0
max: 3
avg 0,086
min: 0
max: 9
avg: 0,223
min: 0
max: 4
avg 2,276
min: 1
max: 16
stream().forEach()avg: 0,018
min: 0
max: 4
avg: 0,049
min: 0
max: 2
avg: 0,254
min: 0
max: 10
avg: 1,819
min: 1
max: 23
stream().map()avg: 0,038
min: 0
max: 1
avg: 0,191
min: 0
max: 16
avg: 0,906
min: 0
max: 27
avg: 5,059
min: 2
max: 43

WARMUP = 100

N100100010000100000
standard forEachavg: 0,009
min: 0
max: 1
avg: 0,064
min: 0
max: 10
avg: 0,199
min: 0
max: 4
avg: 2,023
min: 1
max: 16
stream().forEach()avg: 0,006
min: 0
max: 1
avg: 0,049
min: 0
max: 5
avg: 0,190
min: 0
max: 4
avg: 1,700
min: 0
max: 11
stream().map()avg: 0,042
min: 0
max: 8
avg: 0,120
min: 0
max: 5
avg: 0,762
min: 0
max: 21
avg: 5,375
min: 3
max: 32

Závěr je jednoduchý - tento kód je suverénně nejpomalejší.

PS: Neporovnávám rychlost Java vs. Ruby, protože mi to nepřijde fér a navíc si nemyslím, že by to mělo nějakou vypovídací hodnotu.

Sečtení hodnot pole

Druhým příkladem je sečtení všech prvků pole. V tomto případě nabízí Ruby dvě varianty, první je přímočará:



sum = 0; list_in.each { |row| sum += row};

a druhá o něco sofistikovanější:



sum = list_in.inject(0, :+);

Druhý případ je "jen" syntaktická zkratka pro "folding" funkce a lze zapsat i takto - viz tento článek



array.map(&:to_i).reduce(0, :+)

což odpovídá javovskému:



int sum = list.stream().reduce(0, Integer::sum);

Javovský zápis mi nepřijde ideální, i když má svoji logiku. Metodu stream() už znovu rozebírat nebudu, přidám však další dvě otázky: Proč se metoda, která aplikuje metodu na prvky pole jmenuje reduce() a ne (třeba) apply()? A proč je tam první argument nula? Obojí si sice dovedu vnitřně odůvodnit (degenerovaný map-reduce, iniciální hodnota), ale pořád mi přijde, že Java ve snaze být univerzální dělá jednoduché věci složitě. Zkrátka by se mi v tomto konkrétním případě líbil zápis:



int sum = list.apply(Integer::sum);

To už jsem ale odbočil od Ruby, takže se vrátím zpět a to rovnou k závěru. I když jsem toho zde mnoho neukázal, tak mi přijde, že v Ruby jsou logičtěji pojmenované metody, ale jeho paradigma je stejné jako v Javě - funkcionální rysy jsou implementované jako metody a nejde o rys jazyka. A to, že je zápis v Ruby mnohdy čitelnější a logičtější plyne z jiných jeho vlastností, nikoliv ze způsobu implementace funkcionálních rysů.

Proč ne Ruby

A ještě odpověď na otázku z úvodu, proč jsem se nakonec nerozhodl pro větší programování v Ruby. Hlavním důvodem je dlouhodobá nestabilita, resp. nepředvídatelnost chování. V Ruby může kdokoliv přepsat libovolnou metodu z API (klidně i String.toUpper) a navíc lze třídu zapsat ve více souborech. Takže v jednom souboru lze napsat tři (public) třídy a ve druhém souboru čtvrtou třídu a dvěma již existujícím přidat metodu. Oba tyto rysy jsou fajn, ale mohou vést k hodně zákeřným a těžko odhalitelným chybám.

To, že někdo přepíše knihovní funkci lze (procesně) ošetřit v rámci jednoho (malého) teamu, mnohem horší je, když se to stane v rámci implementace jazyka jako takového nebo nějaké hojně používané knihovny, což kdysi (cíleně) udělali lidi z velmi populární frameworku Ruby on Rails a to v rámci minor verze (plácnu, při přechodu z 1.9.6 na 1.9.7), což by člověk opravdu nečekal. Důsledek byl, že program běžící v samotném Ruby nefungoval v Ruby on Rails správně, protože (měl tu drzost, že) spoléhal na standardní knihovnu. A podobné věci se údajně děly i přímo v Ruby, resp. jeho knihovnách.

Něco takového prostě na mission critical aplikace nenasadím, nebudu riskovat, že mi minor aktualizace rozbije aplikaci. Ona ani Java není v tomto svatá, programátoři v JavaFX by mohli vyprávět o přechodu z 8.0.29 na 8.0.40. Ovšem drobný rozdíl tu je - u Javy, jakožto kompilovaného jazyka nás na tyto chyby upozorní už překladač, takže program ani nepřeložím natož abych ho spustil. U interpretovaného Ruby však nemám žádný způsob, jak tento problém zjistit. Chybu samozřejmě zjistím po spuštění, ale pokud se nedostanu do větve s příslušným kódem (šikovně zašitý else), tak se o ní dlouho nemusím dozvědět, takže jediným řešením jsou důsledné testy. Ale všichni víme, že stoprocentní pokrytí testy je z říše pohádek...

Takže z těchto důvodů je pro mě Ruby sice zajímavá ale pořád jen hračka.

15.03.2017
Přidat názor:
Vyhrazuji si právo libovolný komentář smazat bez udání důvodu. Kritika mi nevadí, ale chci omezit anonymní výkřiky, které nemají s tématem nic společného.
V textu je možné používat HTML tagy a tuto zjednodušenou MarkDown syntaxi
Jméno
Text
Postřehy:
08.02.2019: Most! - Je to DÁŠA!
Chvíli jsem přemýšlel, jestli psát o seriálu Most! do Filmů nebo do Postřehů, nakonec jsem se rozhodl pro Postřehy. Důvod je ten, že seriály moc nesleduji, odrostl jsem na Červeným trpaslíkovi, Jistě pane ministře atd. a poslední, co jsem viděl, byla tak šestá série TBBT. Což je všechno docela vysoká laťka, zvlášť, když si vezmu ty různý český hospody a spol. tak si říkám, že je to
https://www.printertollfreenumber.com/epson-printer-support: Epson is a well-known brand for printers and scanners many users of Epson in worldwide if you are also an Epson customer and you
Poslední diskuse Postřehy
O programování 06 - Návrhové vzory - síla i slabina Javy
P.S. samozrejme "Context" mel byt "Client" .. To jsem jen narazil na nejak divne pojmenovany diagram.. (Context je samozrejme trosku neco jineho...)
...
David | 25.02.2017
O programování 06 - Návrhové vzory - síla i slabina Javy
To k cemu jsi dosel (tedy implementace LooperRunner + ILoopMethod), tak je ta prava Strategy by GoF :) To co je tam dulezite je ze Context (LooperRunner) je oddeleny od Strategy (ILoopMethod),
...
David | 25.02.2017
O programování 03 - Přehlednost funkcionálního zápisu v Java 8
Máš pravdu, to map je tam zbytečné. Odněkud jsem to opsal a nezkontroloval. Tím ovšem trochu padá pointa celého článku.
...
Saha | 14.12.2016
O programování 03 - Přehlednost funkcionálního zápisu v Java 8
Jen takova otazka k tomu druhemu prikladu:
Proc tam tu cast s "map" ktera de facto s prvky toho streamu nic nedela? Nestacilo by
list.stream().reduce(0, Integer::sum);
?
Ja teda moc
...
David P. | 13.12.2016
Paleo na půl - 01 - První tři dny bez mléka
Kvalitní hořké čokolády jsou bez mléka.. :) (a to i ty méně "kvalitní"). Mléko bývá součástí jen těch "sladkých".
...
David | 04.05.2015
Statistiky
Aktualizováno: 04.06.2019
Počet článků/fotek: 1365/13742
(C) Saha - 1990 - 2019 - Verze 1.3.32 - 11.05.2019 - Generated by SHREC 2.214
Veškeré zde uvedené materiály vyjadřují pouze moje soukromé názory (s výjimkou knihy návštěv a diskusí, kam může přispívat kdokoliv), a pokud s nimi někdo nesouhlasí, tak je to jeho problém, nikoliv můj.