Programowanie funkcyjne - Clojure

Wstęp do programowania w Clojure

Polecenia narzędzia Leiningen

lein new - tworzy nowy projekt

lein deps - ściąga zależności do projektu

lein repl - odpala "Clojure Shell"

  • (quote x) lub `x => x
  • (class quote x) => clojure.lang.symbol
  • (load-file "../src/nazwa/core.clj") => wgrany plik do repl

Struktury danych

Listy

Zdefiniowanie listy w Clojure wymaga słowa kluczowego list, w przykładzie lista zostanie zapisana do l1. Więcej informacji: definicja zmiennej globalnej

(def l1 
  (list "a" "b" 3)
)

Zbiory

Zbiór jest tego rodzaju odmianą listy, że jego elementy nie podlegają uporządkowaniu, a każdy z nich musi mieć unikatową wartość. Więcej informacji na stronie: TutorialsPoint - Set

(def s1 
  #{1 2 3 4 5}
)

Wektory

Są podobne do list, ich opis na stronie: TutorialsPoint - Vector

(def v1 
  [1 2 3 4 5]
)

Mapy

Mapa to kolekcja mapująca klucze do wartości. Dostarczane są dwa różne typy map - mieszane i sortowane. Różnice różnią implementacją, zostało to opisane na stronie: TutorialsPoint - Maps

SortedMaps

(def m1 
  (sorted-map "a" "1" "b" "2" "c" "3")
)

HashMaps

(def m2 
  (hash-map "a" "1" "b" "2" "c" "3")
)


Operacje na strukturach danych typu lista. | Przykładowe wywołania lein-repl:

# nth
Wyświetlamy n-tą wartość listy; gdy argument funkcji nth będzie za duży otrzymamy wyjątek:

IndexOutOfBoundsException

user=> (nth li 2)
3


# first
Wyświetlamy pierwszą wartość listy.
user=> (first li)
"a"
# <list>
Wyświetlamy listę.
user=> li
("a" "b" 3)


# map
Definiujemy funkcję kwadrat wartości, przy pomocy funkcji map podnosimy do kwadratu każdą wartość listy.
user=> (defn square [x] (* x x))
user=> (map square li2)
(1 4 9)

W drugim przykładzie tę samą funkcjonalność realizujemy przez funkcje anonimową:

user=> (map (fn [x] (* x x)) li2)
(1 4 9)


# rest
Przechodzimy przez kolejne elementy list rest oznacza resztę. Pierwszy element reszty reszty.
user=> (def li (list 1 2 3 4 5))
user=> (first (rest (rest li)))
user=> 3


# take
Funkcja take wyświetla elementy od początku do wskazanego numeru indeksu końcowego. Jeśli jest za duży zwraca ostatni element, jeśli za mały nic nie zwraca.
user=> (take 3 li)
(1 2 3)
user=> (take 5 li)
(1 2 3 4 5)
user=> (take 7 li)
(1 2 3 4 5)


# reduce
Kod poniżej zespala/redukuje/łączy wszystkie wartości z listy wykonując wybraną operacje arytmetyczną.
user=> li
(1 2 3 4 5)
user=> (reduce * (take 5 li))
120
user=> (reduce + (take 5 li))
15
user=> (double (reduce / (take 5 li)))
0.008333333333333333
# assoc
Kod poniżej tworzy nową listę na podstawie już zdefiniowanej. W przykładzie posłużono się mapą.
user=> (def m1 {"janek" 100 "marcin" 200 "adam" 150 "ewa" 160})
user=> (def m2 (assoc m1 "jarek" 50))
user=> m2
{"janek" 100, "marcin" 200, "adam" 150, "ewa" 160, "jarek" 50}
user=> m1
{"janek" 100, "marcin" 200, "adam" 150, "ewa" 160}

Obiektowość preduralna

# <metoda konstrukcyjna>

Przykład poniżej możemy, oczywiście nie dosłownie, ze względu na całkowicie odmienną naturę języka Clojure, który realizuje paradygmat programowania funkcyjnego, utożsamiać z konstruktorem klasy.

(defn make-person 
  [first-name last-name job-title]
  {
   :first-name first-name 
   :last-name last-name 
   :job-title job-title
  })

Zapis na dole może służyć jako pomoc, nie należy się na nim jednak opierać. Podane poniżej instrukcje nie skompilują się w zakresie Clojure.

Class make-person {
  make-person(first-name, last-name, job-title) {
    this.first-name = first-name 
    this.last-name = last-name 
    this.job-title = job-title
  }
}
# defrecord

"Metoda konstrukcyjna" nie tworzy klasy/typu, rozumianych poprzez paradygmaty języka Clojure. Rekord jest daje nam funkcjonalność klasy. Każda składowa rekordu może zostać rozszerzona, czyli może zawierać w sobie inny rekord.

W przykładzie poniżej definiujemy dwa rekordy, zakładamy że składową address rekordu Person rozszerzymy poprzez rekord Adress z nazwą ulicy, miasta, kodem pocztowym.

user.Person
user.Address

(defrecord Person [first-name last-name address])
(defrecord Address [street city zip])

Definiując teraz nowy obiekt, tak naprawdę rodzaj mapy/listy, nie musimy ale możemy skorzystać z dwóch zdefiniowanych rekordów. Parametr address opisujemy w postaci trzech parametrów typu Address.

(def adam (Person. "Adam" "Kos" 
           (Address. "Malinowa" 
                     "Warszawa" 
                      91018))
)

Wywołując mapę/listę adam otrzymujemy następujący ciąg danych:

#user.Person{
             :first-name "Adam", 
             :last-name "Kos", 
             :address 
                 #user.Address
                 {
                  :street "Malinowa", 
                  :city "Warszawa", 
                  :zip 91018
                 }
            }
  • Więcej o instrukcji defrecord na stronie: ClojureDocs

Funkcje

Opis tworzenia funkcji w Clojure znajduje się na stronie: Clojure.org Słowo kluczowe defn służy do nazywania funkcji z użyciem fn tworzymy funkcje anonimowe.

(defn test1 [x] 
   (+ x 4)
)
#iterate

Pozwala tworzyć "nieskończone" zbiory. Wywołanie takiej procedury skończy się oczywiście zawieszeniem komputera.

(iterate inc 0)

Należy jej wykonanie przypisać do stałej/symbolu.

(def xs 
     (iterate inc 0)
)
#take

Wyświetlenie zawartości zbioru musi określać jakaś granica. Komputer nie ma nieskończonej pamięci. Konstrukcja take pozwala na wybranie fragmentu zbioru.

=>(take 5 (iterate inc 5))
=>(5 6 7 8 9)

Programy

1. Pierwiastek

2. Ćwiczenia - pierwsze programy: GitHub - repo

Wsparcie

1. Kongra - Strona domowa dr. Konrada Grzanka

2. Repo Github - Repozytorium Clojure

3. random:seed - Genialna polska strona prowadzona przez fascynata LISP-a.

4. Webowe środowisko Clojure