11 December 2021

Kitt

inception ?

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

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

1 December 2021

Vee

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

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

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

2014

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

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

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

2015

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

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

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

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

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

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

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

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

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

แต่ข้อจำกัดของตัวคำสมัยนั้นคือ

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

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

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

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

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

2016

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

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

2017

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

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

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

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

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

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

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

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

2018

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

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


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

2019

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

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

#[derive(Debug)]
pub struct Textunit {
    pub bi_text: BiText,
    pub bi_rtoks: BiRToks,
    pub links: Vec<Link>,
}

2020

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

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

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

2021

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

15 November 2021

Vee

Install Emacs 27.2 on openSUSE LEAP 15.3 from a source tarball

Install pre-requisited tools

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

Download Emacs source tarball

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

Extract and change directory

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

Generate makefiles

./configure --prefix=/opt > log

Make

make -j `nproc`

Install

sudo make install

Add path

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

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

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

12 November 2021

Vee

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

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

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

import socket
import ssl
import uuid

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

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

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

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

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

The read_extra_len reads bytes and turn them to integer.

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

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

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

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

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

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

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

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

> python3.10 http_ex.py 
b'{"amt":0.00004661,"bid":86911896,"rat":2140000,"sid":82187323,"stream":"market.trade.thb_btc","sym":"THB_BTC","ts":1636729677,"txn":"BTCSELL0011078574"}\n{"amt":0.23306074,"bid":86912246,"rat":2140000,"sid":82187325,"stream":"market.trade.thb_btc","sym":"THB_BTC","ts":1636729677,"txn":"BTCSELL0011078576"}'
b'{"amt":0.00466121,"bid":86912014,"rat":2140000,"sid":82187324,"stream":"market.trade.thb_btc","sym":"THB_BTC","ts":1636729677,"txn":"BTCSELL0011078575"}'

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

10 November 2021

Vee

Installing Python 3.10 on openSUSE Leap 15.3

Prerequisites

sudo zypper install gcc-c++ make gcc automake autoconf libtool aclocal
sudo zypper si -d python3

Download and unarchive

Download Python-3.10.0.tar.xz; tar xJvf Python-3.10.0.tar.xz; cd Python-3.10.0

Configure

I got this error message by running ./configure

./configure: line 10530: PKG_PROG_PKG_CONFIG: command not found

So I ran this:

docker run --rm --pull=always -v $(pwd):/src quay.io/tiran/cpython_autoconf:latest

Still configure cannot find g++, so I added CXX=c++ in front of configure.

So this was how I used configure:

CXX=c++ ./configure --enable-optimizations --prefix=/opt 2> elog > log

make

This step has no problem.

make -j8
sudo make install

Running python

pip3.10 and python3.10 cannot run since it can't find readline.

So I ran this.

ln -s /opt/lib64/python3.10/lib-dynload /opt/lib/python3.10/lib-dynload

Then python3.10 works now.

P.S. Thank every one who provide answers on issue trackers and forums. I should have kept URLs to those. Sorry that I didn't.

โดย Vee Satayamas ณ 10 November 2021 07:53 +0000

23 October 2021

Thep

IBus-LibThai 0.1.5

IBus-LibThai 0.1.5 ออกไปแล้วเมื่อวันพุธที่ผ่านมา หลังจากไม่มี release มาเกือบ 5 ปี โดยมีเงื่อนไขหลักที่ทำให้จำเป็นต้องออกรุ่นใหม่คือปัญหาในระบบตั้งค่า อาการคือตั้งค่าเสร็จแล้วไม่มีผลต่อตัว engine ตั้งยังไงก็ได้ค่า default

ผมไม่ได้ติดตามการพัฒนาของ IBus มาระยะหนึ่ง จึงไม่ทราบว่ามีความเปลี่ยนแปลงอะไรบ้างจนทำให้วิธีตั้งค่าแบบเดิมใช้ไม่ได้ จึงเริ่มจากตรวจสอบ engine อื่นว่าปัจจุบันเขาทำยังไงกัน ก็ได้พบว่าหลาย engine ได้ย้ายจากการใช้ IBusConfig ซึ่งเป็น API ของ IBus เองสำหรับการจัดการค่าตั้งไปใช้ระบบอื่น บาง engine ถึงกับสร้างระบบ config ของตัวเองขึ้นมาเลย

แต่ผมก็ยังคงพยายามปรับแก้ภายใต้ API ของ IBus ต่อไป โดยเปลี่ยนจากการ restart IBus เพื่อ reload config มาเป็นการ watch ค่า config แล้วปรับใช้ค่าที่เปลี่ยนทันทีแทน ซึ่งก็ทำให้ engine เปลี่ยนพฤติกรรมตามค่าที่ผู้ใช้ตั้งได้ ภายใน session เดิม แต่ไม่สามารถ save ค่าไว้ให้มีผลใน session ถัดไปได้

ผมแน่ใจว่าทำทุกอย่างเท่าที่ document บอกแล้ว ถ้าจะมีอะไรที่ undocumented ก็ต้องแกะซอร์สของ IBus ซึ่งพอลงมือแกะก็พบว่า แม้แต่ IBus แกนหลักเองก็ไม่ใช้ IBusConfig แต่ไปใช้ GSettings ของ GLib!

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

ด้วยระบบ GSettings ซึ่งใช้ dconf เป็น backend ค่าตั้งต่างๆ จะสามารถเข้าถึงได้ด้วยโปรแกรม dconf-editor

ค่า config ของ IBus-LibThai ใน dconf editor

โดยค่า config แต่ละค่าจะมีคำอธิบายจาก GSchema ที่เตรียมไว้

คำบรรยายคีย์ kb-layout ของ IBus-LibThai ใน dconf editor

และผู้ใช้ IBus ก็ยังคงสามารถปรับแต่ง IBus-LibThai ได้จาก preferences ของ engine ใน IBus Preferences ตามปกติ

Preferences ของ IBus-LibThai

พร้อมกันนี้ ยังมีความเปลี่ยนแปลงอื่นในส่วนของผังแป้นพิมพ์:

โดย Thep (noreply@blogger.com) ณ 23 October 2021 15:58 +0000

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