Mo Lang เมื่อผมอยากได้ความรื่นรมย์ในการเขียนโปรแกรม

Share
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

Read more

เร็วแค่ไหนก็ไร้ค่า ถ้าไปผิดทาง

เร็วแค่ไหนก็ไร้ค่า ถ้าไปผิดทาง

อีกบทเรียนที่ผมได้จากหนังสือ Slack: Getting Past Burnout, Busywork, and the Myth of Total Efficiency ของ Tom DeMarco คือ ทำไมองค์กรใหญ่ ๆ ถึงยึดมั่นกับ Efficiency กันนัก Efficiency คืออะไร? Efficiency แปลว่า "ประสิทธิภาพ" ยกตัวอย่างเช่น

By Chokchai Phatharamalai
กฎของจั๊วะ

กฎของจั๊วะ

ปีนี้ที่อายุ 44 ผม Reflect ตัวเอง และพบว่าหลักการใช้ชีวิตของผมได้มาจากหนังสือ The Seven Habits of Highly Effective People เยอะมาก ใน Habit ทั้ง 7 นี้จะมีเกร็ดเล็กเกร็ดน้อยที่ผมไปศึกษามา แล้วค่อย ๆ เติมเข้าไปเพื่อทำให้ Habit นั

By Chokchai Phatharamalai
วงจรชีวิตในมุมมอง Existentialism และศิลปะแห่งการล้มเหลวในราคาถูก

วงจรชีวิตในมุมมอง Existentialism และศิลปะแห่งการล้มเหลวในราคาถูก

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

By Santi
วนเวียนแต่ไม่วนลูป: เมื่อชีวิตคือฟังก์ชัน Recursion และการเดินทางสู่พื้นที่ปลอดภัย

วนเวียนแต่ไม่วนลูป: เมื่อชีวิตคือฟังก์ชัน Recursion และการเดินทางสู่พื้นที่ปลอดภัย

ในโลกที่หมุนไปด้วยอัตราเร่งอย่างทุกวันนี้ หลายครั้งเรามักพบว่าตัวเองติดอยู่ท่ามกลางความสับสนยุ่งเหยิง ปัญหาบางอย่างในชีวิตไม่ได้มาในรูปแบบที่เรียบง่าย แต่กลับซ้อนทับกันเป็นชั้น ๆ เหมือนกล่องของขวัญใบยักษ์ที่พอเปิดเข้าไป ก็

By Santi