21 May 2024

Vee

Lisp: simplifying my problems through code

I struggled to fix bugs in my custom bilingual alignment tree. Unlike standard trees from libraries like React or Tcl/Tk, this tree has unique data manipulation requirements. Most of the bugs stemmed from these data handling complexities, not from the visualization itself. This is just one example of the challenges I faced. I encountered similar issues repeatedly, for instance, when building directed acyclic graphs (DAGs) for word breakers.

I've applied various programming techniques I've learned, but unfortunately, none of them seem to solve my problem. These techniques included tracking my bathroom habits (yes, you read that right!), coding in Ruby and Python with OOP, using static checking in Java, C++, and Rust, and applying a functional programming approach like Haskell's function coloring.
Surprisingly, the answer came when I started working with Lisp.

Non-destructive data transformation

I initially coded in LOGO and Racket (DrScheme), both members of the Lisp family, but these experiences were limited to toy problems. As a result, I didn't grasp the true benefit of Lisp until I revisited it by re-implementing a part of my word breaker in Racket. Notably, Racket is another Lisp dialect that heavily relies on singly-linked lists, the default data structure in Lisp. Unlike array-based lists common in other languages, singly-linked lists manipulated with the CONS function in Lisp avoid destructing existing data. This means constructing a new list based on an existing one doesn't require copying the entire original list, making it ideal for frequent operations involving large datasets. CONS itself operates in constant time, further enhancing efficiency. With Lisp, I can focus on using CONS for data manipulation without concerning on less important things like class design and function coloring. Additionally, since Lisp avoids data destruction, I can leverage the interactive environment conveniently without reloading data from files. While I acknowledge some past frustrations, using Lisp has finally allowed me to achieve what I've been striving for for years. Furthermore, adopting this functional programming approach can be beneficial in other languages, although limitations exist. These limitations will be discussed in the section on building collective team value through programming language choices.

Sustainability

I've been coding for a long time, starting with Python 1.6. As Python evolved through versions 2.X and now 3.X, I've seen the benefits of new features alongside the challenges of breaking changes. While Python's rapid development is great for startups, it can make long-term maintenance difficult for projects with strict stability requirements, such as those for governments or foundations.

Similar challenges exist in other languages. While Java boasts strong backward compatibility, the vast developer landscape can lead to codebases with varying levels of modernity (pre-generics, pre-Java 8, etc.), necessitating additional maintenance efforts. Rust, while powerful, shares Python's characteristic of rapid evolution.

The Stability of Common Lisp

Like other languages, Lisp has evolved over time. However, Common Lisp, the standardized dialect established in 1994 (with Lisp's roots dating back to 1959), offers a key advantage: stability. Code written in Common Lisp is less susceptible to breaking changes due to language updates. Additionally, powerful macros allow for the creation of new syntactic constructs at the library level, without modifying the core language specification that impacts everyone.

Furthermore, my observations of Common Lisp projects on GitHub and elsewhere reveal a remarkable consistency in coding style, even when developers aren't actively collaborating. This suggests a strong community culture that prioritizes code clarity and maintainability.

Building collective team value through programming language choices

Since most of the problems I encounter seem universal, I believe adopting a Lisp-style coding approach could benefit the entire team. However, a complete language switch might not be realistic for many developers and organizations. As a compromise, I attempted to implement Lisp-style coding in Java, but this proved unsuccessful. While elements like parentheses, CAR, CDR, and macros are easy to identify and often the target of complaints, they're not the main obstacles.

However, an experienced programmer once mentioned the difficulty of building complex programs in LOGO despite its easy start. This suggests that the core challenge might lie in the Lisp programming paradigm itself. Introducing Lisp concepts into Java often leads to endless debates rather than progress, even with extensive explanations. However, for team members who grasp the Lisp paradigm, overcoming the hurdle of parentheses becomes a less significant issue.

Searching for how to accomplish something in Java won't yield results written in Lisp style, which can lead to frustration and confusion for developers wondering if their approach is incorrect. In contrast, searching for the same task in Lisp will provide solutions that leverage proper Lisp idioms. This continuous cycle of coding and searching in Lisp builds the common value of the team.

Clojure

While Common Lisp offers a rich ecosystem for web development (front-end, back-end), and mobile apps, some developers may find its toolset less extensive compared to popular languages like JavaScript, Go, or Java. Clojure, a dialect of Lisp, addresses this by seamlessly integrating with Java/Scala/Kotlin libraries. ClojureScript provides a similar bridge to the JavaScript world, and ClojureDart is under development for Dart environments. Beyond its powerful concurrency features and data structures, Clojure and its sub-dialects make Lisp a viable option for building modern applications. A prime example is Nubank, a major online bank with over 100 million customers, which demonstrates the power of Clojure for large-scale applications.

โดย Vee Satayamas ณ 21 May 2024 10:38 +0000

11 April 2024

Kitt

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

วันสงกรานต์ปี 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

โดย kitty ณ 11 April 2024 10:15 +0000

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.

โดย Vee Satayamas ณ 20 March 2024 12:47 +0000

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.

โดย Vee Satayamas ณ 20 March 2024 12:44 +0000

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.

Image description

I can open a database my a simple command.

# for example
./duhin.sh /tmp/my-database.rocksdb

Features

You can download DuHin at codeberg.org/veer66/duhin.

โดย Vee Satayamas ณ 14 February 2024 01:02 +0000

5 January 2024

Vee

Different languages have different core data structure

Sometimes I don't aware that I use different data structures.

โดย Vee Satayamas ณ 5 January 2024 12:21 +0000

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.

Image description

Image description

Image description

Image description

Image description

Image description

Image description

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.

โดย Vee Satayamas ณ 14 November 2023 06:41 +0000

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:

  1. Defining subroutines
  2. An input environment for testing
  3. 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.

โดย Vee Satayamas ณ 4 October 2023 17:57 +0000

1 October 2023

Vee

ข้อได้เปรียบในการใช้ Rust ทำ web backend

มีผู้ถามเข้ามาในกลุ่มตามหัวข้อ ผมก็เลยเอาคำตอบมาเรียบเรียงไว้อีกที

นอกจากประสิทธิภาพแล้ว Rust ได้เปรียบเรื่อง compile time type checking; Lemmy เทียบกับ GotoSocial ที่ใช้ Go มี 2 จุดคือ

  1. GotoSocial ทำ cache เลี่ยงเรียก database บ่อย ซึ่ง cache แชร์กันระหว่าง goroutine ไม่ผ่าน channel ต้อง lock กันเอง; แต่ถ้าเป็น Rust มี compiler คอยช่วยตรวจว่า lock จุดที่ควร lock หรือไม่
  2. Bun ORM ใน GotoSocial เวลา query ก็ส่ง string เข้าไป ไว้ parse เอาตอนรัน ซึ่งทำให้ compiler ช่วยตรวจไม่ได้; Diesel ใน Lemmy ใช้ macro ทำให้ check type ได้

โดย Vee Satayamas ณ 1 October 2023 11:25 +0000

โปรแกรมและโปรแกรมเชิงวัตถุ

(เคยโพสต์ที่ 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

ข้อกำหนดในการเข้าถึง

ตัวอย่างโปรแกรม 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

สังกัดอ็อบเจกต์หรือคลาส

ตัวอย่างคลาส 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

โดย Vee Satayamas ณ 1 October 2023 11:22 +0000

สัญญาอนุญาตสำหรับซอฟต์แวร์เสรี

(โพสต์เมื่อต้นเดือนกันยา 2023)

คำออกตัว

ผมไม่ได้เรียนเกี่ยวกับกฎหมายและไม่ได้ทำงานเกี่ยวกับกฎหมาย

ซอฟต์แวร์เสรีและโอเพนซอร์ส

ซอฟต์แวร์เสรีเป็นซอฟต์แวร์ที่เคารพสิทธิของผู้ใช้และชุมชน ได้แก่ สิทธิในการรัน ทำซ้ำ เผยแพร่ ศึกษา แก้ไข และปรับปรุงโปรแกรม อย่างไรก็ตามในเวลาต่อมามีขบวนการโอเพนซอร์สเกิดขึ้นเพื่อสร้างแนวร่วมกับบริษัทกระแสหลัก โดยแก้จำกัด 2 ประการของซอฟต์แวร์เสรี ได้แก่ (1) ซอฟต์แวร์เสรีในภาษาอังกฤษเรียกว่า “free software” โดยคำว่า free หมายถึงเสรีก็ได้หรือไม่เสียค่าใช้จ่ายก็ได้และมักจะถูกเข้าใจผิดว่าหมายถึงไม่เสียค่าใช้จ่าย และ (2) ศัพท์ว่า "free software" ทำให้ชาวบริษัทจำนวนมากกังวลใจ

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

หลักการ

โปรแกรมใดจะเป็นซอฟต์แวร์เสรีก็ต่อเมื่อมีเสรีภาพ 4 อย่างข้อต่อไปนี้

  1. เสรีภาพที่จะใช้งานโปรแกรมเพื่อจุดประสงค์อะไรก็ตาม
  2. เสรีภาพในการศึกษาและแก้ไขดัดแปลงโปรแกรม ข้อนี้ทำให้ต้องเข้าถึง source code ได้
  3. เสรีภาพในการเผยแพร่ซอฟต์แวร์ที่ยังไม่ได้แก้ไข
  4. เสรีภาพในการเผยแพร่แวร์ที่แก้ไขแล้วออกไป

กฎหมายไทย

จากพระราชบัญญัติลิขสิทธิ์ มาตรา 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 เพราะคนธรรมดาย่อมอยากช่วยส่วนรวมมากกว่าเอกชน ยกเว้น

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

อ่านเพิ่ม

โดย Vee Satayamas ณ 1 October 2023 11:20 +0000

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))

โดย Vee Satayamas ณ 30 September 2023 14:50 +0000

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 แห่ง

ทั้งนี้ถ้ามองจากหลักการแบ่งแยกอำนาจ นิติบัญญัติ-บริหาร-ตุลาการ จะมองได้ดังนี้

(*สถาบันหลักของอำนาจดังกล่าว)

จะเห็นว่า Council of the European Union และ European Commission มีทั้งอำนาจนิติบัญญัติและอำนาจบริหาร แต่ที่มาของอำนาจนั้นจะต่างกัน (ดูต่อข้างล่าง)

สถาบันที่เกี่ยวข้องกับการออกกฎหมายในสหภาพยุโรป

สถาบันที่เกี่ยวข้องกับการตัดสินใจออกกฎหมายและนโยบาย โดยทั่วไป มี 4 สถาบันคือ

ความแตกต่างระหว่าง 2 หน่วยงานแรก และ 2 หน่วยงานหลังก็คือ

2 หน่วยงานแรกจะคำนึงถึงประโยชน์ของ EU ทั้งหมดในฐานะสหภาพ ในขณะที่ 2 หน่วยงานหลังดูแลประโยชน์ของประเทศสมาชิกแต่ละประเทศ

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

ดูร่างได้ที่ 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”

ดูร่างได้ที่ https://data.consilium.europa.eu/doc/document/ST-15698-2022-INIT/en/pdf

ร่างจาก Parliament (14 มิ.ย. 2023)

ร่างนี้เป็น position จาก European Parliament

ดูร่างได้ที่ 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)

อ้างอิง

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

โดย bact ณ 20 August 2023 16:21 +0000

23 July 2023

MrChoke

GitHub Copilot Custom AutoComplete

การใช้ Github Copilot ช่วยทุ่นแรงในการเขียนโค้ดที่เป็นรูปแบบซ้ำ ๆ เช่นการเตรียมข้อมูล Seed สำหรับ migrate database แค่เราเอาข้อมูลที่แบ่งเป็น fields มาแปะไว้ให้ Copilot เรียนรู้มันก็จะจัดการให้เราแบบรวดเร็ว

Demo จาก YouTube

จากตัวอย่างจำลองการ insert ข้อมูลลง table แบบหลาย ๆ row ซึ่งจะมีรูปแบบที่ซ้ำ ๆ กัน โดยได้นำข้อมูลมาแปะขั่วคราวไว้ใน comment ของ code แล้วก็ให้ Copilot ทำการเติมเต็มให้เท่าที่ลองมาจะทำงานค่อนข้างแม่นมาก และ รวดเร็ว คือแปะปุ๊บมันก็เติมให้เลยแทบไม่มีการให้รอเลย ก็ลองเอาไปเล่นกันดูครับ

โดย MrChoke ณ 23 July 2023 22:15 +0000

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

โดย kitty ณ 12 April 2023 09:01 +0000

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

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 กำกับ แล้วคอมไพเลอร์จะช่วยโวยวายให้ถ้ามีความพยายามจะเปลี่ยนค่าที่กำหนดเป็น 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) ณ 29 December 2022 15:36 +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 / มี.ค. 2567)

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

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

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

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

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

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

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

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

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

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

สารภาพว่าเห็นตอนแรกแล้วตาแตกมาก ต้องตั้งสติ แต่คนที่เคยทำงานพวกตรวจมาตรฐานมาคงคุ้นเคยเป็นอย่างดี (ผมนี่ไม่คุ้นนัก) เอกสารการใช้งานของเครื่องมือดังกล่าว น่าจะพอทำให้เห็นภาพได้บ้าง ซึ่งการใช้งานจะอิงกับวิธีการปฏิบัติตาม (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 เรื่อง โดยหนึ่งในนั้นมีเรื่องการกำกับกิจการปัญญาประดิษฐ์สำหรับธุรกิจอิเล็กทรอนิกส์และบริการดิจิทัลด้วย

** ปรับปรุง 9 เม.ย. 2566 ** ร่างกฎหมายเสร็จแล้ว เผยแพร่บนเว็บไซต์ law.go.th เพื่อรับฟังความคิดเห็นจากสาธารณะระหว่างวันที่ 30 มี.ค. – 13 เม.ย. (14 วัน รวมวันหยุดราชการแล้ว สั้นมาก ชนสงกรานต์ด้วย) และเปิดประชุมออนไลน์รับฟังความคิดเห็นในวันที่ 11 เม.ย. 2566

โดยมีร่างกฎหมาย 3 ฉบับ

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

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

มีเอกสาร 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