user=> (def exit (fn [] (. System exit 0)))Brakowało mi tej funkcji w Clojure REPL, bo ^C, czy ^Z + ENTER pachniały mi jakąś niedoróbką. W tak niewielkiej funkcji udało mi się wykorzystać 3 istotne możliwości Clojure - definiowanie zmiennej przez def, której przypisano funkcję anonimową przez fn, która z kolei wykorzystuje integrację Clojure z Javą za pomocą specjalnej formy . (kropka). Ale ze mnie programista, co? :)
#'user/exit
user=> (exit)
Można to jednak lekko podrasować i wykorzystać specjalną formę #(), która również definiuje funkcję anonimową. Owa funkcja anonimowa w niektórych kręgach nazywana jest...domknięciem (ang. closure). Domknięcie w Clojure nie różni się bardzo w porównaniu do tradycyjnego wywołania funkcji, ale tym razem owe wywołanie fn kończy się stworzeniem nowej. Funkcja, która operuje na innych, w tym zwraca je, nazywamy funkcją wyższego poziomu (ang. higher-order function).
Wracając do naszego #() to możnaby powyższe zapisać tak:
user=> (def exit #(. System exit 0))Przeglądając kod źródłowy "Game of Life" Scotta Frasera trafiłem na metodę java.lang.Runtime.availableProcessors(), o której istnieniu nie wiedziałem. W zasadzie o wielu metodach jeszcze nie wiem i wiele z nich wywrze na mnie podobne, pozytywne wrażenie, aczkolwiek ta ma tą zaletę, że utwierdziła mnie w przekonaniu, że mój laptop faktycznie posiada procesor 2-rdzeniowy i 2 wątki powinny działać równolegle (biorąc pod uwagę działanie wszystkich innych programów w tle, pewnie tak nie będzie i jeden z wątków i tak będzie szybszy).
#'user/exit
user=> (exit)
user=> (def available-procs (.. java.lang.Runtime getRuntime availableProcessors))Zadanie dla wytrwałych: Jak zmienić def powyżej, aby możliwe było wykonanie (available-procs), który zwróci 2, tj.
#'user/available-procs
user=> (println available-procs)
2
nil
user=> (available-procs)p.s. Rozpocząłem kolejną ankietę odnośnie języków programowania "Jaki język programowania używasz poza Javą?" (jest na blogu po prawej u góry). Nie ma znaczenia, czy do zabawy, w projekcie, własnej ciekawości, czytasz o nim książkę, czy podobnie. Poświęcasz swój czas, więc wystarczy, aby określić go jako "używasz". Interesuje mnie, czy chociażby rozważasz alternatywę dla Javy i jaki to jest język. Wszystkie języki poza JVM wpadły do kategorii "Inne (poza JVM)". Głosujcie!
2
Odp.:
OdpowiedzUsuń(def available-procs #(.. java.lang.Runtime getRuntime availableProcessors))
Dokładnie tak! Właśnie sobie uzmysłowiłem, więc zadanie uzupełniające: Proszę o wersję krótszą (aż o 10 znaków!) :)
OdpowiedzUsuńjava.lang jest domyślnie zaimportowany więc nie trzeba pisać pełnej nazwy Runtime.
OdpowiedzUsuńStąd też wynika, że wygodnie można tworzyć typy obudowane np. (Integer. "123")
Oczywiście, że Scala :). Czasem jeszcze Python, gdyż studia tego wymagają.
OdpowiedzUsuńTen komentarz został usunięty przez autora.
OdpowiedzUsuńwlaśnie te nawiasy mnie odpychają, z funkcyjnych języków to preferuje Erlanga z kilku powodów:
OdpowiedzUsuń1. bo ma OTP i natywne wsparcie do "clustrów" bez cudów typu gridgain czy teracota
2. bo radzi sobie z większymi obciążeniami niż Java
3. bo jest prosty jak budowa cepa (pare słów kluczowych) a przy tym o wiele czytelniejszy niż wiekszość czysto funkcyjnych języków
4. bo ma wspaniały matching (zarówno na poziomie wyrażeń jaki i funkcji).
co do Clojure, to podobny jest problem jak ze Scalą ... mieszanie kodu funkcyjnego i OO, powoduje że i tak nie wykorzystujemy w pełni FP, kod nie jest pozbawiony side effect bo korzysta bibliotek napisanych w javie. Jak najbardziej funkcyjne konstrukcje w Scali uwielbiam (dodatkowo traits, case clasess + aktorów) podobnie jak w Pythonie i w Rubym, ale moim zdaniem JVM nie nadaje się do czysto funkcyjnego programowania ... bo nie został do tego zaprojektowany.
Mam pytanie do Andrzeja. Czy wykorzystywałeś erlang w komercyjnych rozwiązaniach, czy jest to język, który po prostu chciałeś poznać?
OdpowiedzUsuńjest jakiś powód, dla którego używasz def zamiast defn?
OdpowiedzUsuń@Daniel, super pytanie, które mnie na chwilę wstrzymało zanim znalazłem odpowiedź. def definiuje zmienną globalną (powiedzmy stałą w sensie Javy), a defn definiuje funkcję. Ja chciałem zdefiniować funkcję (exit), więc mogłem wybrać oba podejścia. Po prostu wygodniej mi było z
OdpowiedzUsuń(def exit #(. System exit 0))
zamiast
(defn exit [] (. System exit 0))
Jest krócej o znak. Nie wiem, czy są inne zalety jednego względem drugiego. W pierwszym definiuję domknięcie - funkcję anonimową - a w drugim funkcję nieanonimową. Na chwilę obecną powiedziałbym, że to kwestia gustu.
Ważne jest żeby zrozumieć, że te dwie wersje są równoważne (defn jest makrem, które rozwija się do def; spróbuj zrobić na obu macroexpand-1). Druga jest "kanoniczna" w tym sensie, że przekazuje intencję zdefiniowania nazwanej funkcji (tzn. jak czytasz taki kod, to widzisz od razu, że exit jest czymś, co można wołać). A jeszcze bardziej kanonicznie jest korzystać z cukru składniowego do wołania metod statycznych Javy:
OdpowiedzUsuń(defn exit [] (System/exit))
Pozwolę sobie też sprecyzować różnicę między funkcją anonimową a domknięciem, bo to nie to samo. W Clojure każda funkcja, nawet nazwana, jest (może być) domknięciem; za to istnieją języki, w których ani funkcje anonimowe ani zwykłe nie są pełnoprawnymi domknięciami - OIDP stare wersje Pythona tak miały.
Żeby zrozumieć różnicę, rozważmy następujący kawałek kodu:
(let [tmp (atom 0)]
(defn counter []
(swap! tmp inc)))
Forma let wprowadza tu wiązanie symbolu tmp do pewnej wartości, widoczne *wyłącznie* w ciele tej formy; nie ma sposobu, aby dobrać się do niego z zewnątrz. (Pełna enkapsulacja!) A w ciele let mamy definicję funkcji i w jej obrębie możemy używać tmp. Mówi się, że ta funkcja "domyka się" nad środowiskiem, w którym tmp dowiązane jest do pewnego atomu. (Zahacza to o Clojurowe mechanizmy współbieżności, ale nie trzeba ich znać, żeby zrozumieć to o czym piszę).
W ten sposób zdefiniowaliśmy licznik:
(counter)
1
(counter)
2
(counter)
3
Errata: miało być oczywiście:
OdpowiedzUsuń(defn exit [] (System/exit 0))