Share this page

Learn X in Y minutes

Where X=Common Lisp

Common Lisp ialah programming language yang general-purpose (boleh digunakan untuk semua benda) dan multi-paradigm (konsep yang pelbagai) sesuai untuk pelbagai kegunaan di dalam industri aplikasi. Common Lisp biasa digelar sebagai programmable programming language (programming language yang boleh di-program-kan).

Sumber bacaan yang klasik ialah Practical Common Lisp. Sumber bacaan yang lain dan yang terbaru ialah Land of Lisp. Buku baru mengenai best practices (amalan terbaik), Common Lisp Recipes, baru sahaja diterbitkan.

;;;-----------------------------------------------------------------------------
;;; 0. Syntax
;;;-----------------------------------------------------------------------------

;;; General form (Bentuk umum)

;;; Ada dua asas dalam syntax CL: ATOM dan S-EXPRESSION.
;;; Kebiasaannya, gabungan S-expression dipanggil sebagai `forms`.

10            ; atom; bermaksud seperti yang ditulis iaitu nombor 10
:thing        ; juga atom; bermaksud simbol :thing
t             ; juga atom, bermaksud true (ya/betul/benar)
(+ 1 2 3 4)   ; s-expression
'(4 :foo t)   ; juga s-expression


;;; Comment (Komen)

;;; Comment satu baris bermula dengan semicolon; gunakan empat untuk comment
;;; mengenai file, tiga untuk seksyen penghuraian, dua untuk yang dalam definition,
;;; dan satu untuk satu baris. Sebagai contoh,

;;;; life.lisp

;;; Foo bar baz, disebabkan quu quux. Sangat optimum untuk krakaboom dan umph.
;;; Diperlukan oleh function LINULUKO. Ini merepek sahaja kebaboom.

(defun meaning (life)
  "Memulangkan hasil pengiraan makna KEHIDUPAN"
  (let ((meh "abc"))
    ;; Jalankan krakaboom
    (loop :for x :across meh
       :collect x)))                    ; Simpan hasil ke x, kemudian pulangkan

;;; Komen berbentuk blok, sebaliknya, membenarkan komen untuk bentuk bebas. Komen
;;; tersebut berada di antara #| dan |#

#| Ini adalah komen berbentuk blok di mana
   tulisan boleh ditulis dalam beberapa baris dan
    #|
       juga boleh dalam bentuk nested (berlapis-lapis)!
    |#
|#


;;; Environment (benda-benda yang diperlukan untuk program menggunakan Common Lisp)

;;; Common Lisp ada banyak jenis; kebanyakannya mengikut standard. SBCL
;;; ialah titik permulaan yang baik. Quicklisp boleh digunakan untuk install 
;;; library third party.

;;; CL kebiasaannya digunakan dengan text editor dan Real Eval Print
;;; Loop (REPL) yang dilancarkan dengan serentak. REPL membolehkan kita menjelajah
;;; program secara interaktif semasa program tersebut sedang berjalan secara "live".


;;;-----------------------------------------------------------------------------
;;; 1. Datatype primitif dan operator
;;;-----------------------------------------------------------------------------

;;; Simbol

'foo ; => FOO  Perhatikan simbol menjadi huruf besar secara automatik.

;;; INTERN menjadikan string sebagai simbol secara manual.

(intern "AAAA")        ; => AAAA
(intern "aaa")         ; => |aaa|

;;; Nombor

9999999999999999999999 ; integer
#b111                  ; binary => 7
#o111                  ; octal => 73
#x111                  ; hexadecimal => 273
3.14159s0              ; single
3.14159d0              ; double
1/2                    ; ratio
#C(1 2)                ; complex number

;;; Function ditulis sebagai (f x y z ...) di mana f ialah function dan
;;; x, y, z, ... adalah argument.

(+ 1 2)                ; => 3

;;; Jika anda ingin membuat data sebagai data bukannya function, gunakan QUOTE
;;; untuk mengelakkan data tersebut daripada dikira oleh program

(quote (+ 1 2))        ; => (+ 1 2)
(quote a)              ; => A

;;; Singkatan untuk QUOTE ialah ' (tanda petikan)

'(+ 1 2)               ; => (+ 1 2)
'a                     ; => A

;;; Operasi arithmetic asas

(+ 1 1)                ; => 2
(- 8 1)                ; => 7
(* 10 2)               ; => 20
(expt 2 3)             ; => 8
(mod 5 2)              ; => 1
(/ 35 5)               ; => 7
(/ 1 3)                ; => 1/3
(+ #C(1 2) #C(6 -4))   ; => #C(7 -2)

;;; Boolean

t                      ; true; semua nilai yang bukan NIL ialah true
nil                    ; false; termasuklah list yang kosong: ()
(not nil)              ; => T
(and 0 t)              ; => T
(or 0 nil)             ; => 0

;;; Character

#\A                    ; => #\A
#\λ                    ; => #\GREEK_SMALL_LETTER_LAMDA
#\u03BB                ; => #\GREEK_SMALL_LETTER_LAMDA

;;; String ialah array character yang tidak berubah panjang

"Hello, world!"
"Benjamin \"Bugsy\" Siegel"   ; backslash ialah escape character

;;; String boleh digabungkan

(concatenate 'string "Hello, " "world!") ; => "Hello, world!"

;;; String boleh diperlakukan seperti urutan character

(elt "Apple" 0) ; => #\A

;;; FORMAT digunakan untuk output mengikut format, daripada penggubahan string
;;; yang simple sehinggalah loop dan conditional. Argument pertama untuk FORMAT
;;; menentukan ke mana string akan pergi. Jika NIL, FORMAT
;;; akan pulangkan string sebagai data string; jika T, FORMAT akan output
;;; ke standard output, biasanya di screen, kemudian pulangkan NIL.

(format nil "~A, ~A!" "Hello" "world")   ; => "Hello, world!"
(format t "~A, ~A!" "Hello" "world")     ; => NIL


;;;-----------------------------------------------------------------------------
;;; 2. Variable
;;;-----------------------------------------------------------------------------

;;; Anda boleh membuat variable global (dynamically scoped) menggunakan DEFVAR dan
;;; DEFPARAMETER. Nama variable boleh guna mana-mana character kecuali: ()",'`;#|\

;;; Beza antara DEFVAR dengan DEFPARAMETER ialah DEFVAR tidak akan ubah nilai
;;; variable jika dijalankan semula. Manakala DEFPARAMETER, akan mengubah nilai
;;; jika dijalankan semula.

;;; Kebiasaannya, variable global diletakkan earmuff (asterisk) pada nama.

(defparameter *some-var* 5)
*some-var* ; => 5

;;; Anda juga boleh menggunakan character unicode.
(defparameter *AΛB* nil)

;;; Variable yang tidak wujud boleh diakses tetapi akan menyebabkan undefined
;;; behavior. Jangan buat.

;;; Anda boleh membuat local binding menggunakan LET. Dalam snippet berikut, `me`
;;; terikat dengan "dance with you" hanya dalam (let ...). LET mesti akan pulangkan
;;; nilai `form` yang paling terakhir.

(let ((me "dance with you")) me) ; => "dance with you"


;;;-----------------------------------------------------------------------------;
;;; 3. Struct dan collection
;;;-----------------------------------------------------------------------------;


;;; Struct

(defstruct dog name breed age)
(defparameter *rover*
    (make-dog :name "rover"
              :breed "collie"
              :age 5))
*rover*            ; => #S(DOG :NAME "rover" :BREED "collie" :AGE 5)
(dog-p *rover*)    ; => T
(dog-name *rover*) ; => "rover"

;;; DOG-P, MAKE-DOG, dan DOG-NAME semuanya dibuat oleh DEFSTRUCT secara automatik


;;; Pair

;;; CONS membuat pair. CAR dan CDR pulangkan head (kepala) dan tail (ekor) CONS-pair.

(cons 'SUBJECT 'VERB)         ; => '(SUBJECT . VERB)
(car (cons 'SUBJECT 'VERB))   ; => SUBJECT
(cdr (cons 'SUBJECT 'VERB))   ; => VERB


;;; List

;;; List ialah data structure linked-list, dihasilkan daripada pair CONS dan
;;; berakhir dengan NIL (atau '()) menandakan akhirnya list tersebut

(cons 1 (cons 2 (cons 3 nil)))     ; => '(1 2 3)

;;; LIST ialah constructor untuk memudahkan penghasilan list

(list 1 2 3)                       ; => '(1 2 3)

;;; Apabila argument pertama untuk CONS ialah atom dan argument kedua ialah
;;; list, CONS akan pulangkan CONS-pair baru dengan argument pertama sebagai
;;; item pertama dan argument kedua sebagai CONS-pair yang lain

(cons 4 '(1 2 3))                  ; => '(4 1 2 3)

;;; Gunakan APPEND untuk menggabungkan list

(append '(1 2) '(3 4))             ; => '(1 2 3 4)

;;; Atau CONCATENATE

(concatenate 'list '(1 2) '(3 4))  ; => '(1 2 3 4)

;;; List ialah type utama, jadi ada pelbagai function untuk mengendalikan
;;; list, contohnya:

(mapcar #'1+ '(1 2 3))             ; => '(2 3 4)
(mapcar #'+ '(1 2 3) '(10 20 30))  ; => '(11 22 33)
(remove-if-not #'evenp '(1 2 3 4)) ; => '(2 4)
(every #'evenp '(1 2 3 4))         ; => NIL
(some #'oddp '(1 2 3 4))           ; => T
(butlast '(subject verb object))   ; => (SUBJECT VERB)


;;; Vector

;;; Vector ialah array yang tidak berubah panjang

#(1 2 3) ; => #(1 2 3)

;;; Gunakan CONCATENATE untuk menggabungkan vector

(concatenate 'vector #(1 2 3) #(4 5 6)) ; => #(1 2 3 4 5 6)


;;; Array

;;; Vector dan string adalah sejenis array.

;;; 2D array

(make-array (list 2 2))         ; => #2A((0 0) (0 0))
(make-array '(2 2))             ; => #2A((0 0) (0 0))
(make-array (list 2 2 2))       ; => #3A(((0 0) (0 0)) ((0 0) (0 0)))

;;; Perhatian: nilai awal MAKE-ARRAY adalah bergantung kepada jenis Common Lisp.
;;; Untuk meletakkan nilai awal secara manual:

(make-array '(2) :initial-element 'unset)  ; => #(UNSET UNSET)

;;; Untuk mengakses element di kedudukan 1, 1, 1:

(aref (make-array (list 2 2 2)) 1 1 1)     ;  => 0


;;; Adjustable vector (vector yang boleh berubah)

;;; Adjustable vector mempunyai rupa yang sama dengan
;;; vector yang tidak berubah panjang.

(defparameter *adjvec* (make-array '(3) :initial-contents '(1 2 3)
                                   :adjustable t :fill-pointer t))
*adjvec* ; => #(1 2 3)

;;; Tambah element baru

(vector-push-extend 4 *adjvec*)   ; => 3
*adjvec*                          ; => #(1 2 3 4)


;;; Set hanyalah list:

(set-difference '(1 2 3 4) '(4 5 6 7))   ; => (3 2 1)
(intersection '(1 2 3 4) '(4 5 6 7))     ; => 4
(union '(1 2 3 4) '(4 5 6 7))            ; => (3 2 1 4 5 6 7)
(adjoin 4 '(1 2 3 4))                    ; => (1 2 3 4)

;;; Tetapi, anda perlukan data structure yang lebih baik untuk digunakan dengan
;;; data set yang sangat banyak

;;; Kamus dibuat menggunakan hash table.

;;; Bina hash table

(defparameter *m* (make-hash-table))

;;; Tetapkan nilai

(setf (gethash 'a *m*) 1)

;;; Baca nilai

(gethash 'a *m*) ; => 1, T

;;; CL boleh memulangkan beberapa nilai (multiple value).

(values 1 2) ; => 1, 2

;;; dan boleh digunakan dengan MULTIPLE-VALUE-BIND untuk bind setiap nilai

(multiple-value-bind (x y)
    (values 1 2)
  (list y x))

; => '(2 1)

;;; GETHASH antara contoh function yang memulangkan multiple value. Value
;;; pertama ialah nilai untuk key dalam hash table; jika key tidak
;;; jumpa GETHASH akan pulangkan NIL.

;;; Value kedua menentukan sama ada key tersebut betul-betul wujud dalam hash
;;; table. Jika key tidak jumpa dalam table value tersebut ialah NIL. Cara ini
;;; membolehkan kita untuk periksa sama ada value untuk key ialah NIL.

;;; Dapatkan value yang tidak wujud akan pulangkan nil

(gethash 'd *m*) ;=> NIL, NIL

;;; Anda boleh menentukan value default untuk key yang tidak wujud

(gethash 'd *m* :not-found) ; => :NOT-FOUND

;;; Jom lihat penggunaan multiple return value di dalam code.

(multiple-value-bind (a b)
    (gethash 'd *m*)
  (list a b))
; => (NIL NIL)

(multiple-value-bind (a b)
    (gethash 'a *m*)
  (list a b))
; => (1 T)


;;;-----------------------------------------------------------------------------
;;; 3. Function
;;;-----------------------------------------------------------------------------

;;; Gunakan LAMBDA untuk membuat anonymous function. Function sentiasa memulangkan
;;; value untuk expression terakhir.

(lambda () "Hello World") ; => #<FUNCTION (LAMBDA ()) {1004E7818B}>

;;; Gunakan FUNCALL untuk memanggil anonymous function

(funcall (lambda () "Hello World"))   ; => "Hello World"
(funcall #'+ 1 2 3)                   ; => 6

;;; Panggilan kepada FUNCALL juga boleh terjadi apabila lambda tersebut ialah CAR
;;; (yang pertama) untuk list (yang tidak mempunyai tanda petikan)

((lambda () "Hello World"))           ; => "Hello World"
((lambda (val) val) "Hello World")    ; => "Hello World"

;;; FUNCALL digunakan apabila argument sudah diketahui. Jika tidak, gunakan APPLY

(apply #'+ '(1 2 3))   ; => 6
(apply (lambda () "Hello World") nil) ; => "Hello World"

;;; Untuk menamakan sebuah function, guna DEFUN

(defun hello-world () "Hello World")
(hello-world) ; => "Hello World"

;;; Simbol () di atas bermaksud list kepada argument

(defun hello (name) (format nil "Hello, ~A" name))
(hello "Steve") ; => "Hello, Steve"

;;; Function boleh ada argument optional (tidak wajib); argument tersebut bernilai
;;; NIL secara default

(defun hello (name &optional from)
  (if from
      (format t "Hello, ~A, from ~A" name from)
      (format t "Hello, ~A" name)))

(hello "Jim" "Alpacas")       ; => Hello, Jim, from Alpacas

;;; Nilai default boleh ditetapkan untuk argument tersebut

(defun hello (name &optional (from "The world"))
   (format nil "Hello, ~A, from ~A" name from))

(hello "Steve")               ; => Hello, Steve, from The world
(hello "Steve" "the alpacas") ; => Hello, Steve, from the alpacas

;;; Function juga mempunyai keyword argument untuk membolehkan argument diletakkan
;;; tidak mengikut kedudukan

(defun generalized-greeter (name &key (from "the world") (honorific "Mx"))
  (format t "Hello, ~A ~A, from ~A" honorific name from))

(generalized-greeter "Jim")
; => Hello, Mx Jim, from the world

(generalized-greeter "Jim" :from "the alpacas you met last summer" :honorific "Mr")
; => Hello, Mr Jim, from the alpacas you met last summer


;;;-----------------------------------------------------------------------------
;;; 4. Kesamaan
;;;-----------------------------------------------------------------------------

;;; CL mempunyai sistem kesaksamaan yang canggih. Antaranya adalah seperti berikut.

;;; Untuk nombor, guna `='
(= 3 3.0)               ; => T
(= 2 1)                 ; => NIL

;;; Untuk identiti object (lebih kurang) guna EQL
(eql 3 3)               ; => T
(eql 3 3.0)             ; => NIL
(eql (list 3) (list 3)) ; => NIL

;;; untuk list, string, dan bit-vector, guna EQUAL
(equal (list 'a 'b) (list 'a 'b)) ; => T
(equal (list 'a 'b) (list 'b 'a)) ; => NIL


;;;-----------------------------------------------------------------------------
;;; 5. Control Flow
;;;-----------------------------------------------------------------------------

;;; Conditional (syarat)

(if t                ; test expression
    "this is true"   ; then expression
    "this is false") ; else expression
; => "this is true"

;;; Dalam conditional, semua value yang bukan NIL ialah true

(member 'Groucho '(Harpo Groucho Zeppo)) ; => '(GROUCHO ZEPPO)
(if (member 'Groucho '(Harpo Groucho Zeppo))
    'yep
    'nope)
; => 'YEP

;;; Guna COND untuk meletakkan beberapa test
(cond ((> 2 2) (error "wrong!"))
      ((< 2 2) (error "wrong again!"))
      (t 'ok)) ; => 'OK

;;; TYPECASE adalah seperti switch tetapi untuk data type value tersebut
(typecase 1
  (string :string)
  (integer :int))
; => :int


;;; Loop

;;; Recursion

(defun fact (n)
  (if (< n 2)
      1
    (* n (fact(- n 1)))))

(fact 5) ; => 120

;;; Iteration

(defun fact (n)
  (loop :for result = 1 :then (* result i)
     :for i :from 2 :to n
     :finally (return result)))

(fact 5) ; => 120

(loop :for x :across "abc" :collect x)
; => (#\a #\b #\c #\d)

(dolist (i '(1 2 3 4))
  (format t "~A" i))
; => 1234


;;;-----------------------------------------------------------------------------
;;; 6. Mutation
;;;-----------------------------------------------------------------------------

;;; Guna SETF untuk meletakkan nilai baru untuk variable yang sedia ada. Ini sama
;;; seperti contoh hash table di atas.

(let ((variable 10))
    (setf variable 2))
; => 2

;;; Sebaik-baiknya kurangkan penggunaan destructive function dan elakkan
;;; mutation jika boleh.


;;;-----------------------------------------------------------------------------
;;; 7. Class dan object
;;;-----------------------------------------------------------------------------

;;; Takde dah class untuk haiwan. Jom buat Human-Powered Mechanical
;;; Conveyances (Kenderaan Mekanikal Berkuasa Manusia).

(defclass human-powered-conveyance ()
  ((velocity
    :accessor velocity
    :initarg :velocity)
   (average-efficiency
    :accessor average-efficiency
   :initarg :average-efficiency))
  (:documentation "A human powered conveyance"))

;;; Argument untuk DEFCLASS, mengikut susunan ialah:
;;; 1. nama class
;;; 2. list untuk superclass
;;; 3. list untuk slot
;;; 4. specifier optional (tidak wajib)

;;; Apabile list untuk superclass tidak ditetapkan, list yang kosong bermaksud
;;; class standard-object. Ini *boleh* ditukar, kalau anda tahu apa yang anda buat.
;;; Baca Art of the Metaobject Protocol untuk maklumat lebih lanjut.

(defclass bicycle (human-powered-conveyance)
  ((wheel-size
    :accessor wheel-size
    :initarg :wheel-size
    :documentation "Diameter of the wheel.")
   (height
    :accessor height
    :initarg :height)))

(defclass recumbent (bicycle)
  ((chain-type
    :accessor chain-type
    :initarg :chain-type)))

(defclass unicycle (human-powered-conveyance) nil)

(defclass canoe (human-powered-conveyance)
  ((number-of-rowers
    :accessor number-of-rowers
    :initarg :number-of-rowers)))

;;; Panggilan DESCRIBE kepada class HUMAN-POWERED-CONVEYANCE di REPL akan memberi:

(describe 'human-powered-conveyance)

; COMMON-LISP-USER::HUMAN-POWERED-CONVEYANCE
;  [symbol]
;
; HUMAN-POWERED-CONVEYANCE names the standard-class #<STANDARD-CLASS
;                                                    HUMAN-POWERED-CONVEYANCE>:
;  Documentation:
;    A human powered conveyance
;  Direct superclasses: STANDARD-OBJECT
;  Direct subclasses: UNICYCLE, BICYCLE, CANOE
;  Not yet finalized.
;  Direct slots:
;    VELOCITY
;      Readers: VELOCITY
;      Writers: (SETF VELOCITY)
;    AVERAGE-EFFICIENCY
;      Readers: AVERAGE-EFFICIENCY
;      Writers: (SETF AVERAGE-EFFICIENCY)

;;; Perhatikan apa yang berlaku. CL memang direka sebagai sistem interaktif.

;;; Untuk membuat method, jom kira berapa panjang lilitan untuk
;;; roda basikal menggunakan formula: C = d * pi

(defmethod circumference ((object bicycle))
  (* pi (wheel-size object)))

;;; Nilai PI memang sudah ada dalam CL

;;; Katakanlah kita ingin ambil tahu efficiency value (nilai keberkesanan)
;;; rower (pendayung) di dalam canoe (perahu) adalah berbentuk logarithmic. Ini
;;; boleh ditetapkan di dalam constructor/initializer.

;;; Untuk initialize instance selepas CL sudah siap construct:

(defmethod initialize-instance :after ((object canoe) &rest args)
  (setf (average-efficiency object)  (log (1+ (number-of-rowers object)))))

;;; Kemudian untuk construct sesebuah instance dan periksa purata efficiency...

(average-efficiency (make-instance 'canoe :number-of-rowers 15))
; => 2.7725887


;;;-----------------------------------------------------------------------------
;;; 8. Macro
;;;-----------------------------------------------------------------------------

;;; Macro membolehkan anda untuk menambah syntax language. CL tidak ada
;;; WHILE loop, tetapi, kita boleh mencipta syntax ter. Jika kita buat menggunakan
;;; naluri, kita akan dapat:

(defmacro while (condition &body body)
    "While `condition` is true, `body` is executed.
`condition` is tested prior to each execution of `body`"
    (let ((block-name (gensym)) (done (gensym)))
        `(tagbody
           ,block-name
           (unless ,condition
               (go ,done))
           (progn
           ,@body)
           (go ,block-name)
           ,done)))

;;; Jom lihat versi yang lebih high-level:

(defmacro while (condition &body body)
    "While `condition` is true, `body` is executed.
`condition` is tested prior to each execution of `body`"
  `(loop while ,condition
         do
         (progn
            ,@body)))

;;; Namun, dengan compiler yang modern, cara ini tidak diperlukan; form LOOP
;;; compile sama sahaja dan juga mudah dibaca.

;;; Perhatikan ``` digunakan, sama juga `,` dan `@`. ``` ialah operator jenis quote
;;; yang dipanggil quasiquote; operator tersebut membolehkan penggunaan `,` .
;;; `,` membolehkan variable "di-unquote-kan". @ mengembangkan list.

;;; GENSYM membuat simbol unik yang pasti tidak wujud di tempat-tempat yang
;;; lain. Ini kerana macro dikembangkan semasa compile dan
;;; nama variable di dalam macro boleh bertembung dengan nama variable yang
;;; digunakan dalam code yang biasa.

;;; Baca Practical Common Lisp dan On Lisp untuk maklumat lebih lanjut mengenai macro.

Bacaan lanjut

Maklumat tambahan

Kredit

Terima kasih banyak diucapkan kepada ahli Scheme yang membuat permulaan yang sangat bagus dan mudah untuk diguna pakai untuk Common Lisp.


Got a suggestion? A correction, perhaps? Open an Issue on the Github Repo, or make a pull request yourself!

Originally contributed by Paul Nathan, and updated by 1 contributor.