Mo Lang เมื่อผมอยากได้ความรื่นรมย์ในการเขียนโปรแกรม
เมื่อก่อนผมเขียน Go เยอะมาก มันเป็นภาษาที่ดีและเรียบง่าย เริ่มง่าย ศึกษาง่าย ผมมักพูดเสมอว่า Go มันเหมือนมีดทำกับข้าวของจีนที่เรียบง่ายและทรงพลัง อย่างไรก็ตามหลังจากเขียนไปนานก็พบว่า
ทำไมมันน่าเบื่อจัง ยิ่งเขียนยิ่งรู้สึกว่ามันเป็นภาษาที่เหี่ยวแห้ง
ตัดสินใจหยุดเขียน Go
ผมพบว่าการเขียน Go มันทำให้ความรื่นรมย์ในการเขียนโค้ดหายไปเพราะว่าเราไม่สามารถเขียนโค้ดที่อธิบายความคิดของเราได้
package vanilla
import (
"errors"
"fmt"
"strings"
)
type User struct {
ID int
Name string
}
func FindUser(id int) (User, error) {
db := map[int]User{1: {ID: 1, Name: "Alice"}}
user, ok := db[id]
if !ok {
return User{}, errors.New("user not found")
}
return user, nil
}
func ValidateName(u User) (User, error) {
if len(u.Name) < 3 {
return User{}, errors.New("name too short")
}
return u, nil
}
func FormatOutput(u User) string {
return fmt.Sprintf("USER_%d: %s", u.ID, strings.ToUpper(u.Name))
}
func ExampleCorrected() {
user, err := FindUser(1)
if err != nil {
fmt.Println("Error:", err)
return
}
user, err = ValidateName(user)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println(FormatOutput(user))
}
เราจะเห็นว่าเนื้อหาของ ExampleCorrected มันดูเต็มไปด้วยของที่ผมไม่อยากอ่าน ผมก็รู้นะว่า ภาษามันถูกออกแบบมาให้ทำของบางอย่างให้ explicit และมันจะดีมากเลยถ้า!!!!! เราสามารถเลือกได้ว่าเราจะ explicit ตอนไหนและจะ implicit ตอนไหน ความน่าเบื่อของ if err != nil มันทำให้ผมถอยห่างจากภาษานี้ไปที่ละนิด ทีละนิด เพราะยิ่งเขียนไปก็ยิ่ง เหนือยกับสิ่งนี้และด้วยความเคารพใน principles ของภาษานี้ท้ายที่สุดผมก็หยุดเขียน Go ไปเลยเพราะไม่อยากบิดวัฒนธรรมอันดีงาม
กลับมาเขียน Scala
ระหว่างที่หยุดเขียน Go ไปก็กลับไปเขียนภาษา อย่าง Scala มากขึ้นเพราะรู้สึกว่า Go ช่วยเราได้มากในเรื่องของ Performance แต่สิ่งที่แลกมาคือ เราไม่เหลือความรื่นรมย์ ในการทำงานและเมื่อลองได้ตั้งใจเขียน Scala จริงๆก็พบว่าตัวภาษาเองมี ฟีเจอร์ที่ทำให้เราอธิบายความตั้งใจเราผ่าน code ออกมาได้อย่างตรงไปตรงมา โดยไม่มีของที่ไม่เกี่ยวข้องหลุดออกมาปนเช่น
def processFeed(
fetch: String => Option[String],
parse: String => Option[List[FeedItem]],
formatter: List[(String, String)] => String
)(url: String): Option[String] =
for
fetched <- fetch(url)
parsed <- parse(fetched)
formatted = formatter(parsed.map(item => (item.title, item.link)))
yield formattedนี่น่าจะเป็นสิ่งที่ผมตามค้นหา หลังจากนั้นก็ได้กลับไปเข้าใจ concept ต่างๆที่อยู่เบื้องหลังการออกแบบภาษานี้จนได้เข้าใจว่า ของที่ Scala มีนั้นจริงๆแล้วเราสามารถเอาไปเขียนได้ใน ภาษาไหนก็ได้เพราะมันเป็น pattern ที่อยู่ใน functional programming อย่างไรก็ตามเราจะทำออกมาได้แค่ไหนนั้นมันขึ้นอยู่กับพื้นฐานของภาษานั้นๆ ผมก็เลยได้พัฒนาคลาสใหม่ที่ชื่อว่า Refactoring to Functional Patterns ขึ้นมาเพื่อ อธิบายวิธีคิดที่เป็นขั้นเป็นตอนในการทำให้น้องๆเข้าในแนวคิดการแก้ปัญหาแบบ Functional และเมื่อทำไปสักพักก็เริ่มคิดว่า
ทำไมเราไม่สอนด้วย Go Lang แต่ว่า Go Lang เขามีมาตรฐานการเขียน ที่ดีอยู่แล้วถ้าเราจะไป เขียนแบบแหวกม่านประเพณีเขาน่าจะดูไม่งาม เราเรียกมันว่าเป้น Mo Lang เลยละกัน
กลับมาเขียน Mo Lang
ทำไมต้อง Mo Lang จริงๆไม่ได้มีอะไรซับซ้อน หลักๆเพราะว่าผมไปใช้ lib ที่มีน้องท่านหนึ่งมา comment ใน Facebook ว่าพี่ๆๆๆๆ ลองใช้ samber/mo (https://github.com/samber/mo )สิมันดีมากเลยนะผมก็เลยลองไปใช้ดูสิ่งที่พบคือ จากโค้ดที่ดูเหี่ยวแห้งในตัวอย่างด้านบนเราสามารถแปลงร่างมันให้เป็นแบบนี้ได้
package composition
import (
"fmt"
"strings"
"github.com/samber/mo"
"github.com/samber/mo/option"
)
type User struct {
ID int
Name string
}
// Module 1: Option - Replacing nil pointers
func FindUser(id int) mo.Option[User] {
db := map[int]User{1: {ID: 1, Name: "Alice"}}
user, ok := db[id]
if !ok {
return mo.None[User]()
}
return mo.Some(user)
}
// Module 2: Result - Railway Oriented Programming
func ValidateName(u User) mo.Option[User] {
if len(u.Name) < 3 {
return mo.None[User]()
}
return mo.Some(u)
}
func FormatOutput(u User) string {
return fmt.Sprintf("USER_%d: %s", u.ID, strings.ToUpper(u.Name))
}
func ExampleCorrected() mo.Option[string] {
// The "Pure" Pipeline style: Option[int] -> Option[User] (lookup) -> Option[User] (validate) -> Option[string]
output := option.Pipe3(
mo.Some(1),
option.FlatMap(FindUser),
option.FlatMap(ValidateName),
option.Map(FormatOutput),
)
return output
}ผมรู้สึกได้เลยว่า นี่มันรื่นรมย์มาก สิ่งนี้ทำให้ผมรู้สึกสนุกกับการเขียน Go อีกครั้ง ไม่สิไม่ใช้ Go มันคือ Mo Lang ผมได้ความรู้สึกของ ความสามารถในการอธิบายความตั้งใจ ของผมเข้าไปในโค้ดได้อย่างตรงอย่างที่ผมคิดในหัว
นี่แหละ Mo Lang ของผมภาษาที่ compile เร็วเหมือน Go และเขียนสนุกเหมือน Scala