veer66

veer66

ใน shell script มัน pipe ต่อกันไปได้เรื่อย ๆ แบบนี้

cat input.txt | \
      prog1 x | \
      prog2 y | \
      prog3 z
  • cat อ่านไฟล์ input1.txt แล้วก็ส่งออกมาทาง stdout ต่อมาก็ส่งเข้า prog1 ทาง stdin
  • prog1 อ่านข้อมูลจาก stdin ที่ cat ส่งมา; แล้วประมวลผลแล้วก็ส่งออกไป stdout
  • prog2 อ่านข้อมูลจาก stdin ที่ prog1 ส่งมา; แล้วประมวลผลแล้วก็ส่งออกไป stdout
  • prog3 อ่านข้อมูลจาก stdin ที่ prog2 ส่งมา; แล้วประมวลผลแล้วก็ส่งออกไป stdout
  • โดยที่ prog1 prog2 prog3 ก็รับค่า x y z เข้ามาทาง argument ด้วย

โปรแกรมแบบนี้จะเขียนใหม่เป็น Ruby ก็จะได้ประมาณนี้ หรือจะใช้ Java ก็จะคล้าย ๆ กัน

p read_file("input.txt").prog1(:x)
    .prog2(:y)
    .prog3(:z)

พวก prog1 prog2 prog3 กลายเป็นชื่อ method ใน object ที่ดูจาก code ก็ไม่รู้ว่า object อะไร หรือกระทั่งตอนรันก็ไม่รู้ว่า prog1 ส่งข้อมูลอะไรออกมา

ถ้าเป็น shell script ก็ใส่ tee เข้าไปได้เลย

cat input.txt | \
      prog1 x | \
      prog2 y | \
      tee debug.txt | \
      prog3 z

แล้วก็ไปเปิดดูไฟล์ debug.txt ทีหลังได้ โดยที่โปรแกรมโดยรวมก็ยังทำงานปกติ

แต่ถ้าเป็น Ruby หรือ Java ก็จะกลายเป็นท่ายาก เพราะว่าใส่ tee เข้าไปใน object ก็ยากเพราะไม่รู้ว่า object ไหน class อะไร ใส่ไปแล้วจะมั่วหรือเปล่า

โปรแกรมมันก็จะออกมาประมาณนี้แทน

tmp = read_file("input.txt").prog1(:x)
        .prog2(:y)
write_file(tmp)
p tmp.prog3(:z)

ถ้าลองเปลี่ยนเป็น Clojure มันก็จะประมาณนี้

(-> (slurp "input.txt")
    (prog1 :x)
    (prog2 :y)
    (prog3 :z)
    println)

พวก prog1 prog2 prog3 ก็จะเป็นแค่ function ธรรมดาแทนที่จะเป็น method

นอกจากนั้นก็ยังเขียน tee ขึ้นมาง่าย ๆ ได้แบบนี้

(defn tee [data path]
    (spit path data)
    data)

แล้วก็เอา tee ไปแทรกได้แบบ shell script

(-> (slurp "input.txt")
    (prog1 :x)
    (prog2 :y)
    (tee "debug.txt")
    (prog3 :z)
    println)

โปรแกรมทั้งหมดผมไม่ได้ลองรันจริง ๆ นะครับ อาจจะเจ๊งได้ จาก blog นี้ผมรู้สึกว่าตอบคำถามตัวเองได้ว่าทำไมเวลาเขียน Clojure หรือ Common Lisp ในโปรแกรมที่ค่อนข้างซับซ้อนแล้วรู้สึกสะดวกกว่า Ruby เอาจริงๆ จะเขียน Ruby หรือ Java ให้คล้าย ๆ Clojure ก็คงทำได้ แต่เขียนไปก็จะถูกเรียกว่าไม่ idiomatic อันนี้ก็เป็นอีกเหตุผลนึงให้เปลี่ยนภาษาเพราะว่าภาษามันพ่วงวัฒนธรรมมาด้วย

จริง ๆ จะเขียนให้มันง่าย ๆ กว่านี้ก็ได้ เช่น ส่ง N ไปใน argument เลย แต่มันจะง่ายไป อยากจะดูว่าถ้าใช้ fun แล้วมันจะเรียกตัวเองได้ไหม ปกติก็คือไม่ได้ แต่ก็มีท่าแก้คือส่ง Fib เข้าไปเป็น F แล้วใน Fib เรียก F อีกที

(แก้ code วันที่ 18 มี.ค. 2556)

-module(rec3).
-export([fib/1]).

fib(N) ->
    Fib = fun (F, I, Acc1, Acc2) when I < N -> 
		  F(F, I + 1, Acc2, Acc1 + Acc2);
	      (_, _, _, Acc2) -> 
		  Acc2
	  end,
    Fib(Fib, 0, 0, 1).

ผมลองใส่ N ไป 100,000,000 มันก็ไม่ stack overflow นะครับ แต่ก็ไม่รู้ว่าผลถูกหรือเปล่า 😅

ผมเคยลองเขียนโปรแกรมจัดการ tree และ alignment ด้วย Rust ทั้งตัว เพราะหวังว่า static type มันน่าจะช่วยหาข้อผิดพลาดได้มาก ผลที่ได้คือผมเสียเวลามากไปกับสิ่งที่เป็นรายละเอียดของ Rust เอง ไม่ใช่ใจความหลักของโปรแกรม แล้วก็มีปัญหาช่วง runtime พอ ๆ กับผมเขียน Ruby เลย สุดท้ายก็ต้องมาเขียน automated tests หรือมา test แบบ manual อยู่ดี แน่นอนว่าผมยังไม่แตกฉาน Rust ก็ได้ แต่ด้วยเหตุอะไรก็ตาม ถ้าต้องเขียนโปรแกรมภาษาแรกที่ผมนึกถึงไม่ใช่ Rust

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

ส่วนที่ใช้ Rust แล้วรู้สึกว่าดีคือเขียนโปรแกรมตัดคำ อันนี้เร็วขึ้นมาก ๆ เร็วขึ้นจากใช้ภาษา dynamic type สัก 10 เท่าได้ เทียบกับพวก Kotlin บน JVM ก็เร็วขึ้นสัก 2 เท่า อันนี้ชัดเจนเลยว่าถ้าอยากได้พวกที่ใช้ CPU เยอะ ๆ แล้วเร็วขึ้นนี่มันได้ผลมาก อีกโปรแกรมที่คล้าย ๆ กันคือเอามาเขียนหาพวก shortest path บน graph อันนี้ก็เร็วอีกเหมือนกัน; อีกงานหนึ่งคือเอาจัดการกฎการแปลที่เป็น tree หลาย ๆ ต้น เอามาสร้างเป็น TRIE เพื่อที่จะทำให้แปลได้เร็ว ๆ อันนี้ก็เขียนออกมาแล้วก็เร็วขึ้นจริง

สิ่งหนึ่งที่ไม่คิดมาก่อนว่ามันจะดีคือ error handling ที่ตรงไปตรงมามาก ถ้าไม่อยากยุ่งอะไรก็ใส่ unwrap() ไปก่อน หรือปล่อยผ่านไปออกเลยก็ใช้ ? เช่น funcdosth(x)?; ก็คือปล่อยออกไปได้เลยถ้า function ที่หุ้มอยู่มัน return Result ที่ type ใช้กันได้ ท่าแบบนี้มันเขียนน้อย แต่เมื่อไหร่อยากจะกลับมา handle แต่ละจุดให้ดี ๆ ก็แค่ search unwrap กับ ? ใน text editor ก็มาตามอุดได้แล้ว อันนี้ผมเดาว่าเป็นเหตุให้ desktop app ใหม่ ๆ ที่ใช้ GTK+ ถึงเขียน Rust กันเยอะหรือเปล่า ?

สุดท้ายคือพวก concurrency ข้อดีตามในตำราคือมัน check type ให้ว่า ถ้าอยู่ดี ๆ คือเอาพวก mutable state ไปเที่ยวแชร์กันข้าม thread ไม่ได้ ต้อเอา RWLock หรือ Mutex ไปหุ้มก่อน ถ้าจะตัดปัญหาก็ใช้ channel เอา แต่สิ่งที่สังเกตได้หลัง ๆ คือ library เช่น library ที่ทำ Web Socket หรือว่า coroutine ของ Rust ส่วนมาก code อ่านรู้เรื่อง แล้วใครอยากจะทำ library แบบไหนก็ทำได้แทบจะตามจิตนาการเลย คล้าย ๆ กับ C เพราะว่าคนที่เขียน Library ขึ้นมาไม่ต้องไปยุ่งกับ Garbage collector ใน runtime เลย เพราะว่ามันไม่มี 😅 ก็ทำให้ตัดเรื่องยุ่งยากหลัก ๆ ออกไปได้เลย และสำหรับผม code มันอ่านง่ายกว่า C

แต่ก่อนอยากศึกษา Structured programming, OOP, FP, TDD, design pattern และ programming language ใหม่ ๆ เพราะแต่ก่อนเขียน Pascal นี่ส่วนมากเป็นโปรแกรมแก้โจทย์แบบเขียนคนเดียวไม่เกิน 2 ชั่วโมง ถ้าโครงการใหญ่กว่านั้นล่ม 🥺

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

pattern ที่ลองทำแล้วได้ผลมากคือ

  1. มองเป็น data transformation
  2. ใช้ data หลวม ๆ เช่น map หรือ alist
  3. ถ้าใช้ภาษา dynamic ได้ก็ดี
  4. พยายามทำให้โปรแกรมอ่านจากบนลงล่างได้
  5. พยายามทำให้ function ยาวไม่เกิน 20 บรรทัด (และขึ้นบรรทัดใหม่ตาม style ปกติ)

โปรแกรมที่ผมเขียนส่วนมาเกี่ยวกับ tree แล้วก็มีการโยงกันไปมาระหว่าง tree และ string บางทีก็มี lattice ด้วย ในแต่ละ node พอผ่านแต่ละ function ไปข้อมูลใน node บางทีก็เพิ่มขึ้นเรื่อย ๆ บางทีก็เปลี่ยน string เป็น lattice แล้วเอา lattice ไปเข้าอีก function นึงกลายเป็น lattice ที่เส้นเยอะกว่าเดิม 😛 ผมเคยพยายามจะทำเป็น class หรือ struct แล้วพอว่ามันเปลี่ยนบ่อยมาก

แต่ถ้าทำโปรแกรมคงคลังของธุรกิจเดียวที่ class ก็จะหน้าตาหนี table ใน relational database ไปได้ไม่กี่มากน้อย ข้อ (2) นี่อาจจะไม่จำเป็นก็ได้ (มั้ง) พอเป็น class/struct แล้วบางทีมันก็มีข้อดีว่าช่วยสื่อสารกับคนอื่น ๆ ในทีมได้ แต่โปรแกรมแบบที่ว่าในย่อหน้านี้ผมไม่ค่อยได้ทำไง

การศึกษาภาษาอื่น ๆ เข่น Haskell, Scala, TypeScript ก็อาจจะมีประโยชน์และทำให้ผมสิ่งดี ๆ อื่น ๆ ก็ได้ แต่สำหรับผม ผมมองว่าไม่ใช่เรื่องเร่งด่วน หรือการเรียน design pattern แบบที่ใช้กับ Java หรือ C++ ผมก็มองสำหรับผมรู้ก็ดีแต่ว่าไม่ใช่เรื่องเร่งด่วน

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

อีกอย่างที่รู้สึกว่าจำเป็นต้องเรียนคือพวก numpy, MXNet, PyTorch, Tensorflow ส่วนมากเป็น library ที่มันทำให้โปรแกรมเร็วขึ้นโดยที่ไม่ต้องออกแรงมากเลย ใช้ SIMD ด้วยคำสั่งเดียว ทั้งบน CPU, GPU, TPU

File.open("toto") do |file|
   file.each_line |line|
      line.chomp!
   end
end

พอทดสอบกับไฟล์ 11MB ใช้ pypy3 ทำให้ wordcutpy เร็วขึ้นเกิน 2 เท่า! คือใช้เวลาจาก 16 วินาที เหลือไม่ถึง 8 วินาที

(base) [vee@mint310 wiki]$ python3 wordcutpy.py 
16598
(base) [vee@mint310 wiki]$ sudo docker run -it --rm --name my-running-script -v "$PWD":/usr/src/myapp -w /usr/src/myapp pypy:3 pypy3 wordcutpy.py
7833
(base) [vee@mint310 wiki]$ python3 wordcutpy.py 
16093
(base) [vee@mint310 wiki]$ sudo docker run -it --rm --name my-running-script -v "$PWD":/usr/src/myapp -w /usr/src/myapp pypy:3 pypy3 wordcutpy.py
7821
(base) [vee@mint310 wiki]$ python3 wordcutpy.py 
16272
(base) [vee@mint310 wiki]$ sudo docker run -it --rm --name my-running-script -v "$PWD":/usr/src/myapp -w /usr/src/myapp pypy:3 pypy3 wordcutpy.py
7810
# wordcutpy.py
# การใช้ wordcutpy ที่ถูกต้องคือ copy & paste เลย ไม่ต้องใช้ pip 😅
# แล้วก็ copy bigthai.txt มาไว้ folder เดียวกัน

import sys
import re

class PrefixTree(object):
    def __init__(self, members_with_payload):
        self.tab = {}
        if members_with_payload is None:
            return 
        sorted_members_with_payload = sorted(members_with_payload,
                                             key=lambda i: i[0])

        for i in range(len(sorted_members_with_payload)):
            members, payload = sorted_members_with_payload[i]
            row_no = 0
            for j in range(len(members)):
                is_terminal = len(members) == j + 1
                member = members[j]
                key = (row_no, j, member)
                if key in self.tab:
                    row_no = self.tab[key][0]
                else:
                    val = (i, is_terminal, payload if is_terminal else None)
                    self.tab[key] = val
                    row_no = i

    def lookup(self, i, offset, member):
        key = (i, offset, member)
        if key not in self.tab:
            return None
        return self.tab[key]

UNK   = 1
DICT  = 2
INIT  = 3
LATIN = 4
PUNC  = 5

def is_better(link0, link1):
    if link0 is None:
        return True

    if link1["unk"] < link0["unk"]:
        return True

    if link1["w"] < link0["w"]:
        return True

    return False

def build_path(dix, s):
    left_boundary = 0
    dict_acc_list = []

    path = [{"p":None, "w": 0, "unk": 0, "type": INIT}]

    latin_s = None
    latin_e = None

    punc_s = None
    punc_e = None

    for i, ch in enumerate(s):
        dict_acc_list.append({"s":i, "p":0, "final":False})

        # Update dict acceptors
        _dict_acc_list = dict_acc_list
        dict_acc_list = []                        
        for acc in _dict_acc_list:
            offset = i - acc["s"]
            child = dix.lookup(acc["p"], offset, ch)
            if child is not None:
                child_p, is_final, payload = child
                dict_acc_list.append({"s":acc["s"], "p": child_p,
                                      "final":is_final})

        # latin words
        if latin_s is None:
            if re.match(u"[A-Za-z]", ch):
                latin_s = i

        if latin_s is not None:            
            if re.match(u"[A-Za-z]", ch):
                if i + 1 == len(s) or re.match(u"[A-Za-z]", s[i + 1]):
                    latin_e = i
            else:
                latin_s = None
                latin_e = None

        # puncuation
        if punc_s is None:
            if ch == " ":
                punc_s = i

        if punc_s is not None:
            if ch == " ":
                if len(s) == i + 1 or s[i + 1] != " ":
                    punc_e = i
            else:
                punc_s = None
                punc_e = None

        # select link
        link = None

        # links from wordlist
        for acc in dict_acc_list:
            if acc["final"]:
                p_link = path[acc["s"]]
                _link = {"p": acc["s"], 
                         "w": p_link["w"] + 1, 
                         "unk": p_link["unk"],
                         "type": DICT}
                if is_better(link, _link):
                    link = _link

        # link from latin word
        if latin_s is not None and latin_e is not None:
            p_link = path[latin_s]
            _link = {"p": latin_s, 
                     "w": p_link["w"] + 1, 
                     "unk": p_link["unk"],
                     "type": LATIN}
            if is_better(link, _link):
                link = _link

        # link from puncuation
        if punc_s is not None and punc_e is not None:                
            p_link = path[punc_s]
            _link = {"p": punc_s, 
                     "w": p_link["w"] + 1, 
                     "unk": p_link["unk"],
                     "type": PUNC}
            if is_better(link, _link):
                link = _link

        # fallback
        if link is None:
            p_link = path[left_boundary]
            link = {"p": left_boundary, 
                    "w": p_link["w"] + 1,
                    "unk": p_link["unk"] + 1,
                    "type": UNK}
        path.append(link)
        if link["type"] != UNK:
            left_boundary = i
    return path

def path_to_tokens(txt, path):
    if len(path) < 2:
        return None

    e = len(path) - 1
    toks = []

    while True:
        link = path[e]
        s = link["p"]
        if s is None:
            break
        toks.append(txt[s:e])
        e = s

    toks.reverse()
    return toks

def tokenize(dix, txt):
    if txt is None or txt == "":
        return []
    path = build_path(dix, txt)
    return path_to_tokens(txt, path)

class Wordcut(object):
    def __init__(self, wordlist):
        self.dix = PrefixTree([(word, None) for word in wordlist])


    @classmethod
    def bigthai(cls):
        import os
        "Initialize from bigthai"
        fileDir =  os.path.dirname(__file__)
        filename = os.path.join(fileDir, 'bigthai.txt')
        with open(filename) as dict_file:

            word_list = list(set([w.rstrip() for w in dict_file.readlines()]))
            word_list.sort()
            return cls(word_list)

    def tokenize(self, s):
        return tokenize(self.dix, s)

wordcut = Wordcut.bigthai()

import time

t1 = int(round(time.time() * 1000))

with open("wiki_plain_100k.txt") as fi:
    with open("wiki.cut", "w") as fo:
        for line in fi:
            line = line.strip()
            print(" ".join(wordcut.tokenize(line)), file=fo)

t2 = int(round(time.time() * 1000))

print(t2-t1)

# LICENSE: LGPLv3

https://github.com/veer66/wordcutpy

(เริ่มเขียนเมื่อ 2018-11-25)

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

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

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

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

ในระบบที่เป็นที่นิยมก่อนหน้านี้ เช่น E-mail เมื่อเปลี่ยนผู้ให้บริการก็ยังติดต่อกับญาติมิตรที่ใช้บริการ E-mail เจ้าอื่นได้ หรือแม้แต่กระทั่งโทรศัพท์เปลี่ยนผู้บริการแล้วก็ยังโทรหาคนอื่นได้เหมือนเดิม

Fediverse เกิดจากคำว่า Federation และ Universe รวมกันเป็นเครือข่ายสังคมออนไลน์ รวมถึง blog ที่เครื่องแม่ข่ายต่าง ๆ ทำงานร่วมกันได้ถึงแม้ว่าจะไม่ได้มีเจ้าของเดียวกัน หรือกระทั่งไม่ได้ใช้โปรแกรมเดียวกัน เช่น โปรแกรมสำหรับเขียน micro blog สามารถแสดงความคิดเห็นต่อวิดีโอบนเว็บสำหรับเผยแพร่วิดีโอได้โดยตรง

หลังจากที่โพสต์วิดีโอบน peertube.social ซึ่งเว็บสำหรับโพสต์วิดีโอใน Fediverse เราสามารถค้นหาด้วย URL ของ PeerTube จาก mstdn.io ซึ่งเป็นเว็บสำหรับโพสต์ micro blog ก็จะเห็นว่าวิดีโอใน PeerTube เป็น blog post หนึ่งจาก mstdn.io; สามารถ reply ได้; พอ reply เสร็จแล้วก็จะขึ้นไปเป็นความคิดของวิดีโอนั้นเลย

นอกจากจะแสดงความคิดเห็นข้ามกันได้แล้ว เรื่องทั่วไปอย่างดู video ของ PeerTube ใน Mastodon ก็ทำได้อยู่แล้ว หรือจะ follow/subscribe ก็ทำได้เหมือนกัน

ซึ่งลักษณะที่ว่าทำให้ Fediverse มีเครื่อแม่ขายที่ไม่ได้มีเจ้าของเดียวกันเยอะแยะไปหมด จากเว็บนี้ก็บอกว่ามี 2460 node แล้ว

ผมเลือก node ที่ผมคิดว่าสำคัญมาตามนี้

  • mstdn.io ใช้โปรแกรม Mastodon เป็น micro blog เรื่องทั่ว ๆ ไป
  • mastodon.in.th เหมือนข้างบนแต่เน้นภาษาไทย (เพิ่มเมื่อ 2019-11-30)
  • https://norze.world เน้นภาษาไทยแต่ใช้ Pleroma (เพิ่มเมื่อ 2020-03-30)
  • pawoo.net ใช้ Mastodon เหมือนกัน แต่เป็นชุมชนภาษาญี่ปุ่น
  • ruby.social ใช้ Mastodon เป็น node ที่คุยกันเน้นเรื่องภาษา Ruby
  • peertube.social ใช้ PeerTube เอาไว้สำหรับวิดีโอที่ไม่ใช่การค้า
  • squeet.me ใช้ Friendica คล้าย ๆ Facebook
  • pixelfed.social ใช้ Pixelfed เอาไว้โพสต์รูปเน้นใช้ง่าย ๆ คล้าย Instagram (แก้ไข 2019-02-27)
  • qua.name คล้าย ๆ Medium (เพิ่มเมื่อ 2019-02-27)

โปรแกรม Mastodon, PeerTube, Friendica, Pixelfed ล้วนเป็นซอฟต์แวร์เสรีทั้งสิ้น ซึ่งหมายความว่าเราจะเปิด node ของเราเองก็ได้ จะแก้ไขดัดแปลงแจกจ่ายก็ได้ แต่ละโปรแกรมก็ใช้ภาษาโปรแกรมต่างกันออกไป Mastodon ใช้ Ruby, PeerTube ใช้ TypeScript ส่วน Friendica และ Pixelfed ใช้ PHP

Fediverse แต่ละ node คุยกันได้เพราะว่ามีมาตรฐานกลาง ซึ่งหลัก ๆ ก็คือ ActivityPub ที่สร้างมาบน Acivity Stream และ JSON-LD อีกที

JSON-LD หลักการคล้าย ๆ RDF คือพยายามจะทำให้ข้อมูลจากแหล่งต่าง ๆ เชื่อมโยงกันได้ แต่ว่าใช้ JSON parser ได้เลยเพราะมันเป็น JSON เช่น เปิด json มาอาจจะ field ชื่อ actor ก็อาจจะหมายถึง “นักแสดงชาย” หรือ “ผู้กระทำ” ก็ได้ แต่ถ้าเขียนเลยว่า “https://www.w3.org/ns/activitystreams/actor” อันนี้ก็จะหมายถึงผู้กระทำแน่ ไม่จำเป็นต้องเป็นนักแสดง แต่จะเขียนชื่อ field ด้วย url หรือในเอกสารจะเรียกรวม ๆ ว่า IRI มันก็จะเยิ่นเย้อเกินไป ก็เลยมี field หนึ่งขึ้นมาชื่อว่า @context เราก็เรียน “@context”: “https://www.w3.org/ns/activitystreams” ไว้เสียแต่ต้น ต่อไปในเอกสารก็ใช้ actor ได้เลยเพราะทราบแล้วว่าบริบทคือ “https://www.w3.org/ns/activitystreams” สามารถอ่านเพิ่มเติมได้จาก JSON-LD Spec

ส่วน Activity Stream เป็นมาตรฐานที่ระบุเลยว่าจะเขียนว่าใคร (actor) ทำอะไร (verb) อะไรถูกกระทำ (object) จะต้องเขียนออกมาหน้าตาอย่างไร หน้าตาก็จะออกมาเป็น JSON-LD ตามที่มาตรฐานกำหนดไว้ ที่ผมเห็นใช้งานตอนนี้ก็คือใช้ในเครือข่ายสังคมออนไลน์ทั้งหมด อ่านเพิ่มเติมได้จาก Activity Streams Spec

จาก Activity Stream ก็มีมาตรฐานเกี่ยวเนื่องที่ผมรู้สึกว่าน่าสนใจคือ Activity Vocabulary ซึ่งระบุกระทั่งว่ากริยาที่กระทำใน Activity Stream มีอะไรหน้าตาเป็นอย่างไร เช่น like dislike follow invite ignore ต้องมีหน้าตา มีข้อมูลอย่างไรบ้าง อ่านเพิ่มเติมได้จาก Vocabulary spec

นอกจากประเด็นว่าส่งว่าใคร ทำอะไร อะไรเป็นผู้กระทำแล้ว ก็ยังมีประเด็นว่าต้องส่ง activity stream ไปที่ไหน ในส่วนนี้ก็เป็นมาตรฐาน ActivityPub ซึ่งกำหนดทั้งการส่งจาก client กับ server และ server กับ server เอง เพราะว่า Fediverse ต้องมีการสือสารกันระหว่าง server คนละเจ้าด้วย โดยมีการกำหนดเป็น inbox outbox ของแต่ละ actor ขึ้นมาว่าจะอยู่ใน URL อะไร ฯลฯ ซึ่งอ่านรายละเอียดได้จาก ActivityPub Spec

ใน Fediverse ไม่มีการเอาการติดต่อญาติมิตรของใคร ๆ มาเป็นตัวประกันของแต่ละเจ้าอีกต่อไป ทำให้ลดการผูกขาดลด เมื่อมีอำนาจต่อรองมากขึ้นสมาชิกก็ถูกนำไปขายยากขึ้น แต่ว่า node สามารถนโยบายของตัวเองทำให้ไม่มีการผูกขาดทางศีลธรรม เรื่องโฆษณาใน Fediverse ตอนนี้ยังไม่เห็นครับ ส่วนหนึ่งคือ admin ของแต่ละ node ออกตังเองเพราะแต่ละ node ไม่ใช้เครื่องแรงมากมายพอจะออกให้กันได้ และอีกส่วนหนึ่งก็มีการบริกาคช่วยกัน; ผมเห็น write.as มีแบบเสียตังด้วย แต่ยังต้องศึกษาเพิ่มเติมอีก

แต่ก็มีข้อจำกัดอยู่ว่าย้ายค่ายแล้วต้องเปลี่ยน ID

อนาคตอาจจะมีการใช้ ActivityPub กับบริการออนไลน์อื่นอีก ไม่ว่าจะเป็นซอฟต์แวร์เสรีหรือไม่ก็ตาม หรือก้าวหน้าจนไปถึงแบบ peer-to-peer

ประเด็นอื่น ๆ เช่นว่า

  • Fediverse จะยั่งยืนหรือไม่ ผมเชื่อความทางศาสนาว่าสิ่งเป็นสังขารทั้งหมดไม่ยั่งยืน และผมไม่สามารถพยากรณ์อายุของ Fediverse ในอนาคตได้;
  • ประเด็นว่าทำไมไม่ใช้ peer-to-peer ผมคิดว่าจะมีประเด็นเรื่องพลังงานถ้าใช้อุปกรณ์เคลื่อนที่;
  • ทำไมไม่ใช้ blockchain ผมคิดว่ามีประเด็นด้านประสิทธิภาพ และการกลั่นแกล้งเบียดเบียนกัน Fediverse ไม่จำเป็นต้องปล่อยให้ทุกคนพูดทุกอย่างแล้วลบไม่ได้
  • app บนมือถือ tablet มีทั้ง Android และ iOS ใช้กับ Mastoodon และ Pleroma ได้