veer66

veer66

เป็นอันนึงที่เลือกนานมากคือสีวงลบของ Emacs ใช้ rainbow-delimiters 😅

(custom-set-faces
 ;; custom-set-faces was added by Custom.
 ;; If you edit it by hand, you could mess it up, so be careful.
 ;; Your init file should contain only one such instance.
 ;; If there is more than one, they won't work right.
 '(default ((t (:family "Noto Mono" :foundry "GOOG" :slant normal :weight normal :height 120 :width normal))))
 '(rainbow-delimiters-depth-2-face ((t (:inherit rainbow-delimiters-base-face :foreground "SeaGreen3"))))
 '(rainbow-delimiters-depth-3-face ((t (:inherit rainbow-delimiters-base-face :foreground "DeepPink1"))))
 '(rainbow-delimiters-depth-4-face ((t (:inherit rainbow-delimiters-base-face :foreground "SteelBlue1"))))
 '(rainbow-delimiters-depth-5-face ((t (:inherit rainbow-delimiters-base-face :foreground "orange red"))))
 '(rainbow-delimiters-depth-6-face ((t (:inherit rainbow-delimiters-base-face :foreground "DarkGoldenrod1")))))

AFAIK Generating UUID and Xid needs the MachineGuid, so I looked around how to obtain it.

And this is the solution, which I'm not sure if it will work on every Windows machine. 😅

main.rs

use winreg::RegKey;
use winreg::enums::*;
use std::io;

fn main() -> io::Result<()> {
    let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
    let cur_ver = hklm.open_subkey("SOFTWARE\\Microsoft\\Cryptography")?;
    let guid: String = cur_ver.get_value("MachineGuid")?;
    println!("MachineGuid = {}", guid);
    Ok(())
}

Cargo.toml

[package]
name = "mach_guid"
version = "0.1.0"
authors = ["veer6"]
edition = "2018"

[dependencies]
winreg = "0.6"

เอกสารเดี๋ยวนี้อาจจะไม่ได้มาแบบเป็นเล่ม ๆ อย่างเดียว บางคนอาจจะใช้ E-book reading จอ 6 นิ้วอ่าน บางคนอาจจะ print ใส่กระดาษ A4 บางคนอาจจะอ่านใน Tablet บางคนอาจจะอ่านบนจอใหญ่ บางคนอาจจะอ่านในโทรศัพท์มือถือ

มันคงไม่ค่อยดีเท่าไหร่ที่จะต้องมาจัดหน้าให้ทุกแบบเอง คนที่เขียนเนื้อหาก็ควรจะเขียนเนื้อหาไป ไม่ต้องมากังวลกับการจัดหน้ามาก

MS Word มันกึ่ง ๆ ระหว่างจะเน้นเนื้อหา หรือว่าจะเน้นจัดหน้าดี จะเลือกว่าส่วนไหนเป็น heading ก็ได้ แต่นึกอยากจะกำหนด font กำหนดขนาดตัวอักษรของแต่ส่วนเองก็ได้อีก

จะใช้ Word แบบเน้นเนื้อหาก็ได้ โดยการเลือก style แทนที่จะไปจัดเอง แต่พอทำจริง ๆ ส่วนมากออกมา format ไม่เป๊ะ มันมีวิธีหลายแบบที่จะทำให้เอกสารออกมาหน้าตาคล้าย ๆ กัน มอง style ก็ไม่ค่อยจะเห็นชัดเท่าไหร่ ทีนี้เอามาประมวลผล หรือแค่จัดรูปแบบใหม่ให้เหมาะกับ online หรือแม้กระทั่ง ebook reader จอเล็ก ๆ มันจะเละ

มันมีสิ่งที่คิดมาแก้เรื่องนี้หลายตัวเช่น GNU Texinfo มันก็ใช้ได้เอกสารของ GNU Project ทั้งหมดใช้ตัว Texinfo เวลาดูเว็บของ GNU หน้าตามันจะซ้ำ ๆ เบื่อ ๆ หน่อย แต่ก็เป็นวิธีที่ทำเอกสารที่ทำทีเดียว ออกมาได้ทั้ง HTML, Info, man ทำเป็น PDF ก็ได้ ปัญหาของ Texinfo คือกว่าจะเขียนเป็นมันก็ไม่ได้ยาก แต่ก็ยากกว่า Word

อีกตัวคือ Gitbook อันนี้ใช้เขียนหนังสือเป็นเล่ม ๆ ได้เลย แล้วก็เอามาสร้างได้หลาย format คล้าย ๆ กับ GNU Texinfo แต่ว่าใช้ Markdown คนทาง IT เดี๋ยวนี้หลาย ๆ คนก็เขียน Markdown เป็นเลย เป็น format ที่ดูง่ายมาก ๆ บางคนอาจจะบอกว่าเปิด docx ออกมาอ่าน format ข้างในก็ได้เหมือนกัน แต่มันก็อ่านยากกว่า Markdown มาก ๆ แต่ถึง Markdown จะง่ายอย่างไรมันก็ยากกว่าใช้ Word อยู่ดี Gitbook ในเว็บเขามีตัวแก้เอกสารแบบ WYSIWYG มาให้เลย แต่ format มันก็จะเป๊ะอยู่ดี WYSIWYG ของเขาไม่ใช่แบบที่ให้มาเลือก font จัดโน่นจัดนี่เอง แต่พอเป็นตัวหนังสือก็จะเลือกได้ว่าจะเป็น Heading 1, Heading 2, Heading 3 อะไรทำนองนั้นไป

ผมมองว่าแบบ Gitbook นี่ล่ะที่น่าจะเหมาะเพราะ

  1. แยกกันระหว่างเนื้อหาและการจัดหน้า
  2. แก้ไขแบบเห็นเหมือนจริง (WYSIWYG) ได้เลย ทำให้ง่าย
  3. ตรวจ format ง่าย เพราะว่าถึงจะเปิดแบบ Markdown มาก็พอจะอ่านรู้เรื่องเลย ทำให้รู้ว่ามันเก็บอะไรลงไปกันแน่
  4. อีกข้อหนึ่งที่แถมคือเวลาแก้ไขเอกสารด้วยกันหลาย ๆ คนพร้อมกันผ่านเว็บนี่มันสะดวกกว่าการส่งไฟล์กันไปมาเยอะแยะ

ผมว่าเทคโนโลยีมันพร้อมแล้ว เหลือแต่ว่าคนจะอยากเปลี่ยนเมื่อไหร่

update 2019-08-04

ยกตัวอย่างเพิ่มเติมเพื่อให้เห็นภาพว่า markdown มันเพิ่มเพิ่มขนาด font ตรง ๆ ไม่ได้ ต้องไปทำผ่านการสร้าง header ทำให้เวลาเอาไปใช้ไม่ต้องมีตีความว่า TH Sarabun PSK 24 นี่มันคืออะไร เป็น header หรือเปล่า หรือ header ระดับไหน

สมัยที่เรียนหนังสือเขามักจะเอา Fibonacci number มาเป็นตัวอย่างกัน function ก็เป็นงี้ครับ

fib(0) = 0 fib(1) = 1 fib(n) = fib(n-1) + fib(n-2) สำหรับ n มากกว่า 1

ซึ่งเอามาแปลงเป็น Clojure ได้แบบตรงไปตรงมาเลย

(def fib
  (fn [n]
    (if (<= n 1) n
        (+ (fib (- n 1)) (fib (- n 2))))))

ผมลองรันบนเครื่องผมใช้เวลา 4.324 วินาที ซึ่งมันช้าเพราะไปเรียก fib ซ้ำไปซ้ำมา ดูง่าย ๆ fib(4) ก็ไปเรียก fib(3), fib(2) แล้ว fib(3) ก็ไปเรียก fib(2) อีกซ้ำ พอเลขมากก็ซ้ำไปเรื่อย ๆ

จริง ๆ มันมีวิธีหล่อ ๆ เยอะแยะที่จะแก้ปัญหานี้ให้เร็วขึ้น แต่ถ้าเราไม่ต้องหล่อมาแก้แบบดอกเดียวเสร็จก็ใช้ memoize เขียนแบบนี้

(def fib
  (memoize
   (fn [n]
     (if (<= n 1) n
         (+ (fib (- n 1))
            (fib (- n 2)))))))
(println (time (fib 42)))

แก้นิดเดียวเอา memoize มาครอบ function ไว้ มันจะจำ function ที่เคยเรียกไว้แล้วส่งผลกลับมาเลยโดยไม่ต้องไล่ยาว ผลคือโปรแกรมใช้เวลารันเหลือ 0.820 วินาที

จริง ๆ มันจบแล้วล่ะ แต่เขียนให้ดูอีก function แบบใช้ loop

(defn fib [n]
  (loop [i 1 acc0 0 acc1 1]
    (if (< i n)
      (recur (inc i) acc1 (+ acc0 acc1))
      acc1)))      
(println (time (fib 42)))

อันนี้ใช้เวลาแค่ 0.0976 วินาที แต่ก็จะเห็นได้ว่าโปรแกรมเปลี่ยนไปเยอะ เปลี่ยนวิธีคิดเยอะ memoize นี่มันแทบไม่ต้องคิดต้องเขียนอะไรมากเลย ใส่ไปแล้วได้ผลเลย

ขอบคุณ visibletrap@twitter.com ที่แนะนำ function time มาครับ

ผมมักจะไปสร้าง script ด้วย Python สั้น ๆ ไว้ใน $HOME/bin หรือ /usr/local/bin ก็ได้ แล้วก็ chmod 755 <ชื่อไฟล์> แล้วก็จะได้เรียกจาก shell ได้เลย

ผมเขียนไว้ 3 ตัวตามนี้

/usr/local/bin/deepcut

#!/usr/bin/env python
# * mode: python-mode *

import deepcut
import json
import sys

for line in sys.stdin:
    line = line.strip()
    print(json.dumps(deepcut.tokenize(line)))

/usr/local/bin/newmm

#!/usr/bin/env python
# * mode: python-mode *

from pythainlp.tokenize import word_tokenize

import json
import sys

for line in sys.stdin:
    line = line.strip()
    print(json.dumps(word_tokenize(line ,engine='newmm')))

/usr/local/bin/wordcutpy

#!/usr/bin/env python
# * mode: python-mode *

from wordcut import Wordcut
import json
import sys

wordcut = Wordcut.bigthai()

for line in sys.stdin:
    line = line.strip()
    print(json.dumps(wordcut.tokenize(line)))

เรียกใช้งาน

ใน shell จะเรียกเดี่ยว ๆ ก็ได้แบบ

$ newmm < file1.txt > file1.ndjson

แต่ส่วนมากก็จะเรียกผ่าน for-loop เถื่อน ๆ แบบนี้

$ for x in *.txt; do newmm < $x > $x.ndjson; done

พอได้ไฟล์มาก็เอาไปใช้กับภาษาอื่นต่อได้

/usr/local/bin/wcnt

#!/usr/bin/env ruby
# * mode: ruby-mode *

require "json"
require "csv"

h = Hash.new(0)
while gets
  JSON.parse($_.chomp)
    .each{|w| h[w.chomp] += 1}
end
h.to_a.each{|row| puts CSV::generate_line(row)}

เวลาเรียกก็ไป pipe แบบนี้เลย

cat file1.txt | newmm | wcnt

เดี๋ยวนี้คนดี ๆ เขาคงใช้ Jupyter กันหมดแล้ว ผมก็พยายามฝึกอยู่เหมือนกัน แต่ยังไม่คล่อง บางทีก็เลยเอาแบบนี้ไปก่อน

I failed to download a list of PDFs by Wget, cURL, and Slimerjs. So I used Firefox and shell script.

First, I disable Pdfjs to force Firefox to download PDFs.

I wrote a shell script for opening a URL with PDF file, and then I use xdotool to close the Firefox window. For xdotool command, thank Jon G – Megaphone Tech for his comment at Unix Exchange.

#!/bin/sh

for url in `sort -u eng_pdf_urls.txt | grep -i pdf`
do
    echo $url
    firefox -P exp1 "$url" &
    sleep 10
    echo CLOSE
    xdotool search "Mozilla Firefox" windowactivate --sync key --window 0 --clearmodifiers alt+F4
    sleep 10
done

I didn't use headless because I don't know how to close it properly. I tried timeout command but Firefox detected it as an improper shutdown.

I ported my program for showing concurrency from Erlang to Elixir. I guess this is still not Idiomatic Elixir. 😅

defmodule Con1 do
  def log(p, i) when rem(i, 3000) == 0 do
    IO.puts "#{p} #{i}"
  end
  def log(_, _) do
    :ok
  end

  def cnt(_, 0) do
    :ok
  end
  def cnt(p, i) do
    log(p, i)
    cnt(p, i - 1)
  end

  def par_cnt(0) do
    :ok
  end
  def par_cnt(n) do
    spawn fn() -> cnt(n, 3000000) end
    par_cnt(n - 1)
  end
end

Crystal intentionally looks similar to Ruby. However, Crystal is a static typing programming language. A Thai word segmentation program written in Crystal, which is a CPU bound program, was found almost as fast as one written in Go.

I wondered whether its concurrency support was like what I expected. So I did this experiment. By spawning, Crystal did not create any new thread because it doesn't support multithreading parallelism yet. To support concurrency, Crystal has to be able to switch fibers when a fiber performs non-blocking IO operation.

require "socket"

def load(id, chan)
  puts "ID=#{id}; START"
  (id..11).each do 
    socket = TCPSocket.new("www.ku.ac.th", 80)
    socket.close
  end
  puts "ID=#{id}; FINISH"
  chan.send nil
end

def main
  chan = Channel(Nil).new
  (1..10).each{|i| spawn(load(i,chan))}
  # Wait
  (1..10).each{chan.receive}
end

main 

In this program, a task with the id with a smaller number creates a TCP socket repeatedly more times than a task with a larger number id. For example task#1 establishes a TCP socket for 11 times and task#10 create a TCP socket for one time only. So even task#1 started long before task#10, task#10 should finish before task#1.

ID=1; START
ID=2; START
ID=3; START
ID=4; START
ID=5; START
ID=6; START
ID=7; START
ID=8; START
ID=9; START
ID=10; START
ID=10; FINISH
ID=9; FINISH
ID=8; FINISH
ID=7; FINISH
ID=6; FINISH
ID=5; FINISH
ID=4; FINISH
ID=3; FINISH
ID=2; FINISH
ID=1; FINISH

The result was that task#1 finished after task#10 although task#1 started before task#10, show that Crystal can switch tasks. And the important thing is the code looks like regular Ruby code, without async-await nor promise nor callback.

หลายครั้งผมแค่อยากได้ Ruby ที่มันเร็ว ไม่ต้องแนว ไม่ต้อง functional ก็ได้ ไม่ต้อง minimal ก็ได้

Crystal เป็นภาษาโปรแกรมแบบ static type ความเร็วแบบ thread เดียว ผมเคยเอามาลองตัดคำ ถ้าไม่ optimize อะไรเลย ความเร็วไล่ ๆ กับ Go

ประเด็น hot ช่วงนี้คือถ้าใช้แบบ concurrent จะเป็นอย่างไร Crystal ไม่ได้ใช้ multithreading แน่ ๆ เพราะมันยังทำไม่เสร็จ 🤣 แต่ว่าตอนนี้มีพวก event loop และ fiber แน่ ๆ ดูคร่าว ๆ แล้วน่าจะทำงานคล้าย ๆ Node.js ได้โดยไม่ต้องเขียน async-await

code ต่อไปนี้ผมเขียนมาลองว่าจะเป็นแบบที่คิดจริงหรือไม่

require "socket"

def load(id, chan)
  puts "ID=#{id}; BEFORE"
  (id..11).each do 
    socket = TCPSocket.new("www.ku.ac.th", 80)
    puts "ID=#{id}; Blocking?=#{socket.blocking}"
    socket.close
  end
  puts "ID=#{id}; AFTER"
  chan.send nil
end

def main
  chan = Channel(Nil).new
  (1..10).each{|i| spawn(load(i,chan))}
  # Wait
  (1..10).each{chan.receive}
end

main 

code มันก็จะคล้าย ๆ Ruby หน่อย ผมเขียนให้ ถ้า id ยิ่งน้อยยิ่งรันนาน เพื่อที่จะดูว่างานที่ spawn มาทีหลังมันมันแซงมาเสร็จก่อนได้เปล่า อีกอย่างก็คือ print มาให้ดูด้สนว่าใช้ non-blocking IO

ID=1; BEFORE
ID=2; BEFORE
ID=3; BEFORE
ID=4; BEFORE
ID=5; BEFORE
ID=6; BEFORE
ID=7; BEFORE
ID=8; BEFORE
ID=9; BEFORE
ID=10; BEFORE
ID=1; Blocking?=false
ID=2; Blocking?=false
ID=3; Blocking?=false
ID=4; Blocking?=false
ID=5; Blocking?=false
ID=6; Blocking?=false
ID=7; Blocking?=false
ID=8; Blocking?=false
ID=9; Blocking?=false
ID=10; Blocking?=false
ID=1; Blocking?=false
ID=2; Blocking?=false
ID=3; Blocking?=false
ID=4; Blocking?=false
ID=5; Blocking?=false
ID=7; Blocking?=false
ID=6; Blocking?=false
ID=8; Blocking?=false
ID=9; Blocking?=false
ID=2; Blocking?=false
ID=1; Blocking?=false
ID=10; Blocking?=false
ID=10; AFTER
ID=4; Blocking?=false
ID=5; Blocking?=false
ID=3; Blocking?=false
ID=7; Blocking?=false
ID=6; Blocking?=false
ID=8; Blocking?=false
ID=9; Blocking?=false
ID=9; AFTER
ID=2; Blocking?=false
ID=1; Blocking?=false
ID=4; Blocking?=false
ID=5; Blocking?=false
ID=3; Blocking?=false
ID=7; Blocking?=false
ID=6; Blocking?=false
ID=8; Blocking?=false
ID=8; AFTER
ID=2; Blocking?=false
ID=1; Blocking?=false
ID=4; Blocking?=false
ID=5; Blocking?=false
ID=3; Blocking?=false
ID=7; Blocking?=false
ID=7; AFTER
ID=1; Blocking?=false
ID=2; Blocking?=false
ID=6; Blocking?=false
ID=4; Blocking?=false
ID=5; Blocking?=false
ID=3; Blocking?=false
ID=1; Blocking?=false
ID=2; Blocking?=false
ID=6; Blocking?=false
ID=6; AFTER
ID=4; Blocking?=false
ID=5; Blocking?=false
ID=5; AFTER
ID=3; Blocking?=false
ID=1; Blocking?=false
ID=2; Blocking?=false
ID=4; Blocking?=false
ID=4; AFTER
ID=3; Blocking?=false
ID=1; Blocking?=false
ID=2; Blocking?=false
ID=3; Blocking?=false
ID=3; AFTER
ID=1; Blocking?=false
ID=2; Blocking?=false
ID=2; AFTER
ID=1; Blocking?=false
ID=1; AFTER

อันนี้ก็จะเห็นได้เลยว่า ID=1 ที่เริ่มรันก่อนมันเสร็จทีหลังเลย สรุปก็คือใน Crystal ใช้ non-blocking IO กับ event loop ได้โดยที่ programmer ไม่ต้องทำอะไรพิเศษเลย ก็แค่เขียน ๆ ไปธรรมดา ในกรณีที่เป็น web server ส่วน spawn และ channel ก็ไม่ต้องเขียนเองด้วย

Go, Erlang, Elixir ทำอะไรได้มากกว่านี้ แต่ Crystal มันเป็นภาษาที่แทบไม่ต้องปรับตัวเลย สำหรับคนที่เคยเขียน Ruby งาน CPU bound ก็น่าจะเร็วกว่า Erlang แลพ Elixir ด้วย

ผมจะใช้คำศัพท์ thread และ process ในบริบทของ Linux และ *BSD นะครับ ใน OS อื่น ๆ บางตัวเรียกไม่เหมือนกัน

nginx และ thread pool

  • nginx ใช้ event loop ด้วย ใช้หลาย thread ด้วยครับ 1
  • ผมเข้าใจว่าใช้ event loop เดียวรับ request แล้วไปปล่อย task เข้า thread อื่นอีกที แล้วส่งผลคิน event loop

node.js cluster

  • node.js cluster แตก process (แทนที่จะแตก thread) ครับ มี process หลักมา accept incoming connection แล้วส่งต่อให้ process ลูกครับ2
  • แต่ว่าไม่แน่ใจว่ามี event loop ของแต่ละ process อีกทีหรือเปล่าครับ ผมเดาว่าน่าจะมีแต่ก็ไม่เคยลองดูจริง ๆ ครับ

Tokio

  • Tokio อย่างน้อยจะต่างจาก node cluster ตรงที่ไม่ได้แตก process ครับ แต่ว่าใช้แตก thread ในแง่นี้น่าจะคล้าย nginx มากกว่า node cluster
  • ข้อมูลอีกอย่างคือมี event loop เท่าจำนวน thread ที่แตกออกมาครับ แต่ส่วนนี้ผมยังไม่ได้ดูว่าแบ่งงานกันอย่างไรครับ

CGI

ส่วนพวก CGI คือแตก process (ไม่ใช่ thread) ทุกครั้งที่มี request ครับ

PHP

  • php-fpm แตก process มารับ request
  • mod_php บน Apache ก็แตก process เหมือนกันครับ เพราะว่า php ต้องการแบ่งแยกระดับ process
  • พวกนี้ก็จะทำให้เร็วขึ้นโดยใช้ prefork ไว้ก่อนทำให้เร็วขึ้นได้
  • ใช้ process pool ไม่ได้ fork หมดเหมือน CGI แต่ก็เปลือง RAM อยู่ดีครับ
  • นอกจากนั้นเพื่อที่จะทำให้เร็วขึ้นมันมี opcache ที่ compile โปรแกรมเก็บไว้ก่อนรับ request อยู่แล้วครับ

Gunicorn และ Meinheld

gnuicorn ใช้ process มาเป็น worker โดยที่ worker ที่ใช้ meinheld ข้างในก็ไปใช้ picoev เอามาทำ event loop อีกที