veer66

veer66

New Rustacians usually ask why Rust has many types of strings. I don't know the original intention. Anyways, it has some advantages as follow:

  1. It minimizes the language core. Rust core has only str. String, OsString, CString are in the standard library, which is not a part of the language core.

  2. Programmer can control performance. For example, &str refers to a byte slice so it should be fast, while String is a wrapper of Vec. String should have more performance penalty from heap memory allocation.

  3. OsString and CString improve interoperability with other programming languages and operating systems. Rust can manipulate C-style strings directly without converting them to native Rust strings. OsString is also similar.

String conversation has a performance penalty. Also sometimes string conversation is not obvious. Some systems don't even use Unicode or other standards.

The alternative of having too many types is treating everything as a byte slice. However, type checking doesn't work well with too few types.

I enjoy using key-value data in dynamic languages. For example, in Python, I can create key-value data for storing the metadata of a document as shown below. I don't discuss why I don't use struct, class, named tuple in this post.

doc_metadata = {"title": "The Rust Programming Language",  
                             "type": "book", 
                             "number-of-pages": 584, 
                             "authors": ["Steve Klabnik", 
                                                "Carol Nichols", 
                                                "contributions"]}

I can code read/write a value easily, for example:

# Write
doc_metadata["type"] = "text book"

# Read
print(doc_metadata["type"])

In Perl and Ruby, we can use Hash, which is almost the same thing as Dict in Python. In JavaScript, we can use an object.

Common Lisp is different. We can use a hash table, but it is not as convenient as Dict in Python.

(let ((doc-metadata (make-hash-table)))
  (setf (gethash :title doc-metadata) "The Rust Programming Language")
  (setf (gethash :type doc-metadata) :BOOK)
  (setf (gethash :number-of-pages doc-metadata) 584)
  (setf (gethash :authors doc-metadata) '("Steve Klabnik"
                                                                      "Carol Nichols" 
                                                                      "contributions")))

Besides construction, printing a hash table is not so convenient. Maybe one can create a function or macro to make creating/printing a hash table convenient. I still felt that I abused Common Lisp.

My code is usually too buggy when I keep mutating the same variable. So I prefer using an immutable data structure to prevent me from messing things up. Moreover, my key-value data usually do not have more than five keys. So I don't strictly need to use an efficient data structure, namely, hash table or binary search tree. So I use alist (assosiation list). I can construct a list like below:

(setq doc-metadata '((:title . "The Rust Programming Language")
                     (:type . :BOOK)
                     (:number-of-pages . 542) 
                     (:authors . '("Steve Klabnik"
                                   "Carol Nichols" 
                                   "contributions"))))

IMO, it looks concise and convenient. We can retrieve key-value pair with a specific key using the assoc function, which I suppose it does linear search. Linear search can be slow. However, my alist doesn't have a lot of keys.

Instead of replacing a value with another value, I can add a new key-value pair with an existing key, for example:

(setq another-doc-metadata (acons :type :TEXT-BOOK doc-metadata))

By retrieving the value of :type using assoc, we get the new value because assoc function retrieves the first key found in alist, for example:

(cdr (assoc :type another-doc-metadata))
;; OUTPUT => :TEXT-BOOK

However, with function calls instead of number/string literal, alist doesn't look concise anymore, for example:

(list (cons :title (get-title x y z))
       (cons :type (get-type x))
       (cons :number-of-pages (get-number-of-pages a b c)) 
       (cons :authors (get-authors c d)))

plist looks much more concise, for example:

(setq doc-metadata (list :title (get-title x y z)
       :type (get-type x)
       :number-of-pages (get-number-of-pages a b c) 
       :authors (get-authors c d)))

I can retrieve a value corresponding to a key easily by getf function. For example:

(getf doc-metadata :type)

A new value can be replaced the old value by setf, example:

(setf (getf doc-mentadata :type) :TEXT-BOOK)

setf is different from acons since acons doesn't mutate the existing list, setf does. Therefore plist is not exactly what I'm looking for.

Maybe the best way is using an Alexandria function for converting plist ot alist as Michał “phoe” Herda suggested.

I wanted to create a parser for Apertium Stream. In 2014, I used Whittle in Ruby. If this year were 2001, I would use Lex/Yacc. Anyway, this year is 2021. I wanted to create this parser in Rust. I tried to find what is similar to Lex/Yacc. I found Rust-Peg. I found a link to Nom from Rust-Peg's document. My first impression was Nom example is easy to read. At least, its document claimed Nom is fast.

Apertium Stream format is quite complex, and I didn't know exactly how to use Nom. So I started from an easy case. My simplified Apertium stream is a list of lexical units. A lexical unit looks like this:

^surface_form$

Btw, I didn't test my source code on this post. If you want a runnable example, please check https://github.com/veer66/reinars.

I created a function to match a lexical unit first. It looks like this:

fn parse_lexical_unit(input: &str) -> IResult<&str, &str> {
    let mut parse = delimited(tag("^"), is_not("^$"), tag("$"));
    parse(input)
}

By running parselexicalunit(“^cat$”), it returns Ok((“”, “cat”)).

I hopefully improve by returning a Lexical Unit struct instead of &str.

#[derive(Debug)]
struct LexicalUnit {
    surface_form: String
}

fn parse_lexical_unit(input: &str) -> IResult<&str, LexicalUnit> {
    let mut parse = delimited(tag("^"), is_not("^$"), tag("$"));
    parse(input).map(|(i,o)| (i, LexicalUnit { surface_form: String::from(o) }))
}

“delimited” helps me to match ^ at the beginning and $ at the end. I wanted to capture whatever, which is not ^ or $. So I use is_not(“^$”). Can it be more straightforward?

When I ran parselexicalunit(“^cat$”), I get Ok((“”, LexicalUnit { surface_form: “cat” })) instead. 😃

Then I created a function for parsing the simplified stream.

fn parse_stream(input: &str) -> IResult<&str, Vec<LexicalUnit>> {
    let mut parse = separated_list0(space1, parse_lexical_unit);
    parse(input)
}

In the parsestream function, I use parselexicalunit, which I created before, in separatedlist0. separatedlist0 is for capturing the list, which in this case, the list is the list of lexical units parsed by parselexical_unit; and space1, which is one or more spaces, separate the list.

By running parse_stream(“^I$ ^eat$ ^rice$”), I get:

Ok(("", [LexicalUnit { surface_form: "I" }, 
             LexicalUnit { surface_form: "eat" }, 
             LexicalUnit { surface_form: "rice" }]))

I think this is enough for showing examples. The rest of the parser is the combination of alt, escaped_transform tuple, etc. By doing all these, I feel that this is easier than using Lex/Yacc or even Whittle at least for this task.

ความเห็นเกี่ยวกับ Haskell

ผมเพิ่งเคยลองเขียน Haskell นิด ๆ หน่อย ๆ เรียกว่ายังรู้เรื่องก็ได้แต่อยากบันทึกความคิดของตัวเองไว้ จะผิดถูกอย่างไรก็เรียนเชิญอภิปรายได้เลยครับ

ผมมองว่าคุณของ Haskell มันทำให้ programmer เห็นได้ชัดว่า function ไหนไปยุ่งกับ IO หรือ database และ function ไหนไม่ยุ่ง

ถ้าเขียน Python ดูเผิน ๆ ไม่ไล่ดูเนื้อ function ทั้งสองแบบมันก็หน้าตาเหมือนกัน

พอเห็นได้ว่ามันต่างกัน ก็อาจจะคิดขึ้นมาได้ว่าควรเขียน function ที่อิสระจาก IO จาก database ให้มาก เพราะพวกนี้เขียนง่าย test ง่ายทั้ง auto ทั้ง manual ทำ hot reload ก็ง่าย

แต่คนที่สำเหนียกไม่ได้ก็มี เช่น (ผมเอง) ทำไปทำมามี monad กระจายไปทุก function

แอปหรือบริการออนไลน์มักจะมี database หรือไฟล์ก็ตามไว้เก็บค่าหรือสถานะต่าง ๆ เช่น จำนวนวัคซีนที่ยังไม่ได้มีคนจองของแต่ละโรงพยาบาล แล้วก็เป็นเรื่องธรรมดาอีกเหมือนกันที่ค่าที่ว่าจะถูกแก้ เช่น พอมีคนจองวัคซีนไปแล้วก็ต้องไปแก้จำนวนวัคซีนทีเหลือให้มันลดลง

เวลาอ่านเจอว่าหลีกเลี่ยงโปรแกรมที่รันแล้วมี side effect มันชวนให้เขาใจผิดได้ ผมอยากจะเรียกว่าจัดโปรแกรมให้มีระเบียบก็พอ

จากความเข้าใจบ้าน ๆ ของผมในยุค 80s 90s คนก็ใช้ dBASE เป็นการบังคับจัดให้เป็นระเบียบระดับนึง ข้อมูลทั้งหมดเป็น table ที่เอาใส่ไฟล์ .dbf ไว้ โปรแกรมที่จะเขาไปอ่านไปแก้ข้อมูลอยู่ใน .prg แยกกันอย่างชัดเจน

ปลาย ๆ ยุค 90s คนเริ่มนิยมใช้ database ที่รันต่างหากเป็นอีกโปรแกรมเลย แล้วแอปก็รันแยกต่างหากแล้วสื่อสารส่งข้อมูลกันข้าม process หรือกระทั่งผ่านระบบเครือข่าย พวกนี้ใช้ Visual Basic หรือ Delphi หรือ Tcl/Tk อะไรก็ว่ากันไป ไป แล้วไปต่อกับ Oracle หรือ SQL Server อีกที จะว่าคู่ขนาดกับเครื่องมือพวกนี้ทางค่าย NeXT ซึ่งถูก Apple ซื้อไปทีหลังเขาก็เอาความคิดแบบ MVC มาใช้แล้ว ตั้งแต่ยุค 80s แต่ถ้าสืบสาวไปไปจริง ๆ จะไปถึง Smalltalk-79 จาก Xerox ในยุค 70s แต่ว่าแถวบ้านผมยังไม่ไม่ใช่เทคนิคที่เป็นที่นิยม NeXT computer ผมได้แค่ไปลูบ ๆ เครื่องตัวโชว์

ยุคต่อมาจาก Visual Basic ก็ทำเป็นเว็บจะเอา Perl เขียน หรือใช้ PHP หรือใช้ ASP เขียน โครงสร้างยังเหมือนเดิมคือแบ่ง database ไปไว้เป็นบริการนึง แอปอีกบริการนึง แล้ว code ในแอปก็จะเห็นได้เลยว่าส่วนไหนเอาไว้แก้ข้อมูล เพราะในยุคนี้จะเห็นเลยใช้ภาษา SQL อยากดูว่า code ส่วนไหนแก้ข้อมูลก็หาคำว่า INSERT หรือ UPDATE ก็เจอแล้ว มันก็เป็นระเบียบขึ้น ยุคนี้ข้อดีคือ data อยู่เป็นที่ ส่วนที่กระจัดกระจายออกไปก็มันจะเป็น data อายุสั้นที่อายุมักจะไม่เกิน 10 วินาที

mrbs สำหรับผมเป็นตัวแทนของยุค 2000s เลยนะสำหรับผม เป็นโปรแกรมจองห้องโรงแรมมันก็คล้าย ๆ จองวัคซีนนะ อาจจะซับซ้อนกว่าด้วยซ้ำ เขียนด้วย PHP

โปรแกรมจัดระเบียบส่วนที่แก้ค่าต่าง ๆ อย่างชัดเจนคือ พวกที่แก้ database อยู่ในไฟล์ที่ลงท้ายด้วย _handler.php ข้างในก็มี code สร้าง SQL มีคำสั่ง update ไม่ได้แสดงผลอะไรออกมา ส่วนไฟล์ที่ชื่อคล้าย ๆ กันแต่ไม่มี _handler ลงท้าย พวกนี้ก็เห็นคำสั่ง SELECT ข้างในแล้วก็ไปเรียก code ที่สร้าง HTML อีกที

ยุค 2000s ก็ไม่ใช่ว่าทุกคนทำแอปออกมาแล้วจะเป็นระเบียบแบบ mrbs ต้องอาศัยการวางโครงสร้างที่ดี กระทั่ง mrbs ยังจัดระเบียบให้มีสัดส่วนให้มากกว่านั้นได้อีก

ทำให้มี framework ที่ดังขึ้นมาในยุค 2010s คือ Ruby on Rails เอาแนวคิด MVC มาใช้ซึ่งเจ้าอื่นเขาก็ใช้มาตั้งแต่เริ่มยุค 80s หรือก่อนนั้นแล้ว

Ruby on Rails ตัวอย่างจาก code ของ Mastodon ส่วนที่แก้ค่าต่าง ๆ ก็อยู่ใน model แยกเป็นเรื่องต่าง ๆ มี user post แต่เรียก status เป็นต้น

นอกจากเรื่องประสิทธิภาพเวลารันแล้ว พวกแอปหลัง 2010 มีประเด็นที่ต้องจัดการคือ แอปบน smart phone; แอป web browser แทน server; โปรแกรมซับซ้อนแยกออกมา test เฉพาะส่วนยาก ไม่ว่าจะ automated test หรือ manual test

สามเรื่องนี้ผมมองว่าเกี่ยวกัน ยุคต้น ๆ 2000s ใช้ http เป็นหลัก ระเบียบในการเปลี่ยนค่าคือ ทั้งหมดอยู่ใน database กลาง กับ session db ซึ่งอาจจะเป็น file หรือ database อีกตัวที่เน้นใช้ RAM ส่วนตัวแปรยิบย่อยที่ใช้งานระหว่าง HTTP ทำงานมันก็จะถูกไปในการเรียกครั้งเดียว

แต่พอมาทำแอปบน smartphone และบน web browser ตัวแปรบางตัวอาจจะอยู่ไปเลยทั้งวันก็ได้ หรือหลายวันก็ได้ แต่ว่าตัวอย่างนี้คงใช้กับพวกเว็บลงทะเบียนไม่ได้แล้ว เพราะเว็บลงทะเบียนควรจะลงเสร็จใน 10 นาที ส่วนของเว็บลงทะเบียนที่ต้องมีโปรแกรมบน web browser บ้างเช่น ถ้าเลือกสัญชาติเป็นกัมพูชาหน้าจอก็จะให้กรอกเลข passport แทนเลขบัตรประชาชน validator ก็ต้องเปลี่ยนให้ใส่ตัวอักษรโรมันได้ แทนที่จะเป็นตัวเลขอย่างเดียวแบบเลขประชาชน แต่ถ้าแอปเป็นอย่างอื่น เช่น ตลาดหุ้น คลาดสินทรัพย์ดิจิทัลต่าง ๆ มีกราฟมีราคาพวกนี้บางคนก็เปิดทิ้งไว้เป็นวันเป็นสัปดาห์เลยก็ได้ และสถานะมันก็เปลี่ยนไปตลอด จะมาหวังทำแบบสมัยที่เว็บโหลดมาทีละหน้าแล้วล้างสถานะไปหมดไม่ได้แล้ว

ทางด้าน backend ก็มี business rule หลาย ๆ อย่างที่แยกออกมา test ได้โดยที่ไม่ต้องไปรันกับ database หรือเว็บ แต่ถ้าไม่แยกออกมาแต่แรกต้องเรียกจาก controller หรือใน http handler แบบแยกไม่ออกก็กลายเป็นยากไป

พอมีความจำเป็นต้องแยกส่วนที่ว่าไม่ต้องไปรันกับ database ก็ได้ อันนี้ functional programming ก็กลายเป็นสำคัญขึ้นมา จริง ๆ แล้วก็เป็นเรื่องเก่า บางคนบอกว่าเก่าจนถึง 1920s ด้วยซ้ำ

ความคิดส่วนหนึ่งของ function programming คือการแยก pure function ออกมา พอเป็น pure function นี่เรา test ได้เลยทั้ง auto ทั้ง manual ไม่ต้องเอา database ใส่

ทั้ง Lisp ทั้ง Clojure ส่วนเด่น ๆ คือมันแถม immutable data structure มาให้ด้วย อันนี้ก็ทำให้เขียน pure function ง่ายขึ้น ไม่ต้องแก้ data structure ก้อนเดิม สร้างก็ใหม่ได้ได้เรื่อย ๆ โดยที่ไม่ได้ช้าหรือกินที่มาก

แต่ถึงอย่างนั้น data structure ที่ว่าก็เอาใส่ภาษาอื่นก็ได้ เอา JavaScript ก็ได้ เอา Ruby ก็ได้

OCaml หรือภาษาตระกูล Meta-Language ทั้งหมด เท่าที่ผมเห็นคือทำได้หลาย ๆ อย่างแบบ Lisp มี data structure แบบเดียวกันเลย แต่ว่าใช้แบบ static-typing ได้ วงเล็บน้อยลง ผมเดาว่าคนคงจะบ่นเรื่องนี้เยอะ

ส่วน Haskell ส่วนที่พิเศษคือ ปกติ Lisp กับ OCaml นี่เวลาเขียน pure function นี่ programmer ก็ต้องดูเอาเองว่ามัน pure หรือเปล่า แต่ Haskell นี่บังคับ pure แต่ในความ pure ผมดูคร่าว ๆ มันก็จะมีสองแบบคือ function ที่รับค่าเข้าไปธรรมดา กับ function ที่มี monad อย่างหลังนี่ถ้าไปยุ่งกับ database หรือ IO หรือ State ก็จะกลายเป็นว่ามี monad ขึ้นมา

การแยก pure function ออกมาผมมองว่าพอเอาไปใช้กับ MVC แบบใน Ruby on Rails ผมเห็นว่ามันทำให้จัดระเบียบข้อมูลกับฟังก์ชันได้ดีขึ้นไปอีกขั้น ซึ่งตัวที่ผมเห็นว่าขนาดแนวคิดเรื่อง MVC และ pure function ให้เข้าได้ได้ดีเลยคือ re-frame บน Clojure ขึ้นมา ก็เป็นการบังคับหรือกึ่ง ๆ บังคับให้ใช้ pure function มากขึ้น และแยก data ออกมารวมศูนย์กันเป็นสัดส่วน ข้อดีนอกจากจะ test ง่ายแล้ว ก็ทำ hot reload ง่ายขึ้นด้วย นอกจาก re-frame แล้ว Elm ก็น่าจะคล้าย ๆ กันแต่ผมยังไม่เคยใช้

สรุปว่าที่เล่ามาทั้งหมดการเขียนโปรแกรมตั้งแต่ 30-40 ปีทีแล้วจนถึงเดี๋ยวนี้ สิ่งที่ผมเห็นหลัก ๆ เลยก็คือการจัดระเบียบให้ data กับ function มันแยกกันเป็นสัดส่วน

ป.ล. ตรงไหนงงหรือว่าผมเขียนผิด ถ้าหากแจ้งมาจะขอบคุณมากครับ

unix epoch in milliseconds is equal to milliseconds from 00:00:00.000 on 1st of January 1970 to a point of time. Obtaining unix epoch, for now, is obvious for GNU Coreutils' date. For example:

$ date +%s
1629132447

Obtaining unix epoch in milliseconds is a bit more complicated. I did this.

$ date +%s%N | awk '{ print substr($0,0,13) }'
1629132562414

I had to use awk to chop the string because %N is for nanoseconds, which is too fine-grained in this case.

In Common Lisp, it is even more complex, since Common Lisp uses Universal Time, which is not unix compatible.

First, we can obtain Universal Time in second for 00:00:00.000 on 1st of January 1970 by this command.

(encode-universal-time 0 0 0 1 1 1970 0)

In the rest of the code, I will use a Common Lisp package – local-time. Given I have a timestamp tm from local-time, which I may obtain it like this.

(setq tm (local-time:now))

To get unix epoch, my code converts local-time's timestamp to Universal Time and subtracts Universal Time of 00:00:00.000 on 1st of January 1970 from it.

(- (local-time:timestamp-to-universal tm)
   (encode-universal-time 0 0 0 1 1 1970 0))

Instead of using this long code, local-time has timestamp-to-unix, so we can use it instead.

(local-time:timestamp-to-unix tm)

Since we want timestamp in milliseconds, my program multiplies timestamp above, which is in second with 1000.

(* 1000 (local-time:timestamp-to-unix tm))

local-time can provide a timestamp in microseconds, and we need to add it to the timestamp above.

(+ (* 1000 (local-time:timestamp-to-unix tm))
   (local-time:timestamp-millisecond tm))

And that is it.

คนเขียน blog ทำวิดีโอ ทำ podcast บางทีก็ควรจะได้ค่าตอบแทน

รูปแบบเดิม ๆ บนเว็บเลยก็คือเราก็ดูโฆษณาไป แล้วคนลงโฆษณาก็เป็นคนจ่ายตัง แบบนี้ก็ดีเหมาะกับคนจน หรือคนมีตังแต่ไม่อยากจ่าย แต่ก็เสียเวลา น่ารำคาญได้ หรือที่แต่ที่สุดคือถูกสอดแนมอย่างหนัก

อีกวิธีหนึ่งคือจ่ายตังรายเดือน อันนี้ก็เสียตังนิด ๆ หน่อยไม่แพงมาก ตัดรำคาญ ไม่โดนบริษัทโฆษณาสอดแนม แต่ข้อเสียคือบริการพวกนี้มันมีหลายเจ้า เว็บหนึ่งก็จ่ายรายเดือนทีหนึ่ง สมมุติว่าจ่ายเว็บละ 100 บาทต่อเดือน ดูสัก 20 เว็บบางทีก็อาจจะจ่ายพุ่งไป 2000 บาท บางเว็บก็อาจจะไม่ได้ดูทุกเดือนด้วยซ้ำ แบบนี้ก็ไม่น่าจะดีเท่าไหร่

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

web monetization มาแก้ปัญหานี้เลย แทนที่จะต้องอยู่บน platform เดียวกัน แต่ละเว็บก็แค่ใส่ไปว่าจะให้จ่ายตังไปที่ไหนผ่าน tag นี้

<meta name="monetization" content="$interledger pointer" />

โดยที่ interledger pointer นี่ก็จะได้มาจากการที่เราไปสมัคร wallet ไว้

ส่วนคนที่ดูเว็บตอนนี้ผมเห็นสองทางเลือกคือ ใช้บริการ coil ซึ่งมี extension ให้ทั้ง Chrome และ Firefox เวลาเราเปิดเว็บมันก็จะค่อย ๆ โอน token ไปให้ หรืออีกทางหนึ่งคือใช้ Brave

ทีแรกผมว่านี้มันซับซ้อนหน่อย ๆ เหมือนกัน แต่ในยุคที่วัยรุ่นเล่น defi ผมว่าแค่นี้ง่ายมาก

แต่ทั้งหมดผมก็เพิ่งมาศึกษา ไม่ทราบว่าจ่ายแต่จะได้ตังจริง ๆ หรือเปล่า หรือจะมีปัญหาติดขัดอื่น ๆ หรือไม่

  • I use SBCL in my project but SBCL didn't work well on Ubuntu 20.04 in Docker 19.3 on CentOS 7, so I cannot use Docker.
  • I found that GCC 4.8.5 was too old for building VISL CG3, so I installed GCC 8.5.0.
  • Still it didn't work because VISL CG3 was linked with old libstdc++. So I avoid it by installing libicu using GCC 8.5.0. Then it works.
#!/bin/bash

set -x
mkdir -p apertium-src && \
mkdir -p $MTDIR 

cd apertium-src && \
    wget http://ftp.tsukuba.wide.ad.jp/software/gcc/releases/gcc-8.5.0/gcc-8.5.0.tar.gz -O - \
	| gzip -dc \
	| tar -xf - && \
    cd gcc-8.5.0 && \
    ./configure --prefix=$MTDIR --disable-multilib && \
    make -j $(nproc) && \
    make install && \
    cd .. || exit 1

cd apertium-src && \
    wget https://github.com/unicode-org/icu/releases/download/release-69-1/icu4c-69_1-src.tgz -O - \
    | gzip -dc \
    | tar -xf - \
    && cd icu/source \
    && CC=gcc CXX=g++ ./configure --prefix=$MTDIR \
    && CC=gcc CXX=g++ make -j $(nproc) \
    && CC=gcc CXX=g++ make install \
    && cd ../.. \
	|| exit 1

cd apertium-src && \
    svn checkout http://beta.visl.sdu.dk/svn/visl/tools/vislcg3/trunk vislcg3 && \
    cd vislcg3 && ./get-boost.sh \
    &&  ./cmake.sh -DCMAKE_INSTALL_PREFIX=$MTDIR \
		   -DICU_INCLUDE_DIR=$MTDIR/include \
		   -DICU_LIBRARY=$MTDIR/lib/libicuuc.so \
		   -DICU_IO_LIBRARY=$MTDIR/lib/libicuio.so \
		   -DICU_I18N_LIBRARY=$MTDIR/lib/libicui18n.so \
    && make -j$(nproc) && \
    make install && cd .. || exit 1

cd apertium-src && \
    git clone https://github.com/apertium/lttoolbox && \
    cd lttoolbox && ./autogen.sh --prefix=$MTDIR && make -j $(nproc) && make install && cd ../.. || exit 1

cd apertium-src && \
    git clone https://github.com/apertium/apertium && \
    cd apertium && ./autogen.sh --prefix=$MTDIR && make -j $(nproc) && make install && cd ../.. || exit 1

cd apertium-src && \
    git clone https://github.com/apertium/apertium-lex-tools && \
    cd apertium-lex-tools && ./autogen.sh --prefix=$MTDIR && make -j $(nproc) && make install && cd ../.. || exit 1

cd apertium-src && \
    git clone https://github.com/apertium/apertium-tha && \
    cd apertium-tha && ./autogen.sh --prefix=$MTDIR && make && make install && cd ../.. || exit 1

cd apertium-src && \
    git clone https://github.com/apertium/apertium-tha-eng && \
    cd apertium-tha-eng && ./autogen.sh --prefix=$MTDIR && make && make install && cd .. && \
    cd .. || exit 1

อันนี้ลองเขียน Clojure ดู ข้อดีคือ Clojure ก็ไม่ค่อยใช้สัญลักษณ์มากมายอยู่แล้วยกเว้นวงเล็บ แม้แต่ def ก็เขียน macro มาครอบได้

เอาภาษาไทยมาตั้งชื่อได้ทุกสิ่งทุกอย่างเลย

(defmacro กำหนดให้ [a b]
  `(def ~a ~b))

(กำหนดให้ แสดงผล println)
(กำหนดให้ เอามาคูณกัน *)

(กำหนดให้ จำนวนบ้าน 20)
(กำหนดให้ จำนวนหมู่บ้าน 10)

(แสดงผล (เอามาคูณกัน จำนวนบ้าน จำนวนหมู่บ้าน))

ปกติแล้วก็คงไม่มีใครทำแบบนี้ แต่ก็ไม่แน่บางทีอาจจะมีโอกาสได้ใช้ก็ได้

(cons “dog” “cat”)

+------------------+-----------------+
|                  |                 |
|                  |                 |
|                  |                 |
|        X         |        X        |
|        |         |        |        |
|        |         |        |        |
|        |         |        |        |
+--------|---------+--------|--------+
         |                  |
         |                  |
         V                  V
       "dog"              "cat"

(car (cons “dog” “cat”))

"dog"

(cdr (cons “dog” “cat”))

"cat"