12 August 2022


Where static type checking and type annotation are shining

Many teams don't test their functions separately. They run the whole project to see the result. When something goes wrong, they check the log and use a debugger. They are the majority, at least from my experience. Static type checking and type annotations are efficient for these teams because type annotations give a rough idea about data for each function. They can't look at testing data, which doesn't exist.

Still, I wonder if forcing type annotation is practical. Reading a long function is difficult. By splitting a long function, I found that type annotation can be distracting because instead of focusing on logic, I have to think about type annotation; sometimes, the size of type annotation is about half the function's size. Maybe forcing type annotation only on functions, which an outsider from another module can call, like in OCaml, is practical. I haven't coded in OCaml beyond some toy programs. So I don't know if it is really practical as I imagine.

โดย Vee Satayamas ณ 12 August 2022 17:06 +0000

8 August 2022


Parallelly processing a stream in Common Lisp

My computer has many cores but doesn't have enough RAM to store the whole data. So I usually need to process data from a stream parallelly, for example, reading a Thai text line by line and dispatch them to processors running on each core.

In Clojure, I can use pmap because pmap works on a lazy sequence. However, using Lparallel's pmap on Common Lisp with lazy sequences wasn't in the example. So I used Bordeaux threads and ChanL channels instead. It worked. Still, I had to write repetitive code to handle threads and channels whenever I wanted to process the stream parallelly. It didn't only look messy, but it came with many bugs.

So I created a small library, called stream-par-procs, to wrap threads and channels management. As shown in the diagram, the reader reads a line for the stream, the system dispatch a line to different processors, and finally, the collector creates the final result.

Overview diagram of stream-par-procs

So the code looks like the below:

(with-open-file (f #p"hello.txt")
  (process f
       (lambda (elem state send-fn)
         (declare (ignore state))
         (funcall send-fn (length elem)))
       :collect-fn (lambda (n sum)
             (+ n sum))
       :init-collect-state-fn (lambda () 0)
       :num-of-procs 8))

I only need to define a function to be run in the processor, another function for running in the collector, and other functions for initializing states. It hides details, for example, joining collector thread when every processor sent END-OF-STREAM.

In brief, stream-par-procs makes processing a stream parallelly in Common Lisp more convenient with hopefully fewer bugs by reusing threads and channels management code.

โดย Vee Satayamas ณ 8 August 2022 05:14 +0000

28 July 2022


Fix: Svelte Devtools on Chrome/Brave

สืบเนื่องมาจาก Official Svelte Devtools ไม่ยอมทำงานบน Chrome และ Brave เลยค้นหาดูพบว่ามันเป็น Bug และทางต้นน้ำก็ไม่มี update มาปีกว่าแล้ว มีคน PR เข้าไปแก้ง่าย ๆ แค่หนึงบรรทัดเลยลองทำตามดูก็กลับมาทำงานได้ ใครประสบปัญหาอยู่ก็ลองทำดูได้ครับ ขั้นตอนดังนี้

Git Clone

git clone https://github.com/sveltejs/svelte-devtools.git


หลังจากนั้นให้แก้ไข dest/devtools/index.js โดยการลบ / บรรทัดที่ 6 ออก

- '/devtools/panel.html',
+ 'devtools/panel.html',


ให้ลบของเก่าออกแล้วทำการติดตั้งแบบ Load Unpacked

Chrome Extension installation

หลังจากนั้นก็จะปรากฎ Extension ดังนี้

Chrome Extension



Svelte Devtools



โดย MrChoke ณ 28 July 2022 14:44 +0000

17 July 2022


Error handling in fetching data via HTTP

Fetching data from REST APIs or a plain HTTP server is usually a part of my work. Many problems may arise during fetching data; for example, a server goes offline, my storage is out of space, an API gives the wrong URL, a server sends data in an invalid format, etc. To illustrate the situation, I can oversimplify my task to the function below:

def fetch(uri):

A fetcher usually faces one of these problems. I don't want to rerun a fetcher after running it for an hour or a day. Maybe I can write my fetcher to resume working after it stops at any state, but it will take time, and my fetcher will be more complex. If I code in Python, the fetch function can raise many exceptions, and my fetcher will stop because my code didn't handle any exceptions.

I see two types of environments that will help me fix the problem.

  1. Type checker may help me handle as many types of exceptions as possible upfront. Java definitely can do this because, in Java, I have to declare the list of exceptions as a part of the fetch function, and Javac checks if the caller handles these exceptions. Rust doesn't use a Java-like exception, but the result type encodes the types of errors, so the Rust compiler can check if a caller handles these errors.

  2. Common Lisp runtime will run a debugger if unhandled errors occur. So I can choose what to do next. For example, I can empty my trash folder if my storage is out of space. I still don't know if I can handle the storage space problem later with Erlang runtime.

Python doesn't seem to belong to (1) nor (2). I tried mypy, a static type checker for Python, but it didn't warn me anything. And I don't know how to retry or do something else with Python's exception system rather than let it stop.

โดย Vee Satayamas ณ 17 July 2022 01:47 +0000

15 July 2022


Reading lines using Flexi-streams on a Zstd stream is not fast.

I use Flexi-streams to read 1,000 lines from a ZSTD archive and find the average length of lines. My program looks like the one below.

(defun average-1000-etipitaka-flexi-streams ()                                                                                                                                             
  (with-open-file (f #P"etipitaka.txt.zst" :element-type '(unsigned-byte 8))                                                                                 
    (zstd:with-decompressing-stream (zs f)                                                                                                                   
      (let ((s (utf8-input-stream:make-utf8-input-stream zs)))                                                                                               
        (loop for line = (read-line s nil nil)                                                                                                               
              until (null line)                                                                                                                              
              count 1 into l                                                                                                                                 
              sum (length line) into c                                                                                                                       
              when (> l 1000) do (return (float (/ c l)))                                                                                                    
              finally (return (float (/ c l))))))))    

The text in the file etipitaka.txt.zst looks like below:

สิงสถิต เขตเมืองเวรัญชา พร้อมด้วยภิกษุสงฆ์หมู่ใหญ่ประมาณ ๕๐๐ รูป เวรัญชพราหมณ์                                                                                              
ได้สดับข่าวถนัดแน่ว่า ท่านผู้เจริญ พระสมณะโคดมศากยบุตร ทรงผนวชจากศากยตระกูล                                                                                              
ประทับอยู่ ณ บริเวณต้นไม้สะเดาที่นเฬรุยักษ์สิงสถิต เขตเมืองเวรัญชา พร้อมด้วยภิกษุสงฆ์   

The average line length is 68.8 bytes.

I ran the average-1000-etipitaka-flexi-streams on SBCL 2.2.5-1.1-suse on my laptop with Celeron N4500. It took 1.591 seconds.

Then I change the file to my-data.ndjson.zst, whose average line length is 515.5 bytes. Running average-1000-ndjson-flexi-streams took 4.411 seconds.

So I also tested with my customized utf8-input-stream. Running average-1000-etipitaka-utf8-input-stream, and average-1000-ndjson-utf8-input-stream took 0.019 seconds, and 0.043 seconds respectively, which means utf8-input-stream is 83X faster for short lines, and 102X faster for long lines, than Flexi-streams in these tests.

โดย Vee Satayamas ณ 15 July 2022 09:37 +0000

13 July 2022


Reading lines from a Zstd archive in Common Lisp

Reading lines from a Zstd archive in Common Lisp

I have a big line-separated JSON file, compressed in Zstd format. I will call this file data.ndjson.zst.

I want my program to read the file line by line. I can use https://github.com/glv2/cl-zstd to make a binary stream from the file. Still, I can run the function read-line of a binary stream. So I need to wrap the binary stream with Flexi-stream.

(ql:quickload 'flexi-streams)
(ql:quickload 'zstd)

(defpackage #:ex1
  (:use #:cl #:flexi-streams :zstd))
(in-package :ex1)

(with-open-file (f #P"data.ndjson.zst"
           :element-type '(unsigned-byte 8)
           :direction :input)
  (with-decompressing-stream (zstd-stream f)
    (let ((s (make-flexi-stream zstd-stream
                :external-format (make-external-format :utf-8))))
      (loop for line = (read-line s nil nil)
        until (null line)
        do (print line)))))

Printing those lines is a realistic example. So you can replace (print line) with your practical applications, for example, parse a JSON line and extract a book title.

โดย Vee Satayamas ณ 13 July 2022 11:14 +0000

2 June 2022


Installing Emacs 28.1 on a shared aging OS

I want to use Emacs 28.1 on aging OS that I mustn't modify /usr. I tried Docker but I want to use local command too. SBCL didn't work properly on old Docker. So I install Emacs from a source tarball to my home directory.

However, Emacs failed to verify TLS certs. So I installed GNUTLS, Nettle, Idn, and Unistring.


export PKG_CONFIG_PATH=$HOME/lib64/pkgconfig:$HOME/lib/pkgconfig
export LD_RUN_PATH=$HOME/lib:$HOME/lib64
export LD_LIBRARY_PATH=$HOME/lib:$HOME/lib64
export LDFLAGS="-L$HOME/lib -L$HOME/lib64"

rm -rf libunistring-1.0 libidn2-2.3.2 nettle-3.6 gmp-6.2.1 emacs-28.1

curl https://ftp.gnu.org/gnu/libunistring/libunistring-1.0.tar.gz | tar -xzvf - && \
     pushd libunistring-1.0 && \
     ./configure --prefix=$HOME && \
     make -j `nproc` && \
     make install && \
     popd || exit 1

curl https://ftp.gnu.org/gnu/libidn/libidn2-2.3.2.tar.gz | tar -xzvf - && \
    pushd libidn2-2.3.2 && \
    ./configure --prefix=$HOME && \
    make -j `nproc` && \
    make install && \
    popd || exit 1

curl https://ftp.gnu.org/gnu/nettle/nettle-3.6.tar.gz | tar xzvf - && \
     pushd nettle-3.6 && \
     ./configure --prefix=$HOME --enable-mini-gmp && \
     make -j `nproc` && \
     make install && \
     popd || exit 1 

curl https://www.gnupg.org/ftp/gcrypt/gnutls/v3.7/gnutls-3.7.6.tar.xz | tar xJvf - && \
     pushd gnutls-3.7.6 && \
     ./configure --prefix=$HOME && \
     make -j `nproc` && \
     make install && \
     cd .. || exit 1

curl http://ftp.gnu.org/pub/gnu/emacs/emacs-28.1.tar.gz | tar -xzvf - &&
     pushd emacs-28.1 && \
     ./configure --prefix=$HOME --with-x-toolkit=no --with-xpm=no --with-jpeg=no --with-jpeg=no --with-gif=no --with-tiff=no --with-png=no && \
     make -j `nproc` && \
     make install && \
     cd .. || exit 1

echo "Emacs 28.1 must be ready!"

โดย Vee Satayamas ณ 2 June 2022 08:18 +0000

21 May 2022


The worst thing I did in Rust

The worst thing I did in Rust was using unbounded asynchronous channels. It took days to figure out that my program had backpressure, and channels ate up all RAM.

So, since then, I have always used sync_channel or bounded channels from Crossbeam.

โดย Vee Satayamas ณ 21 May 2022 01:44 +0000

12 May 2022


Trying TruffleRuby

I ran wbrk2.rb:

# encoding: UTF-8
require 'thailang4r/word_breaker'
word_breaker = ThaiLang::WordBreaker.new
line = 'แต่ผมเคยมีปัญหาตอนจ่ายตังหลายปีแล้วแต่ผมเคยมีปัญหาตอนจ่ายตังหลายปีแล้วแต่ผมเคยมีปัญหาตอนจ่ายตังหลายปีแล้วแต่ผมเคยมีปัญหาตอนจ่ายตังหลายปีแล้วแต่ผมเคยมีปัญหาตอนจ่ายตังหลายปีแล้วแต่ผมเคยมีปัญหาตอนจ่ายตังหลายปีแล้วแต่ผมเคยมีปัญหาตอนจ่ายตังหลายปีแล้วแต่ผมเคยมีปัญหาตอนจ่ายตังหลายปีแล้วแต่ผมเคยมีปัญหาตอนจ่ายตังหลายปีแล้ว'

t1 = Time.now
for i in 1..10000
  puts word_breaker.break_into_words(line).join("|")
t2 = Time.now
puts t2-t1

in cRuby, jRUby, TruffleRuby by this script:

IMPLS=("cruby1" "jruby1" "truffle1")
for i in $(seq 1 10); do
    for j in ${IMPLS[*]}; do
    echo $i,$j,$(docker run --rm -it -v $(pwd):/work -w /work $j ruby wbrk2.rb | tail -n1)


FROM ruby:3.1
RUN gem install thailang4r


FROM jruby
RUN gem install thailang4r


FROM ghcr.io/graalvm/truffleruby:22
RUN gem install thailang4r
cRuby jRuby TruffleRuby
1 16.42368491 54.169815 11.385601
2 16.58381034 51.668375 11.99536
3 16.47655229 53.584401 12.088978
4 17.52281777 54.222869 10.893925
5 16.92775603 54.912019 12.24505
6 16.89063941 52.43011 11.413859
7 17.12419062 57.041267 11.395317
8 17.51911099 51.571193 11.551416
9 16.97416603 51.805853 11.578309
10 16.79307381 51.006189 12.517783
mean 16.92358022 53.2412091 11.7065598
Speed 1X 0.32X 1.45X

wbrk2 on TruffleRuby ran at 1.45X speed of cRuby. wbrk2 on jRuby ran at 0.32X speed of cRuby.

โดย Vee Satayamas ณ 12 May 2022 12:37 +0000

11 May 2022


Fortune telling

I was in a world where a code describing type signature was longer than a code telling what a program does. I don't want to bring that situation back.

I expect AI code completion to replace static typing. Ruby Type Signature (RBS) and similar things must be inferred automatically from a database or other data sources.

โดย Vee Satayamas ณ 11 May 2022 06:10 +0000

10 May 2022


C-style for loop in Common Lisp's Loop macro

I try to convert C-style for-loop in JS to Common Lisp's loop macro for a learning purpose.

for (i=0; i<10; i++) {

Above JS code can be converted to:

(loop with i = 0
      unless (< i 10) return nil
      do (print i)
      do (incf i))

And, of course, the version below is more appropriate.

(loop for i from 0 below 10 do
    (print i))

The first version looks more flexible in applying to another problem than the last one.

โดย Vee Satayamas ณ 10 May 2022 18:20 +0000


จำกัดความเร็ว-เพิ่มความปลอดภัย: การใช้การออกแบบและเทคโนโลยีมาช่วยให้คนทำตามกฎได้สะดวกขึ้น

ป้ายเตือนหน้ามหาวิทยาลัยทรินิตีคอลเลจดับลิน แจ้งให้รถใช้ความเร็วไม่เกิน 30 กม./ชม.

เมื่อต้นปีนี้ ไปเที่ยวนอกเมืองตามเส้นทาง Wild Atlantic Way กับเพื่อนๆ ที่ดับลิน รถที่เช่าเป็น Volkswagen Golf ซึ่งระหว่างขับจะแสดงตัวเลขขีดจำกัดความเร็วของถนนช่วงนั้นขึ้นบนหน้าปัดเลย

ถ้าเกินขีดจำกัด ตัวเลขจะเปลี่ยนเป็นสีเหลือง
เกินนานๆ จะมีเสียงร้องเตือน
ถ้าเกินขีดไปมาก ตัวเลขจะเปลี่ยนเป็นสีแดง และยิ่งร้องดังหนัก

บนหน้าปัดยังมีกราฟิกแสดงระยะห่างจากรถคันหน้าที่เหมาะสมกับความเร็วที่ใช้อยู่ในปัจจุบันด้วย โดยจะแสดงเป็นรูปรถและเส้นแสดงระยะ

นอกจากกฎที่เหมาะสมแล้ว เทคโนโลยี + การออกแบบก็ช่วยให้คนตัดสินใจทำตามกฎได้สะดวกขึ้น

รถยี่ห้ออื่นก็มีความสามารถเดียวกันนี้ ลองค้นในเน็ต เจอคู่มือภาษาไทยของ Volvo S90 รุ่นปี 2020 เรื่อง “การ​เตือน​สำหรับ​ขีดจำกัด​ความเร็ว​และ​กล้อง​ตรวจจับ​ความเร็ว​จาก​ระบบ​ข้อมูล​ป้ายถนน” ด้วย

ตัวอย่าง​​ตัว​จำกัด​ความเร็ว​บน​จอแสดงผล​สำหรับคนขับ ในรถวอลโว่ S90 (2020) จากเว็บไซต์ volvocars.com


โดยทั่วไป ความเร็วสูงสุดในเขตเมืองดับลิน (Dublin City Council – ประมาณเทศบาลนครของไทย) คือ 50 กม./ชม. — ส่วนย่านที่อยู่อาศัย ย่านชุมชน จะกำหนดที่ไม่เกิน 30 กม./ชม.

ยกเว้นทางหลวงในเขตเมืองซึ่งถนนแยกเป็นสัดส่วนเฉพาะ จะอยู่ที่ 60 หรือ 80 กม./ชม. — ถ้าพ้นเขตเมืองไปแล้วก็จะสูงกว่านี้


สีชมพูคือพื้นที่ที่ปัจจุบันจำกัดความเร็วที่ 30 กม./ชม. ส่วนพื้นที่สีเหลือง ฟ้า และแดง กำลังถูกเสนอให้จำกัดความที่ 30 กม./ชม. เช่นกัน จะเห็นได้ว่าตัวเมืองเองพยายามจะขยายเขตที่เป็น 30 กม./ชม. ออกไปให้มากขึ้น โดยในเว็บไซต์บอกว่ารัฐบาลท้องถิ่นตั้งใจจะกำหนดให้ทุกย่านที่อยู่อาศัย ใช้ความเร็วไม่เกิน 30 กม./ชม. ภายในสิ้นปีที่ผ่านมา

แผนที่แสดงเขตจำกัดความเร็วที่มีอยู่เดิมและที่เสนอใหม่ ในบริเวณนครดับลิน


สหภาพยุโรปออกกฎความปลอดภัยทั่วไปฉบับปรับปรุง (Regulation 2019/2144 หรือ “General Safety Regulation 2” หรือ “GSR2”) เมื่อ 27 พ.ย. 2562 กำหนดให้ตั้งแต่วันที่ 6 ก.ค. 2565 (ปีนี้) รถในประเภท M1 (รถยนต์นั่ง) และ N1 (รถเพื่อการพาณิชย์ขนาดเบา) รุ่นใหม่ทุกรุ่นที่จะขออนุมัติเพื่อขายในยุโรป ต้องติดตั้งเทคโนโลยีเพื่อความปลอดภัย เช่น ระบบช่วยเตือนเมื่อใช้ความเร็วเกินกำหนด (intelligent speed assistance – ISA) และเทคโนโลยีช่วยในกรณีฉุกเฉินให้ขับอยู่ในช่องจราจร (emergency lane keeping systems – ELKS) ซึ่งประกอบไปด้วยเทคโนโลยีอีกย่อย 2 อย่างประกอบกันคือ การเตือนเมื่อวิ่งคร่อมช่องจราจร (lane departure warning systems – LDWS) และ การช่วยขยับให้กลับช่องจราจรที่ถูกต้อง (corrective directional control functions – CDCF)

ทั้งนี้ภายใน 7 ก.ค. 2567 รถใหม่ทุกคัน ไม่ว่าจะรุ่นใดก็ตาม จะต้องมีเทคโนโลยีความปลอดภัยต่างๆ ข้างต้น

นับจากวันที่ประกาศกฎหมายจนถึงวันบังคับใช้ขยักแรก (เฉพาะรถรุ่นใหม่) 2 ปี 7 เดือน และขยักสอง (รถใหม่ทุกคัน) 4 ปี 7 เดือน


วิดีโออธิบายการทำงานของระบบช่วยเตือนขีดจำกัดความเร็ว โดย Road Safety Authority Ireland

ที่หน้ารถจะมีกล้องที่อ่านป้ายแจ้งขีดจำกัดความเร็วที่ติดไว้ข้างถนน (ตัวป้ายมีมาตรฐานกำหนดหน้าตาไว้) ไม่จำเป็นต้องเชื่อมกับ GPS หรืออินเทอร์เน็ต

นอกจากนี้ยังสามารถใช้แผนที่ดิจิทัลที่มีข้อมูลขีดจำกัดความเร็วของถนนแต่ละช่วง (ข้อมูลน่าจะมาจากหน่วยงานรัฐที่ดูแลเรื่องถนน) + ข้อมูลที่ตั้งของรถจาก GPS เสริมด้วย เผื่ออ่านป้ายไม่ได้ (ป้ายมีฝุ่น ถูกต้นไม้บัง กล้องถูกบัง มีหมอก ฯลฯ)

ระบบนี้จะถูกเปิดโดยอัตโนมัติเป็นค่าปริยายเมื่อจุดเครื่อง และผู้ขับสามารถเลือกกดปิดการทำงานได้

ตัวอย่าง​ของ​ป้าย​จราจร​ที่​รถ Volvo รุ่น S90 (2020) สามารถ​อ่านได้

ตัวอย่าง​ของ​ป้าย​จราจร​ที่​รถวอลโว่รุ่น S90 (2020) สามารถ​อ่านได้
ตัวอย่าง​ของ​ป้าย​จราจร​ที่​รถวอลโว่รุ่น S90 (2020) สามารถ​อ่านได้

กลไกตลาด กับ การส่งเสริมด้วยกฎหมาย

เมื่อต้องควักกระเป๋าจ่ายเอง และเงินในกระเป๋ามีจำกัด คนมีแนวโน้มเลือกซื้อความปลอดภัยสำหรับตัวเองก่อน เช่น ถุงลมนิรภัย (คุ้มครองคนในรถ) ระบบกันเบรกล็อก (ช่วยมากกรณีใช้ความเร็วสูง ซึ่งคนในรถเสี่ยงจะเจ็บด้วย)

ในขณะที่เทคโนโลยีเพื่อความปลอดภัยของคนอื่น เช่น เทคโนโลยีที่ช่วยลดโอกาสชนที่ความเร็วต่ำ (ที่คนในรถมักไม่เจ็บ แต่คนนอกรถเจ็บ) หรือเทคโนโลยีที่ช่วยให้คนขับรถบรรทุก รถลาก สามารถมองเห็นจักรยานหรือรถเล็กในจุดบอดสายตา จะถูกซื้อทีหลัง หรือถูกซื้อน้อยกว่า

การใช้ “กลไกตลาด” เพียงอย่างเดียวจึงอาจไม่มากพอหรือไม่เร็วพอ

การเพิ่มความปลอดภัยบนท้องถนนด้วยการออกเป็นกฎหมายให้รถยนต์ใหม่ต้องมีเทคโนโลยีความปลอดภัยที่คำนึงถึงความปลอดภัยของทุกคนบนถนนก็เป็นอีกแนวทางหนึ่งที่น่าพิจารณา โดยคำนึงถึงผลกระทบและภาระของผู้ซื้อรถหรือใช้รถด้วย อาจจะค่อยเป็นค่อยไปอย่างกรณีของสหภาพยุโรป ที่มาการออกกฎหมายล่วงหน้าหลายปี และเริ่มบังคับกับรถรุ่นใหม่ก่อน รถใหม่ที่ยังเป็นรุ่นเก่าอยู่ยังไม่ต้อง และให้เวลาอีก 2 ปี จึงจะขยับไปสู่รถใหม่ทุกคัน

ใครสนใจเรื่องเทคโนโลยีช่วยความปลอดภัยเชิงรุก (“active”) เพื่อป้องกันไม่ให้เกิดอุบัติเหตุแต่แรก [เทียบกับเทคโนโลยี “passive” เช่น ถุงลมนิรภัย ที่เป็นเรื่องลดความเสียหายเมื่อเกิดเหตุแล้ว] ดูได้เว็บไซต์ RoadSafetyFacts.eu — ซึ่งจะเป็นข้อมูลจากมุมมองของสมาคมผู้ผลิตรถยนต์ European Automobile Manufacturers’ Association (ACEA)


สำหรับข้อมูลเรื่องความปลอดภัยบนท้องถนนในสหภาพยุโรป ดูได้ที่เว็บไซต์ของ European Transportation Safety Council ซึ่งเป็นองค์กรไม่แสวงกำไร ได้รับเงินสนับสนุนจากทั้งภาครัฐที่เป็นผู้กำกับกิจการ และเอกชน ซึ่งรวมถึงผู้ผลิตรถยนต์และสมาคมผู้ผลิตสุรา จะมีข่าวสารเกี่ยวกับพวกกฎหมายใหม่ๆ ด้วย

เผยแพร่ต้นฉบับครั้งแรกเมื่อ 21 ก.พ. 2565 ในเฟซบุ๊ก

โดย bact ณ 10 May 2022 04:46 +0000

9 May 2022


Compile-time type checking in Common Lisp: a small experiment

In static1.lisp:

(defpackage static1 
 (:use #:cl)) 
(in-package :static1) 

(declaim (ftype (function (string string) string) concat-strings)) 
(defun concat-strings (a b)
  (format nil "~A --- ~A" a b)) 

(declaim (ftype (function (fixnum fixnum) string) concat-nums)) 
(defun concat-nums (a b)
  (concat-strings a b))

Then I ran this command:

sbcl --noinform --eval '(compile-file "static1.lisp")' --quit

SBCL showed this warning:

; caught WARNING: 
;  Derived type of STATIC1::A is 
;  conflicting with its asserted type 
;    STRING. 
;  See also: 
;    The SBCL Manual, Node "Handling of Types"

So SBCL - a Common Lisp implementation, can check type in compile-time. Anyway, a programmer needs to read warnings.

โดย Vee Satayamas ณ 9 May 2022 08:04 +0000

25 April 2022


Less visual-centric programming languages

Last week, I watched a video about coding and Docker configuration on a TV. I couldn't read any line of code. Then I thought about visually impaired people. How do they code? Every student in Thailand must learn to code. I presume the situation is similar in every country.

People widely use text-to-speech services these days. I cannot find any text-to-speech service for reading source code aloud. Let's assume we have a modified version of a text-to-speech service.

So I looked at source codes in different programming languages on the CodeRosetta website. I perceive Python code blocks by their visual structure purely. To read Python source code, I have to encode its visual structure, namely indents to sounds. Reading a nested code block won't be easy to understand. For example, reading twelve leading white spaces aloud will be very strange. In Lisp, reading open parenthesis and close parenthesis is more straightforward, but I will forget which parenthesis. So the best form of code blocks is in QuickBasic, which has different keywords between different kinds of blocks. For example, FOR with NEXT, and IF with END IF. Later I got a comment from Lemmy.ml, which told me that Ada also has different keywords between different kinds of blocks. Another idea from Lemmy.ml is the reader must convert the Python code block into a similar form as Ada or QuickBasic before reading.

MBasic refers to code by line numbers instead of code blocks. However, by listening to five lines, I forgot the line number. For example, when I heard gosub 70, I forgot what was at line 70.

In X86 Assembly, a programmer labels only the line that the program will jump to it. So X86 Assembly code looks much better than MBasic.

Still, coding in X86 Assembly can be exhaustive in many cases. For example, X86 Assembly doesn’t support recursion. Writing quick sort in X86 Assembly can be too difficult for learning to code.

Haskell doesn’t rely on code blocks. However, reading the symbols, for example, >>= is challenging. Prolog’s symbols are easy to read. For example, we can read :- as IF. Anyway, the Prolog programming paradigm is different from the mainstream one now. So Erlang, whose syntax is similar to Prolog, is a more practical alternative.

In brief, Erlang is a practical, less visual-centric program language. Because it mostly relies on names instead of code blocks, reading names aloud is much easier than reading code blocks aloud. Furthermore, the Erlang programming paradigm is more mainstream now.

โดย Vee Satayamas ณ 25 April 2022 09:38 +0000

20 April 2022


Clojure's lazy sequence versus transducer benchmark

According to this thread, I compared using only lazy sequences with transducers.

To add the I/O factor, I prepared a data file called "fake.txt" using the program below:

(with-open [w (io/writer "fake.txt")]
  (doseq [n (range 10000000)]
    (.write w (str n "\n"))))

F1 is the lazy-sequence-based version. It reads data from "fake.txt" and does a few steps of computations.

(defn f1
  (with-open [r (io/reader "fake.txt")]
    (->> (line-seq r)
         (map parse-long)
         (map inc)
         (filter even?)
         (map inc)
         (reduce + 0))))

F2 is the transducer-based version of F1.

(defn f2
  (with-open [r (io/reader "fake.txt")]
    (transduce (comp (map parse-long)
                     (map inc)
                     (filter even?)
                     (map inc))
               (line-seq r))))

I evaluated them using Criterium.

(with-progress-reporting (quick-bench (f1) :verbose))
(with-progress-reporting (quick-bench (f2) :verbose))

Here is the result.

#################### F1 ###################
Evaluation count : 6 in 6 samples of 1 calls.
Execution time sample mean : 3.811858 sec
Execution time mean : 3.812064 sec

#################### F2 ###################
Evaluation count : 6 in 6 samples of 1 calls.
Execution time sample mean : 1.490624 sec
Execution time mean : 1.490777 sec

F1, which is the lazy sequence version, took 3.812064 seconds. F2, which is the transducer version, took 1.490777. So the transducer version is 155.71% faster than the lazy sequence version.

In brief, this biased experiment shows the transducer version is much faster than the pure lazy sequence version.

โดย Vee Satayamas ณ 20 April 2022 06:42 +0000

12 April 2022


วันสงกรานต์ปี 2565

สงกรานต์ปี 2565 เป็นปี จ.ศ. (2565 – 1181) = 1384 วันเถลิงศก ตรงกับ(1384 * 0.25875)+ floor(1384 / 100 + 0.38)– floor(1384 / 4 + 0.5)– floor(1384 / 400 + 0.595)– 5.53375= 358.11 + 14 – 346 – 4 – 5.53375= 16.57625= วันที่ 16 เมษายน 2565 เวลา 13:49:48 วันสงกรานต์ ตรงกับ16.57625 – 2.165 = 14.41125= วันที่ 14 เมษายน 2565 … Continue reading วันสงกรานต์ปี 2565

โดย kitty ณ 12 April 2022 11:14 +0000

23 March 2022


I recommend Clojure as the first programming language to learn for beginners.

I recommend Clojure as the first programming language to learn for beginners. I recommend "Poetry of Programming" as the first book for learning Clojure. I recommend "Clojure for the Brave and True" as the second book for learning Clojure.

โดย Vee Satayamas ณ 23 March 2022 06:13 +0000

22 March 2022


สองเหตุผลที่ฉันคิดว่า Lisp น่าสนใจ (เวอร์ชั่นพ.ศ. 2565)

  1. Lisp สอนให้ฉันสร้างฟังก์ชันที่แปลงข้อมูลทีละขั้นตอน แทนที่จะเพิ่มตัวแปรที่ถูกแก้ไขไปเรื่อย ๆ ลงในลูป โครงสร้างข้อมูลที่ไม่ถูกเปลี่ยนค่ามีความสำคัญสำหรับการแปลงข้อมูลทีละขั้นตอนโดยไม่ทำให้ข้อมูลที่มีอยู่แล้วยุ่งเหยิง โครงสร้างข้อมูลเริ่มต้นของ Lisp เป็นรายการเชื่อมโยงทางเดียว (singly linked list) ซึ่งเหมาะสำหรับใช้เป็นโครงสร้างข้อมูลที่ไม่ถูกเปลี่ยนค่า

  2. มาโครของ Lisp เขียนง่ายเมื่อเทียบกับคู่แข่ง มาโครของ Rust นั้นทรงพลัง มาโครของ Rust นั้นเกี่ยวกับการแปลง TokenStream และมาโครของ Lisp นั้นเกี่ยวกับการเปลี่ยนรายการ (list) อย่างไรก็ตามฉันชอบที่จะแปลงรายการกว่าเปลี่ยน TokenStream เนื่องจากฉันไม่ต้องเรียนรู้โครงสร้างใหม่และคำสั่งใหม่สำหรับจัดการโครงสร้างใหม่

โดย Vee Satayamas ณ 22 March 2022 10:40 +0000

Two reasons that I think Lisp is interesting (2022 version)

  1. Lisp taught me to create functions that transform data step by step instead of adding mutable variables into loops. An immutable data structure is important for transforming data step by step without messing up existing data. Lisp's default data structure is a singly linked list.

  2. Lisp macro is easy to write compared to competitors. Rust's macro is powerful. Rust's macro is about transforming a TokenStream, and #Lisp's macro is about transforming a list. However, I prefer transforming a list to a TokenStream since I don't have to learn new structures and new commands for manipulating a new structure.

โดย Vee Satayamas ณ 22 March 2022 10:34 +0000

My Lispy code in Python

I translated my function in Lisp to Python.

def get_the_longest_prefix(a, b, ans):
    if len(a) == 0 or len(b) == 0:
        return "".join(ans)
    if a[0] == b[0]:
        return get_the_longest_prefix(a[1:], b[1:], ans + [a[0]])
    if a[0] == '-':
        return get_the_longest_prefix(a[1:], b, ans + ['-'])
    if b[0] == '-':
        return get_the_longest_prefix(a, b[1:], ans + ['-'])
    return "".join(ans)

And it has too many "return" statements. Anyway, I suppose Python doesn’t prefer a recursion. So this isn’t the case when we code in the Python way.

The code above concatenate Python's lists instead of creating a linked list like in Lisp.

So I made this experiment.

import time

n = 100

# Concatinating vectors
v = []
t1 = time.time_ns()
for i in range(n):
    v = [i] + v
t2 = time.time_ns()

# Creating cons cells for a singly link list
l = None
t3 = time.time_ns()
for i in range(n):
    l = (i, l) # Creating a cons cell
t4 = time.time_ns()


At n = 100, concatenating vectors require 2 times of running time compared to creating cons cells for a linked list. So I keep using linked lists.

โดย Vee Satayamas ณ 22 March 2022 10:30 +0000

14 March 2022


How I install RocksDB

I want to install a specific version of RocksDB across different machines with different operating systems. Thus I can't rely on APT or other package management systems. I followed INSTALL.md by running make on the bundled Makefile, but last Saturday, I found that RocksDB couldn't read Snappy compressed data, although I installed the "libsnappy-dev" package. I tried many different ways to enable Snappy support. Then I decided to use CMake, which appeared only once in INSTALL.md. Now it works. My install script looks like this:

wget https://github.com/facebook/rocksdb/archive/refs/tags/v6.15.5.tar.gz -O - \ 
   | gzip -d \ 
   | tar -xf - \ 
   && pushd rocksdb-6.15.5 \ 
   && DEBUG_LEVEL=0 PREFIX=$MTDIR make -j $(nproc) \ 
   && DEBUG_LEVEL=0 PREFIX=$MTDIR make install \ 
   && popd

$MTDIR is a target directory for installing RocksDB. DEBUG_LEVEL=0 and PREFIX=$MTDIR are perhaps not necessary.

โดย Vee Satayamas ณ 14 March 2022 08:06 +0000

27 February 2022


macOS Monterey DNS

เข้าใจเสมอว่าแก้ DNS ของ macOS สามารถแก้ใน /etc/resolv.conf ได้เลย แต่ล่าสุดพบว่ามันไม่ได้ให้ความสำคัญแล้ว ถ้าให้ได้ผลต้องตั้งใน UI ให้เรียบร้อย แต่ถ้ายังอยากใช้ command line เราก็ยังใช้ได้ดังนี้


ก่อนที่เราจะตั้งต่าต้องรู้ก่อนว่าปัจจุบันเราต่อ network ผ่านอุปกรณ์ไหน และ มีอุปกรณ์อะไรบ้างในระบบ

networksetup -listallnetworkservices


USB 10/100/1000 LAN
Thunderbolt Bridge


ถ้าต่อ WiFi ก็แสดงดังนี้

networksetup -getdnsservers Wi-Fi

หรือ ถ้าใช้สายแลน

networksetup -getdnsservers Ethernet


networksetup -getdnsservers Ethernet


หากต้องการล้มกระดานแล้วใช้ DNS ที่ต้องการตั้งครั้งนี้ก็ใช้คำสั่ง

networksetup -setdnsservers Ethernet

แต่ถ้าต้องการเพิ่มเข้าไปก็ให้ get ค่าเดิม แล้วต่อท้ายเข้าไป เช่น

networksetup -getdnsservers Ethernet

networksetup -setdnsservers Ethernet

หรือถ้าต้องการ clear ไปเลยก็

networksetup -setdnsservers Ethernet Emty


โดย MrChoke ณ 27 February 2022 05:28 +0000

25 February 2022


การใช้ Pydantic Model กับ Form Data ใน FastAPI

ปกติถ้าหากจำนวน Fields ใน Form มีไม่มากก็ไม่มีปัญหาเราสามารถเขียนแบบปกติได้ เช่น

@app.post("/abc", response_model=bool)
def abc(id: int = Form(...), name: str = Form(default=None),status: str = Form(default="single")):
  return True

แต่ถ้า Fields มีเยอะ ๆ สัก 10 Fields ขึ้นไป Code ก็จะเริ่มดูยากยาวมาก การใช้ Pydantic model เข้ามาช่วยก็จะจัดการง่ายขึ้น ปกติเราใช้ Pydantic model ใน Body แบบ JSON กันอยู่แล้วแต่สำหรับ Form Data จะมีลูกเล่นนิดหนึ่ง ค้นหาใน internet ไปเจอวิธีที่คิดว่าง่ายที่สุดละเลยบันทึกไว้สักหน่อย



ตัวอย่าง model.py

from typing import Optional

from fastapi import Form
from pydantic import BaseModel

def form_body(cls):
    cls.__signature__ = cls.__signature__.replace(
            arg.replace(default=Form(default=arg.default) if arg.default is not inspect._empty else Form(...))
            for arg in cls.__signature__.parameters.values()
    return cls

class Profile(BaseModel):
    passport_no: Optional[str]
    hn: Optional[str]
    patient_guid: Optional[str]
    prefix: str = "นาย"
    first_name: str
    last_name: str
    prefix_eng: str = "Mr"
    first_name_eng: Optional[str]
    middle_name_eng: Optional[str]
    last_name_eng: Optional[str]
    gender: int = 1
    birth_date: Optional[str]
    mobile_phone: Optional[str]
    installed_line_connect: Optional[str]
    address: Optional[str]
    moo: Optional[str]
    road: Optional[str]
    chw_code: Optional[str]
    amp_code: Optional[str]
    tmb_code: Optional[str]
    address_full_thai: Optional[str]
    address_full_english: Optional[str]
    nationality: Optional[str]

ในส่วนของ Form มันจะมีหลัก ๆ อยู่สามแบบคือ

ผมเลยปรับในตัว Decorators เพิ่มเติมให้มันปรับตาม Model ที่เรากำหนดไว้

arg.replace(default=Form(default=arg.default) if arg.default is not inspect._empty else Form(...))

ตัวอย่าง Router

from model import Profile
from fastapi import Depends, FastAPI, File, UploadFile

app = FastAPI()

@app.post("/abc", response_model=bool)
def abc(profile: Profile = Depends(Profile), photo: UploadFile = File(...)):

  return True

ตอน Render ก็จะได้ประมาณนี้


Swagger input with default


โดย MrChoke ณ 25 February 2022 03:44 +0000

20 February 2022


การเลือก WebCam บน Google Colab ในกรณีมีมากกว่า 1 ตัว

ปกติ Colab ได้เตรียม Snippet เกี่ยวกับการใช้ WebCam มาให้เราแล้วแค่เลือก Code มาใช้แบบง่าย ๆ เลย

พอดีผมทดสอบ API แล้วใช้ Colab ในการทดสอบ และ แสดงตัวอย่างให้กับ Dev คนอื่น ๆ เลยแก้ไข script นิดหน่อยให้สามารถเลือกกล้องได้ถ้าเราเสียบกล้องมากกว่า 1 ตัว

Code ตัวอย่าง

Google Colaboratory

GitHub logo mrchoke / select_webcam_in_colab

Select WebCam in Google Colab


Select WebCam in Google Colab


จริง ๆ ผมใช้ JavaScript ที่ใช้งานเป็นประจำอยู่แล้ว ซึ่งสามารถนำไปประยุกต์ใช้กับ JavaScript Framework อื่น ๆ ได้ด้วย


// define variables
let camera_id = ''; // current camera id
let stream = null; // current stream
let devices = []; // all camera
const div = document.createElement('div'); // parent div playground
const camera_list = document.createElement('select'); // camera dropdown
const camera_label = document.createElement('h3'); // current selected camera
const video = document.createElement('video'); // video object

// scan all cameras
async function scan_cameras() {
  let camera_found = false;

  await navigator.mediaDevices
      .then((deviceInfos) => {
        deviceInfos.forEach((deviceInfo) => {
           if (deviceInfo.kind === 'videoinput') {
            if(!camera_found) {
              camera_label.textContent = `Camera: ${deviceInfo.label}`;
              camera_id = deviceInfo.deviceId
              camera_found = true



// change camera by id
async function change_camera(e) {
  camera_id = e.target.value;
  camera_label.textContent = `Camera: ${devices.filter(i => i.deviceId == camera_id)[0].label}`;
  await start_camera();

// stop all active camera
async function stop_camera() {
    await stream.getVideoTracks().forEach(track => track.stop());

// start camera by selected id
async function start_camera() {
  await stop_camera();
  stream = await navigator.mediaDevices.getUserMedia({video: {deviceId: {exact: camera_id}, audio: false}});
 video.srcObject = stream;
 await video.play();

// create camera list dropdown
function create_camera_select() {
 camera_list.appendChild(new Option("Select Camera", 0))        

 devices.forEach((device, i) => {
 camera_list.appendChild(new Option(device.label || `Camera ${i + 1}`, device.deviceId));

 camera_list.addEventListener("change", change_camera);


// take photo
async function takePhoto(quality) {
  await scan_cameras();

  const capture = document.createElement('button');
  capture.textContent = 'Capture';

  video.style.display = 'block';
  await start_camera();

   google.colab.output.setIframeHeight(document.documentElement.scrollHeight, true);

  // Wait for Capture to be clicked.
  await new Promise((resolve) => capture.onclick = resolve);

  const canvas = document.createElement('canvas');
  canvas.width = video.videoWidth;
  canvas.height = video.videoHeight;
  canvas.getContext('2d').drawImage(video, 0, 0);
  await stop_camera();
  return canvas.toDataURL('image/jpeg', quality);

ตัวอย่างการเลือกกล้อง WebCam
ตัวอย่างการเลือกกล้อง WebCam

ลองเอาไปประยุกต์ใช้กันดูนะครับ น่าจะได้กับการเลือกไมค์บันทึกเสียงได้ด้วยแต่ผมไม่เคยลอง คือหลักการเดียวกันเลย

โดย MrChoke ณ 20 February 2022 20:22 +0000

14 February 2022



โปรแกรมตัดเกรดเป็นตัวอย่างของ business logic ที่ดีเลยครับ เพราะว่าหลายครั้งก็มีการเปลี่ยนแปลงกฎเกณฑ์ วิธีคิดเกรดของแต่ละสถาบันไม่เหมือนกันก็ได้ บางวิชาในสถาบันเดียวกันก็ตัดเกรดไม่เหมือนกัน บางมีชามี A B C D F บางวิชามีเกรดแค่ S กับ U

อย่างไรก็ตามวิธีคิดเกรดในประเทศไทยมันไม่ค่อยเปลี่ยนแปลงอาจจะไม่เห็นภาพ ถ้าจะปรับให้เหมาะกับบริบทประเทศไทยควรจะเปลี่ยนโจทย์เป็นการคำนวณว่าใครได้เป็นส.ส.และส.ว.บ้างเพราะว่ารัฐธรรมนูญและกฎหมายลูกเปลี่ยนบ่อยเหลือเกิน และยังมีประเด็นในการตีความด้วย

เพราะว่าโปรแกรมเปลี่ยนตามกฎเกณฑ์ต่าง ๆ ซึ่งบางทีก็เปลี่ยนสามเดือนหลังจากที่เขียน บางทีก็สามปี บางทีก็สิบปี คนที่มาแก้ไขก็อาจจะเป็นคนละคนกับที่เขียนทีแรก

ถ้าอยากให้โปรแกรมแก้ไม่ยาก ก็จำเป็นต้องเดาใจคนอื่น เดาใจตัวเอง ว่าเขียนแบบไหนแล้วจะกลับมาอ่านง่ายหลังจากผ่านไปหลายปี และเขียนแบบไหนถึงจะกลับมาแก้ง่ายหลังจากผ่านไปหลายปี เป็นต้น

เรื่องว่าโปรแกรมทำงานช้าเร็วใช้หน่วยความจำมากน้อยก็สำคัญ แต่ก็ต้องพิจารณาเป็นกรณีไป หลายกรณีที่โปรแกรมมีประสิทธิภาพพออยู่ก็ไม่จำเป็นต้องไปปรับแต่งอีก

โดย Vee Satayamas ณ 14 February 2022 07:56 +0000

28 January 2022


My opinion about Node.js

I agree that using JavaScript increases the chance of participation. I released a few versions of Thai word breakers in different programming languages. One on node.js is the most popular. 8 people contributed to the JS-based project compared to 2-3 people in other programming languages. However, JS has a downside too. In 2017, @iporsut and I made an experiment to compare Thai word breakers that we created. JS version running time is 15X of the Rust version. Even by comparing with another dynamic language, the Julia version is faster than the one in JS.

I created a website using node.js in 2014, and it is still running. The performance is good. However, I have a few regrets.

โดย Vee Satayamas ณ 28 January 2022 14:29 +0000

11 December 2021


inception ?

I knew I’m in a dream. It was a bad dream. I wanted to get out I knew I’m in a dream. I decided to commit a suicide so it would wake me up. I jumped out of a window, closed my eyes. It was pitch black. Eyes opened, I woke up .. .. in … Continue reading inception ?

โดย kitty ณ 11 December 2021 16:51 +0000

1 December 2021


โม้ว่าทำอะไรเกี่ยวกับ Rust บ้าง 2021

ทีแรกผมจะเขียนเตรียมไว้พูดในงาน Rustacean Bangkok 2.0.0 แต่ว่าวันงานไม่ว่าง ก็เลยเอามารวมเป็น blog ไว้ก่อนแล้วกัน

ผมจะเล่าว่าผมทำอะไรกับภาษา Rust บ้างซึ่งก็คงไม่ครบถ้วน จะเขียนไปตามที่นึกได้


ผมเริ่มเขียน Rust ครั้งแรกค.ศ. 2014 อย่างน้อยก็ที่ใส่ gist ไว้ code

#[deriving(Encodable, Decodable, Show, Clone)]
pub struct Node {
    pub snode: ~[~[Range]],
    pub children: Option<~[Option<Node>]>,

เป็นช่วงก่อนที่จะออก Rust 1.0 หน้าตาก็ต่างจากเดี๋ยวนี้เยอะเหมือนกัน ส่วนมากเอามาทำงานที่เอาไว้เก็บ string-tree alignment แต่รายละเอียดเรื่องนี้ก็ข้ามไปดีกว่า


ค.ศ. 2015 ผมนึกถึงโปรแกรมพื้นฐานที่ใช้บ่อย ๆ โปรแกรมตัดคำ ถ้าอยากให้ความถูกต้องสูงใช้ deepcut เลย ถ้าอยากให้ความถูกต้องสูงแต่เร็วขึ้นหน่อยใช้ attacut

แต่ถ้าเอาเร็ว libthai chamkho icu

chamkho ผมแบ่งขั้นตอนการตัดคำเป็น 3 ขั้น

  1. สร้าง directed acyclic graph (DAG) ของวิธีตัดคำที่เป็นไปได้ทั้งหมด
  2. หา shortest path บน DAG จากข้อ 1
  3. ถอด path จากข้อสองมาเป็นตำแหน่งของ string ที่ต้องตัด

ส่วนที่ปรับไปมาคือข้อ 1 ผมแยกแบบนี้

1.1 สร้าง edge จากคำในพจนานุกรมที่ตรงกับ sub-string
1.2 สร้าง edge
1.3 ตัด edge ที่ขัดกับกด cluster ซึ่งเป็น substring ที่ตัดไม่ได้ออก ข้อนี้เพิ่มมาในค.ศ. 2021

มีแค่นี้เลย เล่าย้อนกลับไปหน่อยว่าปลายค.ศ. 2002 ผมเริ่มทำโปรแกรมตัดคำออกมาเขียนด้วย C

เพราะผมลองใช้ NLTK ที่เขียนด้วย Python ทั้งตัวแล้วมันทำงานช้าเกิน

แต่ผมก็คิดว่าแอปก็ยังควรจะภาษาที่เขียนง่าย ๆ แบบ Python หรือ Ruby หรือ Lisp อยู่ดี


  1. หลายตัวเขียนด้วย C++ ผมเขียนไม่เป็นหลังจากเรียนอยู่นาน

  2. ไม่ค่อยเหมาะเอามา bind กับ Ruby และอื่น ๆ

  3. แก้ word list และกฎยาก

thaiwordseg ก็เลยใส่ word list ใน file กฎใช้ regex และ bind กับ Ruby มาให้เลย ดูโครงการได้ที่ sourceforge ยังมี package ของโครงการนี้อยู่บน SUSE Linux ด้วยครับ

ปลายค.ศ. 2015 ผมไปพูดเรื่อง Rust ใน Barcamp Bangkhen รอบนึง พูดประมาณในเอกสารนี้


ค.ศ. 2016 พูดเรื่อง Rust ในกลุ่ม Mozilla ไทย มีหลงเหลือ slide อยู่บ้าง

ค.ศ. 2016 ตั้งกลุ่ม Facebook สำหรับชาว Rust ไทยขึ้นมา ถ้าจำไม่ผิดตั้งตามกลุ่ม Clojure ไทยเพราะเห็นว่ามีกลุ่มขึ้นมาก็ดี ทุกวันนี้กลุ่ม RustLangTH ก็ได้คุณ Nui Narongwet และคุณ Wasawat Somno ช่วยกันดูแลเป็นหลักเลย กราบขอบพระคุณครับ


ค.ศ. 2017 ร่วมกับคุณ @iporsut ทดสอบ Rust ที่เอามาตัดคำโดยใช้ word list และยังไม่ได้ใช้ regex สรุปได้เลยว่าเอา Rust เร็วกว่า Go Java คือตัวที่เขียนด้วย Rust ใช้เวลาทำงานเพียง 60% ของ Go ยังไม่ต้องพูดถึงตัวอื่นที่ว่าช้ากว่านั้น

ส่วนพวก Python JavaScript Clojure คือช้ามาก ห่างกันเป็นเท่าตัว ดูรายละเอียดเพิ่มเติม

ค.ศ. 2017 พบปะชาว Rust ไทยไปอีกรอบนึง ตาม blog นี้

ค.ศ. 2017 ตั้งกลุ่ม Rust ไทยบน Telegram ทั้งกลุ่มใน Telegram ทั้ง Facebook ผมส่งมอบสิทธิ admin ไปหมดแล้ว สมมุติว่าตอนนี้ผมตายไปก็ไม่ผลกระทบแน่นอน

ค.ศ.2017 ผมแก้ bug บน servo พอมีบันทึกไว้ใน blog ของ servo ก็พบว่าเป็น bug ที่ผิดง่าย ๆ ใส่ตัวแปรสลับกัน

Rust จะประกาศ type ให้ต่างกันจน compiler ตรวจได้ว่าคนใส่สลับกันได้

แต่ก็แบบที่ให้เวลาใช้งานจริง ๆ ก็ไม่ได้ประกาศ type แบบที่ว่า i32 u32 เต็มไปหมด ใส่สลับกัน compiler ก็ตรวจไม่ได้อยู่ดี

ระหว่างผมเขียน Python กับ Rust รู้สึกว่าเขียน Rust ก็เกิด bug เยอะแยะอยู่ดี อาจจะว่าผมโง่ก็ได้ แต่มันเป็นตัวแปรควบคุมว่าผมก็โง่เหมือนเดิมเวลาเขียน Python หรือ Rust ก็ตาม 😛


ค.ศ. 2018 ส่วนมากก็จะปรับปรุงโปรแกรมเก่า เริ่มเขียน HTTP server มาหุ้มตัวตัดคำบ้าง โดยใช้ Hyper ตอนนี้น่าจะรันไม่ได้แล้ว หน้าตาแบบข้างล่าง

impl Service for WordcutServer {
    type Request = Request;
    type Response = Response;
    type Error = hyper::Error;
    type Future = WebFuture;

    fn call(&self, req: Request) -> Self::Future {
        match (req.method(), req.path()) {
            (&Post, "/wordseg") => wordseg_handler(req),
            (&Post, "/dag") => dag_handler(req),
            _ => not_found(req)


ค.ศ.2019 ส่วนมากจะเป็น code พวก string-tree เหมือนห้าปีก่อน แต่ style ก็เปลี่ยนไปเยอะ หน้าตาประมาณข้างล่าง

quick_error! {
    pub enum TextunitLoadingError {
        CannotLoadToks(lang: LangKey, err: Box<Error>) { }
        CannotLoadLines(lang: LangKey, err: Box<Error>) { }
        CannotLoadLinks(err: Box<Error>) { }
        CannotAlignToks(lang: LangKey, line_no: usize, err: Box<Error>) { }

pub struct Textunit {
    pub bi_text: BiText,
    pub bi_rtoks: BiRToks,
    pub links: Vec<Link>,


ค.ศ. 2020 ผม port extension สำหรับ full-text search ภาษาไทยบน PostgreSQL ของคุณ zdk (Di Warachet S.) มาเป็น Rust

ผมไม่ทราบว่าคุณ zdk คิดอย่างไร แต่ผมมองว่าการลงพวก ElasticSearch เพื่อที่จะใช้ full text search พื้น ๆ หรือแม้แต่เอามา query JSON document บนเว็บที่คนใช้พร้อมกันไม่ถึง 100% คน มันเพิ่มงาน กิน RAM กิน SSD ขึ้นอีกเยอะ ใช้ PostgreSQL แทนได้น่าจะดีกว่า ตัวที่เขียนด้วย Rust ผมตั้งชื่อให้ว่าชำฆ้อพีจี

ค.ศ. 2020 ผมเขียน JSON parser สำหรับ GNU Guile ชื่อ guile-json-parse ด้วย


ค.ศ. 2021 ผมแก้ให้โปรแกรมตัดคำมี regular expression แบบ newmm และ nlpo3 แต่พบว่าทุกอย่างที่เคย optimize มาสูญสลายไปเกือบหมดเลย เพราะว่า regex แบบ fancy ใน Rust มันก็ไม่ได้เร็วกว่า regex engine ของ Python ที่เขียนด้วย C 😰

ถ้าจะคงความเร็วไว้อาจจะต้องเขียนกฎให้เป็นภาษา Rust เลยแบบที่ chamkho เคยทำมา หรือทำตาม libthai ที่มีกฎคล้าย ๆ newmm แต่เขียนด้วย C วิธีนี้เร็วแน่ แต่ว่าแก้ทีเหนื่อย จะเพิ่มภาษาใหม่หนักแรง

แต่ Rust มีทางออกที่สามคือใช้ regex-automata ของ Andrew Gallant ซึ่งตัวนี้ตามชื่อแปลง regex เป็น automata แบบที่ควรจะเป็น แต่ก็จะรับ regex แบบขยายของ newmm ไม่ได้ก็เลยต้องโม regex บ้าง

ทำให้เสร็จแล้ว newmm รันบน xeon ใช้เวลารัน 9.65 เท่าของ chamkho ผมว่าควรจะเขียนถึงเรื่อง profiling ด้วยแค่จะแยกไปอีกโพสต์

อีกภาษาที่หลัง ๆ ทำงานเร็วขึ้นมาก และสำหรับผมเขียนง่ายกว่า Rust เยอะมาก ๆ คือ Julia แต่ว่าก็ติดอยู่ 3 เรื่องคือ

  1. ไม่มี regex-automata บน Julia
  2. runtime ของ Julia เปิดช้า
  3. ทำ lib ลอกเลียนภาษา C ลำบาก ทำให้เอาไปใช้กับ cffi ใน Common Lisp หรือ Ruby ยาก

Common Lisp ก็เป็นอีกภาษาหนึ่งที่ optimize มาก ๆ แล้วก็เร็วสูสี Julia แต่ก็ติดประเด็นเดียวกันทั้งหมด

ทำให้ผมสรุปแบบนี้เลยว่า Rust นอกจากภาษาและ compiler แล้วยังเป็น community ที่ lib ดี ๆ ออกมาเยอะมาก หลายตัวหาในภาษาอื่นยาก

ค.ศ. 2021 อีกอย่างที่คือ เพื่อที่จะไป bind กับภาษาอื่นง่าย ๆ chamkho มี wrapper ทำให้กลายเป็น library ภาษา C แบบปลอม ๆ ชื่อ wordcutw

พอทำแบบนี้เวลาจะ bind กับ Ruby หรือภาษาอื่น ๆ ก็ไม่ต้องคิดอะไรใหม่เลยใช้ FFI ได้เลย ข้อดีของการใช้ FFI หรือ cffi คือมันจะไม่ยึดติดกับ implementation ตัวใดตัวหนึ่ง เช่น Common Lisp อาจจะใช้ implementation เป็น SBCL หรือ Clozure CL ก็ได้ แต่ถ้าใช้ cffi มา bind กับ wordcutw ก็จะใช้ได้ทั้ง SBCL และ Clozure CL โดยไม่ต้องแก้ code เลย

ค.ศ. 2021 ผมคิดว่าน่าจะไป chat กันแบบ protocol ที่เปิดและ decentralize บ้างก็เลยเปิด กลุ่ม Rust ไทยบน Matrix

ค.ศ. 2021 อีกอย่างที่ทำคือเทียบความเร็ว Apache Arrow กับ Pandas ผลคือถ้าเขียน Pandas ดี ๆ ก็เร็วได้ แต่ใช้ Arrow เร็วกว่า ยิ่งใช้ Rust ก็เร็วใหญ่ และไม่ต้องระวังเรื่องแปลงไปมาระหว่าง type ของ Rust และ Python ดูรายละเอียดเพิ่มเติม

ค.ศ. 2021 ก็มีเรื่องอื่น ๆ คือเขียน parser สำหรับ Apertium stream format โดยใช้ Nom ทำให้เขียน parser ง่ายขึ้นมาก; port โปรแกรม Attacut มาบน Rust ให้ชื่อว่า Khatson ปรากฎว่าไม่เร็วขึ้นเลยแต่ทำให้รู้ว่าใช้ tch ใน Rust ที่หุ้ม PyTorch ไว้ให้ เขียนพวก deep learning แบบเดี๋ยวกับใช้ PyTorch สะดวกมาก และสำหรับผมงงน้อยกว่าใช้ PyTorch ตรง ๆ

สถานการณ์ของ Rust ในไทย

สภานการณ์ปัจจุบัน ตอนนี้ผมคิดว่า Rust ติดลมบนไปแล้ว บริษัทขวัญใจมหาชนก็เป็นสมาชิก Rust Foundation ทั้ง AWS Facebook Google Huawei Microsoft Mozilla; celeb ก็เขียน Rust กันแล้ว สถานการณ์ในไทยก็ฝืนกระแสโลกไม่ไหวแน่ ๆ

ส่วนที่เป็นรูปธรรมมากคือคุณ Jojo จากช่อง KubeOps Skills ทำวิดีโอภาษาไทยขึ้นมาสอนเขียน Rust เลย ผมว่าอันนี้ดีมาก ๆ และตามที่เกริ่นไปคุณ Natechewin Suthison จะจัดงาน Rustacean Bangkok 2.0.0 วันที่ 13 ธันวาคม พ.ศ.2564 นี ทุกท่านกดดูรายละเอียดตาม link ไป Facebook ได้เลย

โดย Vee Satayamas ณ 1 December 2021 09:27 +0000

15 November 2021


Install Emacs 27.2 on openSUSE LEAP 15.3 from a source tarball

Install pre-requisited tools

sudo zypper install make gcc automake autoconf libtool aclocal wget
sudo zypper si -d emacs

Download Emacs source tarball

wget https://ftp.gnu.org/pub/gnu/emacs/emacs-27.2.tar.gz

Extract and change directory

tar xzvf emacs-27.2.tar.gz
cd emacs-27.2

Generate makefiles

./configure --prefix=/opt > log


make -j `nproc`


sudo make install

Add path

Add the line below to ~/.bashrc by nano ~/.bashrc

export PATH="/opt/bin:$PATH"

โดย Vee Satayamas ณ 15 November 2021 05:15 +0000

12 November 2021


An incomplete WebSocket client based on only socket, ssl, and uuid in Python

A few days ago, I couldn't get a WebSocket library working with another library on Python 3.10. So to avoid those dependencies, I implemented my WebSocket client on a low-level socket API. I implemented one in Common Lisp first. Then I translated it to Python. My WebSocket client is very far away from being completed, but at least it can run.

The first part is import. I imported only three things that are socket, ssl, and uuid.

import socket
import ssl
import uuid

The second part is based on HTTP headers for telling the server to switch to the WebSocket mode.

def upgrade(s, conn_info):
    with s.makefile(mode = 'rw', encoding = "ISO-8859-1") as f:
        f.write(f'GET {conn_info["path"]} HTTP/1.1\r\n')
        f.write(f'Host: {conn_info["host"]}\r\n')
        f.write("Connection: Upgrade\r\n")
        f.write("Upgrade: websocket\r\n")
        f.write(f'Sec-Websocket-Key: {str(uuid.uuid1())}\r\n')
        f.write("Sec-WebSocket-Version: 13\r\n")

        # reading response
        for line in f:
            if line == "\n":

The third part is reading a content length. I assume that the server keep sending text contents. In read_payload_len(), s.recv(1) read a byte from the socket s. & 0x7F is for masking only 7 bits. If length is 127, the length is in the next 4 bytes instead. If length is 126, the length is in the next 2 bytes. Otherwise the function just returns the length.

def read_payload_len(s):
    match s.recv(1)[0] & 0x7F:
        case 127:
            return read_extra_len(s, 4)
        case 126:
            return read_extra_len(s, 2)
        case l:
            return l

The read_extra_len reads bytes and turn them to integer.

def read_extra_len(s, num_of_bytes):
    buf = s.recv(num_of_bytes)
    len = 0
    for i in range(num_of_bytes):
        len += buf[i] << (8 * (num_of_bytes - i - 1))
    return len

In the fourth part, we read a web socket frame. The program determine a frame type from opcode, which in the last 4 bits of the header. I should implement PING-PONG part but I didn't. According to RFC6455, which I forgot to mention before, opcode == 0x1 means the frame is a text frame. So the program reads payload length and reads the payload.

def read_frame(s):
    header0 = s.recv(1)[0]
    opcode = header0 & 0x0F
    match opcode:
        case 0x1:
            payload_len = read_payload_len(s)
        case 0x9:
        case 0xA:

The last part is for opening connections and SSL/TLS wrapper. The function created a socket and wrapped it with TLS/SSL wrapper. The sending upgrade message to ask the server to switch to Websocket mode and the keep reading frames.

def connect(conn_info):
    ctx = ssl.create_default_context()
    with socket.create_connection((conn_info["host"], conn_info["port"])) as s:
        with ctx.wrap_socket(s, server_hostname = conn_info["host"]) as ss:
            upgrade(ss, conn_info)
            while True:

In the final part, I cannot find any public Websocket endpoint besides ones from cryptocurrency exchanges. So I put Bitkub API.

connect({"host": "api.bitkub.com",
         "port": 443,
         "path": "/websocket-api/market.trade.thb_btc"})

And it works, but if you are unlucky, you will get PING instead.

> python3.10 http_ex.py 

โดย Vee Satayamas ณ 12 November 2021 15:08 +0000