Objective C กับ Clojure (Lisp)

มิตรสหายท่านหนึ่งสังเกตว่า Objective C บางทีก็มีวงเล็บ ๆ เยอะ ๆ คล้าย ๆ Clojure (Lisp แบบหนึ่ง) เหมือนกัน เวลา pass message เช่น

[someObject doSomething]; (จากตัวอย่างของ Apple)

แต่ถ้าเป็น Lisp จะเขียนแบบนี้ครับ (do-something some-object)

หรือถ้ามี argument อื่น ๆ จะเป็นแบบนี้

[someObject doSomething withArg0:arg0 withArg1:arg1];

(do-something some-object arg0 arg1)

แต่ว่าจะทำให้คล้ายกว่านี้อีกก็ได้

(do-something some-object :with-arg0 arg0 :with-arg1 arg1)

แบบนี้ก็ได้

ภาษาไทยใน Mozilla Servo

ผมหวังว่า Servo จะถูกเอามาใช้ใน Firefox รุ่นถัด ๆ ไปตอนมัน stable แล้ว ก็เลยเลิกยุ่งกับ code ของ Firefox ตอนนี้เพราะเขียน C++ ไม่เป็น เขียน Rust ง่ายกว่า
เวลานี้คิดว่ามันน่าจะพอรันได้แล้ว compile จาก source code ก็ไม่ยากแล้ว มี nightly build ให้เล่นด้วย ก็เลยลองดูภาษาไทย ก็ตามคาดพอว่าเจ๊ง

servo_bug1
ก็เลยรายงาน issue นี้ไป #12193
รายงานไปสักพักก็คิดว่าก็คงต้องแก้เอง ก็เลยไปแกะ code ดู พบว่า Servo นี่เวลาจะแสดงผลข้อความมันไปเรียกใช้ Harfbuzz อีกที ผมก็เลยลองวาดรูปออกมาดู
harfbuzz_moz.svg.png
ที่ดู ๆ ไปก็ไม่ค่อยสื่อเท่าไหร่เลยขอให้คุณเทพพิทักษ์ ช่วยดูให้ซึ่งท่าก็อธิบายเพิ่มเติมตามนี้ ผมไม่แน่ใจว่าผมเข้าใจแค่ไหนต่อไปคงเข้าใจมากขึ้น Harfbuzz นี้เราส่งข้อความเข้าไป และก็ส่งฟอนท์เข้าไป มันจะบอกว่าได้คร่าว ๆ ว่าอักขระนั้นหน้าตาอย่างไร ซึ่งตัว ๆ พวกนี้เราจะไม่เรียกมันว่า character แล้วแต่เรียกว่า glyph แทน เพราะมันมีข้อมูลเรื่องรูปร่างหน้าตามเกี่ยวข้อง ไม่เหมือน character ที่แค่บอกว่ารหัสอะไร นอกจากนั้น Harfbuzz ยังจับกลุ่ม glyph สลับที่และรวมกันเป็น cluster ให้ด้วย แต่ว่า Harfbuzz ไม่ได้ทำเรื่องตัดบรรทัด (ตัดคำ) และไม่เปลี่ยน glyph เป็น Raster ให้

สำหรับ Servo แล้วเรื่องพวกนี้อยู่ในไฟล์ components/gfx/text/shaping/harfbuzz.rs
ที่ผมเข้าไปดูเพราะคิดว่ามันอาจจะมี bug ใน harfbuzz.rs ก็ได้ แต่พอลองตรวจสอบดูก็พบว่าปกติดี

นอกจาก Harfbuzz แล้วผมพบว่ามีจุดที่มันน่าเจ๊งอีกอย่างคือ Glyph store ที่พยายามจะเก็บ glyph ทั้งหลายไว้แบบบีบอัด ซึ่ง code อยู่ใน ไฟล์นี้ ก็เลยลองวาดรูปออกมาดูด้วย
servo_gl.png

ถ้าว่ากันคร่าว ๆ ก็คือ glyph store นี่มันพยายามจะเก็บ cluster ของ glyph ไว้ใน entry buffer ซึ่งเป็น integer ขนาด 4 byte ตัวเดียว ทำแบบนี้ได้มันก็จะประหยัดดี แต่ว่าพอ cluster มี glyph หลาย ๆ ตัวมันก็เก็บไม่ได้เลยก็เลยเอามาใส่ detail buffer แทน ที่เปลืองที่กว่า เวลาจะใช้งานก็ต้องมา binary search ใน detail lookup ก่อน ข้อมูลที่จะหาอยู่ไหน แล้วเอาข้อมูลใน detail lookup ไปเปิด detail buffer ดู

ซึ่ง bug ก็อยู่ตอนเปิด detail lookup นี่เอง ผมก็เลย patch แล้ว pull request เข้าไป ก็พบว่าแก้ปัญหาได้
servo_fix

แต่ว่า pull request ก็ยังไม่ผ่านเพราะมาติดว่ามี testcase ตัวหนึ่งมันดันไม่ผ่าน พอไปเปิด test case ดูมันก็ไม่น่าจะผ่านแต่แรกอยู่แล้ว เพราะไปเรียกใช้ offsetWidth แล้ว return 0 ตลอด ที่ผ่านตอนก่อน patch น่าจะเป็นเพราะ CSS มา set width มาบังเอิญตรงพอดี ก็เลยรายงาน issue เรื่อง offsetWidth เข้าไปอีก

นอกจากนั้นมาเปิด testcase ดูปรากฎว่าแสดงผลผิดออกมามีแต่กล่อง

myanmar2

ทั้ง ๆ ที่ควรจะออกมาเป็นอักษรพม่าสวยงามแบบนี้

myanmar1
ก็เลยรายงาน issue เรื่องอักษรพม่าไปด้วย ก็เลยมาเขียนเล่าให้กันฟังครับ เผื่อใครอยากให้ Servo เสร็จเร็ว ๆ ก็จะได้ช่วยกันทำได้โดยไม่ต้องไปแกะใหม่จาก ๐

reftest

reftest นี้มันประมาณว่าเขียน html มา 2 ไฟล์ ถ้าหากว่า pass ผลการ render ของทั้งสองไฟล์ต้องออกมาเหมือนกัน แต่ถ้า fail คือ render ออกมาแล้วไม่เหมือนกัน

อ้างอิง http://testthewebforward.org/docs/reftests.html

ไปซื้อปากกาหมึกซึม Pilot มา

 

27568044503_cabf97cb4c_o

พิกัด 13.726926,100.530962

ผมเดินซื้อปากกาหมึกซึมตามห้างดังส่วนมากก็เจอพวก Lamy Cross Parker อะไรอย่างนี้ ด้วยความอยากระลึกความหลังวัยเด็ก ก็คิดถึง Pilot ขึ้นมาแต่หาซื้อไม่เจอ เลยหาใน duckduckgo แล้วเมล์ไปถามว่าจะซื้อได้ที่ไหน ก็ได้คำตอบว่าต้องไป show room ของบริษัทปากกไพล็อต (ประเทศไทย) บนถนนสีลม ผมก็เลยไปซะงั้นไปก็ไม่ยากขึ้น MRT หรือ BTS ไปก็ได้ลงที่สถานีสีลมหรือศาลแดง รวมค่าเดินทางด้วยจริง ๆ นี่ซื้อ Lamy ได้แล้ว

พอออกจากสถานีก็เดินไปสีลมซอย 3 เงยหน้าดูหน่อยก็จะเห็นป้าย Pilot เลย หาเจอไม่ยาก เข้าไปก็มีทั้งปากกาลูกลื่นและหมึกซึมเต็มไปหมดเลย สิ่งที่เจ๋งของ Pilot คือหมึกมันถูกดีแบบขวดประมาณ 25 บาท แบบหลอดข้างในกล่องมี 5 หลอด กล่องละแค่ 28 บาท

เป็น show room ที่มีหมึกมีปากกาหมึกซึมให้ลองสัก 10 รุ่นได้ เท่าที่เขียน ๆ มาได้สักพักก็รู้สึกว่าเส้นมาก็คมดี แต่ว่าความลื่นก็ดูจะด้อย ๆ ไปสักหน่อยมีถากกระดาษกันอยู่บ่อย ๆ

ปากกามีราคาประมาณ 180 บาท จะไปถึงประมาณ 500 บาท แบบ 180 บาทนี้สูบหมึกได้อย่างเดียว เป็นพลาสติกสีๆ ส่วนพวก 400 บาทก็มักจะเป็นด้ามโลหะสีแบบเงิน ๆ ทอง ๆ ดำ ๆ รุ่นพวกนี้จะใช้หมึกหลอดได้ด้วย สูบหมึกเอาก็ได้
พวก Pilot Takuno อะไรแบบนี้ไม่มีนะครับ บางทีดูในเว็บของประเทศอื่นแล้วมาดู show room กรุงเทพ ฯ แล้วก็สงสัยเหมือนกันว่านี่มันบริษัทเดียวกันหรือเปล่า😛

โดยสรุปแล้วก็ได้ปากกาในตำนานในวัยเด็กมาใช้เรียบร้อย ^^

การ sign off ของทีมแปลไทยของ Mozilla

ปกติแล้วขั้นตอนการแปลก็คือเข้าไปที่ https://mozilla.locamotion.org/th/ แล้วก็เสนอคำแปล จากนั้นนักแปลหลักก็จะมาเลือกอีกที่ว่าคำแปลที่เสนอมาใช้ได้หรือเปล่าหรือจะใช้อันไหนดี
จากนั้น

คำแปลจาก locamotion จะถูกดูดมาเป็นช่วง ๆ เพื่อรอการอนุมัติ (sign off) อีกทีว่าจะใส่ไปกับ Firefox รุ่นที่จะส่งให้ชาวบ้านใช้หรือไม่

พอ sign off แล้วก็จะไปผ่านขึ้นตอน review อีกทีโดยใครก็ไม่ทราบ แล้วทีนี้ก็จะเข้าไปอยู่ใน HG repository ของ Mozilla แล้ว
ก่อนหน้านี้ทางทีมีปัญหาว่าแปลกันไปเยอะแล้ว ออกรุ่น 48 แล้ว แต่คำแปลที่ใช้ยังเป็นรุ่น 44 อยู่ เราจึงแก้ปัญหาด้วยการขอสิทธิที่จะ sign off เอง ผลก็คืออย่างที่เห็น คำแปลล่าสุดถูกส่งเข้า nightly กับ beta แล้ว

signoff

เดี๋ยวก็คงต้องมาลองดูกันว่า เวลาดาวน์โหลดมาใช้แล้วจะได้คำแปลว่าสุดจริง ๆ หรือเปล่า ถ้าขั้นตอนนี้ใช้ได้ ในรุ่น 49 เราคงได้เห็นคำแปลที่ปรับปรุงไปเยอะมาก

การพบปะ Mozillian เมื่อ 29/5/2559

เพื่อพลักดันวงการซอฟต์แวร์เสรี (Free software; open source software) ในประเทศไทย โดยเฉพาะอย่างยิ่งทำให้ Firefox ใช้ภาษาไทยได้เต็ม ในวันอาทิตย์ที่ 29 พฤษภาคม พ.ศ. 2559 เราจึงนัดพบกันที่ Hubba ขอขอบคุณ Hubba ที่ให้ใช้สถานที่ประชุมกลางเมืองที่สะดวกสะบาย

Panansonic Lumix G 14mm f/2.5 ASPH

ประเด็นหลัก ๆ ที่คุยกันก็คือเราจะเลือกคำแปลอย่างไร เพราะว่ามันเลือกได้หลายอย่าง เช่น

  • Sync นี้
    • อาจจะทัพศัพท์ไปเลยว่า Sync ก็ได้
    • หรือจะทับศัพท์แต่แปลงตัวอักษรให้เป็นภาษาไทย เช่น ซิงค์
    • หรือจะแปลเป็นว่า
      • ประสานเวลา
      • ผสานเวลา
      • ฯลฯ
  • หรือว่า asynchronous แบบไม่ย่อนี้ควรแปลเป็นไทยว่าอะไรดี

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

  • ท็อปยกตัวอย่างหนึ่งว่า File ที่ถูกนั้นแปลว่าแฟ้ม แต่มักทำให้คนหนุ่มสาวเข้าใจผิดว่าแฟ้มคือ Folder หรือ Directory ดังนั้นควรจะทับศัพท์ File ว่า “ไฟล์” ไปเลยจะทำให้เข้าใจง่ายกว่าแปลว่าแฟ้ม
  • ถ้าจำไม่ผิดท็อปยกตัวอย่างว่าปุ่ม save ที่เป็นรูป floppy disk เดี๋ยวนี้เด็ก ๆ ไม่รู้แล้วว่ามันคืออะไร ก็อาจจะเป็นการสื่อสารที่ไม่สมประโยชน์
  • น้าธนายกอีกตัวอย่างเวลาเลื่อนหน้าจอขึ้นลง แต่ละคนเข้าใจไม่เหมือนกัน บางคิดแบบการรูด scrollbar บางคนคิดแบบ touchscreen เวลาบอกว่า รูดขึ้น ให้แต่ละคนรูดก็อาจจะรูดไปคนละทางกัน

สำหรับข้อตกลงที่ชัดเจนคือในกรณีที่ Sync เป็นชื่อเฉพาะให้คง Sync ที่เป็นอักษรลาตินไว้ในคำแปลเลย ไม่ต้องแปลงเป็นอักษรไทย แต่หากไม่ใช่ชื่อเฉพาะให้ทับศัพท์ว่า “ซิงค์” น่าจะเข้าใจง่ายที่สุด

ประเด็นย่อย ๆ ก็มีผมโชว์เครื่องแปลภาษาโดยใช้โปรแกรม Moses ไปนิดหน่อย คาดว่าจะเอาขึ้นบน server ให้ทุกคนได้ใช้กันเร็ว ๆ นี้

P5295079.JPG

หลังจากที่คุยกันอย่างเคร่งเครียดนายทุนของเรา – พี่เด่นสินก็พาไปเลี้ยงอาหารญี่ปุ่นที่ Gateway เอกมัย

P5295108.JPG

ขอบคุณน้าธนาสำหรับภาพถ่ายทุกภาพครับ

ผู้เข้าร่วมพบปะครั้งนี้คือ:

  1. Teerapat Taechaiya (ท็อป)
  2. พี่เด่นสิน
  3. น้าธนา
  4. ผม
  5. Rob Burns

หากต้องการพูดคุยเพิ่มเติมพบกันได้ที่ https://www.facebook.com/groups/mozth/ ครับ

แบบว่า CPU เหลือ

เดี๋ยวนี้ (พ.ศ. ๒๕๕๙) คอมพิวเตอร์ของแต่ละคนก็มักจะมี CPU กันหลาย ๆ core ทีนี้พอเขียนโปรแกรมแบบเดิมก็อาจจะรู้สึกว่าใช้ไม่คุ้ม

สมมุติว่าไฟล์ชื่อ ja.txt เป็น text file ขนาดประมาณ 5 GiB ผมต้องการเอามานับว่ามีกลุ่มคำ 3 คำที่ติดกัน เช่น เจอ “กา|บิน|มา” ก็นับ 1 เจอ “นก|จิก|ปู” ก็นับ 1 เจอ “กา|บิน|มา” อีกทีก็นับเป็น 2 โดยที่การนับนี้มันก็ใช้พลัง CPU อยู่พอสมควร

ทั้งเครื่องก็มี CPU อยู่ 8 core ต้องการจะใช้ให้หมดจะทำอย่างไร
1. ใช้ Apache Hadoop และ Spark
2. เขียนโปรแกรมใหม่ด้วย Go และกระจายงานผ่าน Channel
3. ใช้ MPI
4. ใช้ pthread
อื่น ๆ

ท่าที่ว่ามันทั้งหมดมันก็ดีอยู่แต่ว่าใช้พลังมาก ผมก็เลยเขียน Ruby เหมือนเดิม แต่ใช้ GNU Parallel มาช่วย

เดิมที่สั่งงานแบบใช้ core เดียวทำแบบนี้ โดยที่ไฟล์ .kch คือไฟล์ database ของ KyotoCabinet ที่ใช้ hash table เก็บ

cat ja.txt | ruby ngram_kc.rb /mnt/data/ja.kch 

พอใช้ GNU Parallel ก็แค่แก้นิดหน่อย

cat ja.txt | parallel --pipe --block 500M 'ruby ngram_kc.rb /mnt/data/ja{#}.kch' 

แค่นี้มันก็จะส่ง text ไปนับแยกกันเลยเท่าจำนวน core ที่มีอยู่

แต่ประเด็นที่อาจจะมีปัญหาคือเราระบุไปว่า 500M มันจะไปหั่นไฟล์เราแบบ 500 MB พอดีแล้วไปตัดกลางคำพอดีหรือเปล่า คำตอบคือไม่ ใน man บอกทำนองว่า –block จะไม่ตัดผ่า record และจากที่อื่นก็ทราบอีกว่า 1 record โดยปริยายก็คือ 1 บรรทัด ก็คือไม่ตัดกลางบรรทัดแน่

พอสั่งแบบนี้เสร็จก็จะใช้ CPU ได้คุ้มขึ้นมาก และได้ไฟล์ ja1.kch ja2.kch ja3.kch … ทำให้มีงานงอกออกมานิดหน่อยคือต้องจับพวก hash table พวกนี้มา merge กัน ซึ่งผมจะละไว้ไม่เขียนถึงใน post นี้

zip กับชื่อ path ภาษาไทย โดยใช้ rubyzip

ผมได้รับไฟล์ .zip มา ก็ดีใจว่าอย่างน้อย ๆ ก็ใช่ไม่ .rar ที่เวลาเปิดจะลำบากขึ้นไปอีก

สมัยผมหนุ่ม ๆ เขาตั้งเชื่อไฟล์กันได้ 8 ตัวอักษาและ extension อีก 3 ตัว นอกจากนั้นก็ยังใส่ภาษาไทยไม่ได้ เดี๋ยวนี้ไม่ ใส่ภาษาไทย ใส่ช่องว่างได้หมด งานก็เข้าอีก

โปรแกรมแตกไฟล์ zip บางตัวมันก็ไม่สนใจภาษาไทยเลย อ่านแบบคิดว่าเป็น ASCII แบบ 8 bit ตลอด ๆ  ผมก็เลยลองเอา rubyzip มาเล่นดู แล้ว encode ชื่อไฟล์ใหม่เป็น UTF-8 ก็พบว่าเจ้ง เพราะว่าบางทีชื่อไฟล์ก็ไม่ใช่ UTF-8

เลยเปลี่ยนมาใช้ TIS-620 ก่อนแล้วแปลงเป็น UTF-8 อีกที ก็อ่านได้เพิ่มขึ้น แต่ก็ยังมีตัวอักษรบางตัวแปลงไม่ได้

จึงเปลี่ยนจาก TIS-620 มาใช้ Windows 874 แทน ก็ยังมีปัญหาเหลืออีกคือบางทีชื่อไฟล์เป็น UTF-8 อยู่แล้ว ก็เลยต้องมาเขียน check ก่อน


require 'zip'

def decode_path_name(name)
  if name.force_encoding("UTF-8").valid_encoding?
    name.force_encoding("UTF-8")
  else
    name.force_encoding("WINDOWS-874").encode("UTF-8")
  end
end

Zip::File.open(ARGV[0]) do |zip_file|  
  zip_file.each do |entry|
    name = decode_path_name(entry.name)
    entry.extract(name)
  end
end

พอจัดไปครบตามนี้ก็พบว่าแตกได้ทุกไฟล์แล้ว

 

ใช้ CoreNLP ผ่าน Clojure

ผมเริ่มหัดเขียน Java มาตั้งแต่ราว ๆ พ.ศ. ๒๕๔๐ แต่ว่าจนกระทั่งเดี๋ยวนี้ก็ยังรู้สึกว่ายังเขียนไม่เป็น แต่ CoreNLP ที่เอามาใช้ตัดคำก็ได้ บอกชนิดของคำก็ได้ วิเคราะห์ความสัมพันธ์ของคำในประโยคก็ได้ ดันเขียนด้วย Java

การไปเรียกใช้หลัก ๆ มี 2 วิธีคือเรียกตรง ๆ กับเรียกผ่าน HTTP server ผมไม่อยากเรียกผ่าน HTTP server เพราะขี้เกียจไป start server ก่อน ทางออกก็เลยเป็นการเรียกใช้ผ่าน Clojure

API แบบเรียกตรง ๆ ของ CoreNLP มี  2 แบบคือ Simple กับอีกแบบคือ Advance (มั้ง) แน่นอนผมเลือก Simple

;; Copyright (c) 2016, Vee Satayamas
;; All rights reserved.
;;
;; License: GPLv3 http://www.gnu.org/licenses/gpl-3.0.html

'{:dependencies [[org.clojure/clojure "1.8.0"]
                 [edu.stanford.nlp/stanford-corenlp "3.6.0"]
                 [edu.stanford.nlp/stanford-corenlp "3.6.0" :classifier "models"]
                 [com.google.protobuf/protobuf-java "2.6.1"]
                 [org.slf4j/slf4j-log4j12 "1.7.21"]]}

(require '[clojure.pprint :refer [pprint]])
(import edu.stanford.nlp.simple.Document)

(let [doc (Document. "I saw her. She met me.")]
  (pprint (.sentences doc)))

อันนี้เรียกด้วย inlein นะครับ เพราะว่าผมยัด dependencies มาไว้ใน source code เลย ส่วนที่วุ่นวายที่สุดก็คือ dependencies นี่ล่ะครับ ที่เหลือก็แค่สร้าง object ของ Document ขึ้นมา แล้วเรียก method .sentences มันก็คืน instance ของ “ประโยค” ออกมาให้เลย