24 September 2021

Vee

A few remarks about actor and its supervisor in Actix

Actix has a supervisor that helps to restart an actor under its supervision. However, sometimes it doesn't. 😹

AFAIK something has to send a Die (message) to an actor when something went wrong. The actor has to implement a Die message handler. In the handler, it calls ctx.stop (context stop). When "Actor::stopping(...) == Running::Stop", its poll function will return Poll::Ready. Then its supervisor can perform restarting.

In my case, all of these didn't work because of two reasons.

  1. Actor didn't stop properly. It stopped without calling the stopped function. So I suppose it has no chance to handle the Die message.

  2. Actor kept using the CPU when it went wrong. So the poll function was blocked. Then the actor cannot handle the Die message.

I fixed the first case by calling tokio::time::sleep, and the second case by breaking for the loop.

PS I'm not sure about what I wrote. Please tell me if I missed or misunderstood some things or even everything.

โดย Vee Satayamas ณ 24 September 2021 16:24 +0000

18 September 2021

Thep

Red-Black Trees

Red-black tree เป็น self-balancing binary search tree ที่ใช้ในโครงการต่างๆ มากมาย เช่น ใน C++ STL, ใน symbol table ของ compiler/interpreter ต่างๆ และใน Linux kernel ด้วยข้อได้เปรียบ AVL tree เรื่องการเพิ่มข้อมูลเพื่อการ balance ในแต่ละโหนดเพียงบิตเดียว ในขณะที่ AVL tree ต้องการอย่างน้อย 2 บิต รวมถึงการ insert และ delete ที่เร็วกว่า แม้จะใช้เวลา search มากกว่าสักหน่อย แต่ก็ยังเป็น O(log n) เหมือนกัน

ฟังดูน่าศึกษาใช่ไหมล่ะ? แต่พอลองค้นดูจริงๆ มันยุ่งกว่าที่คิดมาก

red-black tree เสนอโดย Leonidas J. Guibas และ Robert Sedgewick (PDF) โดยต่อยอดมาจาก symmetric binary B-tree ของ Rudolf Bayer

symmetric binary B-Tree เป็นการสร้าง perfectly-balanced 2-3-4 tree (B-tree ที่มีทางแยกไม่เกิน 4 และทุก leaf มีความลึกเท่ากันหมด) ด้วย binary tree แบบพิเศษ โดยแทนโหนด B-tree ที่มีทางแยก 3 และ 4 ทางด้วย binary node 2 และ 3 โหนดที่เชื่อมกันด้วยลิงก์แนวนอน (เรียกว่า ρ-arc) ส่วนลิงก์ที่เชื่อมระหว่างโหนดของ B-tree ก็เป็นลิงก์แนวตั้ง (เรียกว่า δ-arc)

Guibas และ Sedgewick ได้นำ symmetric binary B-tree มาสร้างคำอธิบายแบบใหม่ โดยให้ทุกลิงก์เป็นลิงก์แนวตั้งทั้งหมด และเรียก ρ-arc ของ Bayer ว่า red link และเรียก δ-arc ว่า black link แล้วเรียบเรียงนิยามและอัลกอริทึมต่างๆ เสียใหม่ กลายเป็น red-black tree

Red-black representation of a 2-3-4 tree
(ภาพจาก Left-leaning Red-Black Trees โดย Robert Sedgewick)

ส่วนเหตุผลว่าทำไมต้องเป็นสองสีนี้ Sedgewick ตอบไว้ในตอนท้ายของการบรรยายครั้งหนึ่งว่า เป็นเพราะขณะที่พัฒนาเรื่องนี้กันที่ Xerox PARC นั้น เริ่มมีการสร้าง laser printer ที่สามารถพิมพ์สีได้ และสีที่พิมพ์ออกมาดูดีที่สุดในขณะนั้นคือสีแดง

อัลกอริทึมสำหรับ insert ของ red-black tree ค่อนข้างจะเทียบเคียงกับของ 2-3-4 tree ได้อย่างตรงไปตรงมา และใช้ operation พื้นฐานในการ rebalance คล้ายกับของ AVL tree แต่อัลกอริทึมสำหรับ delete นั้น Guibas และ Sedgewick บรรยายไว้ไม่ยาวมาก และยังดูมีขั้นตอนเกินจำเป็น โดยมีการ rebalance ทั้งขาลงและขาขึ้น

หลายตำราจึงคิดโครงสร้างของตัวเองขึ้นมาเพื่อปรับปรุงการ delete ซึ่งหลายกรณีส่งผลถึงวิธีอธิบายโครงสร้างทั้งหมดที่ไม่เชื่อมโยงกับ 2-3-4 tree อีกต่อไป เช่น เรียก red/black node แทน red/black link และอธิบายโครงสร้างในเทอมของ parent/sibling/uncle node ไปเลย ซึ่งทำให้จำเป็นต้องมี parent pointer และไม่อาศัย recursion และพอมาถึงเรื่องการ delete หลายตำราก็อธิบายได้น่าปวดหัวมาก บางฉบับถึงกับจั่วหัวไว้ก่อนเลยว่า You will hate it! บางฉบับข้ามเรื่อง delete ไปเสียดื้อๆ เลยด้วยซ้ำ

Red-black tree with red/black nodes
Red-black tree ที่ใช้ red/black node
(ภาพจาก Wikipedia)

หลังจากอ่านมาหลายแหล่ง รวมถึงฉบับที่ Sedgewick กลับมาเองด้วยโครงสร้าง Left-leaning red-black tree (slide, Coursera) พร้อมกับโค้ดสุด simple แต่ก็มีปัญหาเรื่องการ delete ที่ช้ากว่าของตำราอื่น ดังบทวิจารณ์ Left-Leaning Red-Black Trees Considered Harmful

แต่สุดท้ายก็เจอคำอธิบายที่ผมคิดว่าลงตัวที่สุดจากคอร์สหนึ่งของมหาวิทยาลัย Purdue ซึ่งอธิบาย Red-black tree แบบไม่มี leaning โดยอิง 2-3-4 tree ตั้งแต่ต้นจนจบ อ่านได้ตามลำดับเลยครับ:

  1. (2,4) Trees
  2. 2-3-4 Trees and Red-Black Trees
  3. Deletion from Red-Black Trees

โดย Thep (noreply@blogger.com) ณ 18 September 2021 06:07 +0000

17 September 2021

bact

กรณีข้อมูลผู้ป่วยโรงพยาบาลเพชรบูรณ์หลุดรั่ว

ข้อมูลคนไข้ที่หลุดรั่วออกมา มีชื่อ-สกุลของคนไข้ วันเดือนปีเกิด เพศ ชื่อหมอ ค่าใช้จ่าย สถานะการจ่าย ประเภทสิทธิการรักษา ชื่อวอร์ด

ชื่อวอร์ดนี่ตั้งตามชื่อกลุ่มโรค ก็อาจเป็นข้อมูลอ่อนไหวได้

ประเภทสิทธิการรักษา บางประเภทบอกสถานะความพิการได้

ข้อมูลวันเดือนปีเกิด พอประกอบกับการที่หน่วยงานหลายแห่ง มักใช้วันเดือนปีเกิดเป็นรหัสผ่านตั้งต้น (ทั้งสำหรับลงทะเบียนเข้าระบบ หรือปลดล็อกเอกสาร PDF) มันก็เลยทำให้อันตราย

ข้อมูลชื่อคนไข้ ประกอบกับชื่อหมอ ชื่อกลุ่มโรค ประเภทสิทธิรักษา การเบิกจ่าย พวกนี่เอาไปสร้างความน่าเชื่อถือและหาจุดอ่อนของเป้าหมาย เป็น social engineering เพิ่มโอกาสสำเร็จในการหลอกลวง

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

ชุดข้อมูลเหล่านี้ไม่จำเป็นจะต้องถูกใช้เดี่ยวๆ มันอาจเอาไปประกอบกับข้อมูลชุดอื่นที่หลุดมาก่อนหน้าหรือที่จะหลุดในอนาคตด้วยก็ได้

แม้จนถึงตอนนี้ พ.ร.บ.คุ้มครองข้อมูลส่วนบุคคล พ.ศ. 2562 จะถูก “เลื่อน” หรือยกเว้นไม่ให้ใช้บังคับอยู่เกือบทุกมาตราที่เกี่ยวกับการคุ้มครอง (เลื่อนมา 2 รอบละ) แต่มาตรา 4 วรรค 3 ยังไงก็ยังใช้บังคับอยู่นะ

“ผู้ควบคุมข้อมูลส่วนบุคคลตามวรรคหนึ่ง (2) (3) (4) (5) และ (6) และผู้ควบคุมข้อมูลส่วนบุคคลของหน่วยงานที่ได้รับยกเว้นตามที่กำหนดในพระราชกฤษฎีกาตามวรรคสอง ต้องจัดให้มีการรักษาความมั่นคงปลอดภัยของข้อมูลส่วนบุคคลให้เป็นไปตามมาตรฐานด้วย”

และน่าจะมีอย่างน้อยอีก 4 พ.ร.บ.ที่พอจะใช้ได้ ในมุมของผู้ได้รับผลกระทบ

1) โดยตัวข้อมูล เนื่องจากเป็นข้อมูลสุขภาพ ก็มี พ.ร.บ.สุขภาพแห่งชาติ พ.ศ. 2550 แต่ในรายละเอียดน่าจะมีข้อจำกัดอยู่มาก เนื่องจากไม่ได้ระบุเรื่องข้อมูลรั่วไหลตรงๆ แต่อาจจะไปพึ่ง “ธรรมนูญว่าด้วยระบบสุขภาพแห่งชาติ” อีกต่อหนึ่ง ซึ่งไกลอยู่

ข้อ 69 ในธรรมนูญว่าด้วยระบบสุขภาพแห่งชาติ พ.ศ. 2552 “ผู้บริโภคที่ได้รับความเสียหายจากการบริโภคสินค้าหรือการบริการต้องได้รับการชดเชยและเยียวยาอย่างมีประสิทธิภาพ เหมาะสม และรวดเร็ว”

ข้อ 1 (6) ในหมวดการคุ้มครองผู้บริโภคด้านสุขภาพ ของธรรมนูญว่าด้วยระบบสุขภาพแห่งชาติ ฉบับที่ 2 พ.ศ. 2559 “สิทธิในการร้องเรียนและสิทธิในการได้รับการชดเชยเยียวยาความเสียหายจากการบริโภค”

2) โดยตัวหน่วยงาน ถ้าเป็นโรงพยาบาลของกระทรวงเอง (ไม่ใช่ของมูลนิธิ ของบริษัท ของสภากาชาดไทย) ก็จะเป็นหน่วยงานรัฐ ซึ่ง พ.ร.บ.ข้อมูลข่าวสารของราชการ พ.ศ. 2540 ในมาตรา 23 ได้กำหนดหน้าที่ของหน่วยงานรัฐที่ดูแลข้อมูลอยู่

“หน่วยงานของรัฐต้องปฏิบัติเกี่ยวกับการจัดระบบข้อมูลข่าวสารส่วนบุคคลดังต่อไปนี้ จัดระบบรักษาความปลอดภัยให้แก่ระบบข้อมูลข่าวสารส่วนบุคคล ตามความเหมาะสม เพื่อป้องกันมิให้มีการนำไปใช้โดยไม่เหมาะสมหรือเป็นผลร้ายต่อเจ้าของข้อมูล […] ต้องแจ้งให้เจ้าของข้อมูลทราบในกรณีมีการจัดส่งข้อมูลข่าวสารส่วนบุคคลไปยังที่ใดซึ่งจะเป็นผลให้บุคคลทั่วไปทราบข้อมูลข่าวสารนั้นได้ […]”

3) ซึ่งพอเป็นหน่วยงานรัฐ ก็จะเข้าอีกกฎหมายคือ พ.ร.บ.การบริหารงานและการให้บริการภาครัฐผ่านระบบดิจิทัล พ.ศ. 2562 ในมาตรา 12

“เพื่อให้การบริหารงานและการให้บริการภาครัฐผ่านระบบดิจิทัลเป็นไป
ตามวัตถุประสงค์ตามมาตรา 4 […] ให้หน่วยงานของรัฐ […] ดำเนินการดังต่อไปนี้ให้เป็นไปตามธรรมาภิบาลข้อมูลภาครัฐตามมาตรา 8 จัดให้มีมาตรการหรือระบบรักษาความมั่นคงปลอดภัยในการเข้าสู่บริการดิจิทัลของหน่วยงานของรัฐ เพื่อให้มีความพร้อมใช้ น่าเชื่อถือ และสามารถตรวจสอบได้ โดยอย่างน้อยต้องจัดให้มี ระบบป้องกันหรือรับมือกับภัยคุกคามหรือความเสี่ยงทางไซเบอร์ตามกฎหมายว่าด้วยการรักษาความมั่นคงปลอดภัยไซเบอร์ […]”

(ดูประกาศคณะกรรมการพัฒนารัฐบาลดิจิทัล เรื่อง ธรรมาภิบาลข้อมูลภาครัฐ เพิ่ม)

  1. พ.ร.บ.การรักษาความมั่นคงปลอดภัยไซเบอร์ พ.ศ. 2562 ซึ่งถูกอ้างอิงจากกฎหมายอื่นๆ ข้างบน

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

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

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

หมอนวนรรน ธีระอัมพรพันธุ์ โพสต์ความเห็นส่วนตัวต่อเรื่องนี้ไว้ในเฟซ

The post กรณีข้อมูลผู้ป่วยโรงพยาบาลเพชรบูรณ์หลุดรั่ว first appeared on bact' is a name.

โดย bact ณ 17 September 2021 20:47 +0000

16 September 2021

Vee

The advantages of various types of strings in Rust

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.

โดย Vee Satayamas ณ 16 September 2021 06:08 +0000

6 September 2021

bact

สภากาชาดไทย – หน่วยงานที่รัฐให้อำนาจมาก แต่มีกลไกกำกับน้อย?

สภากาชาดไทยเป็นหน่วยงานรัฐหรือไม่?

สภากาชาดไทยนี่ก็เป็นอีกองค์กรที่สถานะงงๆ ในเว็บไซต์บอกว่าเป็นองค์กรการกุศล (ทำนองสมาคม เอ็นจีโอ) งบประมาณจำนวนมากมาจากงบประมาณแผ่นดิน (ซึ่งก็มาจากภาษี เงินรายได้ เงินกู้ ฯลฯ) ส่วนสภานายก/นายิกาและอุปนายกผู้อำนวยการ กษัตริย์เป็นผู้แต่งตั้งโดยตรง

ในแง่การได้รับงบประมาณจากรัฐ (ไม่ว่าสถานะหน่วยงานจะเป็นอะไรก็ตาม) เรื่องนี้ไม่ได้แปลก เพราะก็มีหลายหน่วยงานได้รับอุดหนุนในลักษณะนี้ รวมไปถึงวัดต่างๆ ด้วย และแม้งบประมาณที่สภากาชาดไทยจะได้รับเป็นตัวเงินที่สูงมาก (ปี 63 ได้รับจัดสรร 10,651 ล้านบาท ปี 64 ได้ 8,872 ล้าน) คิดว่าหลายคนก็คงมองว่า ก็ไม่น่ามีปัญหาอะไร เพราะภารกิจที่ทำก็เป็นสิ่งสาธารณะได้ประโยชน์

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

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

เคยมีคนจะขอข้อมูลจากโรงพยาบาลจุฬาลงกรณ์ โดยใช้พ.ร.บ.ข้อมูลข่าวสารของราชการ พ.ศ. 2540 ซึ่งคณะกรรมการวินิจฉัยการเปิดเผยข้อมูลข่าวสาร สาขาสังคม การบริหารราชการแผ่นดินและการบังคับใช้กฎหมาย มีคำสั่งที่ สค 235/2558 (12 พ.ย. 2558) วินิจฉัยว่าสภากาชาดไทย (ซึ่งเป็นนิติบุคคลบุคคลที่ดำเนินงานโรงพยาบาลจุฬาฯ) ไม่ใช่หน่วยงานของรัฐตามพ.ร.บ.ข้อมูลข่าวสาร ดังนั้นจึงขอข้อมูลไม่ได้

ในคำวินิจฉัยของคณะกรรมการวินิจฉัยฯ มีอ้างถึงคำสั่งศาลปกครองสูงสุดที่ว่าสภากาชาดไทย “มีลักษณะเป็นองค์การอาสาสงเคราะห์ ไม่ใช่หน่วยงานทางปกครอง” ดังนั้นเจ้าหน้าที่ของศูนย์บริการโลหิตแห่งชาติไม่ใช่เจ้าหน้าที่ของรัฐตามพ.ร.บ.จัดตั้งศาลปกครอง

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

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

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

คืองานกาชาดและงานบริการเลือดมันเริ่มจากบริบทสงครามก็จริง (กรณีงานบริการเลือดคือสงครามกลางเมืองสเปน) แต่พอหลังสงคราม ในหลายประเทศก็คลี่คลาย เอางานบริการเลือดนี้ไปเป็นงานสาธารณสุขในภาวะปกติที่รัฐดูแลจัดการ ไม่ได้เป็นเรื่องภาวะพิเศษอีกต่อไปแล้ว เช่นงานเลือดและอวัยวะระดับชาติของสหราชอาณาจักร NHS Blood and Transpant จะเป็นผู้ดูแล ไม่ใช่ British Red Cross ซึ่งพอเป็น NHS มันก็เข้ากลไกร้องเรียน เยียวยา ตามปกติของหน่วยงานรัฐ รวมถึงในแง่การดำเนินนโยบาย มันก็จะประสานไปในทางเดียวกันกับ NHS หลัก — แต่ของไทย งานบริการเลือดของเรายังอยู่กับกาชาด เหมือนสมัยสงครามอยู่จริงๆ

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

คำถามรวมๆ ก็คือ ทำไม power กับ accountability นี่มันไม่มาด้วยกัน

มีคนทำวิทยานิพนธ์เรื่องนี้ด้วย จากมุมกฎหมายมหาชน

อัครพล อร่ามเจริญ. สถานะทางกฎหมายของสภากาชาดไทย. วิทยานิพนธ์มหาบัณฑิต คณะนิติศาสตร์ มหาวิทยาลัย ธรรมศาสตร์, 2561. https://digital.library.tu.ac.th/tu_dc/frontend/Info/item/dc:165987 (ฉบับย่อเป็นบทความ ในวารสารบัณฑิตศึกษานิติศาสตร์ ปีที่ 12 ฉบับที่ 4 เดือน ตุลาคม – ธันวาคม 2562PDF)

กลุ่มองค์กรที่ใช้เงินรัฐ ใช้การอำนวยความสะดวกจากอำนาจรัฐ บุคลากรของรัฐ และทรัพยากรของรัฐ แต่ไม่อยู่ภายใต้ความรับผิดรับชอบแบบที่องค์กรรัฐปกติต้องมี นี่น่าจะมีอีกเยอะ (ราชวิทยาลัยจุฬาภรณ์?) และน่าจะมีไปทั่วทั้งส่วนกลาง ภูมิภาค และท้องถิ่น

เช่นกรณีกรมส่งเสริมการปกครองท้องถิ่น กระทรวงมหาดไทย มีหนังสือด่วนที่สุด มท 0808.2/ว5426 (24 ก.ย. 2561) “ซักซ้อมแนวปฏิบัติ” กับผู้ว่าราชการจังหวัดทั่วประเทศในการตั้งงบประมาณเงินอุดหนุนองค์กรปกครองส่วนท้องถิ่นเพื่อสนับสนุนภารกิจของกาชาดจังหวัด

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

คำสั่งคณะกรรมการวินิจฉัยการเปิดเผยข้อมูลข่าวสาร สาขาสังคม การบริหารราชการแผ่นดินและการบังคับใช้กฎหมาย ที่ สค 235/2558 เรื่อง อุทธรณ์คำสั่งมิให้เปิดเผยข้อมูลข่าวสารเกี่ยวกับประวัติการรักษาพยาบาล (12 พ.ย. 2558) http://www.oic.go.th/FILEWEB/CABINFOCENTEROPM/DRAWER01/GENERAL/DATA0004/00004684.PDF

คำสั่งศาลปกครองสูงสุด ที่ 585/2554 ระหว่าง นาย ส. ผู้ฟ้องคดี และเลขาธิการสภากาชาดไทย ที่ 1 อุปนายกผู้อํานวยการสภากาชาดไทย ที่ 2 ผู้ถูกฟ้องคดี https://www.krisdika.go.th/data/article77/filenew/585-2554.pdf

ที่ มท 0808.2/ว5426 เรื่อง ซักซ้อมแนวปฏิบัติการตั้งงบประมาณและการเบิกจ่ายเงินอุดหนุนขององค์กรปกครองส่วนท้องถิ่นเพื่อสนับสนุนภารกิจของเหล่ากาชาดจังหวัด (24 ก.ย. 2561) http://www.dla.go.th/upload/document/type2/2020/12/24704_3_1608885882330.pdf

โพสต์ครั้งแรกในเฟซบุ๊ก 2 ก.ย. 2564

The post สภากาชาดไทย – หน่วยงานที่รัฐให้อำนาจมาก แต่มีกลไกกำกับน้อย? first appeared on bact' is a name.

โดย bact ณ 6 September 2021 15:13 +0000

5 September 2021

Vee

Key-value data in Common Lisp

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.

โดย Vee Satayamas ณ 5 September 2021 16:54 +0000

2 September 2021

Vee

A little modification of Actix with a large afford

I want to print a log of unparsed #websocket frames used by #awc 2.0.3. If I used Emacs, I'd only add one line of code. However, using Rust and Actix is not that strangeforward. Besides recompilation, I needed to change Cargo.toml to use local crates. For example, in #awc's Cargo.toml, I changed:

actix-codec = "0.3.0" 

to

actix-codec = { path = "../../actix-net/actix-codec" }

However, after I recompiled the app, this change caused compilation errors because other crates still use different versions of actix-codec. So I changed all affected crates. I ended up modifying six crates as shown in the figure below.

Dependencies between awc and actix-codec

Moreover, rustc 1.54.0 does not accept this code below:

pub fn as_error<T: ResponseError + 'static>(&self) -> Option<&T> { 
      ResponseError::downcast_ref(self.cause.as_ref()) 
}

I presume that a previous version of rustc can. I tried to fix by adding "dyn" as the compiler suggested but it didn't work. So I deleted these three lines. Then rustc 1.54.0 can compile my app and Actix again.

I took too much afford for doing all of these just for printing a little more log. So I wrote this blog out. Maybe I did this in the wrong way. So please suggest to me more proper ways. Or in the worst case, I can read this blog when I forget how did this.

โดย Vee Satayamas ณ 2 September 2021 08:58 +0000

27 August 2021

Vee

Using Nom - a parser combinator library

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 parse_lexical_unit("^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 parse_lexical_unit("^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 parse_stream function, I use parse_lexical_unit, which I created before, in separated_list0. separated_list0 is for capturing the list, which in this case, the list is the list of lexical units parsed by parse_lexical_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.

โดย Vee Satayamas ณ 27 August 2021 16:10 +0000

26 August 2021

Vee

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

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

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

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

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

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

โดย Vee Satayamas ณ 26 August 2021 10:59 +0000

25 August 2021

Vee

จัดระเบียบโปรแกรมโดยแยกข้อมูลกับฟังก์ชันให้เป็นสัดส่วน

แอปหรือบริการออนไลน์มักจะมี 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 มันแยกกันเป็นสัดส่วน

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

โดย Vee Satayamas ณ 25 August 2021 13:13 +0000

16 August 2021

Vee

How to get unix epoch in milliseconds in Common Lisp

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.

โดย Vee Satayamas ณ 16 August 2021 17:18 +0000

3 August 2021

Vee

Thai word tokenizers benchmark: nlpo3 vs newmm

Thanathip Suntorntip Gorlph ported Korakot Chaovavanich's Thai word tokenizer - Newmm, written in Python, to Rust called nlpo3. The nlpo3 website claimed that nlpo3 is 2X faster than Newmm. I felt that Nlpo3 must be faster than this claim because in contrast to Python's Regex engine, Rust's regex runs in the linear time since it was constrained not to support looking back/ahead. Moreover, 2X faster is ambiguous.

So I conducted a bit different experiment on Mac mini M1. Both Nlpo3 and Newmm run on from Zsh instead of Python Notebook. I tested on 1 million lines of Thai Wikipedia snapshot. The result is that Newmm took 3.66X of the time that Nlpo3 required for tokenizing the same text on the same computer.

Setup

#!/bin/bash

set -x

INPUT=thwik-head1m.txt

for i in {1..10}
do
  { time python3 newmm.py < $INPUT > newmm.out ; } 2>> bench_newmm.txt
  { time nlpo3 segment < $INPUT > cham.out ; } 2>> bench_o3.txt
done
from pythainlp import word_tokenize
import sys

for line in sys.stdin:
        print("|".join(word_tokenize(line[:-1])))

Result

nlpo3

[root@exper1 ~]# % grep real bench_o3.txt 
real    2m10.923s
real    2m12.014s
real    2m10.931s
real    2m9.448s
real    2m9.055s
real    2m10.570s
real    2m10.672s
real    2m10.140s
real    2m11.220s
real    2m9.941s

newmm

% grep real bench_newmm.txt 
real    7m52.180s
real    7m58.090s
real    7m57.071s
real    8m9.779s
real    7m54.576s
real    7m52.807s
real    7m59.109s
real    7m58.489s
real    7m59.604s
real    7m57.844s

Average

% grep real bench_o3.txt | ruby -lane 'BEGIN { all = 0.0; cnt = 0 }; cols = $F[1].split(/[ms]/).map {|x| x.to_f }; v = cols[0]*60 + cols[1]; all += v; cnt += 1; END { p all/cnt}' 
130.49140000000003
% grep real bench_newmm.txt | ruby -lane 'BEGIN { all = 0.0; cnt = 0 }; cols = $F[1].split(/[ms]/).map {|x| x.to_f }; v = cols[0]*60 + cols[1]; all += v; cnt += 1; END { p all/cnt}'
477.9549

Performance ratio

3.66

โดย Vee Satayamas ณ 3 August 2021 04:09 +0000

28 July 2021

Vee

A script for downloading PeerTube videos

Since peertube.social is closing down, I need a script for downloading all of my videos. I searched for this for a few minutes. I felt that it exists but I can't find, so I wrote mine in Common Lisp.

It probably works on other Common Lisp implementations too but I only tested on SBCL 2.0.1-5.fc34.

;; How to run
;; sbcl --load download-peertube-videos.lisp

(ql:quickload :jonathan)
(ql:quickload :dexador)
(ql:quickload :alexandria)
(ql:quickload :asdf)

(import 'alexandria:assoc-value)

(defparameter *username* "peertube-user-name")
(defparameter *hostname* "peertube.social")
(defparameter *get-videos-url* (format nil "https://~A/api/v1/accounts/~A/videos" *hostname* *username*))

(defparameter *videos-info* (assoc-value (jonathan:parse (dex:get *get-videos-url*)
                                                         :as :alist)
                                         "data"
                                         :test 'equal))

(defparameter *total* (assoc-value (jonathan:parse (dex:get (format nil "https://~A/api/v1/accounts/~A/videos" *hostname* *username*))
                                                   :as :alist)
                                   "total"
                                   :test 'equal))

(defparameter *count* 5)

(defun get-video-id (video-info)
  (assoc-value video-info "id" :test 'equal))

(defun get-video-json (video-id)
  (let ((video-info-url (format nil "https://~A/api/v1/videos/~A" *hostname* video-id)))
    (dex:get video-info-url)))

(defun download-video (video-details)
  (let* ((files (assoc-value video-details "files" :test 'equal))
         (download-url (assoc-value (car files) "fileDownloadUrl" :test 'equal))
         (cmd (format nil "wget ~A" download-url)))
    (format t "CMD: ~A~%" cmd)
    (uiop:run-program cmd :output t)))

(defun save-metadata (video-id video-json)
  (with-open-file (f (format nil "~A.json" video-id)
                     :direction :output
                     :if-exists :supersede
                     :if-does-not-exist :create)
    (write-string video-json f)))

(defun download-video-with-metadata (video-info)
  (let* ((video-id (get-video-id video-info))
         (video-json (get-video-json video-id))
         (video-details (jonathan:parse video-json
                                        :as :alist)))
    (save-metadata video-id video-json)
    (download-video video-details)))

(let ((i 0))
  (loop while (< i *total*)
        do
           (let* ((url (format nil "https://~A/api/v1/accounts/~A/videos?start=~A&count=~A" *hostname* *username* i *count*))

                  (videos-json (dex:get url))
                  (videos-alist (jonathan:parse videos-json :as :alist))
                  (videos-info (assoc-value videos-alist "data" :test 'equal)))
             (format t "*** i = ~A~%" i)
             (loop for video-info in videos-info
                   do
                      (download-video-with-metadata video-info)))
           (setq i (+ i *count*))))

โดย Vee Satayamas ณ 28 July 2021 17:19 +0000

23 July 2021

Vee

ทำไมต้องมี Web monetization

คนเขียน 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 ผมว่าแค่นี้ง่ายมาก

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

โดย Vee Satayamas ณ 23 July 2021 07:54 +0000

16 July 2021

Kitt

Kobe B. Bryant

Kobe กับ Shaq: สองคนนี้เกลียดกัน Kobe เป็นเด็กหัวรั้น ซ้อมหนัก มั่นใจสูงมากว่าลูกบาสในมือเขามีโอกาสมากกว่าในมือคนอื่นในทีมเลยไม่ค่อยจ่ายบอล ในขณะที่ Shaq เข้ามายืนหนึ่งในทีม เฮฮา ปาร์ตี้หนัก ปล่อยเนื้อปล่อยตัว Kobe บ่นเรื่องนี้บ่อย ส่วน Shaq ไม่พอใจความรั้น แต่เขารู้ว่า Kobe จะเป็นคนที่ยิ่งใหญ่แน่ ๆ ช่วงแรกที่สองคนนี้มาอยู่ Lakers มีเกมที่ Kobe ลงสนามแล้วยิง air ball เยอะมากจนโดนโห่ Shaq เป็นคนแรกที่วิ่งไปกอดคอ Kobe แล้วบอกว่า “มึงไม่ต้องไปสนใจว่าใครจะเยาะเย้ย วันหนึ่งเวลาบอลอยู่ในมือมึง คนพวกนี้จะกลัวมึง” จุดเปลี่ยนเกิดในวันที่สองคนทะเลาะกันระหว่างซ้อม เวลานั้น Kobe กับ Shaq ร่วมทีม Lakers มาได้ 2-3 ปีแล้ว แต่ยังไปไม่ถึงแชมป์ NBA สักครั้ง สองคนซ้อมคนละทีม Shaq talk trash … Continue reading Kobe B. Bryant

โดย kitty ณ 16 July 2021 02:44 +0000

23 June 2021

Thep

Norasi Small Caps and Old Style Figures

ฟอนต์ Norasi เพิ่มการรองรับการใช้ small caps และ old style figures (ตัวเลขอารบิกแบบหนังสือยุคเก่า) แล้ว ทั้งใน OpenType และ LaTeX

PUA Glyphs

ฟอนต์ Norasi (ฟช ๓ จากโครงการฟอนต์แห่งชาติของเนคเทค) มี PUA glyph สำหรับตัว small caps และตัวเลขอารบิกในหนังสือยุคเก่า (old style figures) มานานแล้ว ผมไม่แน่ใจว่ามีมาตังแต่แรกเริ่มที่นำฟอนต์มาจากโครงการ Omega เลย หรือว่ามาเพิ่มเอาทีหลังใน TLWG เนื่องจากผู้ที่ทำงานกับ Norasi ในช่วงต้นจะเป็นคุณทิม (ชนพ ศิลปอนันต์) เป็นหลัก แต่มันก็อยู่ในรูป glyph ที่มีรหัสยูนิโค้ดในช่วง Private Use Area (PUA) พร้อมกับชื่อ glyph แบบ Postscript ตามอย่าง Adobe Glyph List เช่น เลขศูนย์แบบเก่ามีรหัสยูนิโค้ด U+F730 และมีชื่อ glyph เป็น zerooldstyle และตัว A small caps มีรหัสยูนิโค้ด U+F761 และมีชื่อ glyph เป็น Asmall เป็นต้น

PUA glyph เหล่านี้ โดยปกติจะไม่ใช้ในข้อความ แต่จะใช้เป็นการภายในของตัววาดข้อความ ในทำนองเดียวกับวรรณยุกต์ตัวต่ำ สระบนหลบหาง ป ฝ ฟ และสระอุ อู หลบหาง ฎ ฏ ในอักษรไทยนั่นเอง เท่ากับว่า PUA glyph เหล่านี้มีอยู่ แต่ยังไม่พร้อมใช้ ยกเว้นในระบบที่พยายามจะใช้ PUA เหล่านี้จริงๆ

OpenType

ขณะเดียวกัน เราจะเห็นแอปพลิเคชันสมัยใหม่พยายามรองรับ OpenType feature ต่างๆ ใน UI มากขึ้น จากเดิมที่มีแต่ใน desktop publishing หรูๆ ตอนนี้แม้แต่ word processor อย่าง LibreOffice Writer และ MS Word ก็เริ่มมี UI สำหรับเลือกเปิด/ปิด discretionary feature (ฟีเจอร์ที่กำหนดใน mark up ของเอกสาร และอาจจะผ่าน UI ให้ผู้ใช้เลือก) ของ OpenType แล้ว ซึ่ง small caps (smcp) และ old style figures (onum) ก็เป็นฟีเจอร์ชนิดนี้

หลังจากเพิ่มฟีเจอร์ดังกล่าวในฟอนต์ Norasi แล้วทดลองใช้กับ LibreOffice Writer ก็ได้ผลดังนี้:

alt

สำหรับการใช้ในเว็บ ผมได้ลองทำ หน้าทดสอบ โดยใช้ font-variant: small-caps และ font-variant-numeric: oldstyle-nums ใน CSS

LaTeX

สำหรับผู้ใช้ XeLaTeX และ LuaLaTeX ก็สามารถใช้ OpenType feature ได้โดยตรง แต่สำหรับ pdfLaTeX ซึ่งยังใช้เทคโนโลยี PostScript ธรรมดา ก็จำเป็นต้องมีการรองรับเพิ่มเติมในแพกเกจฟอนต์

Small Caps

LaTeX2e รองรับ small caps ผ่านคำสั่ง \scshape และ \textsc{...} โดยตัว TeX engine จะไปหาการประกาศ sc shape ใน font description (lthnorasi.fd สำหรับฟอนต์ Norasi) เพื่อเชื่อมโยงไปหาไฟล์ TFM (TeX Font Metrics) ของ shape ดังกล่าว

และตรง TFM นี่แหละ ที่เราสามารถ remap อักขระ lower case ไปหา glyph ที่เป็น small caps ได้ ทำให้ TeX เรียงพิมพ์ตัว lower case ด้วยตัว small caps แทน

เท่ากับว่า จากฟอนต์ Norasi ที่เรามีอยู่แล้ว 6 type face (regular, slanted, italic, bold, bold slated, bold italic) เราจะต้องสร้าง face ใหม่อีกหนึ่งชุดที่ remap สำหรับ small caps กลายเป็น 12 type face

แต่เรายังมีรายละเอียดที่ต้องพิจารณาเพิ่มจากการ remap คือ:

นั่นจึงนำไปสู่การสร้างไฟล์ .enc ต่างหากที่ remap small caps, ตัดกฎ Latin ligature, คงกฎ ligature สำหรับอักษรไทย พร้อมทั้งเขียน make rules สำหรับสร้าง TFM (สำหรับ TeX) และ VF (virtual font ซึ่งใช้ในขั้น dvi) สำหรับ small caps เพิ่มอีกชุดหนึ่งด้วย

สรุปรวมอยู่ใน GitHub commit นั่นแล

และเมื่อใช้งานผ่านคำสั่ง \textsc{...} ก็ได้ผลดังภาพ:

Small caps in pdfLaTeX

Old Style Figures

ด้วยเทคโนโลยี PostScript ธรรมดา การรองรับตัวเลขแบบหนังสือยุคเก่า (old style figures) ก็ยังคงต้องใช้วิธี remap อักขระตัวเลขไปเป็น PUA glyph ที่เป็น old style ไม่ต่างกับ small caps เพียงแต่ old style figures จะมีประเด็นการใช้งานในทางปฏิบัติที่ต้องพิจารณาเพิ่มเติมด้วย

LaTeX2e มีคำสั่ง \oldstylenums{ตัวเลข) เพื่อแสดง ตัวเลข เป็นแบบ old style ได้ โดยจะใช้ glyph จากฟอนต์ของ Knuth เสมอ แต่ในกรณีที่ต้องการให้ผู้ใช้สามารถใช้ตัวเลข old style จากฟอนต์ของเราได้ ก็จะต้องใช้ช่องทางอื่น

use case ที่น่าจะพบบ่อยในชีวิตจริง คือการใช้ตัวเลข old style ทั้งเอกสาร ทั้งเลขบท เลขหัวข้อ เลขหน้า เลขในข้อความ ฯลฯ ซึ่งตรงนี้จะต่างจาก small caps และแพกเกจฟอนต์มักจะรองรับในรูปของ option ของแพกเกจ

อีก use case หนึ่งคือการใช้ตัวเลข old style เฉพาะที่ ซึ่งมีแพกเกจอย่างน้อยสองตัวที่เตรียมคำสั่งไว้ให้ คือ nfssext-cfr และ fontaxes ซึ่งทั้งสองแพกเกจจัดเตรียมคำสั่งไว้คนละชุด ซึ่งก็ถือเป็นเรื่องปกติ แต่ที่ไม่ปกติคือทั้งสองแพกเกจเรียกใช้ฟอนต์ต่างกันด้วย!

old style figures ไม่ได้ถูกจัดให้เป็น shape หนึ่งของฟอนต์เหมือน small caps เพราะมันมีผลแค่กับตัวเลข ไม่ใช่ทั้งฟอนต์ วิธี implement จึงไม่ใช่การประกาศ shape ใน font description แต่เป็นการสร้าง font family ใหม่ไปเลย! โดยจะมีข้อตกลงบางอย่างในการตั้งชื่อ family ใหม่ที่ว่านั้น และเมื่อผนวกกับคุณสมบัติ proportional/tabular ของตัวเลขเข้าไปด้วยก็กลายเป็นข้อตกลงที่มีรายละเอียดอีกหน่อย

ปัญหาคือ แพกเกจ nfssext-cfr และ fontaxes ใช้ข้อตกลงคนละชุดกัน!

จากหลักการทำงานและจากการทดลอง fontaxes ดูจะใช้ได้ในทางปฏิบัติมากกว่า จึงเลือกรองรับ fontaxes เป็นหลัก แต่ก็พยายามรองรับ nfssext-cfr ด้วยตามสมควร

จาก 12 type face ที่ได้จากการทำ small caps มาแล้ว เราก็จะสร้าง font family ใหม่ที่ประกอบด้วย 12 type face นี้ แต่ remap glyph ของตัวเลขไปเป็นแบบ old style เท่ากับว่าเราจะมีทั้งหมด 24 type face แบ่งเป็น 2 family, family ละ 12 type face

เมื่อเขียน make rules เพื่อสร้าง TFM/VF ของ 12 type face ฉบับ old style figures แล้ว เราก็สร้าง LTHnorasij.fd เพื่อประกาศ font family norasij

เพื่อการรองรับ nfssext-cfr เราก็สร้าง LTHnorj.fd ในทำนองเดียวกัน และสร้าง LTHnorx.fd ที่มีเนื้อหาหลักเหมือน LTHnorasi.fd ทุกประการด้วย

ทั้งหมดนี้ก็จะสามารถรองรับใช้ตัวเลขแบบ old style ผ่าน fontaxes และ nfssext-cfr (บางส่วน) ได้แล้ว

Old style figures via fontaxes

Old style figures via nfssext-cfr

สำหรับ use case การใช้ตัวเลข old style ทั้งเอกสาร นั้น เราก็เพิ่ม option norasi-osf และ rmnorasi-osf ใน fonts-tlwg.sty โดยหากเลือกตัวเลือกนี้ก็จะกำหนดฟอนต์ปริยายเป็น norasij แทน norasi เท่านั้นเอง

Old style figures with 'norasi-osf' option

ทั้งหมดนี้อยู่ใน Git แล้ว มีตัวอย่างเอกสารในไดเรกทอรี latex/examples/ คือ:

หลังจาก make install แล้ว สามารถใช้คำสั่ง make กับไฟล์ PDF ปลายทางได้เลย เช่น make oldnum.pdf

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

โดย Thep (noreply@blogger.com) ณ 23 June 2021 04:21 +0000

10 June 2021

Vee

How I installed Apertium on CentOS 7

#!/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

โดย Vee Satayamas ณ 10 June 2021 15:35 +0000

5 June 2021

Vee

สมมุติว่าอยากเขียนภาษาไทย และเป็นอิสระจากเครื่องหมายแปลก ๆ

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

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

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

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

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

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

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

โดย Vee Satayamas ณ 5 June 2021 14:16 +0000

4 June 2021

Vee

cons car cdr

(cons "dog" "cat")

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

(car (cons "dog" "cat"))

"dog"

(cdr (cons "dog" "cat"))

"cat"

โดย Vee Satayamas ณ 4 June 2021 08:57 +0000

Download a list of PDFs with Firefox

12 มีนาคม 2562

I failed to download a list of PDFs by Wget, cURL, and Slimerjs. So I used Firefox and shell script.

First, I disable Pdfjs to force Firefox to download PDFs.

I wrote a shell script for opening a URL with PDF file, and then I use xdotool to close the Firefox window. For xdotool command, thank Jon G - Megaphone Tech for his comment at Unix Exchange.

#!/bin/sh

for url in `sort -u eng_pdf_urls.txt | grep -i pdf`
do
    echo $url
    firefox -P exp1 "$url" &
    sleep 10
    echo CLOSE
    xdotool search "Mozilla Firefox" windowactivate --sync key --window 0 --clearmodifiers alt+F4
    sleep 10
done

I didn't use headless because I don't know how to close it properly. I tried timeout command but Firefox detected it as an improper shutdown.

โดย Vee Satayamas ณ 4 June 2021 07:51 +0000

My first program in Elixir

10 มีนาคม 2562

I ported my program for showing concurrency from Erlang to Elixir. I guess this is still not Idiomatic Elixir. 😅

defmodule Con1 do
  def log(p, i) when rem(i, 3000) == 0 do
    IO.puts "#{p} #{i}"
  end
  def log(_, _) do
    :ok
  end

  def cnt(_, 0) do
    :ok
  end
  def cnt(p, i) do
    log(p, i)
    cnt(p, i - 1)
  end

  def par_cnt(0) do
    :ok
  end
  def par_cnt(n) do
    spawn fn() -> cnt(n, 3000000) end
    par_cnt(n - 1)
  end
end

โดย Vee Satayamas ณ 4 June 2021 07:50 +0000

Concurrency in Crystal (lang)

7 มีนาคม 2562

Crystal intentionally looks similar to Ruby. However, Crystal is a static typing programming language. A Thai word segmentation program written in Crystal, which is a CPU bound program, was found almost as fast as one written in Go.

I wondered whether its concurrency support was like what I expected. So I did this experiment. By spawning, Crystal did not create any new thread because it doesn't support multithreading parallelism yet. To support concurrency, Crystal has to be able to switch fibers when a fiber performs non-blocking IO operation.

require "socket"

def load(id, chan)
  puts "ID=#{id}; START"
  (id..11).each do 
    socket = TCPSocket.new("www.ku.ac.th", 80)
    socket.close
  end
  puts "ID=#{id}; FINISH"
  chan.send nil
end

def main
  chan = Channel(Nil).new
  (1..10).each{|i| spawn(load(i,chan))}
  # Wait
  (1..10).each{chan.receive}
end

main 

In this program, a task with the id with a smaller number creates a TCP socket repeatedly more times than a task with a larger number id. For example task#1 establishes a TCP socket for 11 times and task#10 create a TCP socket for one time only. So even task#1 started long before task#10, task#10 should finish before task#1.

ID=1; START
ID=2; START
ID=3; START
ID=4; START
ID=5; START
ID=6; START
ID=7; START
ID=8; START
ID=9; START
ID=10; START
ID=10; FINISH
ID=9; FINISH
ID=8; FINISH
ID=7; FINISH
ID=6; FINISH
ID=5; FINISH
ID=4; FINISH
ID=3; FINISH
ID=2; FINISH
ID=1; FINISH

The result was that task#1 finished after task#10 although task#1 started before task#10, show that Crystal can switch tasks. And the important thing is the code looks like regular Ruby code, without async-await nor promise nor callback.

โดย Vee Satayamas ณ 4 June 2021 07:49 +0000

19 April 2021

MrChoke

docker compose not docker-compose

docker-compose กำลังจะถูก deprecate แล้วใช้ docker compose แทน มาดูว่าใช้ยังไงกัน

สืบเนื่องมาจาก blog ของ คุณปุ๋ย

https://www.somkiat.cc/docker-compose-to-docker-compose/

ทำให้เกิดอยากลองใช้บ้างซึ่งพบว่า

บน Docker Desktop macOS สามารถใช้คำสั่งนี้ได้เลย (ลองบน M1) เลยลองบน Ubuntu บ้างพบว่ายังใช้ไม่ได้ เลยตามไปสืบค้นดูพบว่า Project คือ

docker/compose-cli

ทำมานานพอสมควรถ้าอ้างตามเอกสารนี้

docker/compose-cli

Docker Desktop macOS และ Windows จะถูก built-in ไปแล้วเลยใช้ได้ทันทีเหมือน buildx เลย

Installation

ตาม link ด้านบนวิธีการติดตั้ง (ผมติดตั้งบน Ubuntu)

curl -L https://raw.githubusercontent.com/docker/compose-cli/main/scripts/install/install_linux.sh | sh

เมื่อติดตั้งสำเร็จจะมีคำสั่ง docker เพิ่มขึ้นมาใน path

/usr/local/bin/docker

ลองทดสอบดูว่าตอนนี้คำสั่ง docker ถูกเรียกจาก path ไหน

type docker

หรือ

which docker

ถ้ายังเรียกไปที่

/usr/bin/docker

ก็ให้ปิด shell แล้วเปิดใหม่ อ้อ!! ดูลำดับ path ของ PATH ด้วยนะครับว่า /usr/local/bin ต้องอยู่ก่อน /usr/bin

คราวนี้เราก็สามารถใช้คำสั่ง

docker compose

ได้แล้วจ้า….

docker compose

โดย MrChoke ณ 19 April 2021 00:33 +0000

14 April 2021

MrChoke

Use latexindent on macOS Big Sur

ตัวช่วยจัด indent source ของ LaTeX คือ latexindent แต่บน macOS จะต้องติดตั้ง cpan modules เพิ่มเติมนิดหน่อย

Can't locate File/HomeDir.pm in [@INC](http://twitter.com/INC) (you may need to install the File::HomeDir module) ([@INC](http://twitter.com/INC) contains: /usr/local/texlive/2021/texmf-dist/scripts/latexindent /Library/Perl/5.30/darwin-thread-multi-2level /Library/Perl/5.30 /Network/Library/Perl/5.30/darwin-thread-multi-2level /Network/Library/Perl/5.30 /Library/Perl/Updates/5.30.2 /System/Library/Perl/5.30/darwin-thread-multi-2level /System/Library/Perl/5.30 /System/Library/Perl/Extras/5.30/darwin-thread-multi-2level /System/Library/Perl/Extras/5.30) at /usr/local/texlive/2021/texmf-dist/scripts/latexindent/LatexIndent/GetYamlSettings.pm line 22.

ถ้าเจอ error นี้ให้ติดตั้ง modules ต่างๆ ดังนี้

เริ่มแรกให้กำหนด CPATH ให้ชี้ให้ถูกต้อง

export CPATH=/Library/Developer/CommandLineTools/SDKs/MacOSX11.1.sdk/System/Library/Perl/5.30/darwin-thread-multi-2level/CORE:$CPATH

หลังจากนั้นก็ให้ติดตั้ง CPAN Modules

cpan install File:HomeDir
cpan install Unicode::GCString

ปล. ผมเลือก sudo ตอนที่ CPAN ถามนะครับ

โดย MrChoke ณ 14 April 2021 19:23 +0000

13 April 2021

Kitt

คำนวณวันสงกรานต์ปี 2564

สงกรานต์ปี 2564 เป็นปี จ.ศ. (2564 – 1181) = 1383 วันเถลิงศก ตรงกับ (1383 * 0.25875)+ floor(1383 / 100 + 0.38)– floor(1383/ 4 + 0.5)– floor(1383 / 400 + 0.595)– 5.53375= 357.85125 + 14 – 346 – 4 – 5.53375= 16.3175 = วันที่ 16 เมษายน 2564 เวลา 07:37:12 วันสงกรานต์ ตรงกับ 16.3175 – 2.165 = 14.1525 = วันที่ … Continue reading คำนวณวันสงกรานต์ปี 2564

โดย kitty ณ 13 April 2021 04:08 +0000

11 April 2021

bact

คนทำงานด่านหน้า-งานจำเป็น ที่จะได้รับวัคซีนลำดับต้นๆ มีใครบ้าง

ลองไล่ๆ ดูในเว็บไซต์หน่วยงานที่รับผิดชอบด้านนโยบายวัคซีนของประเทศต่างๆ ว่าใครคือ “คนทำงานด่านหน้า” (front-line workers) หรือ “คนทำงานสำคัญและจำเป็น” (essential workers) ที่ไม่ใช่เจ้าหน้าที่ด้านสุขภาพบ้าง (กลุ่มสุขภาพเป็นกลุ่มที่เรานึกถึงและได้ยินกันบ่อยๆ อยู่แล้ว) และใครคือกลุ่มที่จะได้รับวัคซีนลำดับต้นๆ ในวิธีคิดของประเทศนั้นๆ

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

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

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

กรมควบคุมโรคสหรัฐ (CDC) ใช้เกณฑ์ ACIP Categories of Essential Workers ซึ่งแบ่งเป็น (1a) Essential Healthcare Workers (คนทำงานที่จำเป็นด้านสุขภาพ ระบุชัดว่าทั้งที่ได้รับค่าจ้างและไม่ได้รับค่าจ้าง) (1b) Frontline essential workers (Non-Healthcare) (คนทำงานที่จำเป็นที่อยู่ด่านหน้า ที่ไม่ใช่สายสุขภาพ ซึ่งทำงานในสภาพแวดล้อมที่ต้องอยู่ใกล้ชิดเพื่อนร่วมงานหรือคนทั่วไป เลี่ยงไม่ได้) และ (1c) Other essential workers (อื่นๆ ตามกำหนด)

รัฐบริติชโคลัมเบียในแคนาดา กำหนด Front-line priority workers มาตามภาพ มีทั้งครูโรงเรียนเด็กเล็ก คนดูแลเด็ก พนักงานไปรษณีย์ พนักงานร้านชำ คนทำงานในภาคการผลิต คนทำงานเรือนจำ คนทำงานขนส่งข้ามแดน ฯลฯ

ส่วนไอร์แลนด์จะเน้นที่คนทำงานด่านหน้าด้านสุขภาพเป็นหลัก และมีเพิ่มเติมคือ “key workers essential to the vaccine programme” คนทำงานที่สนับสนุนการจัดหาและกระจายวัคซีน และการฉีดจะเน้นกลุ่มเปราะบาง คนป่วย (มีการกำหนดชื่อโรคและเงื่อนไขของอาการอย่างชัดเจน) และผู้สูงอายุ ก่อนกลุ่มอื่น และหลังจากที่กลุ่มจำเป็นอื่นๆ ได้รับวัคซีนกันไปแล้ว ลำดับที่เหลือจะมีลักษณะไล่ตามกลุ่มอายุ

โดยในแต่ละกลุ่ม ก็จะมีการอธิบายเหตุผล (Rationale) และแจ้งถึงหลักการทางจริยศาสตร์ (Ethical Principles) ที่ใช้พิจารณาด้วย ว่าทำไมแบ่งแบบนี้และมาอยู่ที่ลำดับนี้

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

ต้นเดือนมีนา 2564 ตอนที่สิงคโปร์ประกาศขยายกลุ่มที่ได้รับวัคซีน ก็ระบุถึงกลุ่มอย่างคนทำงานที่เสี่ยงสัมผัสสูงหรือมีโอกาสส่งต่อได้มาก “Essential workers with higher risk of exposure and onward transmission” ซึ่งรวมครูและเจ้าหน้าที่ในโรงเรียนเด็กเล็กด้วย โดยระบุว่าเนื่องจากยังไม่มีวัคซีนสำหรับเด็กเล็ก การฉีดคนอื่นในโรงเรียนจึงเป็นวิธีป้องกันเด็กที่ทำได้ในตอนนี้

นอกจากนี้ก็มีกลุ่มคนงานข้ามชาติ “Migrant workers living in dormitories” เนื่องจากคนงานข้ามชาติเหล่านี้อาศัยอยู่ในหอพักขนาดใหญ่ที่มีคนมาก (ประกาศของรัฐบาลไม่มีคำว่า “แออัด” แต่ก็นั่นแหละ เข้าใจกันได้ ว่ามันเสี่ยงเพราะคนอยู่เยอะด้วย)

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

ดูของหลายๆ ที่แล้ว แล้วก็น่าจะเป็นการประเมิน/จัดลำดับตาม โอกาสเกิด และ ความเสียหายที่จะเกิด เช่น

เกณฑ์เหล่านี้แต่ละประเทศก็จัดระดับความสำคัญต่างกันไป ส่วนหนึ่งก็น่าจะเกี่ยวกับลักษณะทางประชากรและลักษณะความสัมพันธ์ของคนในสังคมด้วย ว่ามีการพบปะพบเจอกันยังไง พึ่งพาอาชีพไหนมาก ลักษณะการผลิต-การไหลเวียนของสินค้าบริการเป็นยังไง จุดเปราะบางต่อความมั่นคงอยู่ตรงไหน ดังนั้นมันเลยไม่ได้เหมือนกันเป๊ะๆ แต่อย่างน้อยการมีเกณฑ์ให้พิจารณาเป็นหลักเป็นการบ้างก็น่าจะดี

ของไทยเองก็มีสิ่งที่คล้ายๆ เกณฑ์อยู่ โดยอิงกับช่วงการฉีด 3 ระยะ (ซึ่งแบ่งช่วงตามปริมาณวัคซีนที่มี) แล้วก็ระบุกลุ่มที่จะได้รับวัคซีนในช่วงนั้นๆ ไว้กว้างๆ เช่น “ผู้ประกอบอาชีพที่มีโอกาสสัมผัสกับคนจำนวนมาก” แต่ไม่ได้มีรายละเอียดว่ามีอาชีพอะไรบ้าง (ข้อมูล 27 ม.ค. 2564)

ชอบที่ในเว็บไซต์ทางการของหลายประเทศ นอกจากจะแจ้งว่าลำดับการได้รับวัคซีนมีอะไรบ้าง ใครก่อนใครหลัง ยังพยายามอธิบายเหตุผล ที่มาที่ไปของการตัดสินใจ

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

เผยแพร่ครั้งแรก 9 เมษายน 2564 บนเฟซบุ๊ก

The post คนทำงานด่านหน้า-งานจำเป็น ที่จะได้รับวัคซีนลำดับต้นๆ มีใครบ้าง first appeared on bact' is a name.

โดย bact ณ 11 April 2021 11:55 +0000

คิดเงิน “ตามตัวอักษร” แฟร์ไหม?

เช็คอะไรนิดหน่อย ไปเจออันนี้ วิธีการคิดเงินของ Google Cloud Translation API ก็แฟร์ระดับหนึ่งนะ คือคิดตามจำนวนตัวอักษร (code point) แทนที่จะคิดตามปริมาณข้อมูลจริงๆ (byte)

Charged characters

To calculate usage, Google counts usage on a per character basis, even if a character is multiple bytes. Each character corresponds to a code point.

You are charged for all characters that you include in a Cloud Translation request, even untranslated characters. This includes, for example, whitespace characters. If you translate <p>こんにちは</p> to English, it counts as 12 characters for the purposes of billing.

Google also charges for empty queries. If you make a request without any content, Google charges one character for the request.

—-

คือในทางคอมพิวเตอร์ มาตรฐานตัวอักษรที่แพร่หลายในปัจจุบัน คือมาตราฐาน Unicode และวิธีการจัดเก็บตัวอักษรที่นิยมและเข้ากันได้กับแอปบนอินเทอร์เน็ตต่างๆ ก็คือ UTF-8 ซึ่งในตัวอักษรในแต่ละระบบการเขียนอาจใช้จำนวนไบต์ไม่เท่ากัน ตัวละตินที่ไม่มีเครื่องหมายประสม (อย่างที่ใช้ในภาษาอังกฤษ มาเลย์ อินโด) จะใช้เพียง 1 ไบต์ต่อ 1 ตัวอักษร (code point) ส่วนตัวอักษรไทยจะใช้ 3 ไบต์ต่อ 1 ตัวอักษร

จากการตัดสินใจในขั้นการออกแบบ มันเป็นไปได้ที่จะมีการเลือกปฏิบัติ มีความ “ไม่เท่าเทียม” หรือความ “ไม่ปลอดภัย” หรือความใดๆ ที่ถ้าเป็นไปได้ก็ไม่อยากให้มี ฝังอยู่ในระบบสถาปัตยกรรมของระบบ บางอย่างไม่ได้อยากให้มี แต่ด้วยข้อจำกัดของทรัพยากรที่มีจำกัดกว่าหรือสมมติฐานของบริบทการใช้งานในตอนที่ออกแบบ ก็เลยตัดสินใจว่าใช้แบบนี้ไปละกัน มันโอเคสำหรับตอนนั้น – แต่พอเวลาผ่านไป ย้อนกลับไปพิจารณา ก็อาจตัดสินใจอีกแบบได้ (จะมีโอกาสแก้ไขไหมก็อีกเรื่อง)

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

แต่ต่อมาเมื่อสมมติฐานความปลอดภัยนั้นไม่เป็นจริงแล้ว หรือข้อจำกัดทางเทคโนโลยี/เศรษฐกิจนั้นได้คลายลงแล้ว ก็มีการคิดวิธีเข้ารหัสลับให้กับข้อมูลขึ้นมา โดยยังทำงานอยู่บนโปรโตคอลเดิม มีความเข้ากันได้กับระบบเก่าๆ (backward compatible)

การออกแบบคอมพิวเตอร์ดิจิทัลยุคแรก นอกจากจะมีแต่ตัวละตินแล้ว ยังไม่มีกระทั่งตัวอักษรพิมพ์เล็ก เพราะพื้นที่หน่วยความจำและจัดเก็บมีขนาดจำกัด และยังต้องกันที่สำหรับอักขระพิเศษเพื่อควมคุมเครื่องพิมพ์เข้าไปด้วย (จอภาพอาจไม่ใช่ส่วนแสดงผลหลักของระบบคอมพิวเตอร์หลายๆ ระบบในตอนนั้น และการแสดงผลกราฟิกที่แม่นยำที่สุด ก็คือพล็อตเตอร์ ที่เป็นแขนกลวาดรูปด้วยปากกา)

แม้จะมีข้อจำกัด แต่มันก็พอใช้ได้ โดยเฉพาะเมื่อคำนึงถึงบริบทว่าในสมัยนั้น “ผู้ใช้” ระบบคอมพิวเตอร์ก็มีแนวโน้มจะเป็น “ช่างเทคนิค” ในที่ได้รับการอบรมมาในระดับหนึ่งสำหรับการใช้เครื่องระบบดังกล่าว (พูดอีกแบบคือ คนต้องปรับตัวให้เข้ากับเครื่อง เครื่องยังไม่ต้องปรับตัวให้เข้ากับคน)

เมื่อระบบเหล่านี้ขยายใหญ่ขึ้น มีการนำไปใช้กับหลากหลายภาษาวัฒนธรรรมมากขึ้น มีผู้ใช้จำนวนมากขึ้น จากหลากหลายภูมิหลังขึ้น ใช้กับหลากหลายบริบทความปลอดภัยมากขึ้น สมมติฐานหลายๆ อย่างที่เคยใช้ได้ ก็เปลี่ยนไป

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

เช่น เดิมใช้ 7 บิตเพื่อแทนชุดตัวอักษร 128 ตัว ก็ขยายมาเป็น 8 บิต 16 บิต ฯลฯ โดยพยายามทำให้มันรองรับระบบเดิมอยู่ โดย 128 ตัวแรก ยังใช้ตัวอักษรชุดเดิมอยู่อะไรงี้ ซึ่งวิธีที่เก็บตัวอักษรที่ใช้บนอินเทอร์เน็ตส่วนใหญ่ตอนนี้ใช้วิธีนี้อยู่ ชื่อมาตรฐานคือ UTF-8

UTF-8 นี่เป็นวิธีการแปลงรหัสเพื่อบันทึกตัวอักษรแบบที่เรียกว่า “variable-width encoding” คือ ตัวอักษรแต่ละตัวอาจใช้จำนวนข้อมูล (คิดเป็นไบต์) ในการเก็บไม่เท่ากัน — พวกตัวอักษรละตินและสัญลักษณ์พื้นฐาน 128 ตัวแรก จะใช้ 1 ไบต์ ตัวละตินอื่นๆ เกือบทั้งหมดที่เหลือ (ซึ่งภาษาจำนวนมากในยุโรปใช้ รวมถึงภาษาอย่างเวียดนาม ก็ใช้ตัวเขียนเหล่านี้) รวมถึงตัวอักษรกรีก ฮีบรู อารบิก คอปติก พวกนี้ใช้ 2 ไบต์ ตัวอักษรที่เหลือเกือบทั้งหมดจะใช้ 3 ไบต์ ในกลุ่มนี้มีตัวอักษรจีน ญี่ปุ่น เกาหลี ไทย รวมอยู่ด้วย ส่วนพวก 4 ไบต์นี่จะเป็นตัวอักษรที่ไม่พบบ่อยนัก หรือเป็นส่วนขยาย/กรณีพิเศษของตัวอักษรบางตัว

วิธีคิดของคณะออกแบบ UTF-8 ก็คือ ตัวอักษรไหนมีแนวโน้มจะใช้บ่อย ใช้มาก ก็พยายามให้ใช้จำนวนไบต์น้อยๆ เพื่อที่ว่าในภาพรวมจะได้ประหยัดพื้นที่จัดเก็บ เว้นเสียว่าจะทำไม่ได้จริงๆ อย่างกลุ่มตัวอักษรจีน​ญี่ปุ่นเกาหลี (CJK) ที่จำนวนตัวอักษรมันเยอะ จะยัดลง 2 ไบต์ก็ไม่ไหว ก็ใช้ 3 ไบต์ไป

ซึ่งในแง่นี้ การให้ตัวอักษร ASCII ใช้เพียง 1 ไบต์ ก็เข้าใจได้มาก ๆ เพราะตัวอักษรในชุดนี้ถูกใช้เป็นคำสั่งและคำสำคัญต่างๆ ในโปรโตคอลการรับส่งข้อมูลและมาตรฐานข้อมูลอื่น ๆ ในระดับพื้นฐานของคอมพิวเตอร์ ถ้าเกิดตัวอักษรชุดนี้ใช้จำนวนไบต์เยอะ ทุกอย่างในระบบก็จะพองขึ้นทันที ซึ่งผลพลอยได้ก็คือ พวกภาษาที่ใช้ตัวอักษรที่อยู่ในกลุ่มตัวละติน (A-Z, a-z) ก็เลย “โชคดี” ไปด้วย (หรือมองอีกมุม ก็เป็นเพราะคนที่ใช้ภาษาที่ใช้อักษรละติน เป็นคนกำหนดมาตรฐานคอมพิวเตอร์ไง ผลที่ตามมาเลยเป็นแบบนี้)

อย่างไรก็ตาม ก็มีคำถามแหละ ว่าเอ๊ะ แล้วเอาเฉพาะชุดตัวที่ใช้บ่อยๆ ของตัวอักษรที่ตอนนี้ถูกจัดอยู่ในหมวด 3 ไบต์ (อย่าง CJK) ไปอยู่ใน 2 ไบต์ก็ได้รึเปล่า อย่าง ฮันกึลของเกาหลี หรือ คะนะของญี่ปุ่น พวกนี้ก็ไม่ได้เยอะมากแบ่งไปใส่ในชุด 2 ไบต์ได้ไหม แล้วพวกฮันจาหรือคันจิ (ตัวจีน) ค่อยใส่ในชุด 3 ไบต์ ไม่ต้องยกกันไปทั้งยวงก็ได้

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

คำว่า “บ่อย ๆ” นี่เอาอะไรวัด จากมุมของใคร หรืองานแบบไหน

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

กลับไปประเด็นตั้งต้น ถ้าบริการไหนคิดค่าบริการตามจำนวนไบต์ (ขนาดพื้นที่จัดเก็บ/รับส่งข้อมูลจริงๆ) มันก็เป็นประเด็นได้ว่า ทำให้ผู้ใช้ภาษาบางภาษาหลีกเลี่ยงไม่ได้ที่จะต้องจ่ายแพงกว่าภาษาอื่น

วิธีการคิดค่าบริการตามจำนวนตัวอักษร แทนที่จะเป็นจำนวนไบต์ข้อมูลที่จะต้องใช้เก็บตัวอักษรนั้น ก็เลยถูกมองได้ว่าแฟร์ขึ้นมาอีกนิดนึง

แต่ก็อาจมองต่อไปได้ว่า พอถัวเฉลี่ยกัน ก็เลยกลายเป็นว่าคนที่ใช้ภาษาที่ใช้จำนวนไบต์น้อย แทนที่จะได้จ่ายถูก ก็ต้องมารับภาระจ่ายแพงขึ้นหลังจากการถัวรึเปล่า แบบนี้ก็ไม่แฟร์สิ

หรือถ้าไปให้สุดๆ เฮ้ย บางภาษา 1 ตัวอักษร มันเก็บความหมาย อุ้มความหมายเอาไว้ได้เยอะกว่า 1 ตัวอักษรในอีกภาษา ภาษาญี่ปุ่นเขียน 1 ตัว 空 ภาษาไทยต้องเขียน 7 ตัว เพื่อจะได้ความหมายว่า ท้องฟ้า เท่ากัน แบบนี้คนไทยก็ต้องจ่ายแพงกว่าคนญี่ปุ่นรึเปล่า (แบบ 280 ตัวอักษรบนทวิตเตอร์ คนจีนแทบจะเขียนเรื่องสั้นได้แล้ว คนไทยแค่รายงานข่าวอาจจะไม่พอ) แบบนี้การคิดค่าบริการตามตัวอักษรก็ไม่แฟร์อยู่ดีปะ

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

ตราบใดที่การสร้างมันเกิดการกระบวนการตัดสินใจเลือก การเลือกปฏิบัติมันมีอยู่แน่ๆ ในผลงานทางวิศวกรรม

มันไม่จำเป็นต้องเป็นการเลือกปฏิบัติในทางลบ (ภาษากฎหมายระหว่างประเทศใช้คำว่า “การเลือกประติบัติ”) มันอาจเป็นทางบวกก็ได้ (จริงๆ การคำนึงถึงความถี่ในการใช้และพยายามให้ตัวอักษรที่พบบ่อย ใช้จำนวนไบต์น้อยๆ ก็เป็นการเลือกปฏิบัติที่พยายามจะให้เกิดผลบวกในภาพรวม ทำให้ในภาพรวมใช้พื้นที่จัดเก็บน้อยลง ส่งข้อมูลได้ไวขึ้น ประหยัดขึ้น)

และหลายครั้งก็ไม่ได้เป็นเรื่องตั้งใจให้เกิดการปฏิบัติที่แตกต่างกับกลุ่มคนแบบนั้นแต่แรก แต่อาจมาจากความไม่รู้ ไม่ได้อยู่ในความคิด-อันเนื่องมาจากการติดต่อข้ามวัฒนธรรมยังมีจำกัดในอดีต พอมารู้แล้วในตอนหลังจะให้รื้อทำใหม่หมดก็มีข้อจำกัด ก็เลยยังต้องใช้ของที่มีมาแต่เดิมผสมอยู่ด้วย รื้อหมดไม่ได้

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

เผยแพร่ครั้งแรก 7 เมษายน 2564 บนเฟซบุ๊ก

พอโพสต์เรื่องข้างต้นจบ ก็นึกถึงอีกประเด็นในสัปดาห์ก่อนหน้า ที่คนพูดถึงบทความ When Binary Code Won’t Accommodate Nonbinary People ซึ่งก็คิดๆ ไปก็น่าจะเป็นเรื่องของ encoding นี่อยู่เหมือนกัน เป็นการเอาการจัดประเภทฝังลงไปในระบบ

ถ้าได้อ่านในบทความนั้น คิดว่าหลักใหญ่ใจความประเด็นมันคือเรื่องการออกแบบตัวระบบ — ทั้งระดับโครงสร้างพื้นฐานและระดับส่วนติดต่อผู้ใช้ — ไม่ว่าจะเป็น database schema ช่องหรือปุ่มที่มีให้กดในหน้าจอ UI หรือวิธีการทำ data validation ที่ทำให้ข้อมูลที่อยู่นอกเหนือไปจากที่ผู้ออกแบบระบบอนุญาต มันกรอกไม่ได้ รวมไปถึงคู่มือใช้งาน/การอบรมการใช้งานที่ทำให้เจ้าหน้าที่ที่เป็นผู้กรอกยึดอยู่กับคุณค่าบางอย่าง แม้ตัวระบบจะไม่ได้บังคับ (ในระบบที่ตัวเจ้าของข้อมูลไม่ได้เป็นผู้กรอกเอง)

ส่วนตัวมีประสบการณ์นี้ตอนไปทำบัตรประชาชนนานมาแล้ว ตอนผมกรอกข้อมูลในฟอร์มกระดาษ ช่องศาสนาผมไม่กรอก เว้นว่าง แต่พอเจ้าหน้าที่ไปกรอกในฟอร์มคอมพิวเตอร์ เขาใส่เป็น “พุทธ” ให้ (คงคิดว่าผมลืมกรอก เลยเติมให้)

ผมแย้ง ให้เว้นว่าง เขาบอกว่าเว้นว่างไม่ได้ ต้องใส่ค่าอะไรบางอย่าง ไม่งั้นจะคลิกไปต่อไม่ได้ จะเลือก “ไม่มี” ก็ไม่มีให้เลือกใน drop-down list สุดท้ายเลยมาดูจอกัน แล้วค่อยพบว่า เราสามารถกรอก “-” เพื่อให้คลิกต่อได้ ซึ่งเจ้าหน้าที่ก็เพิ่งรู้

พาดหัวมันเล่นกับคำ หลายคนอาจจะ อิหยังวะ เพราะอาจคิดไปถึงเลขฐานสองตรงตัว แต่ถ้าอ่านข้อถกเถียงข้างใน ประเด็นมันก็ตามที่ว่าไว้ข้างบนน่ะ

ซอฟต์แวร์เป็นสถาปัตยกรรมที่กำหนด (อนุญาต/ไม่อนุญาต) ทางที่เราจะใช้ชีวิต{ใน/กับ/ด้วย}สภาพแวดล้อมนี้ได้ ไอเดียเดียวกับที่เลซสิกเสนอว่า code is law น่ะแหละ

เผยแพร่ครั้งแรก 31 มีนาคม 2564 บนเฟซบุ๊ก

The post คิดเงิน “ตามตัวอักษร” แฟร์ไหม? first appeared on bact' is a name.

โดย bact ณ 11 April 2021 11:21 +0000

26 March 2021

bact

ค้าปลีกดิจิทัล – การวิเคราะห์ผู้มีส่วนได้เสีย

วันอาทิตย์ที่ 21 มีนาคม 2564 ที่ผ่านมาได้มีโอกาสไปเชียงใหม่เพื่อร่วมการประชุมเชิงปฏิบัติการของสถาบันเอเชียศึกษา จุฬา เรื่องการเปลี่ยนผ่านสู่ดิจิทัลของการค้าปลีก ตอนแรกก็บอกกับคนจัดไปแล้วว่าไม่ค่อยมีความรู้เรื่องค้าปลีกอะไรเลย แต่ทางผู้จัดก็บอกว่ามันเป็นวงของนักวิจัยที่เขาจะไปศึกษาเรื่องนี้ในเอเชียตะวันออกเฉียงใต้ จะมีนักวิจัยหลายสาขาไปศึกษาต่ออยู่แล้ว เราไปแชร์มุมของเราในประเด็นผลกระทบทางสังคมก็พอ ก็เลยอะ ลองดู

หัวข้อที่พูดคือชวนวิเคราะห์ผู้มีส่วนได้เสีย (stakeholders) โดยมองไปมากกว่าเฉพาะความสัมพันธ์ระหว่างผู้ซื้อ-ผู้ขาย แต่รวมไปถึงคนที่อยู่รายล้อมอื่นๆ ด้วย กรอบที่ใช้ก็ไปยืมวิธีวิเคราะห์การเคลื่อนย้ายของ ทุน สินค้า บริการ และคน ใน “four freedoms” ของนโยบายตลาดเดียวของสหภาพยุโรป (European Single Market) มามอง เผื่อจะมองเห็นผู้มีส่วนได้ส่วนเสียและประเด็นทางสังคมากขึ้นหรือไม่ เช่น

นอกนั้นก็ลองเอาวิธีการวิเคราะห์ความสัมพันธ์ทางอำนาจของฝั่งการผลิตและห่วงโซ่อุปทาน รวมถึงการรวมตัวของแรงงาน ในธุรกิจค้าปลีก จากบทความ Barcode Empires: Politics, Digital Technology, and Comparative Retail Firm Strategies (Watson, 2011) กับการแจกแจงผู้เกี่ยวข้องตามเส้นทางค้าปลีก (ทำแบบ user journey) จากรายงาน Disruptions in retail through digital transformation: Reimagining the store of the future (Deloitte, 2017) มาเล่านิดๆ หน่อยๆ เผื่อนักวิจัยเขาจะสนใจไปดูต่อ

ไม่ได้มีข้อเสนออะไร เพียงแค่ชวนนักวิจัยในห้องคุยประเด็นพวกนั้น และดูว่าจะขยายประเด็นผลกระทบทางสังคมไปยังไงได้อีกบ้าง อาจจะมีชวนนิดหน่อยว่าน่าจะลองไปศึกษาตัว “sites” หรือสถานที่ที่เกิดกิจกรรม/ธุรกรรม ทั้งการผลิต-สะสม (“Things” at Rest) และการแลกเปลี่ยน (“Things” in Transit) ซึ่งก็มโนๆ ขึ้นมานะ

เรื่องที่สนใจอันนึงคือ แล้วใครควบคุม sites เหล่านั้น? ความสัมพันธ์เชิงอำนาจมันเป็นยังไง?

ลิงก์นี้คือสไลด์ครับ: Beyond retailer-consumer relationships

The post ค้าปลีกดิจิทัล – การวิเคราะห์ผู้มีส่วนได้เสีย first appeared on bact' is a name.

โดย bact ณ 26 March 2021 15:13 +0000

25 March 2021

MrChoke

เขียน LaTeX ด้วย VSCode และ TeX Live Docker Image — EP2

เขียน LaTeX ด้วย VSCode และ TeX Live Docker Image — EP2

หลังจากสามารถจัดการและใช้งาน Docker Container ได้แล้วคราวนี้ลองมาเขียน LaTeX กับ VSCode กันดูครับ ถ้าใครไม่เคยเขียนมาก่อนก็คงต้องทำการบ้านมาบ้าง เพราะผมไม่ได้ลงรายละเอียดทั้งหมด

เขียน LaTeX ด้วย VSCode และ TeX Live Docker Image — EP1

Attach Container

Attach to New Window

เลือกรูป Computer บน Side Bar ด้านซ้าย แล้วก็ Click ขวาบนชื่อ Container ที่เราต้องการใช้งาน (สั่ง run ไว้ก่อนหน้านี้แล้ว) แล้วเลือก Attach in New Window ก็จะปรากฎหน้าต่างใหม่ขึ้นมาดังรูปถัดไป

Workspace ใหม่

ซึ่งจะมี Terminal ของ Container มาให้ด้วยให้เราสร้าง Folder สำหรับทำเขียน LaTeX ด้วยคำสั่งดังนี้

สร้าง Folder

เนื่องจากตอนเราสร้าง container ได้มีการ mount volume ไว้ใน /root/data ดังนั้นให้เราเข้าไปใน /root/data แล้วสร้าง folder ขึ้นมา 1 folder

mkdir /root/data/latex1

จะสั่งแบบในรูปหรือสั่งตามตัวอย่างก็ได้

เปิด Folder ใน Workspace

เปิดจาก terminal ง่ายๆ ด้วยคำสั่ง

code /root/data/latex1

หรือจะใช้ mouse click ที่รูป icon files บน Side Bar ด้านซ้าย และ Click ที่ปุ่ม Open Folder

Click Open Folder

ใส่ path หรือจะ Click เลือกไปเรื่อยๆ ก็ได้ดังรูปด้านล่าง

เลือก Folder ที่เราสร้างไว้

สร้าง File ใหม่

เมื่อเปิด Folder เรียบร้อยให้ Click ที่ icon new file หรือ Click ขวาบนพื้นที่ว่างในช่อง แล้วเลือก New File จาก Menu ดังรูปด้านบน

ตั้งชื่อ file1.tex

New File

ใส่คำสั่ง LaTeX แรกดังตัวอย่าง

\documentclass{article}
\title{My First \LaTeX{} Document}
\author{MrChoke}
\date{\today}
\begin{document}
   \maketitle
   Hello \LaTeX{} world!
\end{document}

อ่านเกี่ยวกับ LaTeX เพิ่มเติมได้ที่

https://medium.com/media/ff54de4b800cb059c6627c3b4733d06d/href

คำสั่ง LaTeX

หลังจากนี้เราสามารถสั่งให้ Build คำสั่งนี้ได้แล้วโดยการ Click ที่ปุ่มสีเขียวด้านบนขวา

ปุ่มคำสั่งของ LaTeX Workshop Extension

หลังจาก Build

ถ้าหาก Build สำเร็จจะมี files ต่างๆ เกิดขึ้นทางด้านซ้ายมือ โดยเฉพาะ PDF File คือเป้าหมายของเราถ้ามี Error ตัว Extension ก็จะแจ้งเตือนเรามา ก็ต้องไล่อ่านว่าเกิดจากอะไร

PDF View

ถ้าเราต้องการแสดงผล PDF ก็ให้ Click ที่ปุ่มถัดจากปุ่ม Build สีเขียว ที่มีรูปแว่นขยาย ก็จะได้เหมือนรูปด้านบน หลังจากนี้ถ้าเราแก้ไขคำสั่ง LaTeX แล้ว save มันก็จะทำการ Build ให้อัตโนมัติและแสดงผล PDF ให้ทันทีด้วยเช่นกัน

สำหรับค่า defualt จากตัวอย่างด้านบนตอนสั่ง build จะใช้ engine pdfLaTeX นะครับ

ภาษาไทย

ถ้าเราเขียนเอกสารภาษาอังกฤษก็สบายหน่อยเพราะตัวอย่างข้างบนก็เพียงพอแล้วแต่ถ้าเราจะใช้ภาษาไทยใน LaTeX ละเราต้องทำอย่างไร และ ต้องรู้อะไรบ้าง

เลือก Engine

การใช้ LaTeX Engine ที่นิยมหลักๆ ก็จะมี

pdfLaTeX

โครงการภาษาไทยสำหรับ LaTeX หรือ ThaiLaTeX เป็นโครงการแรกๆ ที่ Contribute สภาวะแวดล้อมการใช้งานภาษาไทยเข้าไป อ่านรายละเอียดได้ที่

https://linux.thai.net/projects/thailatex

ซึ่งหลัก ๆ เป็นการกำหนดสภาวะแวดล้อมภาษาไทยให้ pdfLaTeX เนื่องจาก pdfLaTeX ค่อนข้างล้าสมัยการใช้ fonts ก็มีข้อจำกัดไม่สามารถใช้ fonts ใหม่ๆ อย่าง OpenType ได้การตัดคำก็ต้องทำเองก่อนจะทำการ Build Project การ shapping สระ และ วรรณยุกต์ก็ยังต้องใช้แบบเก่า

มาดูตัวอย่างการใช้ภาษาไทยด้วย pdfLaTeX กันครับ

pdfLaTeX — Thai LaTeX

ตัวอย่างคำสั่ง

\documentclass{article}
\usepackage[thai]{babel}
\usepackage[utf8x]{inputenc}
\usepackage{fonts-tlwg}
\begin{document}
สวัสดีชาวโลก
\end{document}

ตัวอย่าง Thai LaTeX กับ pdfLaTeX

ตั้งแต่บรรทัดที่ 2–4 เป็นสิ่งจำเป็นที่ต้องประกาศเสมอไม่งั้นจะใช้งานภาษาไทยไม่ถูกต้อง

FONTS TLWG

Fonts ภาษาไทยหลักที่ bundle ไปกับ TeX Live ก็จะมีชุดของ TLWG (Thai Linux Working Group) ดังนี้

\documentclass{article}
\usepackage[thai]{babel}
\usepackage[utf8x]{inputenc}
\usepackage{fonts-tlwg}
\begin{document}

\begin{itemize}
  \item \fontfamily{kinnari} \selectfont Kinnari สวัสดีชาวโลก
  \item \fontfamily{garuda} \selectfont Garuda สวัสดีชาวโลก
  \item \fontfamily{norasi} \selectfont Norasi สวัสดีชาวโลก
  \item \fontfamily{laksaman} \selectfont Laksaman สวัสดีชาวโลก
  \item \fontfamily{purisa} \selectfont Purisa สวัสดีชาวโลก
  \item \fontfamily{loma} \selectfont Loma สวัสดีชาวโลก
  \item \fontfamily{sawasdee} \selectfont Sawasdee สวัสดีชาวโลก
  \item \fontfamily{ttype} \selectfont Ttype สวัสดีชาวโลก
  \item \fontfamily{ttypist} \selectfont TTypist สวัสดีชาวโลก
  \item \fontfamily{umpush} \selectfont Umpush สวัสดีชาวโลก
  \item \fontfamily{waree} \selectfont Waree สวัสดีชาวโลก
\end{itemize}

\end{document}

fonts-tlwg

การเลือก Default Font

ปกติถ้าเราใช้ Thai LaTeX font ที่ถูกใช้คือ

แต่ถ้าเราจะเลือก font ตัวอื่นในรายการด้านบนให้เราประกาศใน

\usepackage{fonts-tlwg}

โดยการกำหนด option ดังนี้

\usepackage[fontname]{fonts-tlwg}

เช่น

\documentclass{article}
\usepackage[thai]{babel}
\usepackage[utf8x]{inputenc}
\usepackage[purisa]{fonts-tlwg}
\begin{document}
เป็นมนุษย์สุดประเสริฐเลิศคุณค่า กว่าบรรดาฝูงสัตว์เดรัจฉาน
จงฝ่าฟันพัฒนาวิชาการ อย่าล้างผลาญฤๅเข่นฆ่าบีฑาใคร
ไม่ถือโทษโกรธแช่งซัดฮึดฮัดด่า หัดอภัยเหมือนกีฬาอัชฌาสัย
ปฏิบัติประพฤติกฎกำหนดใจ พูดจาให้จ๊ะๆ จ๋า น่าฟังเอยฯ

ธรรมคุณหนุนความดีฑีฆรัตน์ สรรพสัตว์วัฏวจรถอนวิถี
อริยสัจตรัสสังโยคโลกโมฬี ดังกุมภีร์ผลาญชิวหาบีฑาทนต์
ฟังฉัตรทองของประเสริฐเลิศวิเศษ ฝ่ายต้นเหตุฎรงกรณ์อุดรผล
ยินสถานฌานสมาอารยะชน พึงซ่อนกลปรนนิบัตินิวัฒน์แฮฯ
\end{document}

Purisa

Laksaman

อ่านเพิ่มเติมได้ที่

https://raw.githubusercontent.com/tlwg/fonts-tlwg/master/latex/README.latex

การตัดคำ

without word/line break

สังเกตว่าการตัดคำและบรรทัดยังแปลกๆ มีการใส่ hyphen ยังไม่ถูกต้องนัก ใน TeX Live Docker image ผมใส่ swath ไปให้แล้วเราสามารถสั่งตัดคำได้เลยโดยสั่งดังนี้

swath -f latex -u u,u < original.tex > processed.tex

จากคำสั่งด้านบน

หลังจากนั้นเราก็นำ file ที่ตัดคำแล้วไป build ต่อ

LaTeX Source ที่ถูกตัดคำเรียบร้อยแล้ว

ผลของการตัดบรรทัด และใส่ Hyphen ได้ถูกต้องขึ้น

จากตัวอย่าง pdfLaTeX จะเห็นว่าวิธีการยังค่อนข้างซับซ้อนยังต้องมีการตัดคำก่อนที่จะ process และ fonts ที่ใช้ยังเป็นแบบ Type1 การนำ fonts ของระบบหรือ fonts ภายนอกมาใช้ยากมาก

XeLaTeX, LuaLaTeX

ทางเลือกที่คนนิยมใช้กันในปัจจุบันคือ XeLaTeX ส่วน LuaLaTeX ผมยังไม่เคยได้ลองแต่เห็นหลายคนบอกว่าคล้ายๆ กัน คือใช้ FontSpec เหมือนกัน และ เกิดมาเพื่อประมวลผลแบบ UTF-8 และที่สำคัญคือมีการใช้ ICU ในการตัดคำซึ่งสนับสนุนหลากหลายภาษารวมทั้งภาษาไทยด้วย

สร้าง Workspace ใหม่

สร้าง Folder ใหม่ใน /root/data ชื่อ latex2 ทำใน terminal

mkdir /root/data/latex2

เปิด หน้าต่างใหม่ด้วยคำสั่ง

code /root/data/latex2

สร้าง Build Tools และ Recipes

ค่าปกติในการ Build LaTeX ของ VSCode LaTeX Workshop Extension คือ pdfLaTeX ส่วน XeLaTeX ไม่ได้จัดเตรียมไว้ให้ การตั้งค่า Extension ทำได้หลายระดับมีตั้งแต่

ถ้าเราเขียนหลายแบบก็อาจจะแยกเฉพาะ Workspace ก็ได้ให้เปิด

VSCode Setting → Workspace

แล้วค้นหาคำว่า

latex tools

Recipes & Tools

ให้ Click ที่ Edit in settings.json ของ

Latex-workshop -> Latex: Tools

แล้วเพิ่ม Block ใหม่โดย Copy จาก lualatex

{
      "name": "xelatexmk",
      "command": "latexmk",
      "args": [
        "-synctex=1",
        "-interaction=nonstopmode",
        "-file-line-error",
        "-xelatex",
        "-outdir=%OUTDIR%",
        "%DOC%"
      ],
      "env": {}
  }

ให้เปลี่ยนชื่อ เป็น xelatexmk และ เปลี่ยน args จาก -lualatex เป็น -xelatex

save แล้วปิดหน้าต่าง settings.json ไป

แล้วกลับไป Click ที่ Edit in settings.json ของ

Latex-workshop -> Latex: Recipes

ให้ Copy ฺBlock ของ latexmk (lualatex) แล้วเปลี่ยนให้ตรงกับที่เรากำหนดไว้ใน Tools

{
      "name": "latexmk (xelatex)",
      "tools": [
        "xelatexmk"
      ]
 }

ตั้งให้ใช้ Recipe ล่าสุดในการ Build Project

ให้ค้นหา Setting

latex recipe

เปลี่ยนจาก first เป็น lastUsed

ให้ใช้ Recipe ล่าสุดเสมอ

ถ้าไม่ตั้งแบบนี้มันจะใช้ Recipe อันแรกสุดเสมอ เมื่อเพิ่มค่าต่างๆ ครบแล้วให้ทำการ reload หน้าต่างใหม่อีกครั้ง

Reload Window

ให้เปิด Command Palette จากเมนู หรือจะกด Shotcut ก็

View --> Command Palette

แล้วค้นหาคำว่า

restart

เลือก Restart Extension Host แล้วกด Reload Window หรือจะปิด-เปิด VSCode ใหม่ก็ได้

อย่าลืมว่าถ้าเราตั้งค่าในระดับ Workspace ถ้าเราสร้าง Workspace ใหม่เราก็ต้องตั้งค่าเหล่านี้ใหม่ทุกครั้ง หรืออาจจะ copy .vscode ใน folder ไปก็ได้

Config ของ VSCode ในระดับ Workspace

First XeLaTeX

มาลองเขียน XeLaTeX แรกกัน

สร้าง file1.tex โดย Copy คำสั่งมาจากตัวอย่างแรกของ pdfLaTeX

\documentclass{article}
\title{My First \LaTeX{} Document}
\author{MrChoke}
\date{\today}
\begin{document}
   \maketitle
   Hello \LaTeX{} world!
\end{document}

หลังจากนั้นให้ Click ที่ Icon TeX บน Side Bar ด้านซ้าย

LaTeX WorkShop Extension

ให้ขยายคำสั่ง Build LaTeX project คำสั่งแรกปุ่ม play สีเขียว

Recipe ต่างๆ สำหรับการ Build Project

เราจะเห็นคำสั่งต่างๆ สำหรับการ Build Project ให้สังเกตตรง Recipe โดยปกติเวลาเราสั่งให้ Build มันจะเลือกคำสั่งแรกซึ่งเป็น Recipe: latexmk หรือ pdfLaTeX นั่นเองก่อนหน้านี้เราได้จัดเตรียม latexmk ให้ Build XeLaTeX ไว้แล้วจะเห็นใน Recipe ที่ 4 หรือถ้าใครจะ Build LuaLaTeX ก็เลือกจากตรงนี้เหมือนกันครับ ตรงนี้ให้ Click เลือก XeLaTeX ต้องระวังนิดหนึ่งคือก่อน Click ต้องให้ file ที่เราจะ Build ถูก Focus เสมอนั่นคือให้ Click file ก่อนแล้ว Click Build

Check

รู้ได้ยังไงว่า Build สำเร็จ ?

ให้กลับไปที่หน้า Explorer แล้วดูว่ามี file pdf ถูกสร้างขึ้นมาหรือไม่

หลังจากนั้นก็ลอง Click แสดง pdf ตรง icon มุมขวาบนข้างปุ่ม Build สีเขียว

ปุ่มคำสั่งของ LaTeX Workshop Extension

PDF ที่ได้จาก XeTeX

เรารู้ได้อย่างไรว่า Extension ใช้ Engine อะไร Build ให้เรา ?

หลายคนคงสงสัยว่าที่ Build มานั้นใช้ XeLaTeX จริงหรือเปล่า ผมแนะนำอีกอย่างคือการดู Output ของ Extension ให้เลือกเมนู

View --> Output

แล้วเลือกดู LaTeX Workshop หรือ จะดู LaTeX Compiler ก็แล้วแต่เราจะสนใจนะครับ

Output ต่างๆ

Output

ถ้าเราสังเกตก็จะเห็นบรรทัดคำสั่ง หรือ Recipe ที่เราสั่งรูปด้านบนก็จะบอกว่าเป็น XeLaTeX

นอกจากนั้นการวิเคราะห์ log ของ LaTeX เองก็จะช่วยแก้ปัญหาต่างๆ ได้ดูโดยเลือกเปิด file นามสกุล log ที่มีชื่อเดียวกับ LaTeX ของเรา

file log

XeLaTeX กับภาษาไทย

อย่างที่เคยเกริ่นไปก่อนหน้านี้ว่า XeLaTeX นั้นเกิดมาหลังและสนับสนุน UTF-8 , OpenType และ TrueType Font ตั้งแต่แรกอยู่แล้วการใช้งานพวกนี้ก็ถือว่าง่าย

ข้อดี

ข้อเสีย

Fonts ไทย

fonts-tlwg

fonts-tlwg มีอยู่แล้วใน TeX Live ที่ติดตั้งแบบทั้งหมด หรือ จะเลือกติดตั้งเพิ่มเติมภายหลังก็ได้การใช้งาน font ใน XeLaTeX จะใช้ package fontspec ซึ่งจะต้องมีการประกาศ font เสียก่อนทำได้หลายแบบ ก่อนอื่นให้ copy file2.tex จากตัวอย่างแรกมาก่อน แล้วแก้ดังนี้

\documentclass{article}
\usepackage{fontspec}
\begin{document}
สวัสดีชาวโลก
\end{document}

แล้วลองสั่ง build ด้วย XeLaTeX Recipes จะไม่มีอะไร error แต่เมื่อสั่ง preview pdf จะไม่เห็นข้อความใด ๆ เลย

ไม่มี output

ทีนี้เราลองสั่งเพิ่มเข้าไปหนึ่งบรรทัดคือ

\setmainfont{Laksaman.otf}

แล้วลอง Build ใหม่อีกรอบก็จะมีข้อความขึ้นมา

Output ภาษาไทย

ข้างบนเป็นตัวอย่างหนึ่งที่ง่ายๆ ในการใช้ font แต่เราต้องรู้ชื่อ file font ด้วยนะไม่งั้นมันจะหาไม่ถูก แล้ว fonts-tlwg มี font อะไรบ้างละ ?

fonts-tlwg ที่มากับ TeX Live จะเก็บใน

texmf-dist/fonts/opentype/public/fonts-tlwg/

ถ้า Docker Image ของผมจะอยู่ที่

/usr/local/texlive/2021/texmf-dist/fonts/opentype/public/fonts-tlwg/

รายชื่อ Files

Garuda-BoldOblique.otf
Garuda-Bold.otf
Garuda-Oblique.otf
Garuda.otf
Kinnari-BoldItalic.otf
Kinnari-BoldOblique.otf
Kinnari-Bold.otf
Kinnari-Italic.otf
Kinnari-Oblique.otf
Kinnari.otf
Laksaman-BoldItalic.otf
Laksaman-Bold.otf
Laksaman-Italic.otf
Laksaman.otf
Loma-BoldOblique.otf
Loma-Bold.otf
Loma-Oblique.otf
Loma.otf
Norasi-BoldItalic.otf
Norasi-BoldOblique.otf
Norasi-Bold.otf
Norasi-Italic.otf
Norasi-Oblique.otf
Norasi.otf
Purisa-BoldOblique.otf
Purisa-Bold.otf
Purisa-Oblique.otf
Purisa.otf
Sawasdee-BoldOblique.otf
Sawasdee-Bold.otf
Sawasdee-Oblique.otf
Sawasdee.otf
TlwgMono-BoldOblique.otf
TlwgMono-Bold.otf
TlwgMono-Oblique.otf
TlwgMono.otf
TlwgTypewriter-BoldOblique.otf
TlwgTypewriter-Bold.otf
TlwgTypewriter-Oblique.otf
TlwgTypewriter.otf
TlwgTypist-BoldOblique.otf
TlwgTypist-Bold.otf
TlwgTypist-Oblique.otf
TlwgTypist.otf
TlwgTypo-BoldOblique.otf
TlwgTypo-Bold.otf
TlwgTypo-Oblique.otf
TlwgTypo.otf
Umpush-BoldOblique.otf
Umpush-Bold.otf
Umpush-LightOblique.otf
Umpush-Light.otf
Umpush-Oblique.otf
Umpush.otf
Waree-BoldOblique.otf
Waree-Bold.otf
Waree-Oblique.otf
Waree.otf

ถ้าสังเกตในตัวอย่างเราจะใช้ font Laksaman.otf ซึ่งเป็น font แบบ Nomal แบบเดียวไม่มีตัวหน้า ตัวเอียงให้ใช้ ลองสั่งดูครับ

จะแสดงเป็นตัวธรรมดาทั้งหมด

fontspec จะมี Options สำหรับการกำหนดค่า font ให้ง่ายขึ้นมาดูตัวอย่าง เพิ่มเติมให้กับ \setmainfont กัน

\setmainfont[
  Extension = .otf ,
  ItalicFont = *-Italic ,
  BoldFont = *-Bold ,
  BoldItalicFont = *-BoldItalic 
]{Laksaman}

การกำหนด Font Shape Options นี้ยังสามารถใช้กับ font ที่เรานำมาใช้เอง โดยการ Copy ชุด fonts มาใส่ใน folder project ของเราแล้วประกาศให้ครบตามนี้แต่อย่าลืมว่าแต่ละ font จะมีไม่เหมือนกันบางตัวไม่มีตัวหนา ไม่มีตัวเอียง หรือ จะมีแบบอื่นเพิ่มขึ้นมาเช่น -Oblique , -Regular, -Light เป็นต้นก็ต้องกำหนดให้ถูกต้อง

ตัวอย่าง Font Shape

BoldFont = ⟨font name⟩
ItalicFont = ⟨font name⟩
BoldItalicFont = ⟨font name⟩
SlantedFont = ⟨font name⟩
BoldSlantedFont = ⟨font name⟩
SmallCapsFont = ⟨font name⟩
UprightFont = ⟨font name⟩

Shaping

ตอนนี้ดูเหมือนจะแสดงภาษาไทยได้แล้วแต่สิ่งที่สำคัญของภาษาไทยอีกอย่างที่เราควรรู้และใส่ใจคือการจัดระดับสระวรรณยุกต์ หรือ Shaping ลองดูตัวอย่าง

\documentclass{article}
\usepackage{fontspec}

\setmainfont[
  Extension = .otf ,
  ItalicFont = *-Italic ,
  BoldFont = *-Bold ,
  BoldItalicFont = *-BoldItalic 
]{Laksaman}

\begin{document}

ป่า ป้า ป๊ะ ป๋า ญู ฐู ฏู ญ ฐ - -- --- `fi' ``ff"

\textbf{ป่า ป้า ป๊ะ ป๋า ญู ฐู ฏู ญ ฐ - -- --- `fi' ``ff"}

\textit{ป่า ป้า ป๊ะ ป๋า ญู ฐู ฏู ญ ฐ - -- --- `fi' ``ff" }

\textbf{\textit{ป่า ป้า ป๊ะ ป๋า ญู ฐู ฏู ญ ฐ - -- --- `fi' ``ff"}}
\end{document}

สระวรรณยุกต์ภาษาไทยไม่สวย

จากตัวอย่างเมื่อเราพิมพ์ข้อความภาษาไทยที่มีสระและวรรณยุกต์หลายระดับจะแสดงผลไม่สวยงามอย่าเพิ่งตกใจเมื่อเราใช้ OpenType หรือ TrueType Fonts จะมี Option Script ให้เราเลือกว่าจะใช้ Script แบบไหนใน Fonts-Tlwg ที่เป็น OpenType มี Script Thai ให้เลือกได้เลย วิธีตรวจสอบว่า fonts ที่เราใช้มี Script อะไรบ้างจะใช้คำสั่ง

otfinfo -s fontname.[ttf,otf]

ลองกำหนด Script

Script = Thai

Script = Thai

ผลที่ได้คือภาษาไทยจัดระดับสระวรรณยุกต์ได้สวยงาม แต่!!! สิ่งที่ต้องสังเกตุอีกอย่างหนึ่งคือ การแสดง Ligatures ของภาษาอังกฤษจะไม่สวยงามแทนเพราะมันต้องใช้ Script = Latin นั่นเองวิธีแก้แบบบัวไม่ให้ช้ำน้ำไม่ให้ขุ่น ให้แสดงสวยงามทั้งสองภาษาคือให้ใช้

Script = Default

Script = Default

มาดูผลเปรียบเทียบกันแบบชัดๆ

ไม่กำหนด Script จะเท่ากับ Script = Latin

Script = Thai

Script = Default

ผู้เชี่ยวชาญหลายท่านยังแนะนำอีก Option คือ

Ligatures=TeX

หรือ จะใช้

Mapping=tex-text

สอง options นี้เลือกตัวใดตัวหนึ่งก็ได้จะทำให้การแสดงผลเกี่ยวกับพวก -, — , — เป็นไปตามแบบของ TeX ทำให้สวยงามขึ้น

การตัดคำ

XeLaTeX จะใช้ lib ของ ICU ในการตัดคำลองมาดูกันว่าเมื่อเทียบกับ pdfLaTeX ที่ใช้ swath ในการตัดคำจะเป็นยังไงบ้าง

Copy จาก file4.tex จากตัวอย่าง pdfLaTeX มาแล้วแก้ไขดังนี้

\documentclass{article}
\usepackage{fontspec}

\setmainfont[
  Extension = .otf ,
  ItalicFont = *-Italic ,
  BoldFont = *-Bold ,
  BoldItalicFont = *-BoldItalic ,
  Script = Default,
  Mapping=tex-text
]{Laksaman}

\begin{document}
เป็นมนุษย์สุดประเสริฐเลิศคุณค่า กว่าบรรดาฝูงสัตว์เดรัจฉาน
จงฝ่าฟันพัฒนาวิชาการ อย่าล้างผลาญฤๅเข่นฆ่าบีฑาใคร
ไม่ถือโทษโกรธแช่งซัดฮึดฮัดด่า หัดอภัยเหมือนกีฬาอัชฌาสัย
ปฏิบัติประพฤติกฎกำหนดใจ พูดจาให้จ๊ะๆ จ๋า น่าฟังเอยฯ

ธรรมคุณหนุนความดีฑีฆรัตน์ สรรพสัตว์วัฏวจรถอนวิถี
อริยสัจตรัสสังโยคโลกโมฬี ดังกุมภีร์ผลาญชิวหาบีฑาทนต์
ฟังฉัตรทองของประเสริฐเลิศวิเศษ ฝ่ายต้นเหตุฎรงกรณ์อุดรผล
ยินสถานฌานสมาอารยะชน พึงซ่อนกลปรนนิบัตินิวัฒน์แฮฯ
\end{document}

เมื่อลอง Build ดู

ข้อความภาษาไทยยาวๆ

จะเห็นว่าตอนนี้ยังไม่มีการตัดคำและบรรทัดที่ถูกต้อง XeLaTeX มีคำสั่งกำหนดภาษาสำหรับการตัดบรรทัดไว้ให้คือ

\XeTeXlinebreaklocale "th"

ค่า “th” คือบอกว่าจะใช้ Locale ของ Thai นั่นเอง อาจจะใช้ “th_TH” หรือ “thai” ก็ได้

เพิ่มคำสั่งในการตัดคำ

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

\XeTeXlinebreakskip = 0pt plus 1pt\relax

การ Tune การตัดบรรทัดเพิ่มเติม

เมื่อเราทำการ tune การตัดบรรทัดพบว่าดูดีขึ้นมาก ขอบซ้ายขวาตรงดิ่งสวยงาม แต่ถ้าสังเกตดีๆ จะพบว่ามีการแทรกช่องว่างระหว่างคำเล็กน้อยถ้าเทียบกับ pdfLaTeX ก่อนหน้านี้ จากการทดลองพบว่าถ้าจะให้เหมือนกับ pdfLaTeX แป่ะ ๆ ต้องใช้ package polyglossia ช่วยอันนี้ผมเองไม่แน่ใจเรื่องกลไกว่ามันไปทำยังไงถึงทำให้การจัดหน้า ตัดบรรทัดออกมาได้เหมือนกันขนาดนี้

ใช้ polyglossia

ลองเทียบกันแบบจะๆ ดู

pdfLaTeX + swath

XeLaTeX + Polyglossia + thai locale

การใช้ Font นอก

ปกติถ้าเราใช้ font จากในระบบของ TeX Live ก็เพียงพอสำหรับการเขียนตำรา เขียนบทความต่างๆ นอกเสียจากมีการกำหนดชนิด font ของผู้รับตีพิมพ์ ผมจะลองเอา font จาก Windows และ Fonts จาก Internet มาใช้ดูว่าจะแสดงผลได้สวยงามครบถ้วนเหมือนชุด Fonts-Tlwg หรือไม่

Angsana New (TTF)

ตัวแรกผมลอง Angsana New แบบ TrueType Font เท่าที่ดูค่อนข้างเก่ามากมี font script Thai ตัวเดียวด้วย

ผมสร้าง folder ชื่อ fonts ไว้ใน ที่เดียวกับ source code มี Angsana New มี files ทั้งหมด 4 แบบคือ

angsa.ttf angsab.ttf angsai.ttf angsaz.ttf

fontspec ผมกำหนดดังนี้

\setmainfont[
  Path = fonts/,
  Extension = .ttf ,
  UprightFont = angsa,
  ItalicFont = angsai ,
  BoldFont = angsab ,
  BoldItalicFont = angsaz ,
  Script = Thai,
  Mapping=tex-text
]{Angsana New}

Angsana NEW

เนื่องจาก fonts ภาษาไทยส่วนมากขนาดจะเล็กเมื่อเทียบกับ fonts ภาษาอังกฤษ และ fonts-tlwg ที่ถูก scale ให้เท่ากับ fonts ภาษาอังกฤษแล้ว ใน option ผมจะ scale ขึ้นไปให้เท่าๆ กับ Laksaman ตามแบบด้านล่าง

Scale=1.45

Angsana New + Scaled

ผลที่ได้น่าพอใจคือใกล้เคียงกับ Laksaman

Tahoma (TTF)

สำหรับ Tahoma จะเป็น font ที่มี scale เท่ากับ fonts ภาษาอังกฤษอื่นๆ จึงไม่จำเป็นต้อง scale และ มี Script หลายภาษา แต่จะไม่มีค่า Default ดังนั้นตอนเรียกใช้อาจจะต้องแยกว่าจะเรียกแบบ Thai หรือ Latin

\setmainfont[
  Scale=1,
  Path = fonts/,
  Extension = .ttf ,
  UprightFont = tahoma,
  BoldFont = tahomabd ,
  Script = Thai,
  Mapping=tex-text
]{Tahoma}

Angsana New (TTC)

นอกจาก font ที่มีนามสกุลเป็น ttf แล้ว windows ยังใช้ fonts ที่มีนามสกุลว่า ttc หรือ TrueType Collections คือ font เดียวมีครบทุก face นั่นเองแต่ info ยังคงเหมือน Angsana New ที่แยกเป็น ttf ครับคือมี script เดียวคือ Thai

\setmainfont[
  Scale=1.45,
  Path = fonts/,
  UprightFeatures = {FontIndex=0} ,
  BoldFeatures = {FontIndex=1} ,
  ItalicFeatures = {FontIndex=2} ,
  BoldItalicFeatures = {FontIndex=3} ,
  Script = Thai,
  Mapping=tex-text
]{angsana.ttc}

Angsana New TTC

Browallia New (TTC)

อีกตัวอย่างแบบ TTC ของ font Browallia ซึ่งก็เหมือนกับ Angsana New

\setmainfont[
  Scale=1.45,
  Path = fonts/,
  UprightFeatures = {FontIndex=0} ,
  BoldFeatures = {FontIndex=1} ,
  ItalicFeatures = {FontIndex=2} ,
  BoldItalicFeatures = {FontIndex=3} ,
  Script = Thai,
  Mapping=tex-text
]{browalia.ttc}

Browalloa New TTC

Trirong (Google Font TTF)

ลอง Download Font ไทยจาก Google มาลองดูบ้าง ผมสุ่มเอา Font Trirong มาทดสอบดูพบว่ามันมี Face ให้เลือกใช้เยอะมากซึ่งเราสามารถตั้งค่าเอามาใช้แบบละเอียดได้เช่น Black, Light ExtraBold เป็นต้น (ต้องขออ่านคู่มือเพิ่มเติม)

\setmainfont[
  Path = fonts/Trirong/ ,
  Extension = .ttf ,
  UprightFont = *-Light,
  ItalicFont = *-Italic ,
  BoldFont = *-Bold ,
  BoldItalicFont = *-BoldItalic ,
  Script = Default,
  Mapping=tex-text
]{Trirong}

Google Trirong

การเลือกว่าจะจัดชุด font face ก็ขึ้นอยู่กับที่เราเลือกนะ เช่น เราอยากได้แค่เบาๆ ก็อาจจะใช้ UprightFont แบบ Light หรือ เพิ่มขึ้นหน่อยก็ Regular , Nornal หรือ Medium เป็นต้นลองเล่นกันดูครับ ส่วน font นี้มี script แบบ Default ให้ด้วยก็ตั้งค่าง่ายหน่อย

การใช้ Fonts หลาย ๆ แบบในเอกสาร

ใน Fonts-Tlwg มีหลาย Fonts ให้เลือกใช้ถ้าเราต้องการใช้หลายๆ แบบวิธี set แบบด้านบนอาจจะยุ่งอยากและไม่สามารถ Reuse ได้มีผู้เชี่ยวชาญท่านหนึ่งได้ทำตัวอย่างไว้ให้ผมเลยขอนำมาแนะนำต่อละกัน

** ต้นฉบับ (https://github.com/abhabongse/fonts-tlwg/blob/067c1e4f2625604fe31337c9bd2203f634c98560/latex/fonts-tlwg.sty)

Fontspec สามารถประกาศ Features ต่างๆ ไว้ก่อนได้แล้วนำไป Reuse และ ปรับค่า เพิ่มค่าได้ตามต้องการเช่น fonts ในชุด fonts-tlwg เราสามารถประกาศสั้นๆ แบบรวมทุก fonts ดังนี้

\defaultfontfeatures{
  Extension = .otf ,
  Mapping = tex-text,
  Script = Default
  }

% Font families with regular, oblique, bold, and bold-oblique.
\defaultfontfeatures[Garuda,Loma,Purisa,Sawasdee,TlwgMono,TlwgTypewriter,%
TlwgTypist,TlwgTypo,Umpush,Waree]{
ItalicFont = *-Oblique ,
BoldFont = *-Bold ,
BoldItalicFont = *-BoldOblique ,
}

% TlwgMono don't has Script Default
\defaultfontfeatures+[TlwgMono]{Script=Thai}

% Font families with regular, italic, bold, and bold-italic.
\defaultfontfeatures[Laksaman]{
ItalicFont = *-Italic ,
BoldFont = *-Bold ,
BoldItalicFont = *-BoldItalic ,
}
% Font families with regular, italic, oblique, bold, 
% bold-italic, and bold-oblique.
\defaultfontfeatures[Kinnari,Norasi]{
ItalicFont = *-Italic ,
SlantedFont = *-Oblique ,
BoldFont = *-Bold ,
BoldItalicFont = *-BoldItalic ,
BoldSlantedFont = *-BoldOblique ,
}

\setdefaultlanguage{thai}
\XeTeXlinebreaklocale "th"
\XeTeXlinebreakskip = 0pt plus 1pt \relax

\newfontfamily\thaifont{Norasi}
\newfontfamily\thaifontsf{Garuda}
\newfontfamily\thaifonttt{TlwgTypist}

จากตัวอย่างข้างบนเป็นการกำหนด fonts หลักของเอกสาร 3 แบบโดยเราจะใช้ Polyglossia เป็นตัวช่วยในการจัดการให้โดยจะมี

ตัวอย่างการแสดงผล

\begin{description}
  \item[Roman] การพิมพ์ภาษาไทย -- English
  \item[\sffamily{}Sans] \sffamily การพิมพ์ภาษาไทย -- English
  \item[\ttfamily{}Typewriter] \ttfamily การพิมพ์ภาษาไทย -- English
\end{description}

Fonts Roman, Sans และ TypeWriter

การแสดง fonts อื่นๆ ที่ไม่ใช่สามแบบนี้จะเราสามารถเรียกใช้ได้หลายแบบ แบบที่แนะนำคือการใช้คำสั่ง

\newfontfamily

เช่นประกาศ fonts ต่างๆ

\newfontfamily\garuda{Garuda}
\newfontfamily\norasi{Norasi}
\newfontfamily\kinnari{Kinnari}
\newfontfamily\laksaman{Laksaman}
\newfontfamily\loma{Loma}
\newfontfamily\purisa{Purisa}
\newfontfamily\sawasdee{Sawasdee}
\newfontfamily\tlwgtypist{TlwgTypist}
\newfontfamily\tlwgtypo{TlwgTypo}
\newfontfamily\tlwgmono{TlwgMono}
\newfontfamily\tlwgtypewriter{TlwgTypewriter}
\newfontfamily\umpush{Umpush}[
  FontFace = {l}{n}{ Font = *-Light },
  FontFace = {l}{it}{ Font = *-LightOblique }
]
\newfontfamily\waree{Waree}

เรียกใช้งาน

\begin{itemize}
  \item \kinnari Kinnari สวัสดีชาวโลก
  \item \garuda Garuda สวัสดีชาวโลก
  \item \norasi Norasi สวัสดีชาวโลก
  \item \laksaman Laksaman สวัสดีชาวโลก
  \item \purisa Purisa สวัสดีชาวโลก
  \item \loma Loma สวัสดีชาวโลก
  \item \sawasdee Sawasdee สวัสดีชาวโลก
  \item \tlwgtypewriter TlwgTypewriter สวัสดีชาวโลก
  \item \tlwgtypist TlwgtTypist สวัสดีชาวโลก
  \item \umpush Umpush สวัสดีชาวโลก
  \item \waree Waree สวัสดีชาวโลก
\end{itemize}

ผล

Fonts ต่างๆของ TLWG

ถ้าสังเกตจะเห็นว่า font Umpush จะมีการกำหนดค่าพิเศษกว่าแบบอื่น คือจะมี fontseries Light และ Light Oblique เพิ่มเข้ามาซึ่งไม่สามารถกำหนดได้ใน \defaultfontfeatures แต่สามารถใช้วิธีในตัวอย่าง หรือ จะใช้วิธีก่อนหน้านี้คือ \setmainfont \setsansfont \setmonofont ก็ได้ถ้าไม่ใช้ PolyGlossia จัดการ fonts ให้

วิธีเรียกใช้ fontsereis และ fontshape

ปกติเราจะใช้ macro เช่น

\begin{description}
  \item[\textbf{ตัวหนา}] \bfseries ปาป่าป้าป๊าป๋า
  \item[\textit{ตัวเอียง}] \itshape ปาป่าป้าป๊าป๋า
\end{description}

ตัวหนา ตัวเอียง

หรือจะใช้ \fontseries{}\fontshape{}\selectfont ก็ได้เช่น

\begin{itemize}
  \item \fontseries{m}\fontshape{n}\selectfont ปาป่าป้าป๊าป๋า
  \item \fontseries{m}\fontshape{it}\selectfont ปาป่าป้าป๊าป๋า
  \item \fontseries{b}\fontshape{n}\selectfont ปาป่าป้าป๊าป๋า
  \item \fontseries{b}\fontshape{it}\selectfont ปาป่าป้าป๊าป๋า
\end{itemize}

รูปแบบหนาเอียงธรรมดา

ส่วน Umpush จะมีเพิ่มมาคือ \fontseries{l}\fontshape{n} และ \fontseries{l}\fontshape{it}

\begin{itemize}
  \item \umpush\fontseries{l}\fontshape{n}\selectfont ปาป่าป้าป๊าป๋า
  \item \umpush\fontseries{m}\fontshape{n}\selectfont ปาป่าป้าป๊าป๋า
  \item \umpush\fontseries{l}\fontshape{it}\selectfont ปาป่าป้าป๊าป๋า
  \item \umpush\fontseries{m}\fontshape{it}\selectfont ปาป่าป้าป๊าป๋า
  \item \umpush\fontseries{b}\fontshape{n}\selectfont ปาป่าป้าป๊าป๋า
  \item \umpush\fontseries{b}\fontshape{it}\selectfont ปาป่าป้าป๊าป๋า
\end{itemize}

Umpush

ปิดท้ายด้วยตัวอย่างการใช้งาน Fonts รูปแบบต่างๆ ซึ่งต้นฉบับมาจากของพี่เทพพิทักษ์

PDF ตัวอย่าง

https://github.com/mrchoke/latex-workshop-medium/raw/main/latex2/file11.pdf

ต้นฉบับ

tlwg/fonts-tlwg

Source Code ที่ใช้ประกอบทั้งหมด

mrchoke/latex-workshop-medium

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

โดย MrChoke ณ 25 March 2021 22:54 +0000

เขียน LaTeX ด้วย VSCode และ TeX Live Docker Image — EP1

เขียน LaTeX ด้วย VSCode และ TeX Live Docker Image — EP1

บันทึกการเขียน LaTeX ด้วย VSCode โดยใช้ TeX Live Docker Image และ การใช้ Visual Studio Code Remote — (SSH,Container) และ Visual Studio Code LaTeX Workshop Extension

โดยปกติผมรู้จัก LaTeX มานานแต่ไม่เคยเข้าถึงมันเลย ใช้แค่ระดับทดสอบไม่ได้ใช้จริงจังเป็นกิจวัตรดังนั้นหลายๆ อย่างก็ไม่ค่อยรู้เรื่อง ช่วงนี้ก็นำเอามาใช้งาน เช่นตัวช่วยสร้าง PDF และ ลองเขียนโน้นนี่ไปเรื่อย ปกติเครื่องมือที่ใช้เขียน LaTeX มีหลายตัว แต่ในบันทึกครั้งนี้ผมจะใช้ VSCode + Visual Studio Code Remote — (SSH,Container) Extension+ Visual Studio Code LaTeX Workshop Extension และ TeX Live Docker Image ซึ่งสามารถใช้ได้ทั้งบนเครื่องส่วนตัว และ การตั้งเป็น Server ให้เพื่อนร่วมทีมเข้ามาใช้งานร่วมกันได้

EP1 จะเป็นเรื่องการเตรียม Docker Container นะครับ ส่วน EP 2 จะแนะนำการเขียน LaTeX เบื้องต้น

บันทึกนี้มีอยู่สองสามวิธีค่อยๆ อ่านนะครับอาจจะงงๆ ไปบ้างแต่ถ้าเข้าใจก็จะใช้มันสนุกมาก ถ้าใช้งานปกติใช้ แบบ Remote — Container จะง่ายสุด แต่ถ้าใช้หลายๆ คนบน Server แบบ Remote — Container มันอาจจะยุ่งยากหน่อยเรื่อง docker context เราอาจจะใช้แบบ SSH เข้ามาช่วยก็ได้

Run TeX Live Docker Container

สำหรับ Docker Image ที่ใช้ผมสร้างไว้ใช้เองรายละเอียดอ่านด้านล่างของบทความ

TeX Live Only Image

docker run -itd \
  --hostname texlive \
  --name texlive \
  -v ${PWD}/datas:/root/data \
  mrchoke/texlive

ข้างบนจะใช้ options ดังนี้

With SSH

docker run -itd \
  --hostname texlive \
  --name texlive \
  -v ${PWD}/datas:/root/data \
  -p 2222:22 \
  mrchoke/texlive:2021-ssh

With authorized_keys

docker run -itd \
  --hostname texlive \
  --name texlive \
  -v ${PWD}/datas:/root/data \
  -v ${PWD}/.ssh:/root/.ssh \
  -p 2222:22 \
  mrchoke/texlive:2021-ssh

หรือถ้าใครต้องการใช้ ssh-key ในการ login ก็สามารถ mount .ssh ที่มี file authorized_keys เข้าไปใน /root ของ container ก็ได้

ลองตรวจสอบว่า docker สามารถ run ได้สำเร็จหรือไม่

TeX Live Only

docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f7615fbdb871 mrchoke/texlive “/bin/sh” 6 seconds ago Up 5 seconds texlive

With SSH

docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
24ad39e3cae9 mrchoke/texlive:2021-ssh "/usr/sbin/sshd -D" 4 seconds ago Up 3 seconds 0.0.0.0:2222->22/tcp texlive

ข้อดี — ข้อเสีย

TeX Live Only

With SSH

Remote (SSH,Container)

หลังจาก Start Container เรียบร้อยแล้วเราสามารถเข้าไปยัง container ได้ 3 รูปแบบคือ

  1. docker exec เข้าไปใช้งาน
docker exec -it texlive bash
  1. SSH

ใช้ port ที่ bind ไว้ตอน run ครั้งแรก

ssh -p 2222 root@localhost

หรือ ในกรณีเข้าไปยัง server

ssh -p 2222 root@server-ip

ถ้าไม่ได้ใช้ ssh-key ก็ใส่รหัสผ่าน default 123456 แต่ถ้าจะเปลี่ยนรหัสผ่านก็สามารถใช้คำสั่ง

docker exec -it texlive passwd
  1. Visual Studio Code Remote — (SSH,Container)

ซึ่งจะใช้ความสามารถของ VSCode Remote Extension จะกล่าวในหัวข้อถัดไป

Visual Studio Code Remote — (SSH,Container)

VSCode Remote — Container (localhost)

Remote Container Extension

วิธีนี้ไม่จำเป็นต้อง bind port ssh ออกมานะครับ เพราะเราจะใช้ VSCode เข้าไปใช้งาน Container โดยตรงบนเครื่องของเราเอง โดย Click ที่ icon รูป computer บน Side Bar ด้านซ้านมือ

Remote icon

หลังจากนั้นให้ click เลือก Target ของ Remote Extension

มันจะมีสองแบบคือ

วิธีนี้ให้เลือก Containers

Remote Extension Container

ถ้าเรา start container ไว้ก็ควรจะเห็นเหมือนในภาพตัวอย่างด้านบนให้เรา Click ขวาบนชื่อ container ที่เราต้องการจะเข้าไปใช้งาน

Click ขวา

และให้เลือก Attach to Container หลังจากนั้นก็จะมีหน้าต่างใหม่เปิดขึ้นมาพร้อมเข้าไปยัง directory ที่เราได้ทำการ mount ไว้ตอนสั่ง run คือ /root/data นั่นเอง

หน้าต่าง VSCode ที่ Remote ไปยัง Docker Container

VSCode Remote — Container (Over SSH)

VSCode Remote — Container

สำหรับการใช้ Remote — Container สามารถใช้งานข้ามเครื่องได้โดยการใช้ความสามารถของ Docker ผ่าน DOKCER_CONTEXT หมายความว่า

วิธีการง่ายๆ คือ เราสั่ง docker run texlive image ไว้ที่ฝั่ง Server แล้วเลือก DOCKER_CONTEXT ขี้ไปที่เครื่อง Server หลังจากนั้น ตัว Extension ก็จะสามารถ attach มาใช้งานเปรียบเสมือนอยู่บนเครื่องเราเอง

Create Docker Context

สร้าง Docker Context สำหรับการเชื่อมต่อไปยัง Server ผ่านทาง SSH

docker context create texlive --docker "host=ssh://mrchoke@192.168.1.17"

ลองตรวจสอบดูครับว่าตอนนี้มี context อะไรอยู่บ้าง

docker context ls
NAME TYPE DESCRIPTION DOCKER ENDPOINT KUBERNETES ENDPOINT ORCHESTRATOR
default * moby Current DOCKER_HOST based configuration unix:///var/run/docker.sock swarm
texlive moby ssh://mrchoke@192.168.1.17

จะเห็นรานการทั้งหมดของ context ซึ่งตัวปัจจุบันที่ใช้อยู่จะมีเครื่องหมาย * อยู่หลังชื่อปกติจะเป็น defualt

การเลือกใช้ context

ทำได้สองแบบคือ

  1. แบบชั่วคราว

ให้ใช้ตัวแปร DOCKER_CONTEXT เลือก context ที่ต้องการ วิธีนี้ต้องพิมพ์แบบนี้ทุกครั้งหน้าคำสั่ง docker ซึ่งจะยุ่งยากแต่เหมาะกับการใช้แบบครั้งคราว

DOCKER_CONTEXT=texlive docker ps
  1. แบบ Global

วิธีนี้สามารถสั่งด้วย command โดยตรงก็ได้ หรือ จะใช้ docker extension ของ VSCode เลือกก็ได้ หลังจากเลือกแล้วทุกครั้งที่สั่งคำสั่ง docker สิ่งที่เห็นคือทางฝั่ง server ที่ context ชี้ไป

docker context use texlive
docker ps

ใช้ VSCode Docker Extension

*** สำหรับเครื่อง Server ที่ติดตั้ง docker ไว้ใน path อื่นนอกเหนือจาก /usr/bin ฝั่ง client จะไม่สามารถหาคำสั่ง docker ได้ต้องตั้งค่า sshd เพิ่มเติมคือให้แก้ไข

/etc/ssh/sshd_config

โดยเพิ่ม หรือ แก้ PermitUserEnvironment ให้เป็น yes

PermitUserEnvironment yes

แล้วกำหนด PATH ใน

~/.ssh/environment

เช่นใน macOS docker จะอยู่ใน /usr/local/bin/docker ก็ให้ตั้ง

PATH=/usr/local/bin:/bin:/usr/bin:/sbin:/usr/sbin

Docker Extension get ข้อมูลไม่ได้

จากที่ทดลองเล่นตัว Docker Extension มันจะดึงข้อมูลทางฝั่ง Server มาไม่ค่อยได้ จะ error เหมือนรูปด้านบน แต่ตัว Remote — Container จะเห็นปกติ แต่ก็มีเอ๋อบ้างบางครั้งนะครับ

VSCode Remote — Container Extension

ถ้าได้แบบรูปด้านบนก็สามารถใช้งานเหมือน container อยู่บนเครื่องตัวเองได้เลยครับ

Visual Studio Code Remote — SSH

VSCode Remote — SSH

Remote SSH

ส่วน Remote — SSH นั้นเราต้องสั่ง run พร้อมกับ bind port ออกมาด้วยนะครับตัวอย่างของผมจะ bind ไว้ที่ 2222 ซึ่งสมมติว่าผมจะ remote ไปยังเครื่อง server ละกันจริงๆ เครื่องตัวเองนั้นแหละ ฮาๆ

SSH Targets

ให้เลือกการเชื่อมต่อแบบ SSH ด้านบนก่อนนะครับ หลังจากนั้นให้กดเครื่องหมาย +

ssh command

ให้ใส่คำสั่งเหมือนกับที่เราสั่งบน terminal ปกติได้เลยจากตัวอย่างเช่น

ssh -p2222 root@localhost

ตรงนี้ถ้าเป็นเครื่อง server ก็ให้ใส่ IP หรือชื่อ Host แทน localhost

เก็บ config ไว้ที่ไหน

หลังจากนั้นก็จะถามว่าจะให้ save config นี้ไว้ที่ไหนวิธีที่ง่ายที่สุดคือตัวเลือกแรกคือให้เก็บไว้ใน .ssh/config ใน HOME ของเราเอง

รายชื่อ SSH Targets

หลังจากที่เราป้อนคำสั่งแล้วจะมีรายชื่อเพิ่มขึ้นมาคือ localhost นั่นเอง รายชื่อตรงนี้เราสามารถเข้าไปจัดการได้โดยตรงใน file .ssh/config สามารถตั้งชื่อให้ได้ หรือจะเพิ่มรายการเข้าไปเองโดยตรงก็ได้เช่นกัน

Connect to Host

หลังจากนั้นให้ Click ขวาบนชื่อ SSH Targets ที่เราต้องการจะ Connect เลือกว่าจะเปิดในหน้าต่างปัจจุบันหรือหน้าต่างใหม่

Confirm การเข้าครั้งแรก

ถ้าเราเข้าครั้งแรกจะมีการให้ confirm ก่อนโดยเลือก Continue

Password

ถ้าไม่ได้ใช้ ssh-key ก็ให้ใส่รหัสผ่าน ถ้า default ก็ใส่ 123456 ถ้าเปลี่ยนรหัสผ่านไปแล้วก็ให้ใส่ที่ตั้งไว้ แต่ถ้าตั้ง ssh-key ก็จะไม่มีหน้าต่างนี้

สถานะมุมขวาล่าง

การ Connect ไปยัง SSH Server ครั้งแรกจะช้าหน่อยเพราะมีการติดตั้ง VSCode Server ก่อน แต่ถ้าครั้งต่อไปก็จะเร็วกว่า นอกจากจะมีการ Upgrade Version ของ VSCode

Connect สำเร็จ

ถ้า Connect สำเร็จก็จะขึ้นหน้าต่างดังรูปด้านบนซึ่งจะมีหน้าต่าง Terminal ด้านล่างให้ด้วยเราสามารถพิมพ์คำสั่งของ Linux ในนี้ได้เลย

Visual Studio Code LaTeX Workshop Extension

หลังจากนี้ให้ติดตั้ง Visual Studio Code LaTeX Workshop Extension เพิ่มทางฝั่ง Remote ทำเหมือนกันทั้ง SSH และ Container

Visual Studio Code LaTeX Workshop Extension

Open Folder

เมื่อติดตั้ง Extension เสร็จพร้อมใช้งานแล้วถ้าเป็น Remote SSH เราต้องเปิด Folder ก่อนส่วน Remote Container จะเปิดให้แล้วตั้งแต่แรก วิธีเปิดก็ Click ที่ icon files บน Side Bar ด้านซ้าย

Open Folder

ช่องเลือก

โดยปกติแล้วจะมีช่องให้เลือกโดยเริ่มใน Home Directory ของ root ตอนสั่ง run ผมได้ mount ไว้ที่ /root/data ก็ให้ click ที่ Folder data ได้เลยครับ

เลือก

Work Space

หลังจากนั้นก็จะปรากฎหน้าต่าง Work Space สำหรับการทำงานขึ้นมาโดยชื่อในช่อง Explore ด้านซ้ายจะเป็น Folder ที่เราเลือกไว้เมื่อตะกี้

EP2 จะแนะนำการใช้งาน pdfLaTeX และ XeLaTeX เบื้องต้นต่อไปครับ

เขียน LaTeX ด้วย VSCode และ TeX Live Docker Image — EP2

TeX Live Docker Image

Docker Hub

เป็น Image ที่ผม Build ไว้ใช้เองสำหรับการเขียนเอกสารซึ่งจะมี Python พ่วงเข้าไปด้วย สามารถดู Dockerfile ได้ที่

mrchoke/texlive

วิธี Build ที่ publish ไว้ใน docker hub ผมใช้ buildx โดยให้ใช้ได้ทั้ง amd64 และ arm64 หมายถึงสามารถใช้บน Docker บนเครื่อง Apple M1 ได้ด้วย

docker buildx build --platform linux/arm64,linux/amd64 -t mrchoke/texlive:2021 -t mrchoke/texlive:2021.1 -t mrchoke/texlive:latest --push .

ปกติการติดตั้ง TeX Live ฉบับเต็มจะใช้เนื้อที่ราวๆ 8 GB ในตัว Image ข้างบนผมจะตัดพวก Doc กับ Src ออกเนื้อที่จะลดลงมาเกือบๆ 50% อยู่ที่ราวๆ 4.5 GB

โดย MrChoke ณ 25 March 2021 22:52 +0000