วันสงกรานต์ปี 2567 เป็นปี จ.ศ. (2567 – 1181) = 1386 วันเถลิงศก ตรงกับ(1386 * 0.25875)+ floor(1386 / 100 + 0.38)– floor(1386 / 4 + 0.5)– floor(1386 / 400 + 0.595)– 5.53375= 358.6275 + 14 – 347 – 4 – 5.53375= 16.09375= วันที่ 16 เมษายน 2567 เวลา 02:15:00 วันสงกรานต์ ตรงกับ16.09375 – 2.165 = 13.92875= วันที่ 13 เมษายน 2567 … Continue reading วันสงกรานต์ปี 2567
11 April 2024
Kitt
วันสงกรานต์ปี 2567
20 March 2024
Vee
Dynamic vs Static typing (again)
Static type analysis excels in error checking, autocomplete, and native code generation. Conversely, dynamic typing shines in interactive environments like Jupyter Notebook, where type inference can struggle due to the unpredictable nature of future code. Languages can leverage both.
Lisp-style list in Rust
It seems managing singly-linked lists with shared sub-lists using RAII can be tricky. While I experimented with Rust's reference counting, it led to stack overflows for lists above 300,000 elements. Memory arenas appear more suitable, but effectively handling arena lifetimes in nested lists with varying types is challenging.
You can check my library at the repository at Codeberg. Perhaps, you want to fork it, or even take over the project.
14 February 2024
Vee
Yet another RocksDB browser
I'm tired of checking contents of my database in RocksDB by coding. So I created a GUI tool called DuHin, for browsing RocksDB Databases using cl-gtk4 and cl-rocksdb.
I can open a database my a simple command.
# for example
./duhin.sh /tmp/my-database.rocksdb
Features
- Move forward/backward
- Jump to i-th record
- Search by key
You can download DuHin at codeberg.org/veer66/duhin.
5 January 2024
Vee
Different languages have different core data structure
Sometimes I don't aware that I use different data structures.
- [1,2,3] in Rust is an array.
- [1,2,3] in Elixir is a singly-link list.
- [1,2,3] in PHP is an ordered hash map.
- [1,2,3] in Clojure is a hash array mapped trie.
14 November 2023
Vee
[Programming Notes] Pseudo-REPL-driven programming in Rust with Evcxr and Emacs
I want to develop my Rust library more interactively with Lisp-style read-eval-print loop (REPL). So I wrote this post to show how I used Excvr - yet another REPL for Rust, and inf-excvr - yet another Excvr wrapper for Emacs, step-by-step.
- I cloned the project the wordcut-engine project, and open README.md in Emacs.
- I ran inf-evcxr, which it created a new inf-evcxr window.
I add the project as dependency by running inf-evcxr-add-dep-current-project.
I marked a region over use commands, and ran inf-evcxr-eval-region.
I marked a region over initialization code block, and ran inf-evcxr-eval-region.
I moved the cursor to let txt = "หมากินไก่"; and I ran inf-evcxr-eval-line.
I moved the cursor to wordcut.put_delimiters(txt, "|"). Then I ran inf-evcxr-eval-line. The REPL printed the result of word tokenizer (wordcut::put_delimiters).
- I did the same on wordcut.build_path(txt, &txt.chars().collect::>()). The REPL printed a graph that used for word tokenization.
- I could let the REPL show prettier result by evaluating dbg!(wordcut.build_path(txt, &txt.chars().collect::>()));.
- I redefined txt to กากินกิน, and ran the word tokenizer again.
This allows me to experiment with and manually test functions by varying the input variables to different strings and checking the results in simple strings or even internal structures, without recompiling the program or reloading the data. This is helpful for manual testing and debugging.
4 October 2023
Vee
Testing subroutines in Rust, Common Lisp, JavaScript, and other programming languages, using either automatic or manual methods
Compile-time type checking is a great way to catch errors early, but it is not a guarantee of correctness. Even simple subroutines can be incorrect. For example, is_old_enough is a subroutine for checking whether a person is at least 21 years old.
fn is_old_enough(a_person: &Person) -> bool {
a_person.age > 20
}
Here is an example of how the is_old_enough subroutine could be incorrectly implemented:
fn is_old_enough(a_person: &Person) -> bool {
a_person.age >= 20
}
Adding an equals sign (=) to the code changes the behavior of the subroutine, even though the code is still type-safe. The similar bug is found in Servo, but the type was integer.
Testing the entire program manually or programmatically is essential, but it can be difficult to catch all errors, especially those hidden in the details. Testing subroutines is important because it allows testers to focus on small, well-defined units of code. This makes it easier to identify and fix errors. Here are three prerequisites for testing subroutines:
- Defining subroutines
- An input environment for testing
- Result validation
Defining subroutines
Some programming languages encourage programmers to define subroutines more than others. This is because some languages have features that make it easier and more natural to define and use subroutines.
Defining subroutines in BASIC programming language
In the 1970s, to define a subroutine in BASIC, you would assign it a line number and use the RETURN statement.
1000 PRINT "SUBROUTINE"
1100 RETURN
We can call a subroutine in a program using the GOSUB command, followed by the line number of the subroutine.
GOSUB 1000
Defining a subroutine in BASIC is as simple as using the GOTO statement, but with the added convenience of being able to return to the calling code.
Defining subroutines in Common Lisp
In Common Lisp, a function is a subroutine that always returns a value when it is called with a specific set of inputs. This Common Lisp code processes a-person, which is a member of the list people one-by-one using the DOLIST command. If a-person is at least 21 years old, the program will print it out.
(dolist (a-person people)
(when (> (person-age a-person) 20)
(print a-person)))
We can create a new function from the part (> (person-age a-person) 20) by using the DEFUN command, with a function name - old-enough?, and an input variable, which is a-person.
(defun old-enough? (a-person)
(> (person-age a-person) 20))
Then, in the main program, we substitute the code part (> (person-age a-person) 20) with a function call (old-enough? a-person).
(dolist (a-person people)
(when (old-enough? a-person)
(print a-person)))
Common Lisp encourages programmers to create subroutines by making it easy to copy and paste parts of code, which are also known as expressions, or forms.
Defining subroutines in Java
Here is a Java version of a print-a-person-if-at-least-21 program. Java uses the for loop instead of the Common Lisp DOLIST command.
for (var a_person: people) {
if (a_person.age > 20) {
System.out.println(a_person);
}
}
We can create a function from the expression (a_person.age > 20) using this syntax.
private static boolean isOldEnough(Person a_person) {
return a_person.age > 20;
}
In addition to Common Lisp, Java requires type annotations for functions. The function is_old_enough was annotated as a function that takes a Person as input and returns a boolean. Moreover, In Java, programmers must decide whether a function belongs to a class or an object by using the static keyword. In Java, programmers also use the private and public keywords to control access to functions. Java functions always require a return statement, similar to BASIC subroutines, except for functions that do not return any value.
Java encourages programmers to create subroutines, but with more annotations, it is not as encouraging as Common Lisp.
Defining subroutines in Crystal: Static typing doesn't mean more annotations.
My explanation of Java, a statically typed programming language, may have led to the misconception that statically typed languages require more annotations. Crystal - another statically typed programming language is the counter example. Here is a Crystal version of a print-a-person-if-at-least-21 program. Instead of the DOLIST command, Crystal uses the EACH command.
people.each {|a_person| puts person if a_person.age > 20}
To create a function, we can copy the expression a_person.age > 20, and paste it into DEF ... END block, without any type annotations or any RETURN statement.
def old_enough?(a_person)
a_person.age > 20
end
We can substitute the expression a_person.age > 20 with a function call old_enough?(a_person).
people.each {|a_person| puts a_person if old_enough?(a_person)}
So, the ease of defining a function in Crystal is on par with Common Lisp.
Defining subroutines in Rust
Here is a Rust version of a print-a-person-if-at-least-21 program, which look almost identical to Java version.
for a_person in people {
if a_person.age > 20 {
println!("{:?}", a_person)
}
}
Surprisingly, the Rust version of is_old_enough looks similar to the Crystal version, but with type annotations. Type annotation in Rust is more complicated than in Java because Rust has references and programmers need to think about the lifetime of variables. Type annotations and lifetimes could make it more difficult for programmers to write subroutines in Rust.
fn is_old_enough(a_person: &Person) -> bool {
a_person.age > 20
}
Type annotations make definitions precise and easier to read, but they require more work, can be distracting, and do not help encouraging a programming to create a subroutine.
Preparing an environment for calling a subroutine
Some programming language features and software design can make preparing the environment for calling a subroutine difficult. Moreover, maintaining the code used for preparing the environment could require unnecessary work if the code is too coupled with data structures, which are usually changed.
Preparing an environment in Common Lisp and JavaScript
The variable a-person is an environment for calling the function old-enough?. We create a data structure from a struct in Common Lisp by calling a function make-*. In this example, we call a function make-person.
(make-person :name "A" :age 30)
Moreover, we can make a data structure from a struct using #S syntax, which is in the same form as it is printed.
#S(PERSON :NAME "A" :AGE 30)
This #S syntax is very useful when we have existing data structures, because it allows us to use printed data structures to prepare the environment later. This is especially helpful when we want to build long or complex data structures, such as a list of 1,000 people.
In JavaScript, we can prepare data structures in a similar way to Common Lisp, but without specifying the types of the data.
{"name": "A", "age": 30}
Like Common Lisp, JavaScript can dump data structures to JSON format using the JSON.stringify() command.
It is easy to prepare a data structure as an environment for calling Common Lisp and JavaScript functions, especially because we can reuse the format that a data structure was dumped from memory.
Preparing an environment in Java and Rust
In Java, we create a data structure by instantiating a class using the new keyword. The arguments, which are the input values for creating an object, are sent in a strict order without any keywords, such as :name and :age seen in the Common Lisp example. This style should be fine when the number of arguments does not exceed three.
var a_person = new Person("A", 30);
We can call the function is_old_enough, which in Java is a class method.
is_old_enough(a_person)
Alternatively, we can define the function is_old_enough as an object method, and then call it with this syntax.
a.is_old_enough()
Still, the method for preparing the person data structure remains the same. So class methods are not necessarily easier to test than object methods.
In Rust, we create a data structure with the similar syntax to Rust. However, Rust has a more step, which is converting &str to String using the function to_string.
Person {name: "A".to_string(), age: 30}
Although both Java and Rust cannot use printed format for creating data structure directly. We can use JSON library to dump and load
data.
So, preparing an environment in Java and Rust is not as convenient as Common Lisp or JavaScript, since we cannot copy printed data structure, and directly use it in the program without a help of an additional library.
The difficulty in preparing the environment is caused by the software design.
Sometimes preparing the environment is difficult because of the software design. To create a Person object in this example, we must pass in the person's name and a service that can return their age.
Person(String name, Service service) {
this.name = name;
age = service.getAge(name) ;
}
// ...
var a_person = new Person("A", service);
So, we cannot prepare a person data structure with a specific age without creating a service, which is remotely related to test the function is_old_enough.
Using basic data structure
Instead of defining a class or a struct, we can use a list for representing personal data.
'(:name "A" :age 30)
Using a list removes unnecessary restrictions on creating a person, even though our design is primarily to get a person from a service. Here is an example of calling a function to obtain a person data structure from a service.
(get-person "A" service)
In JavaScript, we can create an object, which is idiomatic for JavaScript, instead of a list.
{"name": "A", "age": 30}
In Java, we use HashMap although creating HashMap in Java does not look as concise as list in Common Lisp.
However, using a list or other basic data structure also has a downside, which will be explained later.
Modifying the data structure affects the code for preparing an environment.
Given, we added reward to the struct person.
struct Person {
name: String,
age: u32,
reward: u32,
}
This code for creating a Person data structure would be broken.
Person {name: "A".to_string(), age: 10}
We have to create a data structure by passing a reward value.
Person {name: "A".to_string(), age: 10, reward: 800}
It may seem trivial, but I've never enjoyed fixing repetitive code in tests.
Use default values for values we don't care about.
In Rust, we can create a data structure with default values, and then we assigned only a value that we care.
let mut a_person = Person::default();
a_person.age = 30
Before we use the function default, we put #[derive(Default)] before the struct definition.
#[derive(Default)]
struct Person {
name: String,
age: u32,
}
In Common Lisp, we can put default values in the struct definition. Then we can call a function make-person by passing a value that we care about.
(defstruct person
(name "")
(age 0))
(make-person :age 30)
Using basic data structure
We can use a list instead of a specific struct, and in a list, we can put only :age with other values. Still, we can run the test.
(setq a-person '(:age 30))
(old-enough? a-person)
Using basic data structures has some downsides. Lists and hash tables do not perform as well as structs, because accessing struct member is very fast. The position of each struct member in memory is calculated arithmetically. Moreover, when everything is a list, a compiler cannot help checking types since their types are the same. A programmer may have no idea how the data structure looks like by looking a function definition. Still, we alleviate solve these problems by using a runtime schema such as JSON Schema.
Preparing an environment for async function and database connection is not convenient
Some subroutines need a database connection to establish. Some subroutines need an async event loop to run before testing, for example, async functions in Rust. Preparing a fake database and connecting the everything before testing is inconvenient, especially for testing a function like is_old_enough?, which can be fixed by improving the software design. Testing async functions become easier by using a tool, such as Tokio::test.
Testing a subroutine in the production environment
Testing in the production environment is not preferable, but sometimes it is necessary, especially when we cannot reproduce the problem somewhere else.
Common Lisp can run Read-Eval-Print Loop (REPL) along with the production, so we can always test subroutines. Many languages come with an REPL, but we have to make sure that libraries and frameworks play well the REPL. In Common Lisp community, libraries and frameworks are usually REPL-friendly.
Result validation
After running a subroutine, we usually want to validate the result either manually or programatically.
Programatical validation
Most data comparison functions check if the data is the same object in memory, which is not what we want in this case. The code below does not return true even if the content of the data structures is the same because the EQ function does not compare the content.
(eq
(get-eldest_person people)
(make-person :name "C" :age 120))
When testing, we usually want to compare data structures for content equality. In Common Lisp, we can use the EQUALP function to do this, instead of the EQ function.
(equalp
(get-eldest_person people)
(make-person :name "C" :age 120))
In Rust, we solve this issue by insert #[derive(PartialEq)] before the struct definition.
#[derive(PartialEq)]
struct Person {
pub name: String,
pub age: u32,
}
Manual validation
Manually validating a complex data structure can be difficult, so there are many tools that can display data in a structured view. In Common Lisp, we can use Emacs inspectors like slime-inspect and sly-inspect, or we can use Clouseau, which is part of McCLIM. For other programming languages, I typically convert data structures to JSON and view them in Firefox.
1 October 2023
Vee
ข้อได้เปรียบในการใช้ Rust ทำ web backend
มีผู้ถามเข้ามาในกลุ่มตามหัวข้อ ผมก็เลยเอาคำตอบมาเรียบเรียงไว้อีกที
นอกจากประสิทธิภาพแล้ว Rust ได้เปรียบเรื่อง compile time type checking; Lemmy เทียบกับ GotoSocial ที่ใช้ Go มี 2 จุดคือ
- GotoSocial ทำ cache เลี่ยงเรียก database บ่อย ซึ่ง cache แชร์กันระหว่าง goroutine ไม่ผ่าน channel ต้อง lock กันเอง; แต่ถ้าเป็น Rust มี compiler คอยช่วยตรวจว่า lock จุดที่ควร lock หรือไม่
- Bun ORM ใน GotoSocial เวลา query ก็ส่ง string เข้าไป ไว้ parse เอาตอนรัน ซึ่งทำให้ compiler ช่วยตรวจไม่ได้; Diesel ใน Lemmy ใช้ macro ทำให้ check type ได้
โปรแกรมและโปรแกรมเชิงวัตถุ
(เคยโพสต์ที่ qua.name เมื่อ 20 กันยา ค.ศ. 2023)-
โปรแกรม
คำสั่งคำสั่งเดียวหรือหลายๆ คำสั่งที่เรียงกันเป็นลำดับ
ตัวอย่างโปรแกรม
Dim i As Integer
For i = 1 To 3
Print "สวัสดี"
Next
ผลการรัน:
สวัสดี
สวัสดี
สวัสดี
ตัวแปร
ตัวแปรคือที่ที่มีชื่อเอาไว้เก็บข้อมูล
ตัวอย่างโปรแกรม
Dim i As Integer
i = 20
Print i
ผลการรัน
20
โปรแกรมย่อย
ส่วนของโปรแกรมที่ถูกเรียกใช้ได้ในโปรแกรมนั้น เรียกอีกอย่างว่า “Subroutine”
ตัวอย่างโปรแกรม
Sub GreetThreeTimes()
Dim i As Integer
For i = 1 To 3
Print "สวัสดี"
Next
End
GreetThreeTimes
GreetThreeTimes
ผลการรัน
สวัสดี
สวัสดี
สวัสดี
สวัสดี
สวัสดี
สวัสดี
ฟังก์ชัน
คือโปรแกรมย่อยที่ทำงานเสร็จแล้วให้ค่าบางอย่างเสมอ
ตัวอย่างโปรแกรม Book.class
Function Add10(n As Integer) As Integer
Dim m As Integer
m = n + 10
Return m
End
Print Add10(20)
ผลการรัน
30
อ็อบเจกต์และคลาส
- อ็อบเจกคือสิ่งประกอบไปด้วยโปรแกรมย่อยและตัวแปร
- คลาสคือสิ่งที่กำหนดอ็อบเจกต์
ตัวอย่างโปรแกรม
Id As String
Title As String
Author As String
Sub PrintObject()
Print Id, Title, Author
End
ข้อกำหนดในการเข้าถึง
- Public ใช้ได้จากทุกส่วนของโปรแกรม
- Private ใช้ได้เฉพาะในอ็อบเจกต์เดียวกัน
ตัวอย่างโปรแกรม Book.class
Public Id As String
Public Title As String
Public Author As String
Public Sub PrintObject()
Print Id, Title, Author
End
การสร้างอ็อบเจกต์
สร้างอ็อบเจกต์ตามที่คลาสกำหนดโดยคำสั่ง "New"
ตัวอย่างแบบยาว
Dim Book1 As Book
Book1 = New Book
ตัวอย่างแบบสั้น
Dim Book1 As New Book
การใช้งานตัวแปรของอ็อบเจกต์
ชื่อออปเจกต์.ชื่อตัวแปร
ตัวอย่างโปรแกรม
Dim Book1 As New Book
Book1.Title = "โฉมหน้าศักดินาไทย"
Print Book1.Title
การใช้งานโปรแกรมย่อยของอ็อบเจกต์
โปรแกรมย่อยของอ็อบเจกต์หรือที่เรียกอีกอย่างว่า “เมท็อด”
ชื่ออ็อบเจกต์.ชื่อโปรแกรมย่อย
ตัวอย่างโปรแกรม
Dim Book1 As New Book
Book1.Title = "โฉมหน้าศักดินาไทย"
Book1.Author = "จิตร"
Book1.Id = "TH001"
Book1.PrintObject
ผลการรัน
โฉมหน้าศักดินาไทย จิตร TH001
สังกัดอ็อบเจกต์หรือคลาส
- ปกติแล้วสังกัดอ็อบเจกต์
- ให้สังกัดคลาสให้ใส่คำว่า “static”
- โปรแกรมย่อยสังกัดคลาสใช้ได้โดยไม่ต้องสร้างอ็อบเจกต์
ตัวอย่างคลาส Book.class
Public Id As String
Public Title As String
Public Author As String
Public Sub PrintObject()
Print Id, Title, Author
End
Static Public Sub Info()
Print "หนังสือเป็นสื่อ"
End
ตัวอย่างการเรียกใช้งานโปรแกรมย่อยที่สังกัดคลาส
Book.Info
สัญญาอนุญาตสำหรับซอฟต์แวร์เสรี
(โพสต์เมื่อต้นเดือนกันยา 2023)
คำออกตัว
ผมไม่ได้เรียนเกี่ยวกับกฎหมายและไม่ได้ทำงานเกี่ยวกับกฎหมาย
ซอฟต์แวร์เสรีและโอเพนซอร์ส
ซอฟต์แวร์เสรีเป็นซอฟต์แวร์ที่เคารพสิทธิของผู้ใช้และชุมชน ได้แก่ สิทธิในการรัน ทำซ้ำ เผยแพร่ ศึกษา แก้ไข และปรับปรุงโปรแกรม อย่างไรก็ตามในเวลาต่อมามีขบวนการโอเพนซอร์สเกิดขึ้นเพื่อสร้างแนวร่วมกับบริษัทกระแสหลัก โดยแก้จำกัด 2 ประการของซอฟต์แวร์เสรี ได้แก่ (1) ซอฟต์แวร์เสรีในภาษาอังกฤษเรียกว่า “free software” โดยคำว่า free หมายถึงเสรีก็ได้หรือไม่เสียค่าใช้จ่ายก็ได้และมักจะถูกเข้าใจผิดว่าหมายถึงไม่เสียค่าใช้จ่าย และ (2) ศัพท์ว่า "free software" ทำให้ชาวบริษัทจำนวนมากกังวลใจ
อย่างไรก็ตามภาษาไทยไม่มีปัญหาความคลุมเครือของคำว่า “เสรี” แบบคำว่า “free” ในภาษาอังกฤษ และยังไม่มีข้อมูลว่าชาวบริษัทในประเทศไทยรู้สึกดีกับศัพท์ว่า “โอเพนซอร์ส” กว่าศัพท์ว่า “เสรี” นอกจากนั้นคำว่า “เสรี” เช่น ตลาดเสรี การค้าเสรี เสรีประชาธิปไตย ก็เป็นคำศัพท์ที่มีความหมายไปในทางทุนนิยมที่ชื่นชอบของบริษัทที่แสวงหาผลกำไรแบบปกติ
หลักการ
โปรแกรมใดจะเป็นซอฟต์แวร์เสรีก็ต่อเมื่อมีเสรีภาพ 4 อย่างข้อต่อไปนี้
- เสรีภาพที่จะใช้งานโปรแกรมเพื่อจุดประสงค์อะไรก็ตาม
- เสรีภาพในการศึกษาและแก้ไขดัดแปลงโปรแกรม ข้อนี้ทำให้ต้องเข้าถึง source code ได้
- เสรีภาพในการเผยแพร่ซอฟต์แวร์ที่ยังไม่ได้แก้ไข
- เสรีภาพในการเผยแพร่แวร์ที่แก้ไขแล้วออกไป
กฎหมายไทย
จากพระราชบัญญัติลิขสิทธิ์ มาตรา 27 “การกระทำอย่างใดอย่างหนึ่งแก่งานอันมีลิขสิทธิ์ตามพระราชบัญญัตินี้ โดยไม่ได้รับอนุญาตตามมาตรา ๑๕ (๕) ให้ถือว่าเป็นการละเมิดลิขสิทธิ์ ถ้าได้กระทำดังต่อไปนี้ (๑) ทำซ้ำหรือดัดแปลง (๒) เผยแพร่ต่อสาธารณชน” เห็นได้ว่ามาตรานี้จำกัดเสรีภาพในดัดแปลงแก้และเผยแพร่งานซึ่งขัดกับหลักซอฟต์แวร์เสรี นอกจากนั้นในมาตรา 4 ระบุไว้ว่าโปรแกรมคอมพิวเตอร์เป็นวรรณกรรมและในมาตรา 6 ระบุว่าวรรณกรรมเป็นงานอันมีลิขสิทธิ์ทำให้โปรแกรมคอมพิวเตอร์เข้าข่ายนี้ด้วย
นอกจากนั้นผู้สร้างสรรค์ผลงานเป็นผู้มีลิขสิทธิ์ ตามมาตรา 8 ดังนั้นผู้มีลิขสิทธิ์จึงเกิดขึ้นโดยที่ไม่ต้องจดหรือลงทะเบียนกับรัฐเลย และเจ้าของลิขสิทธิ์ไม่มีอำนาจในการยกเลิกลิขสิทธิ์ในมาตรา 15 ซอฟต์แวร์เสรีจึงต้องเป็นโปรแกรมคอมพิวเตอร์ที่มีลิขสิทธิ์ ไม่ใช่โปรแกรมไม่มีลิขสิทธิ์
อย่างไรก็ตามเพื่อให้ทุกคนมีเสรีภาพตามหลักการซอฟต์แวร์เสรี เจ้าของลิขสิทธิ์อนุญาตให้ทุกคนมีสิทธิ์ในการทำซ้ำหรือดัดแปลง เผยแพร่ต่อสาธารณชน ให้เช่าหรือให้สำเนาได้เป็นการทั่วไปโดยระบุไว้ในสัญญาอนุญาต
ส่วนประกอบของสัญญาอนุญาตพื้นฐาน
สัญญาอนุญาตก็คืออนุญาตอย่างน้อยให้ทำตามเสรีภาพที่ควรมีของซอฟต์แวรเสรีคืออนุญาตให้ใช้งาน ทำซ้ำ เผยแพร่ เช่น บางส่วนของสัญญาอนุญาตแบบ Expat/MIT «Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:» จะเห็นว่ามีคำว่า use copy modify publish และ distribute ด้วย
ความเป็นสาธารณะ
คนธรรมดามักจะช่วยเหลือส่วนรวมก่อนช่วยเหลือเอกชนหรือบุคคลที่ไม่ได้เป็นอะไรกัน เช่น บริจาคหนังสือให้สมุดสาธารณะแทนที่จะบริจาคให้ห้องสมุดส่วนตัวของคหบดีท่านหนึ่งที่ไม่รู้จักกัน การลงทุนลงแรงกับซอฟต์แวร์เสรีก็คล้ายกัน ในกรณีคนปกติก็อยากช่วยพัฒนาซอฟต์แวร์ที่มีความเป็นสาธารณะมากกว่าซอฟต์แวร์ของคนที่ไม่ได้เกี่ยวข้องกัน
คนทั่วไปยิ่งไม่อยากทำงานให้คู่แข่งในทางต่าง ๆ ได้เปรียบด้วย ซึ่งสำหรับซอฟต์แวร์เสรีการที่คู่แข่งแก้ไขปรับปรุงแล้วเอาขายหรือใช้ในกิจการแต่ไม่เผยแพร่รหัสต้นฉบับ (source code) สู่สาธารณะได้สามารถมองว่าเป็นการเอาเปรียบคนอื่นจนเกินไปได้ สัญญาอนุญาตบางแบบโดย เช่น GNU General Public License (GPL) สร้างมาเพื่อจัดการประเด็นนี้โดยมีข้อความส่วนหนึ่งว่า “You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions:” ซึ่งมีเงื่อนไขให้เผยแพร่รหัสต้นฉบับที่แก้ไขแล้ว ตัวอย่างซอฟร์แวร์ที่ใช้สัญญาอนุญาต GPL เช่น Linux Wordpress VLC Blender ซึ่งเป็นซอฟต์แวร์ที่ได้รับความนิยม และโดยเฉพาะ Linux มีเอกชนหลายเจ้าช่วยกันพัฒนา
อย่างไรก็ตามในยุคที่ใช้งานผ่านเครือข่ายก็มีการอ้างว่าไม่ได้เผยแพร่โปรแกรม จึงไม่ต้องแจกจ่ายรหัสต้นฉบับจึงมีสัญญาอนุญาต GNU AFFERO GENERAL PUBLIC LICENSE (AGPL) ซึ่งมีข้อความส่วนหนึ่งว่า “Notwithstanding any other provision of this License, if you modify the Program, your modified version must prominently offer all users interacting with it remotely through a computer network (if your version supports such interaction) an opportunity to receive the Corresponding Source of your version by providing access to the Corresponding Source from a network server at no charge, through some standard or customary means of facilitating copying of software.” เป็นเงื่อนไขว่าต้องแจกจ่ายรหัสต้นฉบับเมื่อมีนำโปรแกรมที่แก้ไขปรับปรุงไปให้ใช้ผ่านเครือข่ายคอมพิวเตอร์ ตัวอย่างซอฟต์แวร์ที่ใช้สัญญาอนุญาต AGPL เช่น Mastodon Nextcloud OnlyOffice ทั้งหมดเป็นโปรแกรมสำหรับใช้งานผ่านระบบเครือข่าย
สิทธิบัตร
ประเทศไทยยังไม่สิทธิบัตรซอฟต์แวร์ แต่ไม่ใช่ทุกคนอยู่ในประเทศไทยหรือจะอยู่ในประเทศไทยตลอดเวลา จึงต้องคำนึงถึงสิทธิบัตรด้วย เพราะไม่ละเมิดลิขสิทธิ์แต่ละเมิดสิทธิบัตรก็เสียทรัพย์ได้ ดังนั้นสัญญาอนุญาต GPL รุ่นที่ 3 จึงมีข้อความเกี่ยวกับการยุติสิทธิบัตรและค่าสินไหมด้วย «A contributor's “essential patent claims” are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License» เช่น เดียวกับสัญญาอนุญาต Apache รุ่นที่ 2 ก็มีความลักษณะคล้ายกันว่า “Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work”
ส่วนที่ไม่ใช่โปรแกรม
สัญญาอนุญาตที่เป็นที่นิยมใช้กับเอกสาร รูปภาพ วิดีโอ และเพลงโดยเฉพาะคือสัญญาอนุญาตครีเอทีฟคอมมอนส์ ซึ่งเขียนย่อว่า CC โดยมีเงื่อนไขย่อยให้เลือกคือ BY คือให้เครดิตว่าเจ้าของผลงาน SA คือหากมีการแก้ไขปรับปรุงต้องเผยแพร่งานที่แก้ไขในสัญญาอนุญาตแบบเดียวกัน NC คือห้ามใช้เพื่อการค้า ND คือห้ามดัดแปลง ซึ่งจะเป็นได้ว่าเงื่อนไข NC และ ND ขัดกับหลักการซอฟต์แวร์เสรี โครงการที่ใช้สัญญาอนุญาตครีเอทีฟคอมมอนส์ เช่น วิกิพีเดีย
สัญญาอนุญาตครีเอทีฟคอมมอนส์ถึงแม้จะมีบางบางที่เข้ากันไม่ได้กับซอฟต์แวร์เสรี แต่ก็เป็นการให้สิทธิเป็นการทั่วไปกับสาธารณะกล่าวคือทุกคนได้รับสิทธิ ต่างจากข้อตกลงที่ของบริการโซเชียลมีเดียหลายรายที่ผู้ใช้จะใช้งานได้ก็ต่อเมื่อยอมรับข้อตกลงที่ให้สิทธิแพลตฟอร์มนำผลงานไปใช้ ประมวลผล แก้ไขดัดแปลง เผยแพร่ หรือแม้แต่อนุญาตคนอื่นต่อ
การเลือกสัญญาอนุญาต
โดยทั่วไปควรเลือกสัญญาอนุญาตที่มีความเป็นสาธารณะ เช่น GPL หรือ AGPL เพราะคนธรรมดาย่อมอยากช่วยส่วนรวมมากกว่าเอกชน ยกเว้น
- โปรแกรมสั้นมากใช้ APACHE-2.0 เพราะเขียนใหม่เอาก็ได้ง่าย ๆ ใช้ GPL ก็ไม่มีประโยชน์
- ต้องการใช้งานกว้างขวางถึงแม้บุคคลหรือนิติบุคคลที่นำไปใช้จะไม่เผยแพร่ส่วนที่แก้ไขก็ตาม เช่น libogg ที่อยากให้คนใช้ OGG แทน MP3 ให้ใช้สัญญาอนุญาต APACHE-2.0
- ไลบรารี (Library) ที่ไม่ได้รวมเข้ามาเป็นส่วนเดียวกับโปรแกรม (dynamic link) ที่ต้องการให้คนใช้งานกว้างขวาง แต่ไม่ต้องการให้ไลบรารีเองโดนยังคงความเป็นสาธารณะ แต่ไม่มีเงื่อนกับโปรแกรมที่เรียกใช้ไลบรารี
ควรหลีกเลี่ยงสัญญาอนุญาตแบบ Expat หรือสัญญาอนุญาต MIT เพราะอาจจะทำให้ชุมชนที่พัฒนาและใช้งานซอฟต์แวร์ถูกคุกคามโดยใช้สิทธิบัตรได้ อย่างไรก็ตามอาจจะมีความจำเป็นต้องใช้สัญญาอนุญาตแบบนี้กับองค์กรที่มีความต้องพิเศษและพิจารณาแล้วว่าส่งผลดีกับตัวเองและส่วนรวม
อ่านเพิ่ม
- การเลือกสัญญาอนุญาตตามคู่มือของ GNU https://www.gnu.org/licenses/license-recommendations.html
- รายการสัญญาอนุญาตของมูลนิธิซอฟต์แวร์เสรี https://www.gnu.org/licenses/license-list.en.html
- Expat/MIT License https://opensource.org/license/mit/
- Apache License 2.0 https://www.apache.org/licenses/LICENSE-2.0
- GNU General Public License 3.0 https://www.gnu.org/licenses/gpl-3.0.html
- Goodbye, "free software"; hello, "open source" http://www.catb.org/~esr/open-source.html
30 September 2023
Vee
Configuring Emacs, Clojure, and cljKondo in one minute
Given you have Emacs 29.1 and JDK on your GNU/Linux.
Install Clojure
wget https://github.com/clojure/brew-install/releases/latest/download/linux-install.sh -O install_clojure.sh && \
chmod 755 install_clojure.sh && \
su -c ./install_clojure.sh
Install cljKondo
wget https://raw.githubusercontent.com/clj-kondo/clj-kondo/master/script/install-clj-kondo -O install-clj-kondo.sh && \
chmod 755 install-clj-kondo.sh && \
su -c ./install-clj-kondo.sh
Put this in your .emacs file
(setq warning-minimum-level :emergency)
(setq package-archives '(("elpa" . "https://elpa.gnu.org/packages/")
("melpa" . "https://melpa.org/packages/")))
(use-package flycheck
:config
(add-hook 'after-init-hook #'global-flycheck-mode))
(use-package flycheck-clj-kondo
:ensure t)
(use-package clojure-mode
:ensure t
:config
(require 'flycheck-clj-kondo))
(use-package paredit :ensure t
:config
(add-hook 'clojure-mode-hook #'enable-paredit-mode)
(add-hook 'edn-hook #'enable-paredit-mode))
20 August 2023
bact
ลิขสิทธิ์และ generative AI ในร่างกฎหมายปัญญาประดิษฐ์ของสหภาพยุโรป: สถานะปัจจุบัน
เนื่องจากมิตรสหายหลายท่านกำลังคุยกันเรื่องกฎหมายปัญญาประดิษฐ์ ในส่วนที่เกี่ยวกับงานอันมีลิขสิทธิ์และ generative AI (อันเนื่องมาจากความนิยมของแอปแต่งภาพ Loopsie) และพูดถึงพัฒนาการของร่าง AI Act ในสหภาพยุโรป จึงขออนุญาตเล่ากระบวนการที่มาที่ไปโดยรวมของการออกฎหมายในสหภาพยุโรป รวมถึงที่มาของอำนาจในแต่ละสถาบันของสหภาพยุโรป เพื่อจะได้เข้าใจว่าร่างกฎหมายดังกล่าว ซึ่งปัจจุบันมีอยู่ 3 ร่าง* มีที่มาที่ไปอย่างไร (ซึ่งอาจเกี่ยวข้องกับจุดยืนของแต่ละร่าง)
*ร่างริเริ่มข้อเสนอจาก European Commission (21 เม.ย. 2021), ร่าง provisional position จาก Council of the European Union (6 ธ.ค. 2022), และร่าง position ของ European Parliament (14 มิ.ย. 2023)
ผมไม่ใช่นักกฎหมายและไม่ใช่ผู้เชี่ยวชาญสถาบันของสหภาพยุโรป ทั้งหมดเป็นการศึกษาด้วยตัวเองตามความจำเป็นในการใช้งาน ถ้าใครพบว่าตรงไหนคลาดเคลื่อนหรืออธิบายในแบบอื่นน่าจะชัดเจนกว่า ก็ขอให้ช่วยแนะนำด้วยครับ ขอบคุณครับ
(สำหรับคนที่สนเฉพาะเรื่องว่า 3 ร่างนั้นแตกต่างกันอย่างไร ในเรื่อง generative AI และลิขสิทธิ์ ข้ามไปส่วนสุดท้ายได้เลยครับ)
สถาบันในสหภาพยุโรป
สหภาพยุโรปมีสถาบันหลัก (institution) 7 แห่ง
- European Parliament (รัฐสภายุโรป)
- European Council (คณะมนตรียุโรป)
- Council of the European Union (คณะมนตรีแห่งสหภาพยุโรป)
- European Commission (คณะกรรมาธิการยุโรป)
- Court of Justice of the European Union (ศาลยุติธรรมแห่งสหภาพยุโรป)
- European Central Bank (ธนาคารกลางยุโรป)
- European Court of Auditors (ศาลผู้สอบบัญชียุโรป)
ทั้งนี้ถ้ามองจากหลักการแบ่งแยกอำนาจ นิติบัญญัติ-บริหาร-ตุลาการ จะมองได้ดังนี้
- อำนาจนิติบัญญัติ: European Parliament*, Council of the European Union, European Commission
- อำนาจบริหาร: European Commission*, Council of the European Union, European Council
- อำนาจตุลาการ: Court of Justice of the European Union*, European Commission (กึ่งตุลาการ)
(*สถาบันหลักของอำนาจดังกล่าว)
จะเห็นว่า Council of the European Union และ European Commission มีทั้งอำนาจนิติบัญญัติและอำนาจบริหาร แต่ที่มาของอำนาจนั้นจะต่างกัน (ดูต่อข้างล่าง)
สถาบันที่เกี่ยวข้องกับการออกกฎหมายในสหภาพยุโรป
สถาบันที่เกี่ยวข้องกับการตัดสินใจออกกฎหมายและนโยบาย โดยทั่วไป มี 4 สถาบันคือ
- European Parliament
- European Commission
- Council of the European Union
- European Council
ความแตกต่างระหว่าง 2 หน่วยงานแรก และ 2 หน่วยงานหลังก็คือ
2 หน่วยงานแรกจะคำนึงถึงประโยชน์ของ EU ทั้งหมดในฐานะสหภาพ ในขณะที่ 2 หน่วยงานหลังดูแลประโยชน์ของประเทศสมาชิกแต่ละประเทศ
- European Parliament สมาชิกของรัฐสภายุโรปเป็นผู้แทนของพลเมืองในสหภาพ และมาจากการเลือกตั้งโดยตรงของพลเมืองของแต่ละประเทศสมาชิก (ไม่ใช่รัฐบาลประเทศสมาชิกส่งตัวแทนมา) โดยเป็นการเลือกตั้งในฐานะ “พลเมืองสหภาพยุโรป” (ไม่ใช่ในฐานะพลเมืองของประเทศใด ทั้งนี้มีพรรคการเมืองในระดับสหภาพยุโรปด้วย)
- European Commission ประกอบด้วยกรรมการ 27 คน (1 ประเทศสมาชิก 1 คน โดยมาจากกระบวนการทางรัฐสภาของแต่ละประเทศ) กรรมการเหล่านี้โดยหลักการแล้วไม่ใช่ตัวแทนประเทศ และจะทำงานร่วมกันเพื่อประโยชน์ของสหภาพ
- Council of the European Union เป็นตัวแทนของรัฐบาลแต่ละประเทศสมาชิก (ซึ่งมักจะเป็นรัฐมนตรีประจำกระทรวงที่เกี่ยวข้องกับเรื่องที่กำลังพิจารณา จึงเป็นที่มาของอีกชื่อ ว่า Council of Ministers)
- European Council ประกอบด้วยประมุขของแต่ละประเทศสมาชิก
—
European Council*,** ไม่มีอำนาจโดยตรงในการออกกฎหมาย แต่มีบทบาทสำคัญในการวางแนวนโยบาย ซึ่งก็มีอิทธิพลต่อการตัดสินใจของสถาบันอื่นๆ
(* เนื่องจาก European Council ไม่มีอำนาจในการออกกฎหมาย ถ้าเจอคำว่า Council ในบริบทการพิจารณากฎหมาย มีแนวโน้มจะเป็น Council of the European Union มากกว่า
** มีอีกหน่วยงานที่ชื่อคล้ายกันคือ Council of Europe ซึ่งไม่ได้เป็นสถาบันในสหภาพยุโรป)
ดูแผนผังแสดงความสัมพันธ์ระหว่างสถาบันหลักและที่มาของอำนาจ
กระบวนการร่างกฎหมาย
กระบวนการร่างกฎหมายโดยปกติของสหภาพยุโรปจะเรียกว่า Ordinary legislative procedure
ในกระบวนการนี้ European Commission จะเป็นผู้ที่มีอำนาจริเริ่มเสนอร่างกฎหมาย
จากนั้น European Parliament จะพิจารณาร่างดังกล่าว (first reading) และอาจจะอนุมัติร่างโดยให้มีการแก้ไขหรือไม่มีการแก้ไขเลยก็ได้ ทั้งนี้ข้อเสนอในการแก้ไขของ Parliament จะเรียกว่า “position”
จากนั้น Council of the European Union ก็จะพิจารณาว่าจะรับ position ของ Parliament หรือไม่ หรือจะให้มีการแก้ไข
ถ้ามีข้อเสนอแก้ไขก็จะเป็น position อีกฉบับของ Council โดยในกรณีนี้ร่างก็จะกลับไปที่ Parliament เพื่อการพิจารณารอบที่สองของ Parliament (second reading) ซึ่งถ้า Parliament อนุมัติเลยโดยไม่มีการแก้ไข ร่างก็ถูกนำไปใช้ แต่ถ้ามีการแก้ไข ก็จะวนมาถึงการพิจารณารอบที่สองของ Council อีกที
เอกสาร position ของทั้ง Parliament และ Council จะเผยแพร่ต่อสาธารณะเพื่อให้ได้พิจารณาถกเถียง
ถ้าทั้ง Parliament และ Council รับ position ของกันและกันภายในสองรอบ ก็จะจบกระบวนการ นำร่างไปใช้ แต่ถ้าจบไม่ลงในสองรอบ ก็จะนำไปสู่กระบวนการ Conciliation ตกลงประนีประนอมระหว่างรัฐสภาและคณะมนตรี
Conciliation Committee จะประกอบด้วยสมาชิกรัฐสภาและผู้แทนของคณะมนตรีจำนวนเท่าๆ กัน เพื่อพิจารณาเขียนร่างกฎหมายร่วม (joint text) ถ้าสามารถเขียนร่างร่วมกันได้ในขั้นนี้ ข้อเสนอกฎหมายนี้ก็จะตกออกจากระบบไป แต่ถ้าเขียน joint text ร่วมกันได้ ก็จะนำไปสู่การพิจารณาครั้งที่สาม (third reading) ของทั้ง Parliament และ Council ขนานไปพร้อมกัน โดยทั้งสองสถาบันดังกล่าวจะต้องรับ joint text ถ้ามีฝ่ายใดฝ่ายหนึ่งไม่รับ (หรือทั้งสองฝ่ายไม่รับ) ก็จบไม่ได้ไปต่อ ในการพิจารณาครั้งที่สามนี้จะเปลี่ยนแปลงข้อความอะไรไม่ได้แล้ว
ในระหว่างกระบวนการต่างๆ เหล่านี้ อาจมีสิ่งที่การพูดคุยสามฝ่ายอย่างไม่เป็นทางการที่เรียกว่า trilogue* เกิดขึ้น โดยมี Parliament เป็นตัวแทนประชาชนของสหภาพยุโรป, มี Council เป็นตัวแทนของรัฐบาลประเทศสมาชิก, และมี Commission ในฐานะฝ่ายบริหารของสหภาพยุโรป
(*กระบวนการปรึกษาหารือแบบสามฝ่ายนี้ไม่มีกำหนดอยู่ในสนธิสัญญาของสหภาพยุโรป แต่เป็นวิธีการทำงานอย่างไม่เป็นทางการที่เกิดสำหรับกฎหมายบางฉบับที่มีความซับซ้อนหรือมีความครอบคลุมกว้างขวาง ที่จำเป็นต้องเอา position มาพูดคุยกันนอกรอบบ่อยครั้งกว่าปกติเพื่อให้ทำงานได้เร็วขึ้น แต่เนื่องจากกระบวนการปรึกษาหารือนอกรอบนี้อาจทำกันโดยไม่จำเป็นต้องเปิดเผยต่อสาธารณะ [เนื่องจากเป็นกระบวนการแบบไม่เป็นทางการ] จึงถูกวิจารณ์ว่าเสี่ยงต่อความไม่โปร่งใส ต่อมาทางรัฐสภายุโรปจึงได้ออก Parliament Rules of Procedure เพื่อทำให้กระบวนการดังกล่าวนั้นยังโปร่งใสและสะท้อนความเป็นตัวแทนของพลเมืองยุโรป)
จะเห็นว่ากระบวนการพิจารณากฎหมายมาใช้จะต้องทำร่วมกันระหว่าง Parliament และ Council ในฐานะตัวแทนของสหภาพและของประเทศสมาชิก – ถ้าไปดูชื่อกฎหมายต่างๆ อย่างเป็นทางการ จะเห็นว่าใช้ชื่อว่า (Regulation/Directive) “of the European Parliament and of the Council” คือต้องมาคู่กัน
การนำกฎหมายไปใช้บังคับ
เมื่อทุกฝ่ายตกลงกันได้และกฎหมายผ่านการลงคะแนนเสียงและนำกฎหมายมาใช้ ก็จะเป็นเรื่องที่ประเทศสมาชิกจะรับช่วงต่อ ให้กฎหมายใช้บังคับได้ โดยขึ้นกับประเภทของกฎหมาย
ประเภทของกฎหมายมีทั้งหมด 5 ประเภท โดยกระบวนการที่เราคุยกันไปข้างบนนั้นจะเป็นกฎหมายใน 2 ประเภทแรก คือ regulation และ directive เป็นกฎหมายที่มาจากกระบวนการร่วมตามปกติของทั้งตัวแทนสหภาพและตัวแทนประเทศสมาชิก
ประเภท regulation ใช้บังคับได้ทันทีกับทุกประเทศสมาชิก ผู้ออก regulation คือ Parliament กับ Council ร่วมกัน โดยการเสนอของ Commission
ประเภท directive แต่ละประเทศสมาชิกจะต้องไปแก้ไขกฎหมายภายในประเทศตัวเองให้สอดคล้องกับ directive (implementation – ภาษาไทยคือ “อนุวัติการ”) ภายในระยะเวลาที่กำหนด จากนั้นจึงจะใช้บังคับได้
(ตัวอย่างเช่น Directive 95/46/EC ที่ว่าด้วยการคุ้มครองข้อมูลส่วนบุคคล ซึ่งปัจจุบันเลิกใช้แล้ว และไปใช้ GDPR หรือ General Data Protection Regulations แทน เนื่องจากพบว่ากฎหมายที่เกิดจากการอนุวัติการในแต่ละประเทศมีรายละเอียดแตกต่างกัน ทำให้เกิดความยุ่งยากในการส่งข้อมูลข้ามแดน เป็นอุปสรรคต่อตลาดร่วม จึงทำให้เกิดกฎหมายคุ้มครองข้อมูลในแบบ regulation ขึ้น)
อีกประเภทของกฎหมายที่มีผลผูกพัน คือประเภท decision ซึ่งเป็นกฎหมายลำดับรอง ที่ใช้อำนาจการออกตามสนธิสัญญาต่างๆ ของสหภาพยุโรป โดยอาจมีผลผูกพันเฉพาะเรื่องและกับเฉพาะประเทศสมาชิกที่เจาะจงเท่านั้น หรือจะมีผลผูกพันทั่วไปก็ได้ (วิธีการออก decision มีได้หลายแบบ)
ส่วนถ้าเป็นประเภท recommendation ประเทศสมาชิกจะเลือกทำหรือไม่ทำตามก็ได้ recommendation จำนวนมากจะมาจาก Commission แต่สถาบันอื่นอย่าง Parliament, Council และ European Central Bank ก็ออก recommendation ได้เช่นกัน
และสุดท้ายคือประเภท opinion ก็ตามชื่อ คือไม่เชิงเป็นการแนะนำว่าควรทำหรือไม่ควรทำอะไร ด้วยขั้นตอนอย่างไร (แบบ recommendation) แต่เป็นการออกความเห็นเกี่ยวกับเรื่องหนึ่งๆ และเนื่องจากจริงๆ มันไม่ได้มีลักษณะเป็นกฎหมาย สถาบันหลักทั้ง Commission, Council, Parliament ออก opinion ได้เองทั้งหมด (ไม่ต้องมีสถาบันอื่นร่วม) เช่นเดียวกับ Committee of the Regions และ European Economic and Social Committee ก็ออก opinion ได้เช่นกัน
2 ประเภทหลังสุดนี้ (recommendation และ opinion) เป็นการออกกฎหมายฝ่ายเดียว
ส่วน decision มีทั้งการออกฝ่ายเดียวและการออกร่วม
อะไรก็ตามที่จะเป็น legislative act (เช่น กฎหมายที่กำหนดอำนาจใหม่ หน้าที่ใหม่ หรือขอบเขตใหม่) จะต้องเป็นการออกร่วม
ลิขสิทธิ์และ generative AI ในร่าง EU AI Act
สำหรับมาตราที่ว่าด้วยงานอันมีลิขสิทธิ์ และ generative AI ในทั้งสามร่าง ปรากฏดังนี้
ร่างข้อเสนอจาก Commission (21 เม.ย. 2021)
ร่างนี้เป็นร่างข้อเสนอเริ่มแรก หรือ “proposal” จาก European Commission
- ไม่ปรากฏเรื่อง generative AI หรือ foundation model เลย
- เรื่องลิขสิทธิ์หรือทรัพย์สินทางปัญญานั้น ไม่มีส่วนที่พูดโดยตรงถึงการนำเนื้อหาอันมีลิขสิทธิ์มาสอนคอมพิวเตอร์ มีเพียง Article 70 ที่พูดถึงเรื่องความลับทางธุรกิจ ที่ว่ามาตรการว่าด้วยความโปร่งใสจะต้องเคารพสิทธิในทรัพย์สินทางปัญญา
ดูร่างได้ที่ https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX%3A52021PC0206
ร่างจาก Council (6 ธ.ค. 2022)
ร่างนี้เป็น “provisional position” (ชั่วคราว/เฉพาะกาล) จาก Council of the European Union ก่อนที่กระบวนการ first reading จริงๆ ของ Council จะเกิดขึ้น — ร่างนี้มีชื่อเล่นว่า “General approach”
- คำว่า generative AI นั้นปรากฏอยู่ในร่างของ Council 2 แห่ง คือ Recital 6 และ Article 3 (1)
- โดยทั้งสองจุดนั้นว่าด้วยนิยามของระบบปัญญาประดิษฐ์ Recital 6 นั้นบรรยายว่านิยามของปัญญาประดิษฐ์ควรครอบคลุมอะไรบ้าง คำนึงถึงเรื่องอะไรบ้าง ส่วน Article 3 (1) นั้นเป็นส่วนของนิยามจริงๆ ตามกฎหมาย
ดูร่างได้ที่ https://data.consilium.europa.eu/doc/document/ST-15698-2022-INIT/en/pdf
ร่างจาก Parliament (14 มิ.ย. 2023)
ร่างนี้เป็น position จาก European Parliament
- คำว่า generative AI หรือ generative foundation model นั้นปรากฎอยู่ 3 แห่ง คือใน Recital 60g, Recital 60h, และ Article 28b
- โดย Recital 60g บรรยายถึงความจำเป็นในการระบุว่าใครมีส่วนร่วมอะไรในห่วงโซ่ของระบบ AI และการระบุให้ผู้ใช้ทราบว่าเนื้อหาไหนที่สร้างจาก generative AI
- ส่วน Recital 60h เป็นส่วนที่พูดถึงข้อกังวลเรื่องงานอันมีลิขสิทธิ์
- และ Article 28b เป็นการกำหนดหน้าที่ของผู้ให้บริการ foundation model ซึ่งใน Article 28b(4)(c) ระบุว่าผู้ให้บริการปัญญาประดิษฐ์ (AI Provider) ที่ให้บริการโมเดลพื้นฐาน (foundation model) ที่ใช้สร้างเนื้อหาอย่างรูปภาพ ข้อความ เสียง (generative AI) ว่าถ้าเกิดมีการใช้ข้อมูลที่ถูกคุ้มครองตามกฎหมายลิขสิทธิ์มาสอนคอม ก็เป็นหน้าที่ของ AI Provider ที่จะต้องทำเอกสารบันทึกเอาไว้ และต้องแสดงตัวสรุปให้สาธารณะมาอ่านได้ ว่าใช้อะไรไปยังไง
- ทั้งนี้ร่าง Parliament ไม่ได้กำหนดการคุ้มครองทรัพย์สินทางปัญญาแบบใหม่ใดๆ เพียงแต่กำหนดหน้าที่ว่าถ้ามีการใช้งานอันมีลิขสิทธิ์ก็ต้องแจ้งให้สาธารณะทราบ (ซึ่งการบันทึกเป็นเอกสารเหล่านี้ จะเป็นส่วนหนึ่งของกระบวนการ data governance [Article 10] และการทำ technical documentation [Article 11] ซึ่งเป็นหน้าที่ที่ถูกระบุอยู่ในร่างแรกของ Commission อยู่แล้ว ก็เติมเข้าไป)
ดูร่างได้ที่ https://www.europarl.europa.eu/doceo/document/TA-9-2023-0236_EN.html
จากเส้นเวลา ก็จะพบว่าสอดคล้องกับพัฒนาการของเทคโนโลยี คือในตอนปี 2021 ที่ร่างแรกออกมา คำว่า generative AI ยังไม่ได้รับความนิยมมากเท่าปัจจุบัน คือตัวเทคโนโลยีมีแล้ว แต่คนยังนึกไม่ออกชัดๆ ถึงผลกระทบ ร่างที่ออกถัดๆ มาก็ได้สะท้อนความเปลี่ยนแปลงถึงความเข้าใจหรืออย่างน้อยก็การรับรู้ถึงปรากฏการณ์ต่างๆ ในช่วงเวลาระหว่างแต่ละร่าง ซึ่งก็แปลว่ายังมีโอกาสที่ร่างถัดไปหรือร่างสุดท้ายจะมีการเปลี่ยนแปลงอีก
สถานะปัจจุบันของ EU AI Act
ปัจจุบันร่าง AI Act ของสหภาพยุโรปเพิ่งจะผ่านขั้น first reading ของ Parliament ไป
หลังจากนี้จะเป็น first reading ของ Council ซึ่งถ้า Council รับทั้งร่างโดยไม่มีการแก้ไขเลย กฎหมายก็จะออกมาได้ทันที
จะเห็นว่า แม้จะยังไม่ถึงขั้นตอน first reading อย่างเป็นทางการของ Council แต่ทาง Council ก็มี provisional position ออกมาแล้ว ซึ่งก็มีอิทธิพลต่อการทำ position ของ Parliament (ทำให้ position ของ Parliament ลู่เข้าหาจุดยืนของทาง Council มากขึ้น) และเป็นประโยชน์ต่อการพูดคุยสามฝ่ายอย่างไม่เป็นทางการ ซึ่งอาจทำให้กระบวนการต่างๆ ไม่จำเป็นจะต้องไปถึงขั้น Conciliation
กฎหมายปัญญาประดิษฐ์ในประเทศไทย
ประเทศไทยเองก็มีความริเริ่มในการออกแบบกฎระเบียบเกี่ยวกับการใช้ปัญญาประดิษฐ์อยู่เหมือนกัน เช่น
แนวปฏิบัติเกี่ยวกับมาตรฐานการใช้ปัญญาประดิษฐ์ (Thailand Artificial Intelligence Guidelines 1.0 – TAIG 1.0) จากคณะนิติศาสตร์ จุฬา
ดูเพิ่มเติมที่ รวมเอกสารข้อเสนอการกำกับกิจการ AI ของไทย (ปรับปรุงล่าสุด เม.ย. 2566)
อ้างอิง
ดูคำอธิบายเพิ่มเติมเรื่องสถาบันและกระบวนการออกกฎหมายในสภาพยุโรปได้ที่
23 July 2023
MrChoke
GitHub Copilot Custom AutoComplete
การใช้ Github Copilot ช่วยทุ่นแรงในการเขียนโค้ดที่เป็นรูปแบบซ้ำ ๆ เช่นการเตรียมข้อมูล Seed สำหรับ migrate database แค่เราเอาข้อมูลที่แบ่งเป็น fields มาแปะไว้ให้ Copilot เรียนรู้มันก็จะจัดการให้เราแบบรวดเร็ว
Demo จาก YouTube
จากตัวอย่างจำลองการ insert ข้อมูลลง table แบบหลาย ๆ row ซึ่งจะมีรูปแบบที่ซ้ำ ๆ กัน โดยได้นำข้อมูลมาแปะขั่วคราวไว้ใน comment ของ code แล้วก็ให้ Copilot ทำการเติมเต็มให้เท่าที่ลองมาจะทำงานค่อนข้างแม่นมาก และ รวดเร็ว คือแปะปุ๊บมันก็เติมให้เลยแทบไม่มีการให้รอเลย ก็ลองเอาไปเล่นกันดูครับ
12 April 2023
Kitt
สงกรานต์ปี 2566
เป็นปี จ.ศ. (2566 – 1181) = 1385 วันเถลิงศก ตรงกับ(1385 * 0.25875)+ floor(1385 / 100 + 0.38)– floor(1385 / 4 + 0.5)– floor(1385 / 400 + 0.595)– 5.53375= 358.36875 + 14 – 346 – 4 – 5.53375= 16.835= วันที่ 16 เมษายน 2566 เวลา 20:02:24 วันสงกรานต์ ตรงกับ16.835 – 2.165 = 14.67= วันที่ 14 เมษายน 2566 เวลา 16:04:48
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.
29 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 ไว้ แต่ Rust จะให้ตัวแปรเป็น const หรือ immutable โดยปริยาย เมื่อต้องการเปลี่ยนค่าจึงใส่ keyword const
(mutable) กำกับ นัยว่าเป็นการเอื้อต่อ functional programming และ concurrency กระมัง? แต่ในแง่หนึ่งก็เป็นการเน้นให้เห็นชัดเจนว่าค่าไหนมีโอกาสถูกเปลี่ยน เช่นตอนที่ให้ฟังก์ชัน mut
ยืม
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) ณ 29 December 2022 15:36 +0000
15 December 2022
bact
เอดูเขต
มิตรสหายหลายคนมีปัญหากับสิ่งที่พวกเขาเรียกร่วมกันว่า “woke” (ส่วนจะมีความหมายร่วมกันแค่ไหน ผมเองก็ไม่ทราบ) บางคนก็ไม่ได้ถึงขนาดรังเกียจ แต่ถ้าไม่มีธุระด้วย เลี่ยงได้ก็เลี่ยง เพื่อความสงบสบายของชีวิต
นึกไปก็ทำให้รู้สึกว่า สักสิบปีนิดๆ ที่แล้ว สมัยเพิ่งเริ่มเรียนโทที่ไทย ข้าพเจ้าก็น่าจะนับเป็นอะไรที่เรียกว่า “woke” ได้ (ซึ่งไม่รู้ว่าความหมายเดียวกับมิตรสหายแค่ไหน) หรือตอนนี้อาจจะยังเป็นอยู่ด้วยซ้ำ
มิตรสหายร่วมรุ่นทั้งที่เรียนในชั้นเรียนเดียวกันและมิตรสหายโดยทั่วไปจากคณะอื่นหรือที่พอจะเห็นงานกันในพื้นที่ออนไลน์ มีลักษณะร่วมกันในตอนนั้นคือ ความตื่นเต้นกับสำนักคิด ทฤษฎี วิธีวิทยา ข้อค้นพบ เรื่องเล่าจากสังคมที่อยู่ไกลออกไป ฯลฯ ที่ได้เริ่มรู้จักกันในช่วงนั้น
“ความตื่น” นี้ นำไปสู่ความพยายามลองใช้สิ่งเหล่านั้นเป็นเครื่องมือในการคิดกับปรากฏการณ์ร่วมสมัยรอบตัว การแลกเปลี่ยนบนเว็บบอร์ดและการผลิตงานเขียนบนบล็อกส่วนตัว บล็อกกลุ่ม และสื่อออนไลน์อย่างประชาไท มหาวิทยาลัยเที่ยงคืน เป็นไปอย่างคึกคัก ยังไม่นับงานเสวนาต่างๆ ทั้งวงปิด วงเปิด ทั้งบนโต๊ะอาหารและบนเวที
การใช้คำใหญ่ๆ การอ้างถึงมโนทัศน์หรืออะไรที่ค่อนไปทางนามธรรมเพื่อจัดกลุ่มสิ่งของหรือปรากฏการณ์ การพิมพ์/พูดไทยคำนึง วงเล็บภาษาต่างประเทศอีกคำนึง (ถ้าจะให้ดีควรเป็นคำฝรั่งเศส [français] หรือเยอรมัน [Deutsch] เก๋ไปอีกขั้นคือรัสเซีย [русский язык]) การอ้างถึงสำนักคิดของนักปรัชญาคนนั้นคนนี้เพื่อจัดประเภทสายธารความคิดหรือแนวของข้อถกเถียง
ซึ่งหลายครั้งก็มั่วซั่วไม่น้อย กลับไปอ่านที่ตัวเองเขียนส่งเป็นการบ้านก็จะแบบ กูเขียนอะไรไปวะ
แต่ “คนรุ่นผม” ซึ่งไม่ได้หมายถึงคนอายุไล่ๆ กัน หากหมายถึงคนที่มีโอกาสก่อรูปความคิดขึ้นมาในช่วงใกล้ๆ กัน มีความโชคดีอยู่หลายอย่างในช่วงเวลานั้นคือ:
หนึ่ง ชีวิตมันช้ากว่าตอนนี้ หมายถึงจังหวะการโต้ตอบที่ช้ากว่าเรียลไทม์อยู่มาก
สอง แพลตฟอร์มที่เราใช้มันเอื้อให้เราสื่อสารกันด้วยข้อเขียนขนาดค่อนข้างยาว คำอธิบายและการยกตัวอย่างสามารถถูกมัดรวมอยู่ในหีบห่อเดียวกับข้อเสนอได้เลย
สาม เราอยู่ในแวดวงไม่ได้ไกลจากกันมาก มีทุนทางวัฒนธรรมบางอย่างร่วมกัน ซึ่งช่วยลดความเข้าใจผิดในการสื่อสารลงไปได้บ้าง
และ สี่ คือการที่การโยนไอเดียต่างๆ นั้นอยู่ในบริบทของการศึกษา (ทั้งในระบบและนอกระบบ) ซึ่งการถามเพื่อทบทวนคุณค่า หรือการลองเสนออะไรประหลาดๆ หรือดูโง่ๆ มันเป็นเรื่องที่ถูกอนุญาตให้ทำได้ หรือเป็นเรื่องที่ถูกคาดหวังว่าจะต้องทำด้วยซ้ำ
ลองตัดประโยคนึงมาจากข้อเขียนของใครสักคนในรุ่นนั้น มาอ่านในตอนนี้ มันต้องมีขำกันบ้างล่ะ
แต่มันสนุก มันคือการมีโอกาสได้ลอง
ความโชคดีอีกอย่างคือ ครูที่เราได้เจอในตอนนั้นก็ช่วยขีดเส้นใต้ ขีดฆ่า วงปากกาแดง เขียนเครื่องหมายคำถามตัวใหญ่ กระทั่งแก้ภาษาให้เป็นย่อหน้าๆ แดงเถือกไปทั้งหน้ากระดาษ
หรือบางทีก็พูดหน้าเรียบๆ กับเราว่า “เขียนแบบนี้ไม่ต้องเรียนหนังสือก็ได้”
(แล้วพวกเราก็ไปนั่งปรับทุกข์กันที่ร้านข้าวต้ม)
มันเป็นการทดลองที่มีคนช่วยแนะ แล้วเราก็ไปได้เร็วขึ้นจากคำแนะนำหรือข้อวิจารณ์เหล่านั้น ไม่ต้องงมเอง ไม่ต้องเสียเวลาหลงทางนานๆ เราเรียนรู้ระหว่างลงมือปฏิบัติ ปฏิบัติแบบงงๆ นี่แหละ และอีกวิธีที่ครูของเราช่วยให้เราเรียนได้เร็วขึ้น ก็คือการที่เขาทำให้เห็นว่าครูก็ยังเป็นนักเรียนได้ เพื่อให้เราลองเป็นครู
แต่นั่นอาจจะเป็นความหรูหราหรืออภิสิทธิ์สำหรับคนรุ่นเราเท่านั้น?
เพราะถ้าย้อนกลับไปพิจารณา “ความโชคดี” หรือลักษณะ 4 ประการของบริบทที่กล่าวมาข้างต้น ที่คนรุ่นผมผ่านกันมาในช่วงที่พวกเราก่อรูปความคิด มันตรงข้ามกับทวิตเตอร์ (ที่ถูกมองว่าเป็นฐานที่มั่นของชาว woke) หมดเลย
ทวิตเตอร์ และวิธีที่เราใช้และอยู่กับพื้นที่สื่อสารตอนนี้โดยทั่วไป มันเรียลไทม์ มันสั้น มีคนหลากหลายปนเป และไม่ได้ผลัดกันสวมบทนักเรียนและครู (อย่างไรก็ดี สิ่งเหล่านี้ไม่ได้ไม่ดีด้วยตัวเองมันเอง มันก็มีข้อดีของมัน)
สภาพแวดล้อมมันไม่ค่อยสนับสนุนให้ช่วยกันก่อรูปความคิดได้ และเราก็ไม่มีแรงด้วยแหละ (คือมันต้องมีวิธีแน่นอน แต่เราไม่มีแรงหรือไม่มีใจไปทำความเข้าใจและทดลองหาวิธีกับมันแล้วไง)
ถ้าไม่มีคนจ้างหรือไม่ได้รู้สึกว่าสิ่งนี้เป็นพันธกิจของชีวิต จะมีใครสักกี่คนในพวกเราที่จะอยากรับ “ภาร(ะ)กิจ” ในการร่วม “เอดูเขต” ชาว woke (สมมติว่าเราคิดว่ายังเอดูเขตกันไปกันมาไหว) รถคันไหนบนทางด่วนทำอะไรงกเงิ่นก็ด่าก่อนเลยละกัน ลืมว่าคนสมัยนี้อาจไม่เหลือถนนในหมู่บ้านหรือเว็บบอร์ดให้ไปหัดขับแบบสมัยเราแล้ว ทุกคนต้องมาหัดขับบนถนนสาธารณะกันหมด (สาธารณะมากสาธารณะน้อย ก็แล้วแต่จำนวนรีทวีตหรือมิตรรักนักแคป)
แต่นั่นก็อาจจะอธิบายสภาพโดยทั่วไปของคนรุ่นผมก็ได้มั้ง ว่า โอเค กูพอแล้วกับโลก ไม่หวังอะไรมากไปกว่านี้ ไม่มีแรงไปวงปากกาแดงหรือเขียนคอมเมนต์อะไรให้ใครแล้ว นับจากนี้ขอใช้ชีวิตอย่างสงบ ซึ่งก็สมควรได้รับสิทธิ์นั้น พวกมึงก็เหนื่อยกันมาเยอะแล้ว
ส่วนมิตรสหายท่านใดที่อยากจะส่งต่อโอกาสที่เคยได้รับมา ก็ขอให้มีแรง กินน้ำ กินขนม ทำใจร่มๆ
2 December 2022
Vee
How to use Cider with Shadow-cljs
- Run shadow-cljs npx shadow-cljs -d nrepl/nrepl:1.0.0 -d cider/cider-nrepl:0.28.7 cljs-repl app
- Open http://localhost:9630 in Firefox
- Wait until Shadow-cljs finish building
- Open http://localhost:8280 (the app) in Firefox
- In Emacs, run cider-connect-cljs with host = localhost, port = 8777, type = shadow, build = app
Suggestions are welcomed.
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)
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
- https://www.thaipbs.or.th/news/content/284846
- https://mgronline.com/columnist/detail/9650000057542
- https://www.thansettakij.com/economy/512550
- https://pantip.com/topic/41095012
- https://18pee.com/scan/
- https://www.คนละครึ่ง.com/howto/people/verify-fail
- https://krungthai.com/th/content/personal/paotang
—
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.
24 October 2022
bact
รวมเอกสารข้อเสนอการกำกับกิจการ AI ของไทย (ต.ค. 2565 / มี.ค. 2567)
ในช่วงประมาณ 5 ปีที่ผ่านมา การแลกเปลี่ยนความเห็นเพื่อพัฒนากรอบกติกาเกี่ยวกับความปลอดภัยและความรับผิดของระบบที่ใช้ปัญญาประดิษฐ์ มีอย่างต่อเนื่องทั่วโลก ทั้งในสหภาพยุโรป สหรัฐอเมริกา จีน รวมถึงในประเทศไทย โดยหน่วยงานและชุมชนที่เข้าร่วมการแลกเปลี่ยนนี้มีหลากหลาย เช่น ภาครัฐ หน่วยงานคุ้มครองสิทธิ บริษัทผู้ผลิตเทคโนโลยี ชุมชนวิชาการด้านนโยบายสาธารณะ กฎหมาย ปรัชญา คอมพิวเตอร์ ชุมชนวิชาชีพด้านอิเล็กทรอนิกส์ ไปจนถึงหน่วยงานมาตรฐานอุตสาหกรรม
แม้จะพอเห็นพ้องกันในระดับหลักการโดยทั่วไป แต่ในหลายเรื่องการพัฒนาข้อตกลงร่วมกันในระดับปฏิบัติก็ไม่ง่าย ส่วนหนึ่งเพราะยังเห็นปัญหาไม่ชัดเจน เนื่องจากเกี่ยวข้องกับการประยุกต์เทคโนโลยีในเรื่องที่ไม่เคยใช้ในวงกว้างมาก่อน ขณะเดียวกันก็มีหลายเรื่องที่เป็นการประยุกต์เทคโนโลยีกับเรื่องที่สังคมได้ใช้ได้รู้จักมานานแล้ว ทำให้พอจะยืมกรอบวิธีคิดและวิธีปฏิบัติที่มีอยู่แล้วมาใช้ได้ โดยเฉพาะในเรื่องที่เกี่ยวกับความปลอดภัยของมนุษย์-มีมนุษย์เป็นศูนย์กลางของเรื่อง ที่อนุญาตให้เราหยิบวิธีคิดเรื่องสิทธิมนุษยชน สิทธิแรงงาน สิทธิพลเมือง สิทธิผู้บริโภค ฯลฯ มาเป็นคุณค่าแกนกลาง และยืมเอามาตรการที่เคยใช้ได้ดีในการปกป้องคุณค่าดังกล่าว มาเป็นจุดตั้งต้นได้
เช่น มาตรการสำหรับความปลอดภัยทางถนน มาตรการคุ้มครองข้อมูลส่วนบุคคล มาตรการสำหรับความปลอดภัยในการทํางาน ความรับผิดต่อความเสียหายที่เกิดขึ้นจากสินค้าที่ไม่ปลอดภัย หรือกระทั่งกรอบทำงานสิทธิมนุษยชนระหว่างประเทศ — อย่างไรก็ดี มาตรการเหล่านี้ก็อาจมีข้อจำกัดหรือไม่สามารถเสนอทางออกที่ชัดเจนสำหรับความขัดกันแบบใหม่ๆ ระหว่างประโยชน์สาธารณะและสิทธิของปัจเจก
สำหรับประเทศไทย หลายหน่วยงานก็กำลังทำงานเรื่องกรอบกติกาเหล่านี้อยู่ ทั้งหน่วยงานรัฐที่มีอำนาจกำกับกิจการ หน่วยงานรัฐที่ทำเรื่องนโยบายสนับสนุนเทคโนโลยี หน่วยงานรัฐที่ทำงานเรื่องคุ้มครองสิทธิ สถาบันการศึกษา รวมไปถึงภาคธุรกิจ เช่น สมาคมผู้ประกอบการ อย่างไรก็ตาม แม้จะเป็นผู้ติดตามเรื่องนี้อยู่บ้าง หลายคนก็ประสบปัญหาว่ามีหลายเอกสารจากหลายคณะทำงานมาก ตามไม่ทัน ไม่รู้ว่าใครกำลังทำเรื่องอะไรอยู่ หรือเรื่องที่กลุ่มหนึ่งทำอยู่นั้นไปสัมพันธ์อย่างไรกับเรื่องที่อีกกลุ่มกำลังทำอยู่ไหม หรือบางทีก็สับสนจำสลับกัน
โพสต์นี้พยายามจะรวบรวมเอกสารเหล่านั้นเท่าที่หาได้จนถึง 24 ต.ค. 2565 9 เม.ย. 2566 4 มี.ค. 2567 โดยจะพยายามปรับปรุงเป็นระยะเมื่อพบเอกสารใหม่ (จะคั่นหน้าไว้ในเบราว์เซอร์ก็ได้ครับ) และถ้าใครเจอเอกสารใหม่ หรือพบเว็บไซต์ของหน่วยงานที่รับผิดชอบที่ได้รวบรวมเอกสารที่เกี่ยวข้องไว้เป็นหมวดหมู่เข้าถึงสะดวกอยู่แล้ว ก็วานแจ้งด้วยครับ -/\-
เอกสารที่เสร็จและเผยแพร่ให้นำไปใช้ได้แล้ว
เอกสารลักษณะหลักการและแนวปฏิบัติที่เผยแพร่ให้นำไปใช้ได้แล้ว เท่าที่ทราบมี 2 ฉบับ 3 ฉบับ ทั้งหมดไม่ใช่ระเบียบหรือกฎหมายในตัวเอง คือไม่มีบทลงโทษและไม่มีอำนาจบังคับทางกฎหมาย แต่หน่วยงานไหนจะนำไปใช้ภายในขอบเขตอำนาจตัวเองโดยสมัครใจ ก็สามารถทำได้ เอกสารดังกล่าวได้แก่:
- เอกสารแนวปฏิบัติจริยธรรมปัญญาประดิษฐ์ (Thailand AI Ethics Guideline)
- จัดทำโดย สำนักงานคณะกรรมการดิจิทัลเพื่อเศรษฐกิจและสังคมแห่งชาติ โดยมีคณะผู้จัดทำเป็นนักวิชาการจากคณะวิศวกรรมศาสตร์ มหาวิทยาลัยมหิดล มีบริษัท ไมโครซอฟท์ (ประเทศไทย) จำกัด ร่วมเป็นที่ปรึกษา
- กระทรวงดิจิทัลเพื่อเศรษฐกิจและสังคมเสนอคณะรัฐมนตรีเมื่อ 14 ธ.ค. 2563 ให้หน่วยงานราชการใช้เป็นแนวปฏิบัติ และคณะรัฐมนตรีมีมติเห็นชอบเมื่อ 2 ก.พ. 2564 (หนังสือสำนักเลขาธิการคณะรัฐมนตรีที่ นร 0505/ว 74 ลงวันที่ 4 ก.พ. 2564)
- เผยแพร่ฉบับแรกเมื่อ 21 ต.ค. 2562 ในชื่อ หลักการและแนวทางจริยธรรมปัญญาประดิษฐ์ของประเทศไทย ต่อมาได้ปรับปรุงเพิ่มกรณีศึกษาและวิธีการปฏิบัติ
- แนวปฏิบัติจริยธรรมด้านปัญญาประดิษฐ์ของสำนักงานพัฒนาวิทยาศาสตร์และเทคโนโลยีแห่งชาติ (NSTDA AI Ethics Guideline)
- จัดทำโดย สำนักงานพัฒนาวิทยาศาสตร์และเทคโนโลยีแห่งชา (สวทช.)
- สวทช. ประกาศเมื่อ 31 มี.ค. 2565 ให้ใช้กํากับดูแลงานด้านปัญญาประดิษฐ์ “ที่ดําเนินการโดยบุคลากรของสำนักงาน [สวทช.] ผู้ที่ร่วมวิจัยหรือรับการสนับสนุนการวิจัยจากสำนักงาน ภาคเอกชนที่ใช้พื้นที่ภายในอุทยานวิทยาศาสตร์ประเทศไทย และอาคารอื่น ๆ ของสำนักงาน และผู้รับจ้างช่วงที่เกี่ยวข้อง” (ตาม “ประกาศสำนักงานพัฒนาวิทยาศาสตร์และเทคโนโลยีแห่งชาติ เรื่อง แนวปฏิบัติจริยธรรมด้านปัญญาประดิษฐ์”)*
- *ข้อมูลเปิดเผย: ผมมีส่วนร่วมเสนอความคิดเห็นกับเอกสารฉบับนี้ของสวทช.ด้วย ในฐานะตัวแทนจากสมาคมผู้ประกอบการปัญญาประดิษฐ์ประเทศไทย (AIEAT) ร่วมประชุมรับฟังความคิดเห็นในวงจำกัด ในขั้นตอนที่เอกสารมีโครงสร้างและเนื้อหาสมบูรณ์ระดับหนึ่งแล้ว ครั้งแรกเมื่อ 25 ต.ค. 2564 และหลังจากนั้นก็ได้รับคำเชิญประชุมอีกอย่างสม่ำเสมอกระทั่งโครงการแล้วเสร็จ
- แนวปฏิบัติเกี่ยวกับมาตรฐานการใช้ปัญญาประดิษฐ์ (Thailand Artificial Intelligence Guidelines 1.0 – TAIG 1.0)
- จัดทำโดย ศูนย์วิจัยกฎหมายและการพัฒนา คณะนิติศาสตร์ จุฬาลงกรณ์มหาวิทยาลัย
โดยการสนับสนุนของ บริษัท เอพี (ไทยแลนด์) จำกัด (มหาชน), บริษัท แชนด์เล่อร์ เอ็มเอชเอ็ม จำกัด, บริษัท แอ็ดวานซ์อินฟอร์เมชั่นเทคโนโลยี จำกัด (มหาชน), และ บริษัท เทิร์นคีย์ คอมมูนิเคชั่น เซอร์วิส จำกัด (มหาชน) - ตีพิมพ์ ธันวาคม 2565; เปิดตัว 4 มีนาคม 2566 ในงานเปิดตัวโครงการพัฒนาแพลตฟอร์มภาครัฐ เพื่อรองรับการปฏิบัติตามกฎหมายคุ้มครองข้อมูลส่วนบุคคล (Government Platform for PDPA Compliance : GPPC)
- จัดทำโดย ศูนย์วิจัยกฎหมายและการพัฒนา คณะนิติศาสตร์ จุฬาลงกรณ์มหาวิทยาลัย
โครงการต่อเนื่องจากมติคณะรัฐมนตรี 2 ก.พ. 2564
จาก “หลักการและแนวทางจริยธรรมปัญญาประดิษฐ์ของประเทศไทย” โดยกระทรวงดิจิทัลเพื่อเศรษฐกิจและสังคม และมติคณะรัฐมนตรีเมื่อวันที่ 2 ก.พ. 2564 ทำให้เกิด “โครงการสร้างความตระหนักรู้ การประยุกต์ใช้ปัญญาประดิษฐ์ อย่างมีจริยธรรม” (ai-ethics.onde.go.th / เดิมเมื่อปี 2565 ใช้ชื่อโดเมน ethics.tu-onde.com) ซึ่งเป็นโครงการของสำนักงานคณะกรรมการดิจิทัลเพื่อเศรษฐกิจและสังคมแห่งชาติ โดยมีมหาวิทยาลัยธรรมศาสตร์เป็นผู้ดำเนินการ
เนื้อหาในเว็บไซต์ของโครงการ มีหลายส่วน บางส่วนเหมือนจะยังไม่สมบูรณ์ (เมื่อปี 2565) หนึ่งในส่วนที่เข้าไปใช้ได้แล้วคือ:
- เครื่องมือในการตรวจสอบและเพิ่มประสิทธิภาพปัญญาประดิษฐ์ให้มีจริยธรรม
(ต้องสมัครร่วมโครงการถึงจะใช้ได้ สมัครฟรี และใช้ได้ทันทีเมื่อสมัครเสร็จ) โดยน่าจะตั้งใจให้ผู้พัฒนาสามารถตรวจสอบได้ด้วยตัวเอง มีหน้าตาเป็นรายการข้อปฏิบัติเรียงเป็นแถวๆ ให้อ่านและทำเครื่องหมายว่าข้อนี้ทำหรือยัง ผ่านหรือยัง
สารภาพว่าเห็นตอนแรกแล้วตาแตกมาก ต้องตั้งสติ แต่คนที่เคยทำงานพวกตรวจมาตรฐานมาคงคุ้นเคยเป็นอย่างดี (ผมนี่ไม่คุ้นนัก) เอกสารการใช้งานของเครื่องมือดังกล่าว น่าจะพอทำให้เห็นภาพได้บ้าง ซึ่งการใช้งานจะอิงกับวิธีการปฏิบัติตาม (implementation) และกรอบแนวปฏิบัติ (core model) ตามบทที่ 3 ของเอกสารแนวปฏิบัติจริยธรรมปัญญาประดิษฐ์ — ตรงนี้ก็น่าจะต้องการเสียงจากนักพัฒนาระบบปัญญาประดิษฐ์และนักวิชาชีพด้านการตรวจสอบ ว่าใช้งานได้จริงแค่ไหน
กรอบการทำงานด้านปัญญาประดิษฐ์สำหรับภาครัฐ
หากเจาะจงเฉพาะการใช้งานในภาครัฐหรือโดยภาครัฐ (เพื่อให้บริการแก่สาธารณะ) ยังมีเอกสารอีก 2 ชิ้น จากสำนักงานพัฒนารัฐบาลดิจิทัล (องค์การมหาชน) (สพร./DGA) คือ:
- เทคโนโลยีปัญญาประดิษฐ์สำหรับการบริหารงานและการบริการภาครัฐ (AI for Government Administration and Services)
เผยแพร่เมื่อ พ.ย. 2562 แนะนำประเด็นเบื้องต้น กรณีศึกษา ตัวอย่างจากต่างประเทศ และข้อเสนอแนะ มีเนื้อหาข้อเสนอแนะกว้างๆ เกี่ยวกับจริยธรรมและการกำกับดูแลปัญญาประดิษฐ์แทรกอยู่เล็กน้อย โดยในส่วนข้อเสนอแนะสำหรับหน่วยงานภาครัฐ มีหัวข้อ “การสร้างจริยธรรม” (หน้า 135-139) และในส่วนข้อเสนอแนะเชิงนโยบาย มีหัวข้อ “การกำหนดกรอบการกำกับดูแล” (หน้า 142-143) - กรอบการทำงานปัญญาประดิษฐ์ภาครัฐ (AI Government Framework)
เผยแพร่เมื่อ ธ.ค. 2563 นำเสนอแนวคิดและขั้นตอนปฏิบัติในการเปลี่ยนผ่านบริการภาครัฐไปสู่การใช้งานปัญญาประดิษฐ์ รวมถึงการพิจารณาข้อกำหนดขอบเขตของการจัดจ้างบุคคลภายนอก (TOR) โดยมีประเด็นด้านกฎหมายและจริยธรรมแทรกอยู่เล็กน้อย ในเนื้อหาส่วนที่เกี่ยวกับการประเมินความพร้อมของหน่วยงาน/ของรัฐบาล เช่น ในรายการตรวจประเมิน (หน้า 33) และในตารางตัวชี้วัด (หน้า 94)
ทั้งนี้หนึ่งปีหลังจากการเผยแพร่เอกสารชิ้นแรก เมื่อ 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
- สมาคมผู้ประกอบการปัญญาประดิษฐ์ประเทศไทยได้ส่งความคิดเห็นไปตามกระบวนการดังกล่าว และเผยแพร่จดหมายแสดงข้อกังวลและข้อเสนอ เมื่อวันที่ 3 พ.ย. 2565
ส่วนตัวไม่แน่ใจกับผลการศึกษานี้ว่าสุดท้ายจะไปจบกระบวนการลงอย่างไร เพราะเอาจริงๆ สถานะของเอกสารน่าจะยังไม่ใช่ร่างในกระบวนการออกกฎหมายเสียทีเดียว และถ้าดูตอน สดช. ให้ศูนย์บริการวิชาการแห่งจุฬาลงกรณ์มหาวิทยาลัยแห่งเดียวกันนี้ ศึกษากฎหมายลำดับรองของพ.ร.บ.คุ้มครองข้อมูลส่วนบุคคล ทางจุฬาก็ศึกษา ฟังความคิดเห็น ปรับปรุง จนได้ร่างออกมา 29 ฉบับ แต่พอถึงเวลาจะเสนอร่างกฎหมายลำดับรองจริงๆ ทางกระทรวงดิจิทัลไม่ได้ใช้ร่างเหล่านั้นเลย แต่ไปให้ “ขาประจำ” ร่างกฎหมายลำดับรองขึ้นมาใหม่ในเวลากระชั้นชิดแล้วก็ใช้ร่างใหม่แทน (เป็นกลุ่มเดียวกับที่ร่างกฎหมายกันมาตั้งแต่สมัย พ.ร.บ.คอมพิวเตอร์ พ.ร.บ.มั่นคงไซเบอร์ จนมาถึงร่างพ.ร.บ.ส่งเสริมจริยธรรมสื่อฯ) ทั้งนี้ไม่แน่ใจกระบวนการภายในในตอนนั้น ว่าถึงขนาดโยนทิ้งหมดเลยไหม หรือยังพอเอาเค้าโครงหรือความคิดเห็นที่ประชาชนและหน่วยงานต่างๆ ใช้เวลากันนับร้อยนับพันชั่วโมงส่งเข้าไป มาพิจารณาอยู่บ้าง
โครงการเพื่อการศึกษา หารือ และรับฟังความคิดเห็นเกี่ยวกับการกำกับดูแลและส่งเสริมเทคโนโลยีสมัยใหม่
โครงการเพื่อการศึกษา หารือ และรับฟังความคิดเห็นเกี่ยวกับการกำกับดูแลและส่งเสริมเทคโนโลยีสมัยใหม่ มีสำนักงานพัฒนาธุรกรรมทางอิเล็กทรอนิกส์ (สพธอ./ETDA) เป็นเจ้าของโครงการ ดำเนินการโดย บริษัท เบเคอร์ แอนด์ แม็คเคนซี่ จำกัด และคณะนิติศาสตร์ มหาวิทยาลัยธรรมศาสตร์
หลังรับฟังความคิดเห็นทางออนไลน์ (จนถึง 29 ส.ค. 2565) ได้นำข้อคิดเห็นไปปรับปรุงและจัดทำข้อเสนอแนะเกี่ยวกับแนวทางการกำกับดูแลและส่งเสริมเทคโนโลยีดิจิทัลสมัยใหม่ 4 เรื่อง โดยหนึ่งในนั้นมีเรื่องการกำกับกิจการปัญญาประดิษฐ์สำหรับธุรกิจอิเล็กทรอนิกส์และบริการดิจิทัลด้วย
** ปรับปรุง 9 เม.ย. 2566 ** ร่างกฎหมายเสร็จแล้ว เผยแพร่บนเว็บไซต์ law.go.th เพื่อรับฟังความคิดเห็นจากสาธารณะระหว่างวันที่ 30 มี.ค. – 13 เม.ย. (14 วัน รวมวันหยุดราชการแล้ว สั้นมาก ชนสงกรานต์ด้วย) และเปิดประชุมออนไลน์รับฟังความคิดเห็นในวันที่ 11 เม.ย. 2566
โดยมีร่างกฎหมาย 3 ฉบับ
- (ร่าง) พระราชบัญญัติว่าด้วยการส่งเสริมและสนับสนุนนวัตกรรมปัญญาประดิษฐ์แห่งประเทศไทย พ.ศ. … | สรุปสาระสำคัญ — รับฟังความคิดเห็น
- (ร่าง) ประกาศ สพธอ. เรื่องศูนย์ทดสอบนวัตกรรมปัญญาประดิษฐ์ (AI Sandbox) — รับฟังความคิดเห็น
- (ร่าง) ประกาศ สพธอ. เรื่อง การประเมินความเสี่ยงจากการใช้ระบบปัญญาประดิษฐ์ — รับฟังความคิดเห็น
โครงการศึกษาแนวทางการจัดตั้งศูนย์ประเมินการทำงานของโปรแกรมที่มีการใช้เทคโนโลยีปัญญาประดิษฐ์
โครงการศึกษาแนวทางการจัดตั้งศูนย์ประเมินการทำงานของโปรแกรมที่มีการใช้เทคโนโลยีปัญญาประดิษฐ์ มี สพธอ. เป็นเจ้าของโครงการ ดำเนินการโดย ศูนย์เทคโนโลยีอิเล็กทรอนิกส์และคอมพิวเตอร์แห่งชาติ (เนคเทค) สวทช.
มีเอกสาร 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) หรือไม่ (คล้ายกับแนวคิดของกฎหมายฉลากสินค้า-การคุ้มครองผู้บริโภค) ทำให้ตัวผู้รับตรวจไม่จำเป็นต้องเป็นผู้กำหนดเกณฑ์คุณภาพเอง (เช่น จะต้องแม่นยำอย่างน้อยกี่ %) ปล่อยให้เป็นเรื่องที่ผู้ขอรับตรวจจะต้องทำให้ได้ตามที่ตัวเองกล่าวอ้างเอาไว้ — ซึ่งมีข้อดีในแง่ลดความจำเป็นที่ผู้รับตรวจจะต้องมีความรู้ความเชี่ยวชาญในตลาดและอุตสาหกรรมนั้นๆ เพียงพอที่จะกำหนดเกณฑ์คุณภาพที่เหมาะสมได้
โครงการอื่นๆ
- AI for All ชุดโครงการปัญญาประดิษฐ์/วิทยาการหุ่นยนต์สำหรับทุกคน ได้รับการสนับสนุนจากสำนักงานสภานโยบายการอุดมศึกษา วิทยาศาสตร์ วิจัยและนวัตกรรมแห่งชาติ (สอวช.) มีส่วนของบทความและสรุปข่าวประจำเดือน ซึ่งเนื้อหาบางส่วนเกี่ยวข้องกับประเด็นข้อกังวลและกรอบกติกาเมื่อนำระบบปัญญาประดิษฐ์มาใช้ในสังคม
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.
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.
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.
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
หลังจากนั้นก็จะปรากฎ Extension ดังนี้
Use
หลังจากนั้นก็สามารถใช้งานได้ตามปกติแล้ว
ต้นฉบับ:
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.
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.
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.
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.
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.
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
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.
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.