"The Grid. A digital frontier. I tried to picture clusters of information as they move through the computer. What did they look like? Ships? Motorcycles? Were the circuits like freeways? I kept dreaming of a world I thought I’d never see. And then, one day, I got in." — Tron: Legacy

2012-12-20

Clojure z Emacs i Leiningen 2


"Leiningen is for automating Clojure projects without setting your hair on fire."
- czyli jak krok po kroku przygotować Emacsa do współpracy z Clojure i Leiningen. Podam też sposób jad dołączać własne biblioteki do projektu przy użyciu Maven.


Instalacja będzie prowadzona na systemie Ubuntu 12.10 x64. Jeżeli ktoś zauważy jakieś niezgodności, to proszę o komentarz.

Leinngen jest menedżerem pakietów dla projektów Clojure.  Ma on ułatwić pracę z zależnościami. Jednak nie ma tak pięknie jak można by się spodziewać na pierwszy rzut oka. Jeżeli pakietów nie ma w repozytorium Clojure, to trzeba stworzyć sobie własne. O tym na końcu.

Krok 1: Instalacja Emacs 24.x  i Leiningen 2

Najprościej jest to zrobić z terminala tymi poleceniami:

sudo apt-get install emacs24
wget https://raw.github.com/technomancy/leiningen/preview/bin/lein
chmod 755 lein
sudo mv lein /usr/bin
lein self-install


Krok 2: Konfiguracja Emacs 

Poleceniem z konsoli uruchmić:

emacs ~/.emacs.d/init.el

Wkleić to i zapisać:

(require 'package)
(add-to-list 'package-archives
             '("marmalade" . "http://marmalade-repo.org/packages/"))
(package-initialize)

Potem (M-x to klawisz Alt+x, po wciśnięciu wpisać resztę polecenia):

M-x eval-buffer

Krok 3: Instalacja wtyczki clojure-mode

Teraz trzeba załadować listę pakietów. Nie wiem dlaczego tak, ale sposób podany na Github u mnie nie zadziałał, więc metodą prób i błędów tak mi się udało.

Trzeba wyświetlić listę pakietów poleceniem:

M-x package-list-packages 

Znaleźć pakiet clojure-mode. Kliknąć [Install] i zamknąć Emacs.

Krok 4: Przykładowy projekt

Utworzyć projekt poleceniem w terminalu:

lein new clojure-projekt1

Następnie wejść do katalogu projektu i edytować plik konfiguracji project.clj. Emacs by widzieć projekt musi być uruchamiany z katalogu głównego projektu:

cd clojure-projekt1
emacs project.clj

Wkleić tę linijkę:

:plugins [[lein-swank "1.4.4"]]

nad linijkę z :dependencies

W Emacs uruchomić serwer Swank poleceniem:

M-x clojure-jack-in

I voila ;). Powinien się pokazać REPL.

Przykładowe polecenia przydatne przy współpracy z Emacs i SLIME (C - Ctrl, M - Alt):



  • M-. : Skok do definicji var-a
  • M-TAB lub C-c TAB: Autouzupełnianie symbolu w miejscu kursora
  • C-x C-e: Wykonaj wyrażenie pod kursorem
  • C-c C-k: Skompiluj bieżący burfor
  • C-c C-l: Załaduj bieżący bufor i wymuś przeładowanie wymaganych przestrzeni nazw
  • C-M-x: Skompiluj najbardziej zewnętrzne wyrażenie pod kursorem
  • C-c S-i: Podejrzyj wartość
  • C-c C-m: Wykonaj macroexpand dla wyrażenia pod kursorem
  • C-c C-d C-d: Przeszukaj dokumentacje dla var-a
  • C-c C-z: Przełącz się pomiędzy buforem Clojure a REPL
  • C-c M-p: Zmien przestrzeń nazw w REPL dla odpowiedniej z bieżącego bufora
  • C-c C-w c: Wyświetl funkcje wywołujące bieżącą funkcję



  • Dodawanie własnych bibliotek do projektu

    Najpierw trzeba zainstalować Maven2:

    sudo apt-get install maven2

    Następnie trzeba mieć jakąś bibliotekę w bieżącym katalogu, np.  ormlite-core-4.42.jar.
    Poleceniem poniżej dodaje się taką bibliotekę do lokalnego repozytorium mavena:



    mvn install:install-file \
     -Dfile=ormlite-core-4.42.jar \
     -DgroupId=self \
     -DartifactId=ormlite-core \
     -Dversion=4.42 \
     -Dpackaging=jar \
     -DgeneratePom=true



    Po pozytywnym przetworzeniu powinien pojawić się napis BUILD SUCCESSFUL. Następnie do pliku projektu w sekcji :dependencies trzeba dodać linijkę i zapisać:

    [self/ormlite-core "4.42"]

    Następnie w katalogu projektu w terminalu dać polecenie:

    lein deps

    I to wszystko. ;)




    2012-12-17

    Light Table : Factorial fun

    Niedawno na kickstarter.com wystartował projekt IDE dla języka Clojure przez człowieka o nazwisku Chris Granger. Dzisiaj zaprezentuję działanie części edytora: Instarepl. Jest to moduł, w którym można testować kod. Ma tę przewagę nad innymi REPL-ami, że widać wyniki działania poszczególnych poleceń. Nie tylko po wykonaniu polecenia, ale też aktualizację innych w historii jeżeli wcześniejszy kod coś pozmienia. Jak dla mnie bajka. Dawno czegoś takiego nie widziałem. Ekran Instarepl jest podzielony na dwa obszary. Z lewej strony mamy edytor kodu, z prawej kod, i wyniki działanaia. W wynikach przez ciała funkcji zamiast symboli przewijają się dane i widać jak przechodzą przez kolejne polecenia. W przypadku pętli zauważyłem, że widać tylko ostatnią iterację.

    Light table można pobrać z tej strony. Wymagane jest JDK Javy. Przy pierwszym uruchomieniu program pobiera niezbędne biblioteki. Proces ten trwa dość długo, więc nie należy się przerażać, że aplikacja "wisi". Najlepiej za pierwszym razem uruchomić w konsoli poleceniem "java -jar launcher.jar",  bo będzie widać przebieg instalacji. Program także doinstalowuje biblioteki przy przejściu do części projektowej : Table.

    Widok główny Light Table


    Widok Instarepl

    Jak widać na zrzucie ekranu Instarepl wyjście funkcji, które piszą do standardowego wyjścia (terminal/konsola) umieszczane jest na końcu bloku wyników pod linijką "Output:".

    Część Table wygląda tak:


    Z lewej pokazują się bloki funkcji, które można edytować. Polecam zapoznać się ze skrótami klawiszowymi, bo w widoku nie ma żadnych przycisków, czy menu z komendami.

    Z prawej strony ekranu na górze w lewym okienku jest widok przestrzeni nazw. Każda przestrzeń nazw zawiera funkcje, które wyświetlają się w prawym okienku. Niżej na czerwono jest brudnopis do testowania kodu, a jeszcze niżej w zielonym polu pojawiają się wyniki działania funkcji z brudnopisu lub programu. Widać, że autor wzorował się na browserze kodu Smalltalka. Według mnie to dobre rozwiązanie, a przydałaby się jeszcze możliwość podziału funkcji na własne kategorie.

    Dodam, że to IDE jest w wersji alpha, więc zawiera bugi i nie jest tak rozbudowane jak można by sobie życzyć (np. brak refaktoringu).  Jednak w porównaniu do wtyczek Clojure do Eclipse czy Intellij IDEA program ten to ogromny krok naprzód i z przyjemnością się z nim pracuje.

    Na koniec załączam gwóźdź programu, czyli film z testowania Instarepl, na przykładzie realizacji funkcji liczącej silnię z testami ze strony 4Clojure.


    Pod koniec filmu mam małą przywieszkę z tego względu, że nie zauważyłem błędu w funkcji, a środowisko czasem lubi się przywiesić. Np. w wyniku edycji funkcji, program  wpadnie w nieskończoną pętlę. Ten fakt można poznać po zwiększonym zużyciu procesora, lagowaniu interfejsu i braku reakcji na zmiany kodu (factorial3 zawsze pokazywał 1). To mnie wprowadziło w błąd.

    Projekt Light Table jest bardzo ciekawy, więc mam nadzieję, że nie tylko mnie przypadnie do gustu. :)

    2012-12-11

    Smalltalk : Seaside z użyciem Pharo, cz.3

    (c) Robert Tinney

    Mam już schemat jak wyciągnąć komentarze z dowolnego bloga, to teraz czas na część wizualną. Tak jak poprzednio, najpierw z grubsza opiszę poszczególne kroki. Film z podsumowaniem dam na końcu. Jako, że film byłby dość długi gdyby pokazywać tworzenie kodu to pokażę w nim tylko jak wygląda struktura pakietu i kod. Z poprzedniej części mam już w zasadzie napisaną podstawę pojedynczej funkcji, która odczyta komentarze z podanego adresu URL i zwróci tablicę komentarzy. Najpierw jednak opiszę jak utworzyć komponent w Seaside.
    Otwieram System Browser i zaczynam od utworzenia kategorii (paczki obiektów). Prawy myszki na liście kategorii i klikam "Add category...". Jako, że tworzę pod portal dobreprogramy.pl, utworzę kategorię DobreprogramyAPI.

    Tworzenie komponentu głównego

    Ten komponent będzie odpowiadać za zawartość strony głównej. To wszystko o czym będę pisać jest także dostępne dostępne w dokumentacji Seaside.. Kasą główną komponentów jest klasa WAComponent. Po niej odziedziczę funkcjonalność na potrzeby mojej nowej klasy "DPRoot":

    WAComponent subclass: #DPRoot
        instanceVariableNames: ''
        classVariableNames: ''
        poolDictionaries: ''
        category: 'DobreprogramyAPI'

    Pierwsze co trzeba zrobić, to utworzyć metodę "initialize":

    initialize
       super initialize.

    Metoda "initialize" jest metodą wywoływaną zaraz po utworzeniu obiektu, m.in. po wywołaniu metody "new". Dobrą praktyką jest wywołać metodę "initialize" obiektu nadrzędnego za pomocą metody "super". Zielona strzałka do góry, która się pokaże obok nazwy metody mówi, że moja metoda nadpisuje metodę z klasy nadrzędnej.
    Kolejnym krokiem będzie utworzenie metody, która wyświetli coś na stronie. Na chwilę obecną dam jakiś prosty tekst byle zobaczyć, czy działa. Metoda ta posiada określona nazwę, bo Seaside przeszukując obiekty i wyświetlając zawartość szuka właśnie jej:

    renderContentOn: html
       html paragraph: 'Alibaba'.

    Jako, że obiekt DPRoot jest obiektem głównym, trzeba zaznaczyć to tworząc metodę, która poinformuje framework Seaside. W części klasowej trzeba utworzyć metodę:

    canBeRoot
       ^ true.

    Dzięki niej Seaside będzie widzieć obiekt w przeglądarce aplikacji.

    Rejestracja komponentu jako aplikacji

    W przeglądarce przechodzę pod adres http://localhost:8080/config . Klikam w Add na pasku menu. W pole wpisuję 'dp' i wybieram typ jako Application. OK. W sekcji General na kolejnej stronie jako 'Root class' wybieram DPRoot. Klik w Apply. Teraz przechodząc pod adres localhost:8080/dp powinien się wyświetlić Alibaba... Jest. ;)
    To samo można zrobić wywołując w Workspace polecenie:

    WAAdmin register: DPRoot asApplicationAt: 'dp'.

    2012-12-08

    Clojure Easy 2


    "The computing scientist’s main challenge is not to get confused by the complexities of his own making."
     --- E. W. Dijkstra

    Czas na kolejną część zagadek ze strony 4clojure.com.


    Problem 9: Fibonacci Sequence


    Zadanie polega na napisaniu funkcji, która zwróci n pierwszych elementów ciągu Fibonacci.
    Funkcja w E1 bierze za parametr szukaną liczbę elementów ciągu. Dla pierwszego i drugiego elementu zwracane są wartości bezpośrednio. Dla dalszych obliczane są kolejne elementy na podstawie dwóch pierwszych elementów listy acc. W pętli zwiększany jest licznik c i dodawana do początku listy acc suma dwóch poprzednich elementów. Trzeba pamiętać, że funkcja conj dodaje elementy do list od lewej strony, dlatego aby uzyskać poprawną odpowiedź trzeba acc odwrócić przed zwrotem. Odpowiedź E2 jest z wykorzystaniem wektora.

    T1: (= (__ 3) '(1 1 2))
    T2: (= (__ 6) '(1 1 2 3 5 8))
    T3: (= (__ 8) '(1 1 2 3 5 8 13 21))

    E1: (fn fiblist [n]
          (let [a '(1) b '(1 1)]
              (cond (= n 1) a
                   (= n 2) b
                   :else (loop [c 2 acc b]
                        (if (>= c n)
                            (reverse acc)
                            (recur (inc c)
                                   (conj acc (+ (first acc) (second acc)))))))))

    E2 : (fn fiblist [n]
            (let [a [1] b [1 1]]
                (cond (= n 1) a
                      (= n 2) b
                      :else (loop [c 2 acc b]
                                 (if (>= c n)
                                  acc
                                 (recur (inc c) 
                                        (conj acc (+ (acc (- c 1)) (acc (- c 2))))))))))


    Problem 10: Maximum value


    Zadanie polega na napisaniu funkcji, która zwróci parametr o największej wartości.
    Haczyk: nie można użyć funkcji max i max-key.

    Zanim podam wynik wyjaśnię na przykładzie jak to jest z przekazywaniem argumentów do funkcji:

    => ((fn f [a b & more] more) 1 2 3 4)
    (3 4)
    => ((fn f [a & more] more) 1 2 3 4)
    (2 3 4)
    => ((fn f [& more] more) 1 2 3 4)
    (1 2 3 4)

    Przed znakiem & argumenty przypisywane są jak leci 1 do a, 2 do b, itd. Po znaku & reszta argumentów traktowana jest jako lista. W przypadku braku identyfikatorów przed & wszystkie argumenty traktowane są jako lista. Oczywiście a, b, more i args mogą mieć dowolne nazwy.

    T1: (= (__ 1 8 3 4) 8)
    T2: (= (__ 30 20) 30)
    T3: (= (__ 45 67 11) 67)

    E1:(fn maxx [& args]
                (loop [e (first args)
                       re (rest args)
                       mx e]
                      (if (empty? re)
                           mx
                          (recur (first re) (rest re) (if (> e mx) e mx)))))

    Problem 11: Get the Caps


    Zadanie: Znaleźć funkcję, która z podanego łańcucha znaków zwróci łańcuch zawierający tylko wielkie litery. Łańcuchy tekstowe w Clojure to zwykłe String z Javy, więc będą na nich działać funkcje z Javy. Aby dostać listę wielkich liter trzeba przefiltrować tekst za pomocą funkcji filter, którą następnie trzeba złączyć w napis przy użyciu funkcji str i apply.


    T1: (= (__ "HeLlO, WoRlD!") "HLOWRD")
    T2: (empty? (__ "nothing"))
    T3: (= (__ "$#A(*&987Zf") "AZ")

    E1: #(apply str (filter (fn [c] (Character/isUpperCase c)) %))

    Problem 12: Intro to some


    Należy podać wynik działania funkcji some. Funkcja ta bierze za pierwszy parametr funkcję, która zwraca logiczną prawdę lub fałsz , oraz kolekcję, której elementy będą przekazywane do tej funkcji. Jeżeli wynikiem działania funkcji będzie true to zostanie zwrócony pierwszy element dla którego warunek został spełniony.


    T1: (= __ (some #{2 7 6} [5 6 7 8]))
    T2: (= __ (some #(when (even? %) %) [5 6 7 8]))

    E1: 6

    Problem 13: Duplicate a sequence


    Zadanie polega na napisaniu funkcji, która powtórzy elementy sekwencji dwukrotnie.

    T1: (= (__ [1 2 3]) '(1 1 2 2 3 3))
    T2: (= (__ [:a :a :b :b]) '(:a :a :a :a :b :b :b :b))
    T3: (= (__ [[1 2] [3 4]]) '([1 2] [1 2] [3 4] [3 4]))

    E1: #(if (empty? %1) []
             (loop [a (first %1) r (rest %1) ac [a a]]
                  (if (empty? r) ac
                      (let [s (first r)]
                           (recur s (rest r) (conj ac s s))))))

    E2: #(interleave %1 %1)


    Problem 14: Implement range


    Zadanie polega na napisaniu odpowiednika funkcji range .

    T1: (= (__ 1 4) '(1 2 3))
    T2: (= (__ -2 2) '(-2 -1 0 1))
    T3: (= (__ 5 8) '(5 6 7))

    E1: (fn [s e]
            (loop [c s acc []]
                 (if (>= c e) 
                  acc
                 (recur (inc c) (conj acc c)))))

    2012-11-22

    Clojure Easy 1

    "SQL, Lisp, and Haskell are the only programming languages that I've seen where one spends more time thinking than typing."
    - Philip Greenspun


    Na początek zagadka: Ilu inżynierów od sprzętu komputerowego potrzeba by wymienić żarówkę?
    Odpowiedź na końcu wpisu.

    W tym cyklu nie będzie poznawania podstawowych funkcji, a raczej ich zastosowanie w akcji. Przewiną się wszystkie poznane już w poprzedniej części konstrukcje oraz pojawią się bardziej zaawansowane funkcje. Będzie więcej kombinowania. Dodatkowo pojawią się przeszkody w postaci zakazów użycia niektórych funkcji.

    Zalecam użycie Eclipse (dowolna edycja, może być C++, bo mało zajmuje) oraz wtyczkę Counterclockwise (do Clojure), którą można zainstalować z poziomu Eclipse poprzez Help->Eclipse Marketplace... Po utworzeniu projektu Clojure należy utworzyć namespace np. core. Potem uruchomić przyciskiem Run. Uruchomi się w ten sposób linia poleceń Clojure, gdzie można testować kod. Jeżeli program wpadnie w nieskończoną pętlę, bądź się przywiesi, to można go zatrzymać przy pomocy ikonki z trybikiem lub Ctrl+I.

    Problem 1: Last Element


    Zadanie polega na wypisaniu ostatniego elementu kolekcji. Aby zadanie nie było zbyt łatwe nie można użyć funkcji last. Oczywiście nie utrudnia to nam zadania.
    T1: (= (__ [1 2 3 4 5]) 5)
    T2: (= (__ '(5 4 3)) 3)
    T3: (= (__ ["b" "c" "d"]) "d")

    E1: #(first (reverse %))

    Problem 2: Penultimate Element


    Jeszcze trudniejsze zadanie. Tym razem trzeba podać wartość drugiego elementu od końca.

    T1: (= (__ (list 1 2 3 4 5)) 4)
    T2: (= (__ ["a" "b" "c"]) "b")
    T3: (= (__ [[1 2] [3 4]]) [1 2])

    E1: #(second (reverse %))
    E2: #(last (butlast %))

    Problem 3: Nth element


    Zadanie polega na podaniu n-tego elementu kolekcji przekazanej do funkcji. Jest jednak haczyk. Nie można użyć funkcji nth.

    Funkcja take bierze n elementów z listy. Jednak autor testu przyjął podstawę 0. Stąd funkcja musi wziąć o jeden element więcej. Szukany element będzie na ostatniej pozycji z obciętej listy.

    T1: (= (__ '(4 5 6 7) 2) 6)
    T2: (= (__ [:a :b :c] 0) :a)
    T3: (= (__ [1 2 3 4] 1) 2)
    T4: (= (__ '([1 2] [3 4] [5 6]) 2) [5 6])

    E1: #(last (take (inc %2) %1))

    Problem 4: Count a Sequence


    Zadanie polega na wypisaniu ilości elementów listy bez użycia funkcji count
    Pierwszy sprytny sposób to nudne zliczanie każdego elementu w pętli : E1.
    Drugi sprytny sposób: E2, to utworzenie listy jedynek za pomocą funkcji map, które potem będą zsumowane.

    T1: (= (__ '(1 2 3 3 1)) 5)
    T2: (= (__ "Hello World") 11)
    T3: (= (__ [[1 2] [3 4] [5 6]]) 3)
    T4: (= (__ '(13)) 1)
    T5: (= (__ '(:a :b :c)) 3)

    E1: #(loop [c 0 l %]
            (if (empty? l)
                c
                (recur (inc c) (rest l))))
    E2: #(reduce + (map (fn [e] 1) %))

    Problem 5: Sum it all up


    Zadanie polega na napisaniu funkcji, która zwróci sumę wszystkich elementów.
    Dużo pracy tu nie ma, bo wykorzystam poprzednią funkcję, która zamiast zliczać jedynki będzie sumować poszczególne elementy.

    T1: (= (__ [1 2 3]) 6)
    T2: (= (__ (list 0 -2 5 5)) 8)
    T3: (= (__ #{4 2 1}) 7)
    T4: (= (__ '(0 0 -1)) -1)
    T5: (= (__ '(1 10 3)) 14)

    E1: #(loop [sum 0 l %]
             (if (empty? l)
                 sum
                 (recur (+ sum (first l)) (rest l))))
    E2: #(apply + %)
    E3: #(reduce + %)

    Problem 6: Find the odd numbers



    Zadanie polega na znalezieniu wszystkich elementów nieparzystych z podanej listy i zwrócenie ich jako listy. Znając już makro for zadanie będzie dość proste (E1). Zmodyfikuję też poprzednią pętlę i podam jako E2.

    T1: (= (__ #{1 2 3 4 5}) '(1 3 5))
    T2: (= (__ [4 2 1 6]) '(1))
    T3: (= (__ [2 2 4 6]) '())
    T4: (= (__ [1 1 1 3]) '(1 1 1 3))

    E1: #(for [x % :when (odd? x)] x)
    E2: #(loop [wyn [] lst %]
                (if (empty? lst)
                    wyn
                    (recur (if (odd? (first lst))
                               (conj wyn (first lst))
                               wyn)
                            (rest lst))))

    Problem 7: Reverse a Sequence


    Zadanie polega na zmianie kolejności elementów kolekcji bez używania funkcji reverse i rseq.

    T1: (= (__ [1 2 3 4 5]) [5 4 3 2 1])
    T2: (= (__ (sorted-set 5 7 2 7)) '(7 5 2))
    T3: (= (__ [[1 2][3 4][5 6]]) [[5 6][3 4][1 2]])

    E1: #(loop [wyn [] lst %]
                   (if (empty? lst)
                       wyn
                       (recur (conj wyn (last lst))
                              (butlast lst))))


    Problem 8: Palindrome detector.


    Zadanie polega na napisaniu funkcji, która testuje czy podana sekwencja jest palindromem.
    Ciąg jest palidromem, jeżeli pierwsza połowa sekwencji jego elementów jest równa odwróconej drugiej połowie. Jeżeli liczba elementów ciągu jest nieparzysta środkowy znak się ignoruje. Należy więc wziąć liczbę n elementów ciągu , podzielić całkowicie przez dwa (funkcja quot), wziąć n/2 pierwszych elementów , odwrócić listę, wziąć n/2 pierwszych elementów i porównać listy (E1). Drugi sposób to porównywać pierwszy element z ostatnim (przy okazji usuwać te elementy), aż lista nie będzie pusta (E2). Trzeci sposób to wziąć sobie jakiś licznik i porównywać n-ty z k-n element listy, gdzie k to długość listy, a n: <0, k-1>.

    T1: (false? (__ '(1 2 3 4 5)))
    T2: (true? (__ "racecar"))
    T3: (true? (__ [:foo :bar :foo]))
    T4: (true? (__ '(1 1 3 3 1 1)))
    T5: (false? (__ '(:a :b :c)))

    E1: #( let [lst %
            n (quot (count lst) 2)
            pp (take n lst)
            dp (take n (reverse lst))]
           (if (< n 1)
               true
              (reduce (fn [x y] (and x y)) (map (fn [a b] (= a b)) pp dp))))

    E2: #( let [lst %
            n (quot (count lst) 2)]
            (loop [f (first lst) l (last lst) tmp lst]
              (if (empty? tmp) true
              (if (not= f l)
                   false
                  (recur (second tmp) (last (butlast tmp)) (rest (butlast tmp)))))))

    E3: #( let [v (vec %)
               k (count v)
               n (quot k 2)]
               (loop [f 0 l (dec k)]
                 (if (>= f n)
                     true
                     (if (not= (v f) (v l))
                         false
                         (recur (inc f) (dec l))))))

    ------------------------------------------------------------------------------------------------------------
    Odpowiedź na zagadkę: Żadnego. Problem obejdzie się w sofcie. 

    Clojure Elementary 4


    "If I had a nickel for every time I've written "for (i = 0; i < N; i++)" in C I'd be a millionaire."

    - Mike Vanier



    Po części trzeciej, czas na ostatnią, czwartą część łamigłówek o podstawowym stopniu trudności. W kolejnej serii przejdę do stopnia trudności określanego jako Easy.


    Taka mała zagadka: Ilu programistów .NET potrzeba, by zabić karalucha?
    (odpowiedź na końcu wpisu.).

    Problem 27: for the win


    Zadanie polega na odgadnięciu wyniku działania makra for.
    Makro to bierze za pierwszy argument wektor powiązań (podobnie jak let i opcjonalnie trzy modyfikatory:
    - :let [ powiązania dla dodatkowych zmiennych pomocniczych ]
    - :when ( test ) - zmienne w iteracji zostaną przekazane po spełnieniu testu
    - :while ( test ) - for zakończy działanie po negatywnym wyniku testu

    Powiązania (nie dotyczy :let) muszą być sekwencjami np.: (for [x '(3 4) z [1 2]] (...) ). Elementy sekwencji będą łączone na zasadzie każdy z każdym. To jest dla każdego elementu x zostanie przypisany każdy element z i taka postać przekazana jako parametry do bloku wykonania. Blok wykonania zwraca wartość, z której po każdej iteracji będzie budowana sekwencja. Tak, że for służy do budowania sekwencji na podstawie innych sekwencji.

    W teście T1 mam for, które będzie się wykonywać dla każdego x z sekwencji zbudowanej przy pomocy funkcji range). W tym przypadku range zwróci kolekcję liczb od 0 do 39 włącznie z krokiem 1. Iterując po każdym elemencie zostaje sprawdzony warunek :when, który zwraca prawdę jeżeli reszta z dzielenia elementu x kolekcji przez 4 (funkcja rem będzie równa 1. Dzięki temu zostanie zbudowana kolekcja elementów, których reszta z dzielenia przez 4 jest 1. Wynik w ostateczności powinien wyglądać tak: (1 5 9 13 17 21 25 29 33 37).

    W teście T2 x jest nieskończoną kolekcją zwróconą przez funkcję iterate. Funkcja ta bierze za pierwszy argument funkcję, a za drugi wartość początkową. Funkcja anonimowa zwraca wartość argumentu powiększonego o 4. Stąd x będzie mieć postać: (0 4 8 12 ... ). Modyfikator :let tworzy zmienną pomocniczą z, która będzie mieć wartość elementu x powiększonego o 1. Modyfikator :while sprawdza, czy z jest mniejsze od 40, jeżeli nie, to kończy działanie for

    W teście T3 for iteruje po parach liczb utworzonych poprzez utworzenie kolekcji liczb
    (0 1 2 3 ... 19) podzielonej na kolekcję dwuelementowych list : ( (0 1) (2 3) (4 5) .... (18 19) ).
    Wynikiem jest lista, której elementy to sumy par liczb.
    Pary i większe kolekcje liczb można przypisywać do zmiennych lokalnych w rózny sposób, np:

    (let [ x 1 y 2 ] (+ x y))
    i
    (let [ [x y] '(1 2) ] (+ x y))
    vs
    (let [ x (first '(1 2)) y (second '(1 2)) ] (+ x y))
    I zadanie:

    T1: (= __ (for [x (range 40)
                :when (= 1 (rem x 4))]
            x))
    T2: (= __ (for [x (iterate #(+ 4 %) 0)
                :let [z (inc x)]
                :while (< z 40)]
            z))
    T3: (= __ (for [[x y] (partition 2 (range 20))]
            (+ x y)))

    E1: '(1 5 9 13 17 21 25 29 33 37).
    E2: [1 5 9 13 17 21 25 29 33 37].

    Problem 28: Logical falsity and truth


    W Clojure w testach logicznych tylko nil i false zwracają logiczny fałsz, wszystko inne ewaluuje się do logicznej prawdy. Funkcja if Służy do wykonywania dwóch rodzajów kodu w zależności od wyniku przekazanego do niej testu. Za pierwszy argument bierze test, który zwraca prawdę lub fałsz. Jeżeli wynikiem testu będzie prawda, zostanie wykonany kod podany w drugim argumencie w przeciwnym wypadku zostanie wykonany kod w trzecim. Jeżeli trzeciego argumentu nie będzie, a test sugeruje na jego wykonanie, to if zwróci nil.

    T1: (= __ (if-not false 1 0))
    T2: (= __ (if-not nil 1 0))
    T3: (= __ (if true 1 0))
    T4: (= __ (if [] 1 0))
    T5: (= __ (if [0] 1 0))
    T6: (= __ (if 0 1 0))
    T7: (= __ (if 1 1 0))
      
    E1: 1

    Problem 29: Map defaults


    W przypadku pozyskiwania wartości rezydującej pod kluczem mapy, oprócz sposobu jaki był przedstawiony w problemie nr 26 jest jeszcze jedna metoda na poradzenie sobie z sytuacją w której dany klucz nie istnieje. Można do wyrażenia pobierającego wartość spod klucza dodać wartość domyślną, która zostanie zwrócona, gdy klucz nie istnieje, np.:

    (:k {:a 0, :b 1, :c nil} :not_exist)
    Zwróci :not_exist.

    A co jeżeli chcemy utworzyć mapę i nadać kluczom jakaś domyślną wartość?
    Zadanie polega na utworzeniu funkcji, która pobierze sekwencję kluczy, wartość domyślną i utworzy z nich mapę. Mapę w najprostszy sposób tworzy się poprzez (hash-map klucz dane) lub {klucz dane}. Wiadomo, że przez elementy sekwencji można iterować przez użycie map lub for. Funkcja map bierze za parametr funkcję, którą stosuje dla każdego elementu sekwencji. Stąd mając domyślą wartość klucza i sekwencję kluczy można zwrócić sekwencję map. Złączyć to można poleceniem conj i funkcją apply lub reduce.

    Rozpiszę główne etapy:
    1. map ... : ({:a 0} {:b 0} {:c 0})
    2. apply conj ... : {:a 0 :b 0 :c 0}
    z czego apply tworzy listę w sposób: (conj {:a 0} {:b 0} {:c 0})
    reduce: (conj {:a 0} {:b 0}) -> (conj {:a 0 :b 0} {:c 0}) ...

    Zadanie:

    (= (__ 0 [:a :b :c]) {:a 0 :b 0 :c 0})
    (= (__ "x" [1 2 3]) {1 "x" 2 "x" 3 "x"})
    (= (__ [:a :b] [:foo :bar]) {:foo [:a :b] :bar [:a :b]})

    E1: #(reduce conj (map (fn [d] {d %1}) %2))
    E2: #(apply conj (map (fn [d] {d %1}) %2))
    ------------------------------------------------------------------------------------------------------------
    Odpowiedź na zagadkę: Dwóch. Jeden trzyma karalucha, by nie uciekł, a drugi instaluje na nim Windows.

    Clojure Elementary 3

    “Don't worry about what anybody else is going to do. The best way to predict the future is to invent it.”

    — Alan Kay 
    Kolejna część zmagań z zadaniami ze strony 4Clojure.
    Do dzieła!: ;)



    Problem 17: Sequences : filter.

    Zadanie polega na zgadnięciu rezultatu filtrowania listy za pomocą funkcji testującej. Funkcja filter bierze za pierwszy argument funkcję testującą a drugi sekwencję. Zwraca elementy sekwencji, które przejdą test pozytywnie. Tutaj funkcja anonimowa zwraca logiczną prawdę jeżeli przekazany do niej parametr jest większy od 5, a więc filter powinien zwrócić wszystkie elementy sekwencji większe od 5.
    T1: (= __ (filter #(> % 5) '(3 4 5 6 7)))
    E1: '(6 7)
    E2: [6 7]


    Problem 18: Local bindings.


    Funkcja let pozwala na lokalne przypisanie nazw funkcjom bądź danym. Bardzo ułatwia czytanie kodu. Budowa jest postaci:
    (let [zmienna1 funkcja1 zmienna2 funkcja2 .... zmiennaN funkcjaN] (obliczena z wykorzystaniem zmienna1..N))
    Zadanie polega na odgadnięciu rezultatu działania funkcji.
    T1: (= __ (let [x 5] (+ 2 x)))
    T2: (= __ (let [x 3, y 10] (- y x)))
    T3: (= __ (let [x 21] (let [y 3] (/ x y))))
    E1: 7


    Problem 19: Let it Be


    Zadanie odwrotne do zadania 18. Zamiast zgadywać wynik, trzeba zgadnąć postać przypisania. Zadanie jest trudne bo trzeba zgadnąć wartości przypisań. Najlepiej zacząć od dołu (T3).
    T1: (= 10 (let __ (+ x y)))
    T2: (= 4 (let __ (+ y z)))
    T3: (= 1 (let __ z))
    E1: [z 1 y 3 x 7]

    Problem 20: Regular Expressions


    Wyrażenia regularne. Tym razem trzeba zgadnąć rezultat działania funkcji re-seq, która za pierwszy parametr bierze postać wyrażenia regularnego, a jako drugi ciąg znaków do przeszukania. Wyrażenie regularne w Clojure ma postać: #" ". re-seq zwraca każde poprawne wystąpienie tekstu spełniającego wyrażenie w postaci sekwencji. Funkcję str już znamy. Funkcja apply natomiast powoduje użycie listy jako argumentów do funkcji str.
    Zamiast:
    (str "a" "b" "c")
    jest:
    (apply str '("a" "b" "c"))
    I zadanie:
    T1: (= __ (apply str (re-seq #"[A-Z]+" "bA1B3Ce ")))
    E1: "ABC"

    Problem 21: Intro to Reduce



    Funkcja reduce bierze dwa lub trzy argumenty. Pierwszym jest funkcja, która może przyjąć dwa argumenty. Jeżeli drugim argumentem jest kolekcja, reduce bierze pierwsze dwa elementy z kolekcji i przekazuje je do funkcji. Jeżeli są jeszcze elementy w kolekcji, to brany jest wynik poprzedniej operacji na elementach, kolejny element z listy i przekazywane do podanej funkcji aż do wyczerpania elementów. Jeżeli zamiast kolekcji podamy jakąś wartość i kolekcję, to pierwszym elementem przekazanym do funkcji będzie ta wartość, a drugim pierwszy element z kolekcji.
    Zadnie polega na zgadnięciu postaci funkcji jaka zastosowana według powyższych reguł da podany wynik.
    T1: (= 15 (reduce __ [1 2 3 4 5]))
    T2: (=  0 (reduce __ []))
    T3: (=  6 (reduce __ 1 [2 3]))
    E1: +
    E2: (fn add
          ([] 0)
          ([a b] (+ a b)))
    Uwaga. W odpowiedzi użyłem dziwnie wyglądającej funkcji anonimowej. Wcześniej napisałem, że funkcja reduce potrzebuje za pierwszy argument funkcji, która potrafi przyjąć dwa argumenty. Niestety, test T2 wymaga, by funkcja radziła sobie z brakiem argumentów. Clojure pozwala skonstruować funkcje tak, by w jednej definicji przewidywały odpowiedź na różną ilość argumentów. Te definicje oddziela się nawiasami okrągłymi i mogą być w dowolnej kolejności.

    Problem 22: Simple Recursion


    Rekurencja. Występuje wtedy, gdy funkcja wywołuje samą siebie. W tym zadaniu funkcja foo bierze liczbę za argument x. Jeżeli x jest większe od 0 (when) wykonuje funkcję conj, która tworzy sekwencję z funkcji foo pomniejszonej o 1 i aktualnego x. Funkcja conj łączy do początku listy, ale wykona się rekurencyjnie dopiero po napotkaniu warunku brzegowego (x > 0 ). Jeżeli x będzie 0 funkcja when zwróci nil. conj z nil i x daje (x). Ostatnim x przed warunkiem jest 1, pierwszym jest 5. Czyli łącząc do początku listy od 1 do 5 mamy listę w kolejności odwrotnej.

    T1: (= __ (   (fn foo [x]
                          (when (> x 0)
                                (conj (foo (dec x))
                                      x)))
                   5 ))

    E1:  '(5 4 3 2 1)
    E2: [5 4 3 2 1]


    Problem 23: Rearranging Code: ->


    Makro -> pozwala wykonywać na danych operacje sekwencyjnie zamiast zagnieżdżać w nawiasy. Co my tutaj mamy?

    Funkcja reverse odwraca kolejność elementów w kolekcji, czyli będzie: [6 3 1 4 5 2]. rest zwraca wszystkie elementy oprócz pierwszego, więc: [3 1 4 5 2]. sort porządkuje elementy w kolejności rosnącej: [1 2 3 4 5]. Zadanie polega na zgadnięciu ostatniej funkcji, która po zastosowaniu na ostatnim wyniku zwróci 5.

    T1: (= (__ (sort (rest (reverse [2 5 4 1 3 6]))))
           (-> [2 5 4 1 3 6] reverse rest sort __)
           5)

    E1: last
    E2: #(first (reverse %))

    Uwaga, może nie zadziałać na 4Clojure mimo, że odpowiedzi są poprawne.

    Problem 24: Recurring Theme



    Clojure posiada konstrukcję, która pozwala na zapętlanie bez zapychania stosu - taki odpowiednik for. W językach takich jak C, Java kolejne wywołania funkcji powodują, że na stosie odkładane są kolejne ich parametry. Dodatkowo na stosie odkładane są zmienne lokalne funkcji. Jako, że stos nie jest nieskończony może się dość szybko zapchać, szczególnie, gdy stosujemy pętle rekurencyjne, które polegają na wywołaniach funkcji przez samą siebie.
    Jako anegdotę dodam, że twórcom gry MMO : Eve Online udało się osiągnąć limit jaki stawia stos C. Wtedy w obroty wzięli odnogę Pyhona: Stackless, który nie zużywa stosu. Jako, że Python ma problemy ze skalowalnością horyzontalną (winowajca: GIL) ciekawi mnie jak rozwiązali sprawę komunikacji pomiędzy procesami działającymi na wielu rdzeniach.
    Wracając do tematu. Konstrukcją Clojure, która nie powoduje przepełnień stosu jest loop - recur .
    loop bierze wektor zmiennych tymczasowych podobnie do let, które są aktualizowane przez funkcję recur.
    Rozkładając na czynniki pierwsze T1 mamy zainicjalizowaną zmienną x wartością 5 i sekwencję result jako pusty wektor: []. Idąc dalej mamy test x > 0. Jeżeli x będzie większe od 0 zostanie wywołana funkcja recur z nowym x pomniejszonym o jeden, a do pustego result zostanie dodany wynik powiększenia x o 2. Jeżeli warunek x > 0 nie będzie spełniony zostanie zwrócona aktualna wartość result. Czyli zaczynając od 5 do 1 włącznie zostanie zbudowany wektor [7 6 5 4 3].

      

    T1: (= __
          (loop [x 5
                 result []]
            (if (> x 0)
              (recur (dec x) (conj result (+ 2 x)))
              result)))

    E1: [7 6 5 4 3]

    Problem 25: Rearranging Code: ->>


    Makro ->>. Służy do poprawy czytelności kodu. Jest to rozwinięta wersja ->, gdzie funkcje były jedno-parametrowe. Makro bierze dane z pierwszego argumentu i wstawia je na koniec listy podanej w kolejnym argumencie. Jeżeli są dodatkowe listy, to wynik poprzedniego działania jest wstawiany na koniec kolejnej. Na końcu tak zbudowana lista jest ewaluowana (wykonywana).
    W zadaniu mamy wektor [2 5 4 1 3 6], który jest skracany o dwa elementy z przodu funkcją drop. Wynik to : [4 1 3 6] Następnie z wyniku brane są trzy pierwsze elementy : [4 1 3]. Kolejna rzecz, to za pomocą map zwiększenie elementów o 1, dając: [5 2 4].
    Teraz trzeba znaleźć taką funkcję, która zrobi jakoś z tych danych 11. Okazuje się, że suma elementów to 11, więc użyję + wraz z funkcją operująca na listach.

    T1: (= (__ (map inc (take 3 (drop 2 [2 5 4 1 3 6]))))
           (->> [2 5 4 1 3 6] (drop 2) (take 3) (map inc) (__))
           11)

    E1: reduce +
    E2: apply +

    Problem 26: A nil key


    Zadanie polega na znalezieniu funkcji, która pobierze dwa argumenty: klucz i mapę, oraz zwróci prawdę jeżeli klucz istnieje w tej mapie i ma wartość nil. Jest to dość ciekawe zadanie, bo mapa zwraca nil jeżeli klucz nie istnieje. Jak więc dowiedzieć się, że klucz istnieje i ma wartość nil?

    Potrzebne będą dwa porównania. Jedno sprawdza, czy klucz istnieje za pomocą funkcji contains?, a drugie, czy wartość przypisana do klucza jest nil. Stąd rozwiązanie E1. Drugie rozwiązanie E2 jest lepsze, bo nie iteruje dwukrotnie po mapie tylko raz. W pętli loop biorę pierwszą parę klucz-dane z mapy i sprawdzam, czy nie dostaję nil. Dostanę nil jeżeli mapa będzie pusta. Jeżeli jest pusta zwracam falsz. Jeżeli nie sprawdzam, czy pierwszy element pary to mój klucz i przy okazji sprawdzam czy drugi to nil. Jeżeli to nasz klucz i ma wartość nil zwracam true. W przeciwnym wypadku biorę kolejny element z mapy oraz jej resztę i przekazuję jako nowe wartości do loop za pomocą recur.
    Update:

    Przykład E3 wykorzystuje możliwość przypisania do zmiennej lokalnej wartości domyślnej jeżeli klucz w mapie nie istnieje. Trzeba jednak uważać, by wartość domyślna nie mogła wystąpić w danych, bo dostaniemy niepoprawny wynik.

    T1: (true?  (__ :a {:a nil :b 2}))
    T2: (false? (__ :b {:a nil :b 2}))
    T3: (false? (__ :c {:a nil :b 2}))

    E1: #(and (contains? %2 %1) (nil? (%2 %1)))
    E2: #(let [klucz %1 mapa %2]
            (loop [p (first mapa) r (rest mapa)]
                     (if (nil? p)
                         false
                         (if (and (= (first p) klucz) (nil? (second p)))
                                 true
                                 (recur (first r) (rest r))))))
    E3: #(let [dane (%1 %2 :not_exist)]
            (if (nil? dane)
            true
            false))