echo Impossible|sed 's/Im/To be /'

December 19, 2017

Lisp Web - Parenscript *ps-lisp-library* - HowTo

Parenscript 是 Lisp 的子集,簡單說就是可以用 Lisp 語法撰寫 javascript,但本質上還是在撰寫 JS 。

建議閱讀 Vladimir Sedach 在 Slideshre 上的簡報,可以直接暸解 PS (Parenscript) 它的目的及能力範圍。

建立 JSON

用 create / array 來建立 Object 輸出 Json 範例如下:

;; Json target sample
;; { dataInfo : [{ msg : 'apple' },
;;               { msg : 'banana' },
;;               { msg : 'coconut' }] };
;;
;; { el : '#app',
;;   data : { items : [{ message : 'Apple' },
;;                     { message : 'Banana' },
;;                     { message : 'Coconut' }] },
;;   methods : { clickme : function () { return ++this.count; }}
;;   data1 : dataInfo };
(ps
(create data-info (array (create msg "apple")
                 (create msg "banana")
                 (create msg "coconut")))
(Create el "#app"
    data (create items
             (array (create message "Apple")
                    (create message "Banana")
                    (create message "Coconut")))
    methods (create clickme
                (lambda () (incf (@ this count))))
    data1 data-info))

直覺想用 cl-json 來建立 json 。於是找到 Stack overflow 有篇 is there a way to insert raw javascript in parenscript code? 在討論如何置入 js code,其中 lisp-raw 這自建 function 滿好用的。實際上也可以用 JSON.parse 將 string 轉成 json, **誤區範例** 如下:

;; Json target sample
;; { el : '#app',
;;   data : { items : [{ message : 'Apple' },
;;                     { message : 'Banana' },
;;                     { message : 'Coconut' }] } }

(ql:quickload :parenscript)
(in-package :parenscript)
(ql:quickload :cl-json)

(ps
  (defvar data ((@ *json* parse)
        (lisp (json:encode-json-alist-to-string
               '((:el . "#app")
                 (:data . (((:message . "Apple"))
                           ((:message . "Banana"))
                           ((:message . "Coconut"))))))))))
;; var data = JSON.parse('{"el":"#app",
;;                         "data":[{"message":"Apple"},
;;                                 {"message":"Banana"},
;;                                 {"message":"Coconut"}]}');

(define-expression-operator lisp-raw (lisp-form)
  `(ps-js:escape
    ,lisp-form))
(defun lisp-raw (x) x)

(ps
  (defvar data (lisp-raw (json:encode-json-alist-to-string
                    '((:el . "#app")
                      (:data . (((:message . "Apple"))
                                ((:message . "Banana"))
                                ((:message . "Coconut")))))))))
;; var data = {"el":"#app",
;;             "data":[{"message":"Apple"},
;;                     {"message":"Banana"},
;;                     {"message":"Coconut"}]};
;;

嘗試後有三個缺點,因為是 alist 轉成 string 後直接操作,所以…

  1. 沒辦法直接在 json 裏 link 到另一個 json
  2. 沒辦法直接在 json 裏放 lambda function
  3. 語法相較於 create / array 并沒有比較簡單明暸

變數函數名稱大小寫

由於 common lisp 預設 symbol variable function 都是大寫,所以轉成 JS 需要作 Symbol conversion 範例如下:

bla-foo-bar = blaFooBar;
*array = Array;
*global-array* = GLOBAL-ARRAY;

why not (boo.bar.baz foobar)?

簡報裏有說明為什麼不要直接使用 dot 。請改用 getprop / @ / chain 來串

  • (GETPROP object {slot-specifier}*)
  • (@ {slot-specifier}*)
  • (CHAIN {slot-specifier | function-call}*)
(funcall (getprop document 'write) "hello world")
(funcall (@ document write) "hello world")
(chain document (write "hello world"))
((@ document write) "hello world")

;; 相同的輸出結果
;; document.write('hello world');

Runtime library *ps-lisp-library*

  • (MAPCAR function {array}*)
  • (MAP-INTO function array)
  • (MAP function array)
  • (MEMBER object array)
  • (SET-DIFFERENCE array1 array2)
  • (REDUCE function array object)
  • (NCONC {array}*)

*ps-lisp-library* 有許多有用的 library , 於是寫了些 samples 說明如下:

以下範例請先將 ps 轉成 js ,後用滑鼠複製到 chrome 的 web console 下執行。

;; testing *ps-lisp-library* mapcar map-into map member set-difference reduce nconc
(ps
  ;; 引入 *ps-lisp-library* 當成 runtime library
  (lisp *ps-lisp-library*)

  (mapcar #'(lambda (i j k) (+ i j k)) '(1 2 3) '(2 3 4) '(3 4 5))
  ;; 執行結果 [6, 9, 12]

  (let ((a '(1 2 3)))
    (map-into #'(lambda(i) (+ i 5)) a) a)
  ;; 執行結果 a = [6, 7, 8]

  (map #'(lambda (i) (+ i 5)) '(1 2 3))
  ;; 執行結果 [6, 7, 8]

  (member 5 '(1 2 3 4))
  ;; 執行結果 false
  (member 3 '(1 2 3 4))
  ;; 執行結果 true

  (set-difference '(1 2 5 1 3 4 5) '(2 7 3 4))
  ;; 執行結果 [1, 5, 1, 5]

  (reduce #'(lambda (i j) (* i j)) '(1 2 3 4 5))
  ;; 執行結果 120 (1 * 2 * 3 * 4 * 5 = 120)
  (reduce #'(lambda (i j) (+ i "-" j)) '("hi" "lloyd" "huang" "hello" "world"))
  ;; 執行結果 "hi-lloyd-huang-hello-world"

  (nconc '(1 2 3) '(3 4 5 6 7) '(7 8 9))
  ;; 執行結果 [1, 2, 3, 3, 4, 5, 6, 7, 7, 8, 9]
  )

引入 *ps-lisp-library* 後終於有像在寫 Lisp 了。XD

SLIME integration

lisp 開發搭配建議使用 slime 與 editor 串接,有興趣的人請參考 lisp 入門介紹 How I got started with Common Lisp in 2017

在 slime-mode 下有二個 command 可以方便預覽 JS code。

  • C-c C-j or M-x slime-eval-last-expression-in-repl
  • C-c M-m or M-x slime-macroexpand-all

或是搭配 Emacs - Trident-mode 也非常方便。

Posted by Lloyd Huang in on December 19, 2017