Wpis z mikrobloga

#programowanie #learnclojurewithmikroblog #clojure

Odcinek 3 - więcej o funkcjach wyższego rzędu, uzupełnienie niektórych rzeczy które pominąłem.

Dla odmiany zacznijmy najpierw od zadania z poprzedniego odcinka.

Najpierw napiszę jedną funkcję tak, jakby nie było map reduce ani nic takiego:

; suma wektorow 2wymiarowych v1 i v2
(


defn 
```**```
v2d+ [v1 v2]
``````
      [(
```**```

```**```
(v1 0)
``````
          (v2 0))
``````
       (
```**```

```**```
(v1 1)
``````
          (v2 1))])
```Resztę funkcji można zapisać analogicznie. Jest to jednak sporo kodu, i jest on specyficzny dla wersji dla wektorów 2-wymiarowych. Fajnie by było napisać to tak, żeby działało również dla wersji 3-wymiarowej. Można zauważyć, że robimy to samo (sumujemy) wszystkie elementy obu wektorów. Więc może map i reduce?```
(defn v2d+ [v1 v2] (map + v1 v2))

(defn v2d- [v1 v2] (map - v1 v2))

(defn v2d*s [v s] (map (fn [x] (* x s)) v))

(defn v2d*v2d [v1 v2] (reduce + (map * v1 v2)))

(defn v2d-len [v] (Math/sqrt (v2d*v2d v v)))

```Trochę krócej ;) I mamy tą zaletę, że wszystkie funkcje działają dla wektorów dowolnej wielkości. Ale przetestujmy te funkcje.```
(v2d+ [1 2] [3 4]) ; zwraca (4 6). Czemu nie [4 6] ?

```Otóż clojure oprócz konkretnych typów danych ma interfejsy. Interfejsem używanym przez większość kolekcji jest ISeq. Zawiera on 3 funkcje:```
(first coll) ; zwraca pierwszy element kolekcji (lub nil jak jest pusta)

(rest coll) ; zwraca sekwencję kolejnych elementów kolekcji oprócz pierwszego (jak nie ma to pusta sekwencja)

(cons item coll) ; tworzy sekwencję przez dołączenie item na początku kolekcji coll

```Sekwencje mogą być leniwe, czyli clojure nie musi znać wszystkich elementów sekwencji od razu w momencie jej utworzenia. Wystarczy, że wie, jak ma je wyliczyć w razie, jak będą potrzebne. Dzięki temu Clojure może bezproblemowo operować na nieskończonych sekwencjaach tak długo, jak ich nie każemy w całości wyświetlić, albo w inny sposób nie spowodujemy konieczności wyliczenia wszystkich elementów.Przykład nieskończonej sekwencji:```
(repeat 5) ; zwróci Execution Timed Out! bo okazuje się, że nie umiemy wyświetlić wartości nieskończonej sekwencji

(take 10 (repeat 5)) ; zwróci (5 5 5 5 5 5 5 5 5 5), bo funkcja take bierze pierwsze n elementów sekwencji

```Co ciekawe - można operować na leniwych sekwencjach bez ich wyliczania. Np map nie wymusza wyliczenia sekwencji. Przykład:```
(def tmp (map + (repeat 5) (repeat 1))) ; właśnie dodaliśmy nieskończoną liczbę 1 do nieskończonej liczby 5. Szybko się z tym clojure uwinęło, prawda :)

(take 10 tmp) ; zwróci (6 6 6 6 6 6 6 6 6 6)

(take 1 tmp) ; zwróci (6)

```Przydatną funkcją zwracającą sekwencje jest range. (range n) zwróci sekwencję 0..n-1 włącznie. (range) zwróci nieskończoną sekwencję od 0 w górę.```
(range 10) ; zwróci (0 1 2 3 4 5 6 7 8 9)

(take 10 (range)) ; zwróci (0 1 2 3 4 5 6 7 8 9) czyli to samo.

```Zauważmy, że```
(def tmp2 (map + (repeat 5) (range)))

(take 3 tmp2) ; zwróci (5 6 7)

```Na razie nie będziemy się skupiać na sekwencjach, więcej o nich (i o ogrmonej liczbie funkcji operujących na nich) można poczytać w [http://clojure.org/sequences](http://clojure.org/sequences)Jak widać map operuje nie na wektorach, tylko na wspólnym interfejsie kolekcji - sekwencjach, i zwraca sekwencje. Dlatego dostaliśmy (4 6) z naszej funkcji, a nie [4 6]. Dla pewności możemy sprawdzić typ zwracanej wartości, bo listy w clojure również są wyświetlane jako (4 6).```
(type (v2d+ [1 2] [3 4])) ; zwraca clojure.lang.LazySeq czyli jest to sekwencja (i to leniwa, choć leniwa sekwencja 2-elementowa to trochę overkill)

```Jeśli nam to nie przeszkadza - możemy nasze funkcje zostawić w takiej postaci sposób. Jeśli chcemy, żeby zawsze zwracały wektory, można skonwertować sekwencje do wektorów:```
(defn v2d+ [v1 v2] (vec (map + v1 v2)))

(defn v2d- [v1 v2] (vec (map - v1 v2)))

(defn v2d*s [v s] (vec (map (fn [x] (* x s)) v)))

```Tutaj można użyć skrótu do zapisywania funkcji anonimowej, zamiast pisać```
(fn [x] (* x s))

```można napisać```
#(* % s)

```Tak zrobił @rubberbandman. % jest argumentem tej funkcji. Dla funkcji wielo-argumentowych używa się %1 %2 itd, ale dla większych funkcji lepiej nie korzystać ze skrótu, tylko użyć fn i ponazywać argumenty.Poprzednim razem obiecałem wyjaśnić sprawę optymalności rekurencji jako sposobu na pętle. Kłamałem ;) Będzie w następnym odcinku, bo i tak długie są te wpisy.Zadanie 3. napisać swoją wersję funkcji map działającej dla funkcji 1-argumentowych i 1 kolekcji.```
(defn nasz-map [f coll] ...)

Można skorzystać z reduce albo z rekurencji.

Jak zwykle proszę o uwagi i komentarze. Co się nie podoba, co niejasne, czy za bardzo skaczę po tematach, jak z długością wpisów? Robi ktoś te zadanka?
  • 9
@tell_me_more: Szczerze mówiąc pierwszy raz spotykam się z tego typu językiem. Zawsze tylko C, Java, PHP itd. Mógłbyś mi pokrótce przedstawić zalety tego języka? Gdzie i kiedy warto go stosować? Rozumiem, że nie jest to zamiennik do rodziny C, tylko raczej bardzo specyficzny język do konkretnych działań - jakich dokładnie?
@Xanatos: clojure przede wszystkim nadaje się do aplikacj, które muszą działać wielowątkowo, i takich, w których większość kodu to skomplikowane algorytmy. Ma tą zaletę, że można używać bibliotek javy bezpośrednio (więc prawie wszystkie API jakie istnieją stoją otworem), i jednocześnie można pisać makra jak w LISPach, więc da się zrefaktorować kod i pozbyć się duplikacji, której w językach imperatywnych nie da się pozbyć.

Z tego co zauważyłem, ludzi stosują clojure do