KMI/PRCL FORMAT, LOOP, packages, QuickLoad
Ještě chvilku makra (defmacro with-gensyms (vars &body body) `(let,(loop for v in vars collect `(,v (gensym))),@body)) (defmacro once-only ((&rest names) &body body) (let ((gensyms (loop for n in names collect (gensym)))) `(with-gensyms (,@gensyms) `(let (,,@(loop for g in gensyms for n in names collect ``(,,g,,n))),(let (,@(loop for n in names for g in gensyms collect `(,n,g))),@body))))) (defmacro numeric-if (expr pos zero neg) (with-gensyms (val) `(let ((,val,expr)) (cond ((plusp,val),pos) ((zerop,val),zero) (t,neg)))) (defmacro square (x) (once-only (x) `(*,x,x))) nešlo by to ještě lépe?
defmacro/g! (defun g!-symbol-p (s) (and (symbolp s) (> (length (symbol-name s)) 2) (string= (symbol-name s) "G!" :start1 0 :end1 2))) (defmacro defmacro/g! (name args &body body) (let ((syms (remove-duplicates (remove-if-not #'g!-symbol-p (flatten body))))) `(defmacro,name,args (let,(mapcar (lambda (s) `(,s (gensym,(subseq (symbol-name s) 2)))) syms),@body))))
defmacro/g! (defmacro/g! nif (expr pos zero neg) `(let ((,g!result,expr)) (cond ((plusp,g!result),pos) ((zerop,g!result),zero) (t,neg)))) > (macroexpand-1 '(defmacro/g! nif (expr pos zero neg) `(let ((,g!result,expr)) (cond ((plusp,g!result),pos) ((zerop,g!result),zero) (t,neg))))) (DEFMACRO NIF (EXPR POS ZERO NEG) (LET ((G!RESULT (GENSYM "RESULT"))) `(LET ((,G!RESULT,EXPR)) (COND ((PLUSP,G!RESULT),POS) ((ZEROP,G!RESULT),ZERO) (T,NEG)))))
defmacro! (defun o!-symbol-p (s)...) (defun o!-symbol-to-g!-symbol (s) (symb "G!" (subseq (symbol-name s) 2))) (defmacro defmacro! (name args &body body) (let* ((os (remove-if-not #'o!-symbol-p args)) (gs (mapcar #'o!-symbol-to-g!-symbol os))) `(defmacro/g!,name,args `(let,(mapcar #'list (list,@gs) (list,@os)),(progn,@body)))))
defmacro! (defmacro! square (o!x) `(*,g!x,g!x)) > (macroexpand '(square (incf x))) (LET ((#:X848 (INCF X))) (* #:X848 #:X848)) (defmacro! square (o!x) `(progn (format t "[~a results in ~a]" ',o!x,g!x) (* g!x g!x))) > (let ((x 5)) (square (incf x))) [(INCF X) results in 6] 36 nif?
Ještě chvilku printer nejmocnější funkce pro tisk: FORMAT (jazyk v jazyku :-) format destination control-string &rest args => result destination: nil, t, stream, string result: nil (pro destination různé od nil) daný string (pro destination nil) control-string formátovací řetězec (mnohem! bohatší než printf)
control-string ochutnávka direktivy začínají ~ následují nepovinné parametry znak dané direktivy > (format t "~$" pi) 3.14 > (format t "~5$" pi) 3.14159
Direktivy direktivy začínají ~ nepovinné parametry oddělují se čárkou buď přímo hodnota nebo znaky v či # nepovinné modifikátory znak : či @ znak dané direktivy > (format t "~$" pi) 3.14 > (format t "~5$" pi) 3.14159 > (format t "~v$" 3 pi) 3.142 > (format t "~#$" pi) 3.1 > (format t "~d" 1000000) 1000000 > (format t "~:d" 1000000) 1,000,000 > (format t "~@d" 1000000) +1000000 > (format t "~:@d" 1000000) +1,000,000 > (format t "~,5F" pi) 3.14159
Základní formátování ~A (aesthetic) human-readable form (format nil "The value is: ~a" 10) ==> "The value is: 10" (format nil "The value is: ~a" "foo") ==> "The value is: foo" (format nil "The value is: ~a" (list 1 2 3)) ==> "The value is: (1 2 3) (format nil ~a ~:a nil nil) ==> "NIL ()" ~S forma zpětně čitelná readerem (netisknutelné obj. <# >) ~A, ~S: až čtyři parametry pro formátování (padding) ~% newline ~& freshline (newline jen když je potřeba) ~~ tilda
Tisk znaků ~C totéž jako ~A, ale jen pro znaky ~:C netisknutelné znaky tiskne jako jména (format t "Syntax error. Unexpected character: ~:c" char) ==> Syntax error. Unexpected character: a ==> Syntax error. Unexpected character: Space ~@C tisk v Common Lisp notaci (format nil "Znak ~C je v CL ~@c" #\A #\A) ==> Znak A je v CL #\A ~:@C dodatečná informace, jak daný znak napsat na klávesnici ne vždy přesné ne každá implementace obsahuje (format nil "~:@c" (code-char 0)) ==> "^@ (Control @)"
Tisk celých čísel pomocí ~A, ~S nebo pomocí speciálních direktiv ~D, ~X, ~O, ~B, ~R ~D tisk čísel v desítkové soustavě první parametr: minimální počet znaků výstupu druhý parametr: znak pro padding (výchozí je mezera) (format nil "~12d" 1000000) ==> " 1000000" (format nil "~12,'0d" 1000000) ==> 000001000000" třetí a čtvrtý parametr ovlivňují výpis s modifikátorem : (format nil "~:d" 100000000) ==> "100,000,000" (format nil "~,,'.,4:d" 100000000) ==> 1.0000.0000 ~X, ~O, ~B tisk čísel v hexadecimální, oktálové, binární soustavě ~R tisk čísla v zadané číselné soustavě + speciální chování (viz dále)
Tisk floating-point čísel pomocí ~A, ~S nebo pomocí speciálních direktiv ~F, ~E, ~G, ~$ ~F fixed-format tiskne v decimálním formátu pokud je číslo příliš malé nebo velké, použije se vědecká notace (format nil "~f" pi) ==> "3.141592653589793d0" (format nil "~,4f" pi) ==> "3.1416" ~E exponential tiskne vždy ve vědecké notaci (format nil "~e" pi) ==> "3.141592653589793d+0" (format nil "~,4e" pi) ==> 3.1416d+0" ~$ monetary zjednodušení ~F parametry naopak než u ~F (format nil "~$" pi) ==> "3.14" (format nil "~2,4$" pi) ==> "0003.14"
Jazykové hrátky ~R číslo slovně (format nil "~r" 1234) ==> "one thousand two hundred thirty-four" ~:R pořadové číslo slovně (format nil "~:r" 1234) ==> "one thousand two hundred thirty-fourth" ~@R, ~:@R číslo pomocí římských číslic (format nil "~@r" 1234) ==> "MCCXXXIV" (format nil "~:@r" 1234) ==> "MCCXXXIIII" ~P množné číslo, s dvojtečkou znovu zpracuje předchozí argument (format nil "file~p" 1) ==> "file" (format nil "file~p" 10) ==> "files" (format nil "file~p" 0) ==> files (format nil "~r file~:p" 1) ==> "one file" (format nil "~r file~:p" 10) ==> "ten files" (format nil "~r file~:p" 0) ==> "zero files" (format nil "~r famil~:@p" 1) ==> "one family" (format nil "~r famil~:@p" 10) ==> "ten families" (format nil "~r famil~:@p" 0) ==> "zero families"
Jazykové hrátky ~( ~) změna velikosti písmen (format nil "~(~a~) ~a" "FOO" "FOO") ==> foo FOO (format nil "~(~@r~)" 124) ==> "cxxiv" (format nil "~(~a~)" "the Quick BROWN fox") ==> "the quick brown fox (format nil "~@(~a~)" "the Quick BROWN fox") ==> "The quick brown fox (format nil "~:(~a~)" "the Quick BROWN fox") ==> "The Quick Brown Fox (format nil "~:@(~a~)" "the Quick BROWN fox") ==> "THE QUICK BROWN FOX"
Podmíněné formátování ~[ první; druhá; ~] na základě argumentu se vybere jedna část, která je dále zpracována formatem (format nil "~[cero~;uno~;dos~]" 0) ==> "cero" (format nil "~[cero~;uno~;dos~]" 1) ==> "uno" (format nil "~[cero~;uno~;dos~]" 2) ==> dos" (format nil "~[cero~;uno~;dos~]" 3) ==> " (format nil "~[cero~;uno~;dos~:;mucho~]" 100) ==> "mucho" využití # (počet zbývajících argumentů) (defparameter *list-etc* "~#[NONE~;~a~;~a and ~a~:;~a, ~a~]~#[~; and ~a~:;, ~a, etc~]. ) (format nil *list-etc*) ==> "NONE." (format nil *list-etc* 'a) ==> "A." (format nil *list-etc* 'a 'b) ==> "A and B." (format nil *list-etc* 'a 'b 'c) ==> "A, B and C." (format nil *list-etc* 'a 'b 'c 'd) ==> "A, B, C, etc." (format nil *list-etc* 'a 'b 'c 'd 'e) ==> "A, B, C, etc."
Podmíněné formátování modifikátor : jen dvě části oddělené ~; vybere se první, pokud je argument nil; jinak druhá (format t "~:[FAIL~;pass~]" test-result) modifikátor @ jen jedna část pokud není argument nil, tak se část zpracuje; argument je k dispozici (format nil "~@[x = ~a ~]~@[y = ~a~]" 10 20) ==> "x = 10 y = 20" (format nil "~@[x = ~a ~]~@[y = ~a~]" 10 nil) ==> "x = 10 " (format nil "~@[x = ~a ~]~@[y = ~a~]" nil 20) ==> "y = 20" (format nil "~@[x = ~a ~]~@[y = ~a~]" nil nil) ==> ""
Iterace ~{ tělo ~} argument musí být seznam postupně bere prvky seznamu a vyhodnocuje tělo (format nil "~{~a, ~}" (list 1 2 3)) ==> "1, 2, 3, ~^ přerušení vyhodnocení těla při posledním prvku seznamu (format nil "~{~a~^, ~}" (list 1 2 3)) ==> "1, 2, 3 modifikátor @ zbytek argumentů se zpracuje jako seznam (format nil "~@{~a~^, ~}" 1 2 3) ==> "1, 2, 3 parametr # uvnitř iterace počet prvků zbývajících ke zpracování lze využít s ~[ ~]
FORMAT-uji, -uješ, -uje ~* zkonzumuje argument bez tisku ~:* umožní předchozí argument znovu použít (format nil "~r ~:*(~d)" 1) ==> "one (1)" ~* uvnitř ~{ ~} umožňuje skákat mezi prvky procházeného seznamu ~/ zavolá určenou funkci pro zpracování argumentu a spousta dalších http://www.lispworks.com/documentation/lw50/clhs/body/22_c.htm
L p REPL (loop (print (eval (read))))
Funkce loop mocný nástroj pro cykly jazyk v jazyku (podobně jako format) makro loop umožňuje: cyklit přes numerické hodnoty nebo prvky datových struktur sbírat, počítat, sčítat, minimalizovat či maximalizovat hodnoty, které se objeví v průběhu cyklení vyhodnocovat libovolné s-výrazy řídit ukončení cyklu vytvářet lokální proměnné a další
Iterace většinou začíná klíčovým slovem for nebo as umožňuje iterovat přes: interval čísel, vzestupně i sestupně prvky seznamu cons-buňky tvořící seznam prvky vektorů (včetně např. znaků tvořících řetězec) dvojic klíč-hodnota v hash tabulkách výsledky opakovaného vyhodnocení s-výrazu lze mít více klauzulí for iterují se postupně podle výskytu cyklus končí, jakmile jedna z částí for skončí (loop for item in list for i from 1 to 10 do (something))
Iterace (interval čísel) za slovem for jedna až tři z následujících částí následovaná hodnotou: odkud: from, downfrom, upfrom kam: to, upto (<=), below (<), downto, above o kolik: by výchozí hodnoty: 0 (loop for i upto 10 collect i) při odčítání nutno specifikovat, že se bude odčítat (loop for i from 20 to 10...) ; tělo se neprovede ani jednou (loop for i from 20 downto 10 ) or (loop for i downfrom 20 to 10 ) namísto for lze použít repeat počet-opakování-těla
Iterace (kolekce) Za slovem for: in (seznamy) (loop for i in (list 10 20 30 40) collect i) ==> (10 20 30 40) in by (loop for i in (list 10 20 30 40) by #'cddr collect i) ==> (10 30) on (přes cons-buňky) (loop for x on (list 10 20 30) collect x) ==> ((10 20 30) (20 30) (30)) on by (loop for x on (list 10 20 30 40) by #'cddr collect x) ==> ((10 20 30 40) (30 40)) across (vektory) (loop for x across "abcd" collect x) ==> (#\a #\b #\c #\d) being (hash tabulky, packages) (loop for var being things in hash-or-package ) (loop for k being the hash-keys in h...)
Iterace (equals-then) ve tvaru (loop for var = initial-value-form [ then step-form ]...) při první iteraci je hodnota var dána hodnotou initial-value-form při dalších iteracích se vyhodnotí step-form a výsledek se naváže na var pokud část then chybí, při každé iteraci se znovu vyhodnotí initial-value-form (loop repeat 5 for x = 0 then y for y = 1 then (+ x y) ;zde se využívá už nová hodnota x collect y) ==> (1 2 4 8 16) (loop repeat 5 for x = 0 then y and y = 1 then (+ x y) ;pracuje se starou hodnotou x collect y) ==> (1 1 2 3 5)
Práce s proměnnými with var [ = value-form ] lokální proměnné implicitní destructuring-bind (loop for (a b) in '((1 2) (3 4) (5 6)) do (format t "a: ~a; b: ~a~%" a b)) a: 1; b: 2 a: 3; b: 4 a: 5; b: 6 NIL (loop for cons on list do (format t "~a" (car cons)) (loop for (item. rest) on list do (format t "~a" item) when (cdr cons) do (format t ", )) when rest do (format t ", ")) (loop for (a nil) in '((1 2) (3 4) (5 6)) collect a) ==> (1 3 5)
Akumulace hodnot verb form [ into var ], kde verb je: collect, collecting sbírá hodnoty do seznamu append, appending, nconc, nconcing count, counting počet non-nil hodnot sum, summing maximize, maximizing, minimize, minimizing (defparameter *random* (loop repeat 100 collect (random 10000))) (loop for i in *random* counting (evenp i) into evens counting (oddp i) into odds summing i into total maximizing i into max minimizing i into min finally (return (list min max total evens odds)))
Vyhodnocování s-výraziva do, doing (loop for i from 1 to 10 do (print i)) return, return-from (+ pojmenování loop bloku) (block outer (loop for i from 0 return 100) ; 100 returned from LOOP (print "This will print") 200) ==> 200 (block outer (loop for i from 0 do (return-from outer 100)) ; 100 returned from BLOCK (print "This won't print") 200) ==> 100 (loop named outer for list in lists do (loop for item in list do (if (what-i-am-looking-for-p item) (return-from outer item))))
Podmíněná exekuce klauzule do => normální lisp (if, when) (loop for i from 1 to 10 do (when (evenp i) (print i))) takto však nelze podmíněně vyhodocovat collect atd. v rámci loop lze využít if, when, unless (dokonce jsou anaforické) (loop for i from 1 to 10 when (evenp i) sum i) ==> 30 (loop for key in some-list when (gethash key some-hash) collect it)
Drobky na závěr initially, finally mohou se odkazovat na proměnné cyklu initially proběhne vždy termination tests while, until podmínka jemnější než return, provede se finally always, never podmínka testují podmínku, celý loop vrací t/nil (if (loop for n in numbers always (evenp n)) (print "All numbers even. )) (if (loop for n in numbers never (oddp n) (print "All numbers even.")) thereis non-nil hodnota způsobí ukončení cyklu a návrat této hodnoty (loop for char across "abc123" thereis (digit-char-p char)) ==> 1 (loop for char across "abcdef" thereis (digit-char-p char)) ==> NIL
iterate knihovna, https://common-lisp.net/project/iterate loop na steroidech, lze plně programovat instalace: ruční, nebo lépe pomocí quicklispu nejdelší seznam v seznamu seznamů: (iterate (for elt in list-of-lists) (finding elt maximizing (length elt))) pomocí loop: (loop with max-elt = nil with max-key = 0 for elt in list-of-lists for key = (length elt) do (when (> key max-key) (setq max-elt elt max-key key)) finally (return max-elt))
Packages text => symbol => intern v aktuální package dvě různé knihovny, obě mají funkci hello-world jak se vyhnout kolizím názvů? package: tabulka mapující řetězce na symboly (+ něco navíc) package má své jméno vždy se nacházíme v nějaké package: *package*
find-symbol, intern find-symbol hledá v package symbol pro daný textový řetězec lze zadat i ve které package má hledat vrací symbol nebo nil intern podobně jako find-symbol pokud nenalezne odpovídající symbol, vytvoří vazbu v package
(ne)kvalifikovaná jména nekvalifikovaná jména jména symbolů bez dvojteček reader převede na uppercase a předá intern => získá symbol pracuje s aktuální package kvalifikovaná jména obsahují jednu nebo dvě dvojtečky reader rozdělí název na jméno package a jméno symbolu jedna dvojtečka symbol musí být exportovaný z dané package dvě dvojtečky lze přistoupit i k neexportovaným symbolům speciální chování: keywords :FOO, do-not-intern symboly #:FOO
Základní pojmy accessible symbol lze v dané package najít pomocí find-symbol buď je přímo v package je importovaný z jiné package pro dané jméno lze mít jen jeden přístupný symbol při importu symbolu z jiné package lze zastínit jiný symbol
Základní packages COMMON-LISP-USER (CL-USER) využívá CL COMMON-LISP (CL) exportuje všechna jména ze standardu CL KEYWORD slouží k internování keywords symboly v REPLu nové symboly se internují v CL-USER standardní funkce, makra se berou z CL pokud by CL-USER nepoužívala CL: (cl:defun add-2 (x) (cl:+ x 2))
Vlastní package (for kittehz) užití: chceme náš program, knihovnu, atd. dát k všeobecnému užitku zabalíme do package exportované symboly tvoří API nelze vynutit zákaz přístupu k internals (dvojdvojtečková notace) CL-USER> (defpackage :meow.kittehzunited.mouse-catch-helper (:use :common-lisp)) CL-USER> (in-package :meow.kittehzunited.mouse-catch-helper) MOUSE-CATCH-HELPER> ;změna aktuální package
and for dogz too! CL-USER> (defun hello-world () (princ Woof")) CL-USER> (in-package :meow.kittehzunited.mouse-catch-helper) MOUSE-CATCH-HELPER> (defun hello-world () (princ " ")) MOUSE-CATCH-HELPER> (hello-world) MOUSE-CATCH-HELPER> (in-package :cl-user) CL-USER> (hello-world) Woof CL-USER> (meow.kittehzunited.mouse-catch-helper::hello-world)
itteh bitteh kitteh committeh recommendz CL-USER> (defpackage :meow.kittehzunited.mouse-catch-helper (:use :common-lisp) (:export :find-mouse :lock-target :catch)) ;využití celé package v jiné package: CL-USER> (defpackage :meow.kittehzunited.kittehz-utils (:use :common-lisp :meow.kittehzunited.mouse-catch-helper)) ;cherry-picking symbolů pro využití v dané package: CL-USER> (defpackage :meow.kittehzunited.kittehz-advanced-utils (:use :common-lisp) (:import-from :meow.kittehzunited.nap :sleep-on-keyboard))
itteh bitteh kitteh committeh recommendz CL-USER> (defpackage :meow.kittehzunited.kittehz-advanced-utils (:use :common-lisp :org.animalz.stuff :meow.kittehzunited.core) (:import-from :meow.kittehzunited.nap :sleep-on-keyboard) (:shadow :bark) ;zastíníme symbol štěkat (:shadowing-import-from :meow.kittehzunited.core :walk))
cave-eats CL-USER> (meow) The function COMMON-LISP-USER::MEOW is undefined. [Condition of type UNDEFINED-FUNCTION] CL-USER> (use-package :meow.kittehzunited.core) Using package `CORE' results in name conflicts for these symbols: meow [Condition of type PACKAGE-ERROR]
QuickLisp https://www.quicklisp.org/beta/ library manager for Common Lisp aktuálně přes 1200 knihoven kompatibilní s velkým množstvím implementací CL
QuickLisp HowTo instalace: stáhnout quicklisp.lisp a načíst jej $curl -O https://beta.quicklisp.org/quicklisp.lisp $sbcl --load quicklisp.lisp This is SBCL 1. ==== quicklisp quickstart loaded ==== To continue, evaluate: (quicklisp-quickstart:install) * (quicklisp-quickstart:install)
QuickLisp HowTo použití: je třeba načíst quicklisp.lisp (load "~/quicklisp/setup.lisp") lze zautomatizovat přidáním do konfiguračního skriptu (ql:add-to-init-file) načtení knihovny pomocí ql:quickload (ql:quickload vecto ) automaticky stáhne a načte všechny knihovny, na kterých daná knihovna závisí ql:uninstall, ql:system-apropos, ql:update-all-dists