Back to site
Since 2004, our University project has become the Internet's most widespread web hosting directory. Here we like to talk a lot about web servers, web development, networking and security services. It is, after all, our expertise. To make things better we've launched this science section with the free access to educational resources and important scientific material translated to different languages.

Zašto volim Lisp


Ovaj članak je iz intervjua koji sam dao u Simplificator , gde radim, pod nazivom “Why I love Smalltalk and Lisp”. Postoji još jedan članak “Why I love Smalltalk” koji je izdat pre ovoga.

Lisp je stari jezik. Veoma stari.
Danas ima mnogo Lisp jezika,
ali se ni jedan ne zove više Lisp.
Zapravo, ima toliko mnogo Lisp
jezika koliko i Lisp programera.
Zato jer pojedinac odmah postane
Lisp programer kada ode u
pustinju sam i napiše interpreter
za vaš ukus lisp napisan štapom
na pesku.

                                                                                                        Desert od Guilherme Jófili
Postoje dva glavna Lisp ovih dana: Common Lisp i Scheme , oba standarda sa mnogo dodataka. Različiti Common Lisps su više manje isti, ati različiti Schemes su isti na osnovnom nivou, pa se kasnije razlikuju, ponekad baš mnogo. Ova su interesantna, ali ja nisam uspeo da pronađem praktičnu stranu ni jednog ni drugog. Obe me zanimaju na različite načine, i od svih Lisp moj omiljeni je Clojure. Neću da zalazim u pojedinosti, to je stvar ličnog ukusa i mnogo bi mi vremena to oduzelo.

Clojure, kao i bilo koji drugi Lisp, ima REPL (Read Eval Print Loop) gde se može napisati kod i odmah se pokrenuti. Na primer:

5
;=> 5

"Hello world"
;=> "Hello world"

Obično imate odmah prompt, kao user>, ali ovde ja koristim radostan Clojure primer koda . Možete probati ovaj REPL i pokrenuti od sa ove pozicije Try Clojure .

Možemo i ovako nazvati funkciju:

(println "Hello World")
; Hello World
;=> nil

Odštampao je “Hello World” i vratio nil. Znam da zagrade izgledaju kao da su na pogrešnom mestu, ali za to postoji razlog i onda ćete primetiti da to nije ništa drugačije od Javaish odlomka:

println("Hello World")

osim to što Clojure koristi zavese na taj način za sve operacije:

(+ 1 2)
;=> 3

Kod Clojure mi takođe imamo vektore:

[1 2 3 4]
;=> [1 2 3 4]

simboli

'symbol
;=> symbol

Razlog citiranja je zato što simboli imaju varijable. Bez citiranja bi pokušao da pronađe svoju vrednost. Neke nalistane:

'(li st)
;=> (li st)

i napravljene liste

'(l (i s) t)
;=> (l (i s) t)

Ovde se vidi definisanje variajbli i korišćenje njih da bi izgledale ovako

(def hello-world "Hello world")
;=> #'user/hello-world

hello-world
;=> "Hello world"

Idem veoma brzo, preskačem mnogo detalja i možda neke stvari nisu potpuno ispravne. Nosite se samnom, zelim da dođem što pre do dobrih stvari.

Kod Clojure možete napraviti ovakve funkcije:

(fn [n] (* n 2))
;=> #<user$eval1$fn__2 user$eval1$fn__2@175bc6c8>

Ta dugačka ružna stvar jetse način na koji je kompajlirana funkcija štmpana. Ne brinite, to nije nešto što često viđate. To je funkcija, koju je operater napravio kao fn, kao jedan argument, tako zvano n, koje multilicira argument put dva i vraća rezultat. Kod Clojure kao i kod Lisp, vrednost poslednjeg izražaja funkcije se vraća.

Ako želite da znate kako se funkcija naziva:

(println "Hello World")

primetićete obrazac, otvoreni obrazac, funkcije, argumente, zatvorene zagrade. Ili ako želite na drugačiji način da kažete, lista gde je prva stavka operater, a ostalo su argumenti.

Hajde da pozovemo tu funkciju:

((fn [n] (* n 2)) 10)
;=> 20

Šta tamo radim jeste definisanje anonimne funkcije i direktno njeno primenjianje. Hajde da tu funkciju imenujemo:

(def twice (fn [n] (* n 2)))
;=> #'user/twice

i onda je po imenu možemo koristiti:

(twice 32)
;=> 64

Kao što možete da vidite, funkcije su sačuvane kao varijable kao bi podatak. Pošto je to nešto što se često dešava, postoji skraćenica:

(defn twice [n] (* 2 n))
;=> #'user/twice

(twice 32)
;=> 64

Hajde da napravimo da funkcija ima maksimum 100 ako koristi ako:

(defn twice [n] (if (> n 50) 100 (* n 2))))

Operator ako ima tri arguenta, predikat, ekspresija vrednosti za procenjivanje kada je predikat tačan i jedan kada je netačan. Možda je na taj način lakše sve pročitati:

(defn twice [n]
  (if (> n 50)
      100
      (* n 2)))

Dosta o osnovnom stvarim, hajde da pređemo na nešto interesantno.

Hajde da pretpostavimo da želite da napišete Lisp unazad. Operator je na zadnjoj poziciji, kao npr:

(4 5 +)

Hajde da ovaj jezik nazovemo Psil (to je Lisp unazad… kako sam pametan). Očigledno, ako ga samo pokrenete neće raditi:

(4 5 +)
;=> java.lang.ClassCastException: java.lang.Integer cannot be cast to clojure.lang.IFn (NO_SOURCE_FILE:0)

To vam Clojure govori da 4 nije funkcija (implementirani objekat je interface clojure.lang.IFn).

Lako je napisati funkciju koja prebacuje iz Psil u Lisp:

(defn psil [exp]
  (reverse exp))

Probem je, kada pokušam da ga koristim, kao ovo:

(psil (4 5 +))
;=> java.lang.ClassCastException: java.lang.Integer cannot be cast to clojure.lang.IFn (NO_SOURCE_FILE:0)

Očigledno je greška, jer pre nego je psil pozvan, Clojure pokušava da proceni argument to je , (4 5 +) i to nije uspelo. Možemo posebno to pozvati pozivajući se na argument sa liste, kao npr:

(psil '(4 5 +))
;=> (+ 5 4)

ali ga to ne procenjuje, samo rezerviše. Evaluacija nije toliko teška:

(eval (psil '(4 5 +)))
;=> 9

Obično završite tako što što ih sabere. Počinjete da uviđate snagu LIsp. Činjenica da je kod samo gomila nagomilanih lista, dozvoljava vam da lako generišete radni program van komada programa.

Ako to ne vidite, samo pokušajte to da uradite u vašm omiljenom jeziku. Počnite sa strelicom koja sadrži dva broja i plus, i završi se tako što koncentrišete nizove ili radite druge prljave stvari.

Ovakav način programiranja je tako uobičajen za Lisp da je to u stvari apstraktni način za korišćenu stvar zvanu macros. Macros su funkcije koje primaju neprocenjene argumente, pa se onda rezultat procenjuje sa Lisp.

Hajde da prebacimo psil u macro:

(defmacro psil [exp]
  (reverse exp))

Jedina razlika je da ja sada pozivam defmacro umesto defn. To je stvarno izuzetno:

(psil (4 5 +))
;=> 9

Pogledajte kako argument nije validan Clojure ako nema grške. To je zato što nije procenjen dok ga psil ne procesuira. Ovaj psil makro uzima argument kao podatak. Kada čujete da ljudi govore da je Lisp kod podatak, to je ono o čemu se ovde govori. Podacima možete manipulisati da biste generisali druge programe. To je ono što vam dozvoljava da izmislite vaš sopstveni programski jezik na Lisp i imate bilo koju željenu semantiku.

Postoji operater kod Clojure nazvan macroexpand koji pravi makro preseke procena, tako da možete da vidite šta je kod koji će biti generisan:

(macroexpand '(psil (4 5 +)))
;=> (+ 5 4)

Možete razmišljati o makro kao funkciji koja radi u kompajlirano vreme. Istina je da, Lisp, kompajlira i pokreće vreme na mešani način stalno menjajući ova dva. Možemo napraviti naš psil makro kao verbose da bi se videlo šta se dešava, ali pre toga trebalo bi proveriti.

do je veoma jednostavan operater, ima listu komresija i pokreće ih jednu za drugom, a one su sve grupisane u jednu jedinstvenu ekspresiju koju možete nositi okolo, na primer:

(do (println "Hello") (println "world"))
; Hello
; world
;=> nil

Sa do, možemo napraviti da se makro vraća više od jednoj ekspresiji i da napravi vrbose:

(defmacro psil [exp]
  (println "compile time")
  `(do (println "run time")
       ~(reverse exp)))

Ti novi makro printovi “compile time” i vraća se kao do koji štampa “run time” i pokreće exp unazad. Ovaj back-tick, ` je kao citiranje ' osim što vam dozvoljava da ne citirate unutar korišćenjem tilde, ~. Ne brinite ako još ništa ne razumete, hajde da ga pokrenemo:

(psil (4 5 +))
; compile time
; run time
;=> 9

Kao što smo i očekivali, kompajlirano vreme se dešava pre pokrenutog. Ako koristimo macroexpand stvari će postati jasnije:

(macroexpand '(psil (4 5 +)))
; compile time
;=> (do (clojure.core/println "run time") (+ 5 4))

Videćete da se kompajlirana faza već dogodila i došli smo do zaključka da će štampati “run time” i onda proceniti (+ 5 4). Takođe se println produžilo u potpuni oblik, clojure.core/println, ali i to možete da ignorišete. Kada se to dogodi kod je procenjen u toku radnog vremena.

Rezultat makro je ključan:

(do (println "run time")
    (+ 5 4))

i u makro sve se ovako piše:

`(do (println "run time")
     ~(reverse exp))

Ovaj back-tick je na kraju napravio vrstu tempalta gde je tržište deo procesa procenjivanja ((reverse exp)) dok je ostalo ostavljeno.

Ima još više iznenađenja iza makro, ali za sada dosta je zahvata hokus pokus.

Snaga ove tehnike možda nije još potpuno upoznata. Pratite me na Why I love Smalltalk post, hajde da zamislimo da Clojure nije došao sa samo jednim if, već i sa cond. Ovo nije najbolji primer, ali je i pored toga dovoljno jednostavan.

cond je kao switch ili case kod drugih jezika:

(cond (= x 0) "It's zero"
      (= x 1) "It's one"
      :else "It's something else")

Oko cond možemo napraviti funkciju my-if bukvalno odmah::

(defn my-if [predicate if-true if-false]
  (cond predicate if-true
        :else if-false))

ii na početku izgleda ko da radi:

(my-if (= 0 0) "equals" "not-equals")
;=> "equals"
(my-if (= 0 1) "equals" "not-equals")
;=> "not-equals"

ali postoji problem. Da li ga možete uočiti? my-if procenjuju sve argumente, tako da ako nešto ovako uradimo, rezultat nije ono što mi očekujemo:

(my-if (= 0 0) (println "equals") (println "not-equals"))
; equals
; not-equals
;=> nil

Konvertovanje my-if u macro:

(defmacro my-if [predicate if-true if-false]
  `(cond ~predicate ~if-true
         :else ~if-false))

rešava problem:

(my-if (= 0 0) (println "equals") (println "not-equals"))
; equals
;=> nil

Ovo je samo kratak pregled snage makroa. Jedan veoma zanimljiv slučaj jeste kada objektno orijentisani programi su bili napravljeni (Lisp je stariji od toga ) i Lisp programeri u hteli da ga koriste.

C programeri su morali da izume nove jezike, C++ i Objective C, sa kompajlerima. Lips programeri su napravili gomili makro, kao defclass, defmethod, itd. I zahvaljujući makro revolucija kod Lisp se tek zahuktava.

Zahvalni Gonzalo Fernández, Alessandro Di Maria , Vladimir Filipović zbog čitanja kratkog opisa ovoga.




Published (Last edited): 10-12-2012 , source: http://pupeno.com/2011/08/16/why-i-love-lisp/