pondělí 9. září 2013

Functional Programming Principles in Scala - 3. běh

16. září začíná již třetí běh online kurzu Functional Programming Principles in Scala (dále budu z pohodlnosti zkracovat na ProgFun). Nejprve si poslechněme pár slov přímo od vedoucího kurzu, kterým není nikdo jiný než Dr. Martin Odersky, autor Scaly:



Progfun je tedy webový kurz organizovaný vyučujícími ze švýcarského École Polytechnique Fédérale de Lausanne (EPFL), kterého se může zadarmo zúčastnit každý (já osobně absolvoval druhý běh běžící letos na jaře). Konkrétně měly jeho první dvě iterace dohromady přes 100 000 účastníků, z nichž cca 20% je dokončilo. To se sice může jevit jako malé procento, ale je to více než dvojnásobek hodnoty obvyklé u podobných online kurzů. A to přesto, že účast v kurzu nic nestojí a není ani nijak závazné ho dokončit (pouze nedostanete "certifikát"). Důvodem pravděpodobně bude jednak v současné době poměrně žhavé téma a jednak velmi kvalitní obsah i organizace kurzu.


Důkladný úvod do FP


ProgFun tedy trvá 7 týdnů, z nichž je každý zaměřen na jednu konkrétní oblast funkcionálního programování. Detailní popis najdete v sylabu, ale v kostce se postupně probírají rekurze, funkce jakožto hodnoty, neměnné objekty, pattern matching, funkcionální listy a kolekce a nakonec líné vyhodnocování a streamy. Ke každému tématu přísluší vždy několik kratších video přednášek přímo od Oderskyho, jež v součtu dají zhruba délku jedné standardní 90minutové přednášky.

Jako feedback od studentů směrem k učitelům pak slouží domácí úkoly, které ověří vaše pochopení látky daného týdne. Zpravidla nejsou moc rozsáhlé, mají předem jasně danou strukturu a pořadí, ve kterém se při implementaci postupuje, a vyžadují spíše trochu zamyšlení nad daným problémem a technikou, nicméně se dá říct, že obtížnost i časová náročnost postupně trochu stoupá. Odevzdávání a kontrola řešení probíhá automaticky: Pomocí sbt (scalovská obdoba Mavenu) projekt z příkazové řádky zkompilujete, otestujete přiloženými unit testy a následně jednoduše odevzdáte na server kurzu, kde se podrobí důkladnějšímu automatickému testování a během několika minut se výsledné body objeví ve vašem profilu na webu. Celkem máte na odevzdání 5 pokusů, přičemž jako výsledný se bere ten s nejlepším dosaženým skóre.


Ryzí funkce == ryzí zlato?


Body za úlohu jsou kromě korektnosti programu (tj. splnění potřebných unit testů) založené z menší části také na správném programovacím stylu. Tím není samozřejmě myšleno správné odsazování, jmenné konvence a podobné podružné věci. Použitý style checker kontroluje, zda vaše programy jsou ryze funkcionální, tzn. že nikde nepoužíváte cykly, měnitelné (nefinální) proměnné a měnitelné (mutable) datové struktury. Ne, nedělám si legraci, opravdu to je možné a nejen to, dokonce to jde velmi dobře. Cykly nahradíte rekurzí nebo higher-order funkcemi, immutable kolekce jsou stejně dobře použitelné jako jejich mutable bratříčci a co se týče final proměnných (ve Scale nazývaných vals) - dnes už i nejeden javista jistě pochopil, že znovu přiřazovat do proměnné je vlastně potřeba docela zřídka.

Tento "eye-opener" (to že je možné se na programy dívat jinak než jen ryze imperativně) je dle mého názoru jedna z nejcennějších věcí, které tento kurz nabízí. Psát krátké, stručné, a přesto srozumitelné programy, u kterých si nemusíte lámat hlavu nad tím, co všechno se může jak a kdy měnit, která změna je závislá na čem atd., je jednoduše osvěžující. Samozřejmě že reálné aplikace s nějakým stavem pracovat ve výsledku vždy musí, ale naučit se ho minimalizovat tam, kde není potřeba, vám velmi zlehčí váš programátorský život, a to i v případě, že programujete v obyčejné Javě (což už kdysi psal i Joshua Bloch).

I v samotných přednáškách se objeví řada velmi zajímavých myšlenek - za všechny například způsob, jak implementovat boolean a logické operace nad ním pouze pomocí objektů a jejich metod, čili bez do jazyka zabudovaných primitivních typů, hodnot a operátorů. Případně obdobným způsobem realizovat přirozená čísla. V reálném světě se samozřejmě takováto řešení kvůli efektivitě nepoužívají, ale už jenom přemýšlet nad takovýmito problémy je velmi podnětné a naučí vás dívat se na věci z trochu jiného úhlu.

Ať už se chcete naučit základy Scaly přímo od jejího autora, pochopit, o čem že to je to v poslední době vzývané (a přesto ideově překvapivě staré) funkcionální programování, anebo se jen stát o něco lepším programátorem, určitě ProgFun zkuste. Nic za to nedáte (doslova!) a navíc:


Functional programmers have more fun.

PS: V listopadu pak začíná první iterace navazujícího kurzu týkajícího se konkurentních a distribuovaných systémů jménem Principles of Reactive Programming.

středa 26. prosince 2012

Lokalizace data a času v Androidu

Jedna ze sympatických věcí při vývoji pro Android je od základu zabudovaná podpora pro lokalizaci aplikace. Vlastně nejen podpora - programátor je v podstatě jemně tlačen k tomu, aby veškeré texty, které se zobrazují na uživatelském rozhraní, neumisťoval natvrdo do kódu javovských tříd nebo XML layoutů. I v případě, že daná aplikace bude ve výsledku pravděpodobně pouze jednojazyčná (zpravidla anglicky), je přeci jen dobrým zvykem oddělit data od aplikační logiky a grafické reprezentace a texty umisťovat do standardního XMLka res/values/strings.xml.


Babylónské zmatení


V každém případě je třeba pamatovat na to, že lokalizace nekončí pouze u překladů textových hlášek, ale že zahrnuje i způsob, jakým jsou formátovány datum a čas nebo třeba peněžní částky. Způsobů, jakými lze po celém světě zapsat jeden časový okamžik, je překvapivě velké množství, a to i za předpokladu, že se vyhneme jazykově specifickým jménům měsíce a dne v týdnu a budeme pouze číselně zohledňovat rok, měsíc, den v měsíci, hodinu a minutu.

Už hned rok lze zapsat buď dvěma anebo čtyřmi číslicemi. Číslo dne a měsíce lze v případně jednociferné hodnoty zapisovat s počáteční nulou či bez ní. Pak je tu samozřejmě otázka oddělovače - pomlčka, lomítko, či tečka? A pokud tečka, tak s mezerou za ní nebo bez? A to nejlepší nakonec - v jakém pořadí vlastně tyhle tři hodnoty zapsat? Jako Čechovi mi přijde zcestné jakékoliv jiné pořadí než to nejlogičtější - od nejmenšího k největšímu (v krajním případně v opačném), ale kupodivu existují i státy, kde letos měli Štědrý den na 12. 24. 2012. Nemluvě o státech, kde týden nezačíná naprosto logicky v pondělí, ale z nějakých pochybných důvodů v neděli (i jako ateista vím, že Bůh si přece dával oraz v neděli a ne v sobotu), ale to v tuhle chvíli raději přejděme.

S časem je to možná o trochu jednodušší, ale o to podivnější. Přestože den má +- 24 hodin, tak beru, že na ciferník se jich tolik moc dobře nevejde (byť klasik by nesouhlasil: Byl jasný, studený dubnový den a hodiny odbíjely třináctou...). Ale proboha, který šílenec vymyslel, že 12:30 PM je dříve než 1:30 PM?? Kdo nevěří, nechť vyzkouší následující kód. Za pozornost v něm také stojí fakt, že hodnota 11 na pozici měsíce opravdu znamená prosinec. Cenu za trolling pro člověka, který tohle do JDK protlačil.
Calendar c1 = Calendar.getInstance();
c1.set(2012, 11, 26, 12, 30);

Calendar c2 = Calendar.getInstance();
c2.set(2012, 11, 26, 13, 30);

DateFormat format = new SimpleDateFormat("hh:mm aa", Locale.US);
System.out.printf("%s < %s: %b",
    format.format(c1.getTime()), format.format(c2.getTime()), c1.before(c2));
Výstup:
12:30 PM < 01:30 PM: true

Kudy ven?

Problém je, tak jako v životě, nejjednodušší obejít, ale výsledek v takovém případě za moc nestojí. Můžete jakékoliv formátování prohlásit za zbytečnost a všude jednoduše natlačit java.util.Date.toString(), jenže s výstupem typu Wed Dec 26 20:42:15 GMT+00:00 2012 bez ohledu na jazykové nastavení telefonu vás vaši uživatelé pošlou kamsi. Navíc standardní dialogy pro výběr data a času jsou v Androidu automaticky lokalizované, i když aplikace je sama o sobě anglicky, takže je vhodné zobrazovat příslušné views ve stejném nebo obdobném formátu. Použít SimpleDateFormat s ručně daným formátem je vzhledem k výše popsanému nesmyslné, takže jak na to?

V U.O.me jsme postupovali tak, že jsme vytvořili dedikovanou třídu s metodami pro formátování pouze data, pouze času a data i času (každá ve dvou variantách pro Date a Calendar). Pro jejich implementaci jsme použili tři různé instance DateFormatu získané přes dostupné factory metody, které automaticky použijí locale (národní prostředí) dané nastavením daného telefonu. Důvod, proč jsme tuto třídu neudělali komplet statickou, je ten, že statické konstanty žijí po celou dobu běhu aplikace, tudíž změna systémových nastavení by se neprojevila, dokud by aplikace nebyla restartována. A to může být v některých zařízeních trochu problém, takže čistější řešení je v každé aktivitě formatter znovu instancovat.

import java.text.DateFormat;

public class DateTimeFormatter {

    private final DateFormat dateFormat = DateFormat.getDateInstance();
    private final DateFormat timeFormat = DateFormat.getTimeInstance();
    private final DateFormat dateTimeFormat = DateFormat.getDateTimeInstance();

    public String formatDate(Date date) {
        return dateFormat.format(date);
    }

    public String formatDate(Calendar date) {
        return formatDate(date.getTime());
    }

    public String formatTime(Date time) {
        return timeFormat.format(time);
    }

    public String formatTime(Calendar time) {
        return formatTime(time.getTime());
    }

    public String formatDateTime(Date dateTime) {
        return dateTimeFormat.format(dateTime);
    }

    public String formatDateTime(Calendar dateTime) {
        return formatDateTime(dateTime.getTime());
    }
}

Zde se velmi nepěkně projevuje, jak blbě je javovské API pro datum a čas pojmenováno. Proč se třída, která reprezentuje datum a čas, jmenuje Date a metoda, kterou objekt typu Date získáme z Calendaru, se jmenuje getTime()?? Na druhou stranu, kdyby tohle byly jeho jediné chyby... Samozřejmě tu máme mnohem lepší alternativy v podobě Joda Time, případně JSR 310, ale na Androidu je s přibalováním knihoven poněkud problém, protože zvětšovat kvůli třem použitím dvou tříd výsledné APK o 500 kB se nevyplatí.

Respektujte systémová nastavení


Nicméně tento kód fungoval poměrně dobře a byli jsme s ním nějakou dobu spokojeni. Nedávno se ovšem na Google Play objevil poměrně překvapivý, ale podnětný komentář. Uživatel si v něm "stěžoval" na to, že aplikace ignoruje systémové nastavení pro formát data a času. Po prozkoumání možností nastavení jsem zjistil, že kromě standardního výběru locale se v telefonu opravdu ještě nacházejí volby pro již zmiňované pořadí den-měsíc-rok a pro 12/24hodinový režim času. Mohlo by se zdát, že to je prkotina, kterou se nemá cenu zabývat, ale faktem je, že tato nastavení ovlivňují vzhled dialogů pro výběr data a času napříč systémem. Pokud tedy uživatel má při zadávání data měsíc v prvním sloupci, je rozumné mu ho také následně zobrazit na prvním místě.


Po chvíli googlení se ukázalo, že metody pro získání těchto nastavení se nacházejí ve třídě android.text.format.DateFormat, jejíž metody vracejí instance typu java.text.DateFormat. Wow. Punk is not dead! A to jsem ještě nezmínil druhý způsob, jakým lze zvolený formát data získat - naprosto killer metoda getDateFormatOrder(Context context), jejíž návratový typ je char[]. Ano, uhádli jste správně, opravdu vrací pole tří znaků - ['d', 'M', 'y'] uspořádané podle aktuálního nastavení! Tohle už nelze nazvat jinak než jako retro. :)

Ale vážně, úprava předchozího kódu tím pádem byla naštěstí snadná, jediná výrazná změna byla, že formatter nyní vyžaduje v konstruktoru oblíbený Context. Kromě toho byl také formát pro "datumočas" odstraněn (neexistuje pro něj factory metoda) a formátování je tedy prováděno pro datum a čas samostatně a výsledné stringy se pak jednoduše spojí.
import java.text.DateFormat;

public class DateTimeFormatter {

    private final DateFormat dateFormat;
    private final DateFormat timeFormat;

    public DateTimeFormatter(Context context) {
        this.dateFormat = android.text.format.DateFormat.getDateFormat(context);
        this.timeFormat = android.text.format.DateFormat.getTimeFormat(context);
    }

    public String formatDate(Date date) {
        return dateFormat.format(date);
    }

    public String formatTime(Date time) {
        return timeFormat.format(time);
    }

    public String formatDateTime(Date dateTime) {
        return formatDate(dateTime) + " " + formatTime(dateTime);
    }

    // metody pro Calendar zustavaji stejne
}
Už jen drobností je pak zobrazení dialogů pro výběr času v 12 či 24hodinovém režimu na základě nastavení:
TimePickerDialog timePickerDialog = new TimePickerDialog(context, callBack,
    hourOfDay, minute, android.text.format.DateFormat.is24HourFormat(context));

čtvrtek 20. prosince 2012

public class HelloWorld...?

Stejně tak jako na úvodní hodině algoritmizace nelze v prvním příspěvku blogu jinak než pozdravit svět. Trochu to ovšem kazí fakt, že pozítří má podle všeho svět skončit. A pak, že prý není nikdy pozdě začít s něčím novým. :)

Ať už bude v pátek pršet, sněžit nebo budou padat meteority, alespoň si vyzkouším, jak moc dobře na Bloggeru funguje zvýrazňování zdrojáků pomocí Prettify.

Edit:
Po chvíli pokusů a omylů jsem namísto Prettify nakonec použil Syntax Highlighter, který vypadá o něco lépe a umožňuje jednoduché kopírování kódu. Stačí double-click do rámečku s kódem a pak už jen Ctrl+C. Zároveň se tímto celý blok přepne do módu, kdy je možné provádět výběr na surovém zdrojáku, aniž by se do toho pletla čísla řádek.

public class GoodbyeWorld {

    public static void main(String... args) {
        LocalDate endOfTheWorld = new LocalDate(2012, 12, 21);
        if (LocalDate.now().equals(endOfTheWorld))
            System.out.println("Goodbye World");
    }
}