27 January 2023

Vee

The worst mistake that I made in Common Lisp programming

My programs have defects because some functions destroy or mutate shared data. I avoid mutating shared data in Common Lisp; for example, I use CONS. However, I made a mistake by using SORT, which wasn't aware that SORT is destructive. Sometimes I still forget that SORT mutates its input data. The function sort-snode below sorts a part of a tree.

(defun sort-snode (snode attr)
  (flet ((key-fn (r)
       (cdr (assoc attr r))))
    (sort snode #'< :key #'key-fn)))

Since it is based on SORT, it changes the input data, which is snode. After I found the output tree didn't look what I expected, I took days to see where my mistake was.

My workaround is running COPY-LIST before SORT, as shown below.

(defun sort-snode (snode attr)
  (flet ((key-fn (r)
       (cdr (assoc attr r))))
    (sort (copy-list snode) #'< :key #'key-fn)))

In brief, if you are new to Common Lisp, please beware of destructive operations, which is not limited to SORT. It can be NCONC.

โดย Vee Satayamas ณ 27 January 2023 05:06 +0000

31 December 2022

Thep

From C++ to Rust So Far

บันทึกเมื่อเรียน Rust ได้ครึ่งทาง

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

ครั้งนี้ผมเลือกที่จะเรียนแบบมี quiz จากแหล่งซึ่งมีเนื้อหาเดียวกับหนังสือ The Rust Programming Language (the book) แต่แทรก quiz เป็นระยะ

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

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

Ownership

ด้วยความรู้ครึ่งทางนี้ ผมได้เห็นความพยายามของ Rust ที่จะจัดการ memory แบบไม่ใช้ garbage collection โดยขจัดปัญหา memory leak หรือ memory corruption ต่างๆ ที่พบใน C/C++ โดยเฉพาะใน C โดยเครื่องมือหลักที่ใช้คือ ownership ที่ฝังไว้ในตัวภาษาเลย

เวลาเขียน C เราจะสร้าง contract ต่างๆ ของ API ว่า object ต่างๆ ที่สร้างขึ้น เป็นหน้าที่ของใครในการทำลาย โดยเขียนไว้ใน API documentation และเป็นเรื่องของนักพัฒนาที่ ควร พยายามทำตาม บางครั้งมีการถ่ายโอนความรับผิดชอบในการทำลายไปให้ object อื่นก็ควรทำให้ชัดเจน โครงการไหนที่มีการจัดการเรื่องนี้ดีก็จะปลอดปัญหา แต่ถ้ามีจุดผิดพลาด ก็กลายเป็นการเขมือบทรัพยากรระบบ หรือทำให้โปรแกรมตาย หรือกระทั่งโดนจารกรรมข้อมูลได้ โดยมีการพัฒนาเครื่องมือต่างๆ มาช่วยตรวจสอบ เช่น valgrind

ถ้าเป็น C++ ก็มี constructor/destructor รวมถึง smart pointer ต่างๆ มาช่วยจัดการให้ ก็สะดวกกว่า C ขึ้นมาหน่อย แต่ก็ยังอาศัย วินัย ของโปรแกรมเมอร์บ้างอยู่ดี การแยก composition/aggregation ออกจาก association ใน class diagram ก็เพื่อสื่อสารเรื่องนี้

แต่ Rust กำหนดเรื่องนี้ไว้ในตัวภาษาเลย เรียกว่า ownership และใช้ move semantics เป็นหลักใน assignment และการ pass argument ทำให้มีการถ่ายโอน ownership เพื่อลดการ clone โดยไม่จำเป็น โดยที่ยังมี reference คล้ายของ C++ ให้ใช้ในกรณีที่แค่ ยืมใช้ (borrow) โดยไม่ take ownership โดยทั้งหมดนี้มีการตรวจสอบให้ตั้งแต่ตอนคอมไพล์!

ตอนที่เรียนเรื่องนี้ก็รู้สึกว่าเป็นความกล้าหาญมากที่ Rust พยายามจัดการ memory ด้วยคอมไพเลอร์ และถ้าทำได้จริงโดยไม่เพิ่มความยุ่งยากเกินไปก็เป็นเรื่องดี แต่ในใจก็สงสัยว่าจะทำได้แค่ไหน มีอะไรต้องแลกแน่ๆ ซึ่งก็เป็นไปตามนั้น เยอะเสียด้วย มันเหมือนการเลือก program design แบบหนึ่งที่อาจมีในโครงการ C/C++ บางโครงการ แล้วบังคับให้ใช้แบบนั้นไปเลย โดยบัญญัติเป็น syntax และ semantics ของภาษา โปรแกรมเมอร์ภาษา Rust ซึ่งเรียกว่า Rustacean (มันคือ Crustacean หรือสัตว์พวกกุ้งกั้งปูที่ไม่เขียน C? :P) ต้องใช้โหมดความคิดอีกโหมดหนึ่งในการเขียนโปรแกรมไปเลย

และขอบอกว่าสมัยที่เรียน C นั้น ผมไม่เคยมึนกับ pointer ของ C เท่ากับที่มึน ownership ของ Rust เลย! ตอนที่เขียน C นั้น ownership ต่างๆ เกิดจากวินัยที่เราสร้างขึ้นเองจากความเข้าใจ แต่พอมันกลายเป็นข้อบังคับของภาษา มันจะเหมือนการเดินที่ต้องคอยระวังไม่ให้เหยียบมดหรือกระทั่งทำให้มดตายทางอ้อม ขนาดนั้นเลย

Safe Borrows

บ่อเกิดของความมึนคือ Rust มีการสับรางการยืมไม่ให้เกิด race condition โดยอนุญาตให้มีการยืมแบบอ่านอย่างเดียว (immutable borrow) ได้พร้อมกันหลายทาง แต่ทันทีที่มีการยืมแบบเขียนได้ (mutable borrow) เกิดขึ้น การยืมอื่นทุกทางจะหมดอายุทันที ไม่ว่าจะยืมแบบเขียนได้หรือไม่ได้ และถ้ามีการใช้การยืมที่หมดอายุแล้ว ก็จะเกิด error ทันที โดยเป็น error ที่ตรวจพบตั้งแต่ตอนคอมไพล์!

การตรวจสอบนี้ ช่วยป้องกันบั๊กอย่างเช่นมีการลบ element ในลูปที่ iterate ใน vector เพราะการลบ element ต้องยืม vector แบบ mutable ทำให้ iterator ที่กำลังยืม vector อยู่เหมือนกันไม่สามารถ iterate ต่อไปได้ หากต้องการลบ element ในลูปจริงๆ ก็คงต้องเลี่ยงไปใช้ indexing แทน ซึ่งปลอดภัยกว่า เป็นต้น

ฟังดูเหมือนไม่มีอะไร แต่เอาเข้าจริงเวลาอ่านโค้ดมันซับซ้อนว่ามีการยืมซ่อนอยู่ที่ไหน ใครหมดอายุตอนไหน เวลาทำ quiz จะมึนมาก แต่เวลาคอมไพล์จริง error message ช่วยได้เยอะมาก

Lifetime

ใน C/C++ มีบั๊กประเภท dangling pointer หรือ use after free ที่เกิดจากการ dereference pointer ชี้ไปยัง object ที่ถูกทำลายไปแล้ว Rust ป้องกันปัญหานี้ด้วยการกำหนด lifetime ของ object ทำให้การใช้ reference ที่นอกจากไม่อนุญาตให้เป็น null แล้ว ยังไม่อนุญาตให้ reference ไปยัง object เกินอายุขัยของ object ด้วย ถ้าตรวจพบก็จะมี compiler error เช่นกัน!

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

ยังมีเรื่องอื่นที่ขัดกับความรู้สึกของคนที่มาจากภาษา C/C++

The Default Immutability

ในขณะที่ C/C++ และภาษาอื่นจำนวนมากใช้ ตัวแปร เพื่อการคำนวณ โดยปกติตัวแปรจึงสามารถเปลี่ยนค่าได้ ยกเว้นเมื่อต้องการห้ามเปลี่ยนจึงใส่ qualifier const กำกับ แล้วคอมไพเลอร์จะช่วยโวยวายให้ถ้ามีความพยายามจะเปลี่ยนค่าที่กำหนดเป็น const ไว้ แต่ Rust จะให้ตัวแปรเป็น const หรือ immutable โดยปริยาย เมื่อต้องการเปลี่ยนค่าจึงใส่ keyword mut (mutable) กำกับ นัยว่าเป็นการเอื้อต่อ functional programming และ concurrency กระมัง? แต่ในแง่หนึ่งก็เป็นการเน้นให้เห็นชัดเจนว่าค่าไหนมีโอกาสถูกเปลี่ยน เช่นตอนที่ให้ฟังก์ชัน ยืม object ไป อาจมีการเปลี่ยนค่าใน object นั้นกลับมา ซึ่งโค้ดที่เขียนด้วย C/C++ จำนวนมากที่ไม่ค่อยเคร่งครัดการใช้ const ก็จะเห็นเรื่องนี้ได้ไม่ชัด แต่กับ Rust ถ้าไม่เคร่งครัดคุณก็หมดสิทธิ์เปลี่ยนค่า ซึ่งก็อาจจะดี แต่คุณจะเผลอสับสนกับโหมดความคิดอยู่บ่อยๆ

The Default Move Semantics

ใน C++11 มีสิ่งที่เรียกว่า move semantics ซึ่งใช้ย้ายข้อมูลจาก object หนึ่งไปยังอีก object หนึ่งที่เป็น class เดียวกันพร้อมกับล้างค่าใน object ต้นทาง ทำให้เกิดการย้าย ownership โดยไม่มีสำเนาเกิดขึ้น ซึ่งนอกจากจะช่วยจัดการ ownership แล้ว ยังช่วยลดการ clone และ destruct โดยไม่จำเป็นอีกด้วย เพราะ move constructor สามารถย้ายเฉพาะ pointer ที่ชี้ data ได้เลย ไม่ต้องสำเนาตัว data ใน object ใหม่ และทำลาย data ใน object เก่า โปรแกรม C ที่ออกแบบดีๆ ก็สามารถ move data ในลักษณะนี้ได้เช่นกัน แต่เรื่องนี้ก็ยังไม่ใช่ default ของทั้ง C และ C++ ต้องมีการทำอะไรบางอย่างเพิ่มเติมเพื่อจะใช้ เช่น ใน C++ ก็ใช้ std::move()

แต่ Rust ใช้ move semantics โดยปริยาย ทั้งใน assignment และ function call ยกเว้นว่าต้องการทำสำเนาจึง clone เอา หรือแค่ให้ยืมผ่าน reference เอา ซึ่งก็อาจทำให้โค้ดโดยรวมมีประสิทธิภาพขึ้น แต่คุณก็ต้องระวังผลของการเปลี่ยนแปลง ownership ที่เกิดขึ้นด้วย

นอกจากนี้ ก็มีหลายสิ่งที่เป็นประโยชน์ใน Rust

Enum as Tagged Union

เวลาใช้ union ใน C/C++ เรามักใช้ร่วมกับ field ที่ระบุว่าเราใช้ข้อมูลชนิดไหนใน union นั้น ซึ่งเคยเห็นบางตำราเรียกว่า tagged union

Rust เอาสิ่งนี้มารวมเข้ากับ enum โดยกำหนดให้ enum สามารถมีข้อมูลประกอบได้ ซึ่งหาก implement ด้วย C/C++ ก็คือการใส่ union ประกอบกับ enum เป็น tagged union นั่นเอง ทำให้สร้าง tagged union ใน Rust ได้สะดวกสบาย ไม่ต้องเขียนโค้ดในการอ่าน/เขียนเอง

Option, Result

Rust ลงทุนสร้าง enum แบบมีข้อมูลประกอบ ก็นำมาใช้ประโยชน์กับค่า return ของฟังก์ชันที่สามารถ return ทั้งสถานะ success/failure และผลลัพธ์ที่ได้ในคราวเดียว

Option คือ enum ที่มีสองค่า คือ None กับ Some โดยค่า Some สามารถมีข้อมูลประกอบได้ ใช้กับฟังก์ชันที่ทำอะไรสักอย่างให้ได้ผลลัพธ์โดยอาจล้มเหลวได้ เช่น การค้นหาข้อมูล ในภาษา C/C++ เราอาจให้ฟังก์ชัน return สถานะ success/failure และถ้า success ก็เขียนข้อมูลลงใน parameter ที่ call by reference มา เช่น

bool Table::FindData(const char* key, Record& result) const;

หรืออีกแบบคือ return pointer ไปยัง object ที่สร้างขึ้นใหม่ โดยถ้าล้มเหลวก็ return NULL

Record* Table::FindData(const char* key) const;

แต่ Rust ไม่มี null pointer/reference แต่จะ return เป็น Option มาเลย

impl Table {
    pub fn find_data(&self, key: &str) -> Option<Record> {
        ...
    }
}

โดยผู้เรียกจะตรวจสอบค่า enum ที่ return มาก่อนดึงค่าออกมาใช้ก็ได้ หรือจะใช้ method ของ Option ในการแกะห่อเอาค่ามาใช้ก็ได้ ซึ่งเป็นวิธีที่ถือว่าดูดี

Result ก็คล้ายกัน คือเป็น enum ที่มีสองค่า คือ Ok กับ Err โดยค่า Ok สามารถมีข้อมูลผลลัพธ์ประกอบได้ และค่า Err ก็มีข้อมูล error ประกอบได้ (ต่างกับ None ของ Option ที่ไม่มีข้อมูลประกอบใดๆ)

Trait-Bound Generic

trait คือสิ่งที่คล้ายกับ interface ในภาษา OOP ต่างๆ แต่ไม่เหมือนกันเสียทีเดียว

generic ก็เหมือนกับ template ของ C++ ซึ่งหากใครเคยใช้ STL ของ C++ จะรู้ว่า type ที่จะมาเป็น parameter ของ template หนึ่งๆ มักมีข้อแม้บางอย่าง แล้วแต่ template ที่ใช้ เช่น ต้องเปรียบเทียบค่ากันได้ หรือต้องรองรับการ move ฯลฯ แต่ไม่เคยมีการประกาศชัดเจน จะรู้ได้ก็ตอนที่คอมไพเลอร์ฟ้องว่าขาดคุณสมบัติ แล้วค่อยไปหาทางอุดทีละเรื่องเอา (ผมว่านี่แหละคือสิ่งที่คล้ายกับ duck typing ใน C++)

แต่ generic ของ Rust สามารถประกาศคุณสมบัติของ type ที่เป็น parameter ได้ว่าต้องรองรับ trait ใดบ้าง ทำให้รู้แต่ต้นว่าต้องเตรียมคุณสมบัติอะไรไว้

Rust ยังมีรายละเอียดให้เรียนรู้อีกเยอะ นี่ผมเพิ่งมาได้ครึ่งทาง ก็ยังต้องเรียนรู้กันต่อไป

โดย Thep (noreply@blogger.com) ณ 31 December 2022 01:35 +0000

15 December 2022

bact

เอดูเขต

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

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

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

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

การใช้คำใหญ่ๆ การอ้างถึงมโนทัศน์หรืออะไรที่ค่อนไปทางนามธรรมเพื่อจัดกลุ่มสิ่งของหรือปรากฏการณ์ การพิมพ์/พูดไทยคำนึง วงเล็บภาษาต่างประเทศอีกคำนึง (ถ้าจะให้ดีควรเป็นคำฝรั่งเศส [français] หรือเยอรมัน [Deutsch] เก๋ไปอีกขั้นคือรัสเซีย [русский язык]) การอ้างถึงสำนักคิดของนักปรัชญาคนนั้นคนนี้เพื่อจัดประเภทสายธารความคิดหรือแนวของข้อถกเถียง

ซึ่งหลายครั้งก็มั่วซั่วไม่น้อย กลับไปอ่านที่ตัวเองเขียนส่งเป็นการบ้านก็จะแบบ กูเขียนอะไรไปวะ

แต่ “คนรุ่นผม” ซึ่งไม่ได้หมายถึงคนอายุไล่ๆ กัน หากหมายถึงคนที่มีโอกาสก่อรูปความคิดขึ้นมาในช่วงใกล้ๆ กัน มีความโชคดีอยู่หลายอย่างในช่วงเวลานั้นคือ:

หนึ่ง ชีวิตมันช้ากว่าตอนนี้ หมายถึงจังหวะการโต้ตอบที่ช้ากว่าเรียลไทม์อยู่มาก

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

สาม เราอยู่ในแวดวงไม่ได้ไกลจากกันมาก มีทุนทางวัฒนธรรมบางอย่างร่วมกัน ซึ่งช่วยลดความเข้าใจผิดในการสื่อสารลงไปได้บ้าง

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

ลองตัดประโยคนึงมาจากข้อเขียนของใครสักคนในรุ่นนั้น มาอ่านในตอนนี้ มันต้องมีขำกันบ้างล่ะ

แต่มันสนุก มันคือการมีโอกาสได้ลอง

ความโชคดีอีกอย่างคือ ครูที่เราได้เจอในตอนนั้นก็ช่วยขีดเส้นใต้ ขีดฆ่า วงปากกาแดง เขียนเครื่องหมายคำถามตัวใหญ่ กระทั่งแก้ภาษาให้เป็นย่อหน้าๆ แดงเถือกไปทั้งหน้ากระดาษ

หรือบางทีก็พูดหน้าเรียบๆ กับเราว่า “เขียนแบบนี้ไม่ต้องเรียนหนังสือก็ได้”

(แล้วพวกเราก็ไปนั่งปรับทุกข์กันที่ร้านข้าวต้ม)

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

แต่นั่นอาจจะเป็นความหรูหราหรืออภิสิทธิ์สำหรับคนรุ่นเราเท่านั้น?

เพราะถ้าย้อนกลับไปพิจารณา “ความโชคดี” หรือลักษณะ 4 ประการของบริบทที่กล่าวมาข้างต้น ที่คนรุ่นผมผ่านกันมาในช่วงที่พวกเราก่อรูปความคิด มันตรงข้ามกับทวิตเตอร์ (ที่ถูกมองว่าเป็นฐานที่มั่นของชาว woke) หมดเลย

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

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

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

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

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


โพสต์ครั้งแรกบนเฟซบุ๊ก

โดย bact ณ 15 December 2022 03:59 +0000

2 December 2022

Vee

How to use Cider with Shadow-cljs

  1. Run shadow-cljs npx shadow-cljs -d nrepl/nrepl:1.0.0 -d cider/cider-nrepl:0.28.7 cljs-repl app
  2. Open http://localhost:9630 in Firefox
  3. Wait until Shadow-cljs finish building
  4. Open http://localhost:8280 (the app) in Firefox
  5. In Emacs, run cider-connect-cljs with host = localhost, port = 8777, type = shadow, build = app

Suggestions are welcomed.

โดย Vee Satayamas ณ 2 December 2022 02:56 +0000

2 November 2022

bact

รวมกรณีหมายเลขโทรศัพท์หลุดรั่ว 2561-2564 (บางส่วน)

กรณีการหลุดรั่วของข้อมูลส่วนบุคคลระหว่างปี 2561-2564 (บางส่วน) เน้นเฉพาะกรณีที่เกี่ยวข้องกับหมายเลขโทรศัพท์มือถือ

1. กรณีข้อมูลลูกค้าร้านค้าออนไลน์ ไอทรูมาร์ท (iTrueMart) จำนวนประมาณ 46,000 แฟ้ม รั่วไหลเมื่อต้นเดือนมีนาคม 2561 — โดยเป็นแฟ้มภาพสำเนาบัตรประจำตัวประชาชน ใบขับขี่ และหนังสือเดินทาง ที่ใช้ในการลงทะเบียนซิมกับผู้ให้บริการโทรศัพท์มือถือ ทรูมูฟเอช (TrueMove H) ตามประกาศ กสทช. ข้อมูลดังกล่าวเป็นข้อมูลที่ถูกเก็บระหว่างปี 2559-2561 โดยทางบริษัทได้ปิดการเข้าถึงสำเร็จในวันที่ 12 เมษายน 2561

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

3. กรณีฐานข้อมูลการสั่งซื้อสินค้าออนไลน์จำนวน 13 ล้านรายการของซูเปอร์มาร์เก็ตออนไลน์ เรดมาร์ต (RedMart) บริษัทลูกของลาซาดา (Lazada) หลุดรั่ว ในเดือนพฤศจิกายน 2563 — ซึ่งในฐานข้อมูลดังกล่าวมีข้อมูลของบริษัทในไทยคือ อีททิโก (Eatigo) และวงใน (Wongnai) อยู่ด้วย โดยข้อมูลของอีททีโกที่หลุดรั่ว ประกอบด้วยอีเมล ค่าแฮชรหัสผ่าน ชื่อ หมายเลขโทรศัพท์ เพศ และโทเค็นเฟซบุ๊ก (กุญแจชั่วคราวสำหรับเข้าระบบ) ส่วนข้อมูลของวงใน ประกอบด้วยอีเมล ค่าแฮชรหัสผ่าน หมายเลขไอพีที่ใช้ลงทะเบียน หมายเลขระบุผู้ใช้เฟซบุ๊กและทวิตเตอร์ วันเกิด หมายเลขโทรศัพท์ และรหัสไปรษณีย์

4. กรณีข้อมูลขนาด 200 กิกะไบต์ ของสายการบินบางกอกแอร์เวย์สถูกเผยแพร่สู่สาธารณะ ในเดือนสิงหาคม 2564 — ซึ่งข้อมูลดังกล่าวประกอบด้วยข้อมูลลูกค้าของสายการบินด้วย โดยมีชื่อ ข้อมูลหนังสือเดินทาง สัญชาติ เพศสภาพ ที่อยู่ หมายเลขโทรศัพท์ อีเมล ข้อมูลการเดินทาง รวมถึงข้อมูลบัตรเครดิตบางส่วน และในเดือนเดียวกันมีการพบข้อมูลหนังสือเดินทางของผู้เคยเดินทางเข้าประเทศไทยกว่า 106 ล้านรายการถูกเผยแพร่ในอินเทอร์เน็ต

5. กรณีข้อมูลรายชื่อผู้ป่วยประมาณ 17,890 ราย หลุดรั่วจากโรงพยาบาลเพชรบูรณ์ ในเดือนกันยายน 2564 — ซึ่งมีชื่อผู้ป่วย ที่อยู่ หมายเลขโทรศัพท์ วันเดือนปีเกิด เลขประจำตัวประชาชน และข้อมูลเวชระเบียน

6. กรณีข้อมูลส่วนบุคคลของลูกค้าของร้านซีพีเฟรชมาร์ท หลุดรั่วในเดือนกันยายน 2564 — และทางซีพีเฟรชมาร์ทระบุว่าข้อมูลที่หลุดมี ชื่อ นามสกุล หมายเลขโทรศัพท์ อีเมล ที่อยู่ ซึ่งผู้ไม่หวังดีอาจใช้เพื่อการหลอกลวงทางโทรศัพท์และการหลอกลวงทางอีเมลได้

ข้อมูลบางส่วนจากสเปรดชีต Data leak ข้อมูลรั่ว (เน้นตั้งแต่ 27 พ.ค. 2563)

โดย bact ณ 2 November 2022 06:55 +0000

25 October 2022

bact

[AI Incident Report] Wallet app failed to recognize faces, bars Thai citizens from claiming government cash handout

Submitted to the AI Incident Database on 25 October 2022 (my first time!). Based on a report by Thai PBS on 4 October 2019, with information from additional sources. Appeared in the database on 26 October 2022. I will keep the extended report here for archival purpose. For a concise report and citation, please link to Incident 375 in the AI Incident Database.

Lots of Thais cannot register for the government cash handout scheme as the app managing government wallet failed to recognize their faces during the authentication process. People entitled to the handout have to wait for a very long queue at their local ATMs instead to get authenticated.

The handout is limited to 10 million recipients in the first round and a recipient has to register to claim it. Thailand has almost 70 million population. The registration involved the authentication process of photo taking the citizen identification card and the face of the card holder. If not successful, the citizen can do it at a supported ATM machine. There are about 3,000 ATMs that support the process nationwide.

On social media, internet users share the problems they had, screenshots of messages from the app, and also photo taking techniques that may pleased the facial recognition. The tips include applying face powder makeup, put the hair up, take off eyeglasses, take the photo during daytime in the sunlight, look straight, make the face and comb the hair to match one in the ID card, and avoid having shadow on the ID card.

Elder people are one of the groups that suffer the most from the facial recognition issue, as their current faces can be more different from ones in their ID cards. Thai citizen ID card law said people who are 70 years old or more are no longer need to renew their cards, which normally requires to be renewed every eight years. Because of this, the face on the ID card and the actual face can be very different.

Background

The cash handout program, called “Chim, Shop, Chai” (ชิมช้อปใช้ roughly translated as “eat, buy, spend”) is aimed to promote domestic tourism.

The same government wallet, “G Wallet”, will be used later for many other rounds of cash handout and co-pay programs to come during the COVID-19 pandemic, such as “Khon La Khrueng” (คนละครึ่ง, “each pay half”) – where the same facial recognition issue still occurs.

Total number of individuals registered for these financial support programs is around 26.5 million. The wallet itself is inside “Paotang” (เป๋าตัง) super app, developed and managed by a state enterprise Krungthai Bank. Paotang has 34 million active users in June 2022.

Sources

Suggested citation format:

Suriyawongkul, Arthit. (2019-09-29) Incident Number 375. in Lam, K. (ed.) Artificial Intelligence Incident Database. Responsible AI Collaborative. Retrieved on October 26, 2022 from incidentdatabase.ai/cite/375.

โดย bact ณ 25 October 2022 09:27 +0000

24 October 2022

bact

รวมเอกสารข้อเสนอการกำกับกิจการ AI ของไทย (ต.ค. 2565)

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

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

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

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

โพสต์นี้พยายามจะรวบรวมเอกสารเหล่านั้นเท่าที่หาได้จนถึง 24 ต.ค. 2565 โดยจะพยายามปรับปรุงเป็นระยะเมื่อพบเอกสารใหม่ (จะคั่นหน้าไว้ในเบราว์เซอร์ก็ได้ครับ) และถ้าใครเจอเอกสารใหม่ หรือพบเว็บไซต์ของหน่วยงานที่รับผิดชอบที่ได้รวบรวมเอกสารที่เกี่ยวข้องไว้เป็นหมวดหมู่เข้าถึงสะดวกอยู่แล้ว ก็วานแจ้งด้วยครับ -/\-

เอกสารที่เสร็จและเผยแพร่ให้นำไปใช้ได้แล้ว

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

โครงการต่อเนื่องจากมติคณะรัฐมนตรี 2 ก.พ. 2564

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

เนื้อหาในเว็บไซต์ ethics.tu-onde.com (.com ก็มา) ของโครงการ มีหลายส่วน บางส่วนเหมือนจะยังไม่สมบูรณ์ หนึ่งในส่วนที่เข้าไปใช้ได้แล้วคือ:

สารภาพว่าเห็นตอนแรกแล้วตาแตกมาก ต้องตั้งสติ แต่คนที่เคยทำงานพวกตรวจมาตรฐานมาคงคุ้นเคยเป็นอย่างดี (ผมนี่ไม่คุ้น) เอกสารการใช้งานของเครื่องมือดังกล่าว น่าจะพอทำให้เห็นภาพได้บ้าง ซึ่งการใช้งานจะอิงกับวิธีการปฏิบัติตาม (implementation) และกรอบแนวปฏิบัติ (core model) ตามบทที่ 3 ของเอกสารแนวปฏิบัติจริยธรรมปัญญาประดิษฐ์ — ตรงนี้ก็น่าจะต้องการเสียงจากนักพัฒนาระบบปัญญาประดิษฐ์และนักวิชาชีพด้านการตรวจสอบ ว่าใช้งานได้จริงแค่ไหน

กรอบการทำงานด้านปัญญาประดิษฐ์สำหรับภาครัฐ

หากเจาะจงเฉพาะการใช้งานในภาครัฐหรือโดยภาครัฐ (เพื่อให้บริการแก่สาธารณะ) ยังมีเอกสารอีก 2 ชิ้น จากสำนักงานพัฒนารัฐบาลดิจิทัล (องค์การมหาชน) (สพร./DGA) คือ:

ทั้งนี้หนึ่งปีหลังจากการเผยแพร่เอกสารชิ้นแรก เมื่อ 27 พ.ย. 2563 สพร.ได้เปิดตัวศูนย์ปัญญาประดิษฐ์ภาครัฐ (AI Government Center: AIGC) ถ้าใครจะติดตามแนวปฏิบัติสำหรับภาครัฐ ก็ติดตามได้จากศูนย์นี้ (หน้าเฟซบุ๊ก) และทางสพร.

เอกสารที่อยู่ในระหว่างการพัฒนา

นอกจากนี้ ยังมีโครงการศึกษาเพื่อจัดทำข้อเสนอเกี่ยวกับการส่งเสริมและกำกับกิจการที่เกี่ยวข้องกับปัญญาประดิษฐ์ ซึ่งอยู่ระหว่างดำเนินการอยู่ เท่าที่ทราบอีก 3 โครงการ:

รายละเอียดมีดังนี้

โครงการจัดทำระเบียบ มาตรการ และมาตรฐานที่เกี่ยวข้องกับปัญญาประดิษฐ์

โครงการจัดทำระเบียบ มาตรการ และมาตรฐานที่เกี่ยวข้องกับปัญญาประดิษฐ์ มีสำนักงานคณะกรรมการดิจิทัลเพื่อเศรษฐกิจและสังคมแห่งชาติ (สดช./ONDE) เป็นเจ้าของโครงการ ดำเนินการโดย ศูนย์บริการวิชาการแห่งจุฬาลงกรณ์มหาวิทยาลัย (หลักๆ เป็นนักวิชาการจากคณะนิติศาสตร์ จุฬาฯ มีจากที่อื่นบ้าง)

โดยหลังจากประชุมระดมความคิดเห็น (focus group) ต่อ “(ร่าง) ข้อเสนอเชิงนโยบายที่เกี่ยวข้องกับปัญญาประดิษฐ์ของประเทศไทย” และ “(ร่าง) ระเบียบ มาตรการ และมาตรฐานที่เกี่ยวข้องกับปัญญาประดิษฐ์ของประเทศไทย” ไปเมื่อ 1-5 และ 8-9 ส.ค. 2565 (7 รอบ) ก็ได้ปรับปรุงเอกสาร และเผยแพร่เอกสารชุดใหม่ออกมา 2 ฉบับเพื่อรับฟังความคิดเห็นสาธารณะ (public hearing) ในชื่อ:

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

ทั้งนี้ได้จัดประชุมรับฟังความคิดเห็นสาธารณะไปเมื่อ 18-19 ต.ค. 2565 (2 รอบ 18 ต.ค. ภาคเอกชน และ 19 ต.ค. ภาครัฐ) (ข่าวบนเว็บไซต์ สดช.)

ดูเอกสารนำเสนอ และส่งความคิดเห็นทางออนไลน์ ได้ถึงวันที่ 2 พ.ย. 2565

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

โครงการเพื่อการศึกษา หารือ และรับฟังความคิดเห็นเกี่ยวกับการกำกับดูแลและส่งเสริมเทคโนโลยีสมัยใหม่

โครงการเพื่อการศึกษา หารือ และรับฟังความคิดเห็นเกี่ยวกับการกำกับดูแลและส่งเสริมเทคโนโลยีสมัยใหม่ มีสำนักงานพัฒนาธุรกรรมทางอิเล็กทรอนิกส์ (สพธอ./ETDA) เป็นเจ้าของโครงการ ดำเนินการโดย บริษัท เบเคอร์ แอนด์ แม็คเคนซี่ จำกัด และคณะนิติศาสตร์ มหาวิทยาลัยธรรมศาสตร์

หลังรับฟังความคิดเห็นทางออนไลน์ (จนถึง 29 ส.ค. 2565) ยังอยู่ระหว่างการจัดทำข้อเสนอแนะเกี่ยวกับแนวทางการกำกับดูแลและส่งเสริมเทคโนโลยีดิจิทัลสมัยใหม่ 4 เรื่อง โดยหนึ่งในนั้นมีเรื่องการกำกับกิจการปัญญาประดิษฐ์สำหรับธุรกิจอิเล็กทรอนิกส์และบริการดิจิทัลด้วย

(ยังไม่พบการเผยแพร่เอกสาร)

โครงการศึกษาแนวทางการจัดตั้งศูนย์ประเมินการทำงานของโปรแกรมที่มีการใช้เทคโนโลยีปัญญาประดิษฐ์

โครงการศึกษาแนวทางการจัดตั้งศูนย์ประเมินการทำงานของโปรแกรมที่มีการใช้เทคโนโลยีปัญญาประดิษฐ์ มี สพธอ. เป็นเจ้าของโครงการ ดำเนินการโดย ศูนย์เทคโนโลยีอิเล็กทรอนิกส์และคอมพิวเตอร์แห่งชาติ (เนคเทค) สวทช.

มีเอกสาร 2 ฉบับ

และได้จัดรับฟังความคิดเห็นไปเมื่อ 5 ต.ค. 2565 (บันทึกวิดีโอ)

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

ในเอกสารนำเสนออ้างอิงถึง หลักการและแนวทางจริยธรรมปัญญาประดิษฐ์ของประเทศไทย (กระทรวงดิจิทัลฯ), แนวปฏิบัติจริยธรรมด้านปัญญาประดิษฐ์ของสำนักงานพัฒนาวิทยาศาสตร์และเทคโนโลยีแห่งชาติ (สวทช.), มาตรฐาน ISO/IEC 25040:2011 ข้อกำหนดด้านระบบและคุณภาพซอฟต์แวร์และการประเมิน, ISO/IEC DIS 25059:2022 ข้อกำหนดด้านระบบและคุณภาพซอฟต์แวร์และการประเมิน สำหรับระบบเทคโนโลยีปัญญาประดิษฐ์, ISO/IEC 29119-11:2020 แนวทางการทดสอบระบบที่มีการใช้เทคโนโลยีปัญญาประดิษฐ์, ISTQB: CT-AI หลักสูตรใบรับรองมาตรฐานสากล การทดสอบระบบเทคโนโลยีปัญญาประดิษฐ์, และโครงการ A.I. Verify ของรัฐบาลสิงคโปร์ ที่เพิ่งเปิดตัวเมื่อเดือน พ.ค. ปีนี้

โครงการ A.I. Verify ของสิงคโปร์น่าสนใจในแง่ใช้แนวคิด MVP (minimum viable product) หรือ “ผลิตภัณฑ์ตั้งต้น” พยายามตรวจในเรื่องที่มีเครื่องมือให้ตรวจได้สะดวกในปัจจุบันก่อน ซึ่งรวมถึงการเน้นตรวจว่าระบบปัญญาประดิษฐ์นั้นมีประสิทธิภาพตามที่ผู้ขอรับการตรวจได้กล่าวอ้าง (claim) หรือไม่ (คล้ายกับแนวคิดของกฎหมายฉลากสินค้า-การคุ้มครองผู้บริโภค) ทำให้ตัวผู้รับตรวจไม่จำเป็นต้องเป็นผู้กำหนดเกณฑ์คุณภาพเอง (เช่น จะต้องแม่นยำอย่างน้อยกี่ %) ปล่อยให้เป็นเรื่องที่ผู้ขอรับตรวจจะต้องทำให้ได้ตามที่ตัวเองกล่าวอ้างเอาไว้ — ซึ่งมีข้อดีในแง่ลดความจำเป็นที่ผู้รับตรวจจะต้องมีความรู้ความเชี่ยวชาญในตลาดและอุตสาหกรรมนั้นๆ เพียงพอที่จะกำหนดเกณฑ์คุณภาพที่เหมาะสมได้

โครงการอื่นๆ

โดย bact ณ 24 October 2022 09:24 +0000

12 August 2022

Vee

Where static type checking and type annotation are shining

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

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

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

8 August 2022

Vee

Parallelly processing a stream in Common Lisp

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

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

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

Overview diagram of stream-par-procs

So the code looks like the below:

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

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

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

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

28 July 2022

MrChoke

Fix: Svelte Devtools on Chrome/Brave

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

Git Clone

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

Edit

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

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

Install

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

Chrome Extension installation

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

Chrome Extension

Use

หลังจากนั้นก็สามารถใช้งานได้ตามปกติแล้ว

Svelte Devtools

ต้นฉบับ:

https://github.com/sveltejs/svelte-devtools/pull/78

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

17 July 2022

Vee

Error handling in fetching data via HTTP

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

def fetch(uri):
    requests.get(uri)

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

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

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

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

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

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

15 July 2022

Vee

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

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

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

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

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

The average line length is 68.8 bytes.

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

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

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

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

13 July 2022

Vee

Reading lines from a Zstd archive in Common Lisp

Reading lines from a Zstd archive in Common Lisp

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

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

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

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

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

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

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

2 June 2022

Vee

Installing Emacs 28.1 on a shared aging OS

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

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

#!/bin/bash

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

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

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

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

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

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

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

echo
echo
echo "Emacs 28.1 must be ready!"
echo

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

21 May 2022

Vee

The worst thing I did in Rust

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

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

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

12 May 2022

Vee

Trying TruffleRuby

I ran wbrk2.rb:

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

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

in cRuby, jRUby, TruffleRuby by this script:

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

Dockerfile.cruby:

FROM ruby:3.1
RUN gem install thailang4r

Dockerfile.jruby:

FROM jruby
RUN gem install thailang4r

Dockerfile.truffle:

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

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

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

11 May 2022

Vee

Fortune telling

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

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

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

10 May 2022

Vee

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

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

for (i=0; i<10; i++) {
  console.log(i)
}

Above JS code can be converted to:

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

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

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

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

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

bact

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

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

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

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

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

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

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

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

การกำหนดขีดจำกัดความเร็วตามลักษณะการใช้สอยพื้นที่

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

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

เว็บไซต์รัฐบาลท้องถิ่นมีแผนที่ข้อเสนอการจำกัดความเร็วให้ดาวน์โหลด

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

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

เทคโนโลยีที่ช่วยให้ขับรถปลอดภัยขึ้น

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

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

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

เทคโนโลยีช่วยควบคุมความเร็วอัจฉริยะทำงานอย่างไร

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

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

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

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

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

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

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

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

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

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

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

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

ระบบความปลอดภัยเชิงรุกของรถยนต์

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

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

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

9 May 2022

Vee

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

In static1.lisp:

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

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

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

Then I ran this command:

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

SBCL showed this warning:

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

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

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

25 April 2022

Vee

Less visual-centric programming languages

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

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

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

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

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

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

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

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

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

20 April 2022

Vee

Clojure's lazy sequence versus transducer benchmark

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

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

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

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

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

F2 is the transducer-based version of F1.

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

I evaluated them using Criterium.

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

Here is the result.

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

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

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

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

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

12 April 2022

Kitt

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

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

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

23 March 2022

Vee

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

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

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

22 March 2022

Vee

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

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

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

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

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

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

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

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

My Lispy code in Python

I translated my function in Lisp to Python.

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

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

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

So I made this experiment.

import time

n = 100

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

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

print((t2-t1)/(t4-t3))

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

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

14 March 2022

Vee

How I install RocksDB

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

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

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

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

27 February 2022

MrChoke

macOS Monterey DNS

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

ตรวจสอบอุปกรณ์

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

networksetup -listallnetworkservices

ผลที่ได้จะประมาณนี้

Ethernet
USB 10/100/1000 LAN
Thunderbolt Bridge
Wi-Fi

เรียกดูค่าปัจจุบัน

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

networksetup -getdnsservers Wi-Fi

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

networksetup -getdnsservers Ethernet

ผลที่ได้จะประมาณนี้

networksetup -getdnsservers Ethernet
10.222.21.14
10.222.21.15
10.18.12.222
10.18.12.223

การตั้งค่า

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

networksetup -setdnsservers Ethernet 10.26.21.14 10.26.21.15

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

networksetup -getdnsservers Ethernet
10.222.21.14
10.222.21.15

networksetup -setdnsservers Ethernet 10.222.21.14 10.222.21.15 10.18.12.222 10.18.12.223

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

networksetup -setdnsservers Ethernet Emty

ลองเอาไปประยุกต์ใช้กันดูครับ

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

25 February 2022

MrChoke

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

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


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

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

ต้นฉบับที่ผมนำมาใช้

stackoverflow

ตัวอย่าง model.py

from typing import Optional

from fastapi import Form
from pydantic import BaseModel


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

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

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

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

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

ตัวอย่าง Router

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

app = FastAPI()

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

  return True

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

Swagger

Swagger input with default

ลองเอาไปประยุกต์ใช้กันดูครับ

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