20. září 2017

Kniha Clojure Applied

Kniha Clojure Applied je občas na internetu doporučovaná jako "druhá" kniha o Clojure, kterou byste si měli přečíst. Možná jsem se tím nechal po svém návratu ke Clojure podvědomě ovlivnit a tak po "refresh" knize od Carin Meier (o které jsem psal na jaře) jsem sáhnul po Clojure Applied. A musím říct, že spíš než "druhou", bych ji doporučil jako "třetí".

Proč by to měla být 2. Clojure kniha?

Důvody, proč je Clojure Applied doporučovaná jako druhá kniha jsou zřejmé. První kniha nového Clojure adepta bude pravděpodobně nějaký úvod do jazyka. Za sebe bych mohl doporučit tituly Programming Clojure (3rd edition), Living Clojure a The Joy of Clojure (2nd edition). A špatná asi nebude jakákoliv jiná, která pokrývá minimálně verzi 1.6+.

Co v těchto úvodních knihách chybí, je praktický aspekt Clojure:
  • Jak v Clojure designovat rozsáhlejší produkční aplikaci.
  • Jak strukturovat Clojure projekt.
  • Jak vytvářet znovupoužitelné a dobře definované komponenty.
  • Jak z těchto komponent stavět komplexnější řešení a jak komponenty provázat.
  • Datové formáty pro výměnu dat (obligátní JSON, ale hlavně nativní Clojure formát EDN).
  • Konfigurace prostředí.
  • Jak aplikaci/knihovnu distribuovat (tohle je obecné, ne Clojure-specific).

Tyhle témata kniha pokrývá dobře a nováčkovi v Clojure může hodně pomoci.

Proč by to měla být 3. Clojure kniha?

Tohle bude asi dost subjektivní, ale osobně mi některá témata z knihy přišly docela náročná - necítil jsem se na ně ještě připravený. Clojure nedělám na fulltime, takže víc praxe by určitě pomohlo. Občas mi přišlo, že ten skok začátečník-expert je příliš velký a začátečník (po 1. knize) nedoskočí a zůstane "pod útesem".

Nálož je třeba už hned první kapitola, probírající doménový model, která byla hodně hutná. Zamíchání multimethod a protokolů do modelování entit mi nepřišlo úplně šťastné a téma spíš zamlžilo.

Podobně nevhodné mi přišlo uvedení externí knihovny pro validaci Prismatic Schema hned v úvodní kapitole. To by mi sedělo spíš v kapitole o testování. Navíc bych v úvodu ocenil na validaci něco víc "vanilla-Clojure".

Podobný, jen silnější dojem jsem měl ze sekce o Transducers a pipelines. Asi je dobře, že to bylo pohromadě v kapitole nazvané Use Your Cores. Ale musím se přiznat, že tady mne autoři ztratili a budu se muset k transducers a core.async ještě (opakovaně) vrátit.

Měla by to být 2½. Clojure kniha

Celkově mi kniha přišla nevyvážená, co se týče seniority témat. Některá témata byla vyloženě začátečnická - kolekce, sekvence, běžná concurrency (atoms, refs, agents), základní testování (clojure.test), či zakládání projektu na GitHubu. Čekání s těmito tématy až do 3. knihy je tak vlastně činí zbytečnými, jsou příliš esenciální.

Co bych tedy doporučil? Vybrat si jako druhou nějakou Clojure doménovou knihu. Zajímá vás web? Zkuste Web Development with Clojure (2nd edition). Zajímá vás machine learning? Zkuste Clojure for Machine Learning. Zajímá vás data science? Zkuste Clojure for Data Science.

A k tomu můžete po částech přikusovat Clojure Applied.

Co by mělo být v druhé edici?

Knihy stárnou. Technické knihy zastarávají ultra-rychle. Clojure Applied není výjimkou. Co by v potenciální druhé edici této knihy nemělo chybět?

Především velké téma posledního roku a nadcházející verze Clojure 1.9: clojure.spec. Nevím, jestli se nějaké knihy o clojure.spec dočkáme, ale druhá verze Clojure Applied by byla vhodným místem pro toto téma (se simultánní náhradou kapitoly o test.check).

clojure.spec by mi taky sedlo jako dobrá náhrada za již zmiňovanou knihovnu Prismatic Schema (samozřejmě separátně od úvodní doménové kapitoly).

Související články


11. července 2017

Clojure web development: Ring Middleware

V minulém článku jsme se podívali na úplně nejzákladnější základy webového vývoje v Clojure - jak zpracovat HTTP request a response pomocí knihovny Ring. Tu nejzajímavější část Ringu - Middleware - jsme ale zmínili jen letmo a byla by škoda se do tohoto zajímavého konceptu trochu více neponořit.

Připomenutí základních konceptů

Než zrychlíme z 0 na 100, připomenu čtyři základní komponenty Ringu:
  • Handler - funkce, která přijímá mapu reprezentující HTTP request a vrací mapu představující HTTP response.
  • Request - mapa reprezentující HTTP request, která obsahuje sadu "standardních" klíčů, které nám budou povědomé ze Servletu: :server-port, :server-name, :remote-address, :request-method ad.
  • Response - mapa, představující HTTP response, která obsahuje tři klíče: :status, :header, :body.
  • Middleware - funkce, která handleru přidá dodatečné funkcionality. Věci jako session, cookies, či parametry jsou řešené middlewarem.

Anatomie Middlewaru

Middleware je funkce vyššího řádu, která přijímá jako parameter handler a vrací jiný handler (jako anonymní funkci), který nějakým způsobem obohatil buď request, nebo response. V posdtatě se dá na middleware nahlížet jako na takový funkcionální decorator.

Pokud se podíváme na obecnou podobu middlewaru, má často následující strukturu (kudos to StackOverflow):


Balení middlewaru

Middleware většinou bývá jenom velmi jednoduchá funkce, takže komplexnější chování dostaneme zřetězením jednotlivých middlewarů a to tak, že je postupně zabalujeme do sebe:


Způsob, jak middlewary do sebe zabalit je dvojí - buď klasické zanořené funkce, nebo častější způsob je pomocí thread-first makra (->):


A jen pro úplnost - zabalit se anglicky řekne "wrap", proto se používá konvence, že middlewary začínají prefixem wrap-.

Ilustrační příklad

Řekněme, že bysme psali RESTovou službu, která přijímá data pomocí PUT. Pomineme funkční logiku, zpracování dat i routování a budeme se soustředit jen na middleware, který se bude chovat následovně:
  • Pokud request metoda není PUT, vrátí middleware status 405 a text "Method Not Allowed".
  • Pokud je request metoda PUT, vrátí status 204 (No Content) a prázdné body.

Aby middlewary byly malé a znovupoužitelné, rozdělíme si každou funkčnost, popsanou v předešlých odrážkách, do samostatné funkce:
  • wrap-no-content bude vracet status 204 a prázdné body.
  • wrap-put-allowed vrátí buď 405 (a popis v body), pokud metoda není PUT, nebo jenom zavolá původní handler.


Teď už stačí jen dát tyto dva middlewary dohromady, abychom dostali požadované chování:


GitHub projekt

Pokud vám výše popsané principy neštymují dohromady, mrkněte na GitHub, kde je malý spustitelný projektík, plus pár middleware unit testů:

Co příště?

O Ringu a jeho middlewarech by se dalo psát ještě dlouho, ale je čas pokročit. Logicky následným tématem je Compojure - routovací knihovna pro Ring webové aplikace.

Související články


1. května 2017

Clojure web development: Ring

Webový vývoj v Clojure je dobře etablovaný. Nebylo by to ale Clojure, kdyby si věci nedělalo trochu po svém. A tak nabízí, místo rozsáhlých aplikačních frameworků, množinu knihoven, které se dají pospojovat dohromady. Trochu to připomíná unixovou filozofii - malé, jednoúčelové prográmky, které lze propojovat do komplexnějších řešení.

Když jde o web, tak jde v první řadě o HTTP. Clojure na to jde od podlahy a jeho odpovědí je Ring - "Clojure HTTP server abstraction". Možná teď nebudu úplně přesný: Ring je Clojure implementací abstrakcí HTTP protokolu a zároveň je částečně kompatibilní s Java Servlety.

Ring vládne čtyřem komponentám

Každá Ring aplikace se skládá ze čtyř základních částí:
  • Handler - funkce, která přijímá mapu reprezentující HTTP request a vrací mapu představující HTTP response.
  • Request - mapa reprezentující HTTP request, která obsahuje sadu "standardních" klíčů, které nám budou povědomé ze Servletu: :server-port, :server-name, :remote-address, :request-method ad.
  • Response - mapa, představující HTTP response, která obsahuje tři klíče: :status, :header, :body.
  • Middleware - funkce, která handleru přidá dodatečné funkcionality. Věci jako session, cookies, či parametry jsou řešené middlewarem.

Ring Hello, world!

Začneme tradičně, vytvořením Leiningen projektu:
$ lein new blog-ring
a v souboru project.clj doplníme dependency na Ring:


Dále upravíme soubor src/blog_ring.core.clj, aby obsahoval náš Hello, world handler. Přidáme také funkci -main, abychom mohli aplikaci rovnou spustit:


Aplikaci spustíme příkazem:
$ lein run
a můžeme si ji prohlédnout v browseru na URL http://localhost:3000.

Request

Práce s requestem je přímočará - request je v Ringu prezentován mapou, takže stačí pomocí klíče vytáhnout požadovanou hodnotu a nějak ji zpracovat.


Předešlý handler nám vrátí následující stránku:

Response

V hello world příkladu jsme si celou response mapu sestavili sami. Ring nabízí řadu funkcí, soustředěných v namespace ring.util.response, které práci s response usnadňují:


Middleware

Tak, to bylo triviální. Co si teď střihnout nějaký middlewérek? Middleware je, podle definice, "funkce vyššího řádu, která přidává handleru dodatečné funkcionality. Prvním argumentem midddleware funkce je handler a její návratovou hodnotou je nový handler, který bude volat handler původní."

Seznam standard middleware funkcí se dá najít na wiki stránce Ringu, nebo na stránce API (všechno co je ring.middleware).

Pro potřebu článku jsem si vybral dva middlewary:
  • wrap-params, který rozparsuje parametry z formuláře a query stringu a přidá do requestu klíče :query-params, :form-params a :params
  • wrap-keyword-params, který zkonvertuje stringové klíče v mapě :params na keyword klíče (protože všichni přece máme rádi keyword klíče v Clojure mapách).
Middleware funkce se dají dobře testovat pomocí funkce identity, kdy není potřeba funkci podstrčit celý handler a následně request, ale jen část (request) mapy, která nás z hlediska middlewaru zajímá.

Tady je ukázka, co s requestem dělají uvedené funkce wrap-params a wrap-keyword-params:


Jak naznačuje definice, midddlewary jsou navržený tak, aby šly pospojovat za sebe. Je to v podstatě analogie Java (servlet) filtrů. Jak middlewary zřetězíme? Můžeme funkce jednoduše zanořit do sebe. Ale čitelnější, i používanější, je použití thread-first makra:


Kompletní kruh

Tak, a jsme na konci. Probrali jsme všechny čtyři komponenty, ze kterých se Ring skládá - handler, request, response a middleware. Pokdu je všechny spojíme do jednoduché, spustitelné aplikace, může to vypadat takto:


A výsledek v browseru:

Co příště?

Můj původní záměr o příštím článku bylo napsat o routování pomocí Compojure. Když jsem se ale ponořil do psaní sekce o middleware a připravoval si příklady, začalo se tohle pod-téma dost rozrůstat. Middleware je hodně zajímavý koncept - jednoduchý a velmi silný - a proto by bylo škoda se mu nevěnovat samostatně.

GitHub projekt

Pokud vám výše popsané principy neštymují dohromady, mrkněte na GitHub, kde je malý spustitelný projektík:

Související články

5. dubna 2017

Kniha Living Clojure

Jsem beznadějný čtenář knih. Musím denně číst a ačkoliv jsou dnes k dispozici tuny textů v různých formách, mě nejvíc vyhovují knihy.

Jedinou úlitbou modernosti je, že pro odbornou literaturu mi více vyhovují ty digitální. Přece jenom - do Kindlu se toho vejde kupa a zastaralé vydání vám nepřekáží v reálném světě.

Back to the Future

Ani nevím, co mě to cvrnklo přes nos, ale po pětileté přestávce se vracím ke Clojure. Pět let je v softwarovém inženýrství dlouhá doba. Technologie můžou zaniknout, či se nečekaně rozvinout, nebo nabrat úplně jiný směr. A těší mě, že Clojure stále rozkvétá.

Říkal jsem si - jak se do toho zase dostat? No dobře, je to řečnická otázka. Spíš jsem si říkal, kterou knihu si vybrat? Když jsem s Clojure začínal, byla na trhu jediná kniha - Programming Clojure od @StuartHalloway. Byla to tehdy bible Clojure. A možná ještě je - letos vyjde třetí edice!

Udělal jsem si seznam a jako první volbu jsem hodně zvažoval oceňované Joy of Clojure, jejíž první edici jsem kdysi nedočetl. Nakonec ale převážila náhoda. (Mimochodem, kdyby vás ten seznam zajímal, najdete ho u mne na Goodreads. Nebo ve webové verzi zaskrolujte po pravé straně dolů.)

Gigasquid!

Že jsem zase vlítnul do Clojure je více méně náhoda. Nebo osud. Minulý podzim jsem se byl po čase podívat na Java konferenci - český GeeCON - a tam jsem potkal Carin Meier, aka @Gigasquid. (Už jsem o tom psal na svém druhém blogu - GeeCON Prague 2016, den 2.)

Carin stojí za to sledovat. Kromě výše a níže zmíněné knihy, pracuje v Cognitectu. Ano, v té firmě, kde se koncentruje Clojure smetánka (posuďte sami), včetně polo-boha Riche Hickeyho.

Pokud budete mít možnost, určitě si s Carin popovídejte. Zvládl jsem to i já, zavilý introvert. Zakecal jsem se s ní tak moc, až jsem v podstatě prošvihnul následnou přednášku.

Alice in Wanderland

No dobře, to bylo keců... co ta kniha? Je dobrá, dal jsem jí na Goodreads 5 hvězd. Samozřejmě, v daném kontextu - je to dobrá kniha pro začátečníky, nebo pro někoho, kdo si potřebuje po čase Clojure oživit.

Kniha prochází všechny základní témata, která byste v učebnici čekali a skládá je v rozumném pořadí:
  • Kolekce a sekvence
  • Funkcionální programování
  • Stav a konkurence
  • Java interoperabilita
  • Leiningen a Clojure projekt
  • Asynchronni komunikace s core.async
  • Clojure web applikace
  • Makra
Napsáno pěkně, stručně, přehledně. Kromě toho, ale přináší druhá část knihy tréninkový program: Clojure v 7 týdnech.

Jak píše Carin v úvodu, učení se nového jazyka má silné paralely, jako když chcete zčista jasna začít běhat: důležitá je motivace, pravidelnost a... nepřetrénovat se.

Ohledně běhání, na webu je dnes spousta tréninkových plánů od 5 kilometrů až po maraton. Carin k tomu přistupuje obdobně - začít zvolna a postupně navyšovat objemy a obtížnost.

Prvních pár týdnů jde o řešení problémů na 4clojure. Pak se přesuneme na GitHub, kde má Carin několik kata, inspirovaných příběhem Alenky v říši divů: wonderland-clojure-katas. Poslední týden je pak věnovaný deploymentu Clojure aplikace na Heroku.

A je to! Kniha je přečtená a vy jste fluent v Clojure :-)  No, tak jednoduché to není. Ale je to dobrý začátek. Jeden z možných.

25. března 2017

Clojure concurrency: Vars

Významnou vlastností Clojure jsou neměnitelné datové struktury. Je to taková dvojsečná vlastnost (i když benefity výrazně převažují). Na jednu stranu to vývoj zjednodušuje, protože se nemusíme bát, že se nám data změní pod rukama.

Nadruhou stranu vyvstává otázka, jak s neměnitelnými daty pracovat - nic v našem světě není neměnné (a všechno jednou pomine). Jak tedy Clojure řeší změnu stavu a s tím související konkurenci?

K dispozici jsou čtyři mechanizmy, jak měnit stav:
  • Vars jako globální úložiště, pro eventuální per-thread změny.
  • Atoms pro synchronní, nekoordinované změny.
  • Refs pro synchronní, v transakci koordinované změny.
  • Agents pro asynchronní, nekoordinované změny.
Postupně bych se chtěl podívat na všechny způsoby, ale dnes začneme tím nejjednodušším, čemu se žádný začátečník v Clojure nevyhne.

Vars

Var je způsob, jakým Clojure odkazuje na úložiště. Toto úložiště obsahuje "standardní", neměnná data. Var, ale můžeme dočasně přesměrovat na jiné úložiště. Podstatnou vlastností je, že toto dočasné přesměrování - binding - je vidět jenom v rámci aktuálního vlákna, ostatní vlákna vidí pořád původní hodnotu.

Statické Vars

Klasické Var se vytvoří speciálním formem def: (def x 42). Takto definováno, je Var statické - pokud bychom ho chtěli svázat s jiným úložištěm, vyhodí Clojure výjimku IllegalStateException.


Dynamické Vars

Pokud chceme, aby Var ukazovalo na jiné úložiště, musíme ho explicitně definovat jako dynamické pomocí instrukce ^:dynamic. Samotné přesměrování se provádí makrem binding. Po dobu trvání bloku binding směřuje Var na nové úložiště, aby se po jeho skončení vrátilo ke své původní hodnotě.

Dle konvence se dynamické Vars uzavírají do *earmuffs*. Označují se tak věci, určené pro re-binding.


Vars jsou globální

Vars jsou globální - viditelné pro všechny thready. Pokud tedy vytvoříme Var v jiném vlákně, je dosažitelné také ze všech ostatních vláken.

V následujícím příkladu vytvoříme Var v jiném vlákně pomocí makra future:


Namespace

V rámci namespace, v němž byly vytvořeny, jsou Vars přístupné svým názvem. Pokud se přepneme do jiného namespacu, je potřeba na Var odkazovat plně kvalifikovaným názvem, nebo ho "naimportovat" z původního namespace pomocí funkce refer.


Funkce jsou také Vars

Ve Vars se neukládají jenom data, ale také funkce. Všechno výše napsané tedy platí pro funkce úplně stejně, včetně toho, že je můžeme dynamicky re-bindovat.

To může být zajímavý mechanismus, jak v runtimu dynamicky měnit chování funkcí. Nicméně, užívejte s mírou - jazyk to umožňuje, ale je to výjmečné řešení.


A kde je ta konkurence?

Se samotnými Vars si moc konkurentního programování neužijeme. Je to ale základní stavební kámen, od kterého se odvíjejí ostatní způsoby. Konec konců, všechny v úvodu zmíněné mechanizmy - Atomy, Refs a Agenti - jsou ve výsledku uloženy ve Vars.

GitHub projekt

Clojure concurrency chci pojmout jako mini-seriál (viz Související články níže), který bude podložený projektem na GitHubu, kam budou postupně přibývat jednotlivé příklady:

Související články

  • TBD: Clojure concurrency: Atoms
  • TBD: Clojure concurrency: Refs
  • TBD: Clojure concurrency: Agents

Starší související články

18. března 2017

Catalanova čísla a syntax highlighting

Je to téměř 5 let, co jsem naposled napsal něco na tento blog. Ale doufám, že se to teď změní - moje láska ke Clojure opět propukla s neztenčenou silou a tak snad ponese nějaké užitečné ovoce.

Chvilku ale potrvá, než uvedu blog do použitelného stavu, takže začnu takovým zahřívacím tématem. Tedy, dvěma tématy - sice to není úplně fér, ale vrazil jsem do titulku dva nesouvisející motivy.

Syntax highlighting 3.0

Když jsem s Clojure před šesti lety začínal, bylo ve verzi 1.2 a jeho podpora v různých nástrojích byla nízká, nebo žádná. Jednu z věcí, které jsem tehdy řešil, bylo jak zobrazit na blogu syntax highlighting.

Moje první volba padla na tehdy všudypřítomný SyntaxHighlighter. Musel jsem použít jakýsi externí Clojure brush, protože SyntaxHighlighter dodnes nemá nativní podporu Clojure.

S výsledkem jsem nebyl spokojen, takže jsem po čase zvolil nové řešení - jako letitý uživatel famózního editoru Vim, jsem se rozhodl pro trochu ruční práce a zaangažoval vimovskou funkci :TOhtml. S tou jsem maximálně spokojen a používám ji i na svém druhém blogu SoftWare Samuraj. Přesto jsem se rozhodl potřetí pro změnu syntax highlightingu.

Ten třetí důvod má konsekvence s oživením blogu. Aniž bych si tady vylíval srdíčko, jeden z důsledků mých aktuálních rozhodnutí je, vybudovat si na svém GitHubu jakési Clojure portfolio (už jsem o tom kdysi psal).

Volba GitHubu pro mne nebyla úplně přímočará - nejsem "git guy" a pokud můžu, preferuji Mercurial. Pravdou ale je, že veškerá Clojure komunita je GitHubu, tak proč se tomu vzpírat. No, a když GitHub, tak Gist, to dá rozum.

Catalanova čísla

V rámci svého Clojure-zmrtvýchvstání jsem procházel své staré Clojure kódy a narazil jsem na jeden, který se mi líbil - funkce pro výpočet Catalanových čísel. Pikantní je, že si po těch pěti letech nepamatuju, proč jsem to napsal, nicméně funkce se mi líbily natolik, že jsem se rozhodl se o ně - v rámci zahřátí na provozní teplotu - podělit.

Catalanova čísla jsou sekvencí přirozených čísel, která má zajímavé využití v kombinatorice - binární stromy, průchod mřížkou(?), rozdělení polygonu na trojúhelníky ad. Sekvence je definována následujícím vztahem:


Pokud, tak jako já, nejste úplně kovaní v matematice, tak chviličku googlování potrvá, než zjistíte, že pro implementaci tohoto vzorečku budete potřebovat kombinační číslo (binomial coefficient) a tím pádem faktoriál. Pak už je jednoduché poskládat tyto základní funkce do sebe, jako matrojšku:


Když už jsem se v tom tak vrtal, říkal jsem si: nedá se to udělat nějak jednodušeji, efektněji, víc "Clojure"? A dá. Jen je potřeba vyjít z jiného vzorečku, který definuje Catalanova čísla rekurzivně:
Při použití posledního vzorečku nám tak vyjde pěkná Clojure rekurze. Klíčová slova jsou speciální operátory loop a recur:


GitHub project

Pokud si chcete pohrát s Catalanovými čísly trochu víc, anebo dostat malý bonus navíc, podívejte se na můj projekt na GitHubu:
Navíc dostanete Leiningen projekt, testy v Midje a lazy sekvenci Catalanových čísel.

Související články

5. května 2012

Map a reduce, funkcionální elegance

Na svém druhém blogu o softwarovém inženýrství jsem se pustil do kratičké sérieHadoopu - Java implementaci MapReduce. Protože tento algoritmus je inspirován dvěma funkcemi pocházejícími z funkcionálního programování - map a reduce - podíval bych se trochu blíže, jak tyto funkce vypadají v Clojure.

Map

Začněme výpisem dokumentace a odkazem na referenci:

Dokumentace funkce map
Jak je vidět, funkce map přijímá v nejjednodušší podobě dva parametry - funkci a kolekci. Na každou položku kolekce pak aplikuje danou funkci.
(map inc '(1 2 3))
;=> (2 3 4)
(map #(* % %) '(1 2 3))
;=> (1 4 9)
(map #(.toUpperCase %) '("a" "b" "c"))
;=> ("A" "B" "C")
Pokud v argumentech po funkci následuje více kolekcí, je funkce postupně aplikována na všechny první položky, druhé položky atd., dokud v jedné z kolekcí nedojde počet položek.
(map str '(1 2 3 4) '("a" "b" "c"))
;=> ("1a" "2b" "3c")

; takes the first column
(map first [[:a1 :a2 :a3]
            [:b1 :b2 :b3]
            [:c1 :c2 :c3]])
;=> (:a1 :b1 :c1)

; removes the first column
(map rest [[:a1 :a2 :a3]
           [:b1 :b2 :b3]
           [:c1 :c2 :c3]])
;=> ((:a2 :a3)
;    (:b2 :b3)
;    (:c2 :c3))

Reduce

Jestliže u funkce map vkládáme do funkce kolekci a výsledkem je opět (nějak zpracovaná) kolekce, u funkce reduce sice také vkládáme do funkce kolekci, ale výsledkem je jedna hodnota, objekt, entita. Algoritmus je následující: funkce reduce vezme úvodní hodotu a první hodnotu z kolekce a aplikuje na ně danou funkci, pak vezme výsledek a druhou hodnotu z kolekce a aplikuje na ně danou funkci. Takto iteruje až do konce kolekce.

Dokumentace funkce reduce
Pokud funkce neobsahuje úvodní hodnotu, je první aplikace funkce na první dvě hodnoty kolekce. Zbytek už je stejný.
(reduce * '(3 4 5))
;=> 60
(reduce * 2 '(3 4 5))
;=> 120
(reduce + (range 1 1000001))
;=> 500000500000
(reduce max '(7 4 -3 42 -6 12))
;=> 42
(reduce conj [1 2] [3 4 5])
;=> [1 2 3 4 5]

Příklad

Pojďme si teď napsat praktický příklad, na kterém si obě funkce vyzkoušíme a to i s postupným výkladem, jak jsme se k jejich potřebě dopracovali. Dejme tomu, že chci mít funkci, která mi vrací SHA-1 hash daného řetěze. Něco jako:
(sha1 "Sometimes Clojure")
;=> "339e36ff40c581cebf3bec542c9a699a4ffbb453"
Pro výpočet hashe (digestu) použijeme Java třídu MessageDigest. Naše funkce by mohla vypadat nějak takhle:
(import java.security.MessageDigest)

(defn digest [string]
  (.digest (MessageDigest/getInstance "SHA-1")
           (.getBytes string)))

(digest "Sometimes Clojure")
;=> #<byte[] [B@36d6526d>
Metoda/funkce digest (ta naše i ta Javovská) vrací pole bytů (binární řetězec). Můžeme si je prohlédnou např. příkazem:
(reduce conj [] (digest "Sometimes Clojure"))
;=> [51 -98 54 -1 64 -59 -127 -50 -65 59
;   -20 84 44 -102 105 -102 79 -5 -76 83]
(apply vector (digest "Sometimes Clojure"))
;=> [51 -98 54 -1 64 -59 -127 -50 -65 59
;   -20 84 44 -102 105 -102 79 -5 -76 83]
Pokud bychom pole bytů převedli na řetězec, nebude to asi to, co bychom čekali:
(String. (digest "Sometimes Clojure"))
;=> "3�6�@Łο;�T,�i�O�"
Napíšeme si proto pomocnou funkci, která převede byte na hexadecimální hodnotu:
(defn hex [bt]
  (if (< (bit-and 0xff bt) 0x10)
    (str 0 (Integer/toHexString (bit-and 0xff bt)))
    (Integer/toHexString (bit-and 0xff bt))))
Nyní již můžeme použít funkci map, abychom získali hash v podobě hexadecimálního pole:
(map hex (digest "Sometimes Clojure"))
;=> ("33" "9e" "36" "ff" "40" "c5" "81" "ce" "bf" "3b"
;    "ec" "54" "2c" "9a" "69" "9a" "4f" "fb" "b4" "53")
No a samozřejmě, že cílem je, mít hash jako řetězec. K tomu nám dopomůže právě funkce reduce:
(reduce str (map hex (digest "Sometimes Clojure")))
;=> "339e36ff40c581cebf3bec542c9a699a4ffbb453"
Celý kód pak vypadá takto:
(ns blog-map-reduce.core
  (:import java.security.MessageDigest))

(defn digest [string]
  "Returns a SHA-1 digest of the given string."
  (.digest (MessageDigest/getInstance "SHA-1")
           (.getBytes string)))

(defn hex [bt]
  "Returns a hexadecimal value of the given byte."
  (if (< (bit-and 0xff bt) 0x10)
    (str 0 (Integer/toHexString (bit-and 0xff bt)))
    (Integer/toHexString (bit-and 0xff bt))))

(defn sha1 [string]
  "Returns a SHA-1 hash of the given string."
  (reduce str (map hex (digest string))))
S funkcemi si pak můžeme hrát ještě dál. Můžeme např. mít pole řetězců a budeme chtít vrátit pole hashů:
(map sha1 '("Sometimes Clojure" "Guido"))
;=> ("339e36ff40c581cebf3bec542c9a699a4ffbb453"
;    "28aeafc90fae0f3318d4bdf5b1a59e5dc506b50a")
A nebo můžeme jít ještě dál a chtít jako výsledek mapu, kde hash bude klíčem k původnímu řetezci. Hash-klíče navíc převedeme, kvůli performance, na Clojure keywords:
(defn sha1-map [strings]
  "Returns a map of SHA-1 hashes as a keyword keys
   and given string as a value."
  (zipmap (map keyword (map sha1 strings)) strings))

(sha1-map '("Sometimes Clojure" "Guido"))
;=> {:28aeafc90fae0f3318d4bdf5b1a59e5dc506b50a
;        "Guido",
;    :339e36ff40c581cebf3bec542c9a699a4ffbb453
;        "Sometimes Clojure"}

Závěr

V nadpisu jsem zmiňoval (funkcionální) eleganci. Pro vyznavače Clojure a Lispu je to samozřejmě nošením sov do Athén. Nicméně i člověk nezasažený Lispovskou syntaxí a funkcionálním programováním by mohl uznat, že uvedené ukázky jsou elegantní a pokud si za Lisp/Clojure dosadíme pseudo-kód, tak i čitelné a pochopitelné.

Jen pro představu, jak by vypadal výše uvedený kód v jiném jazyce, jsem přepsal funkci sha1-map do Groovy. Při srovnání bych řekl, že kód v Groovy je "ukecanější" a méně flexibilnější než ten v Clojure. To samozřejmě není nic proti tomu, pokud bychom to srovnávali s Javou - Groovy má alespoň metodu each, která přijímá closure.
import java.security.MessageDigest

def digest(string) {
    def md = MessageDigest.getInstance("SHA-1")
    md.digest(string.getBytes())
}

def hex(bt) {
    if ((0xff & bt) < 0x10) {
        "0" + Integer.toHexString(0xff & bt)
    } else {
        Integer.toHexString(0xff & bt)
    }
}

def sha1(string) {
    def result = new StringBuilder()

    digest(string).each {
        result.append(hex(it))
    }

    result
}

def sha1Map(strings) {
    def result = new HashMap()

    strings.each {
        result.put(sha1(it), it)
    }

    result
}

sha1Map(["Sometimes Groovy", "Guido"])
// [28aeafc90fae0f3318d4bdf5b1a59e5dc506b50a :
//      Guido,
//  ac0416ec9a83cd93a5f5a2aa5e6a452cd33840db :
//      Sometimes Groovy]