ตลาดสด if err != nil ใน Go
Go เป็นภาษาที่ไม่สามารถทำ partial apply ได้โดยธรรมชาติ ... ทำให้ การลดรูป function ต่างๆให้มาเป็น unary function เป็นของแปลกสำหรับคนเขียน Go ฟังก์ชั่นของพวกเขาก็จะรับของเยอะและ compose ยาก ส่งผลให้ การทำ function composition เป็นเรื่องนอกกรอบมากสำหรับชาว Go - ส่วนผมนั้นสาย Mo Lang เป็นพวกทนไม่ได้เลยเขียน CurryF เองเลยเขามี Generic ให้แล้วนี่ จะมี CurryF2, CurryF3 และ CurryF4 เขียนเสร็จแล้วก็กลายเป็น Mo Lang เลย
// Generic currying function for any func(A, B) C
func curryF2[A, B, C any](f func(A, B) C, a A) func(B) C {
return func(b B) C {
return f(a, b)
}
}
func main() {
add2 := curryF2(add, 2)
multiply3 := curryF2(multiply, 3)
// Compose functions
mr := option.Pipe2(
mo.Some(2),
option.Map(add2),
option.Map(multiply3),
)
//Switch to Result for error handling
if mr.IsSome() {
result := mr.OrElse(0)
fmt.Println("Result of composed function:", result)
}else{
println("No result")
}
}ความ return (value, err) ของ Go ทำให้มันทำ function composition ยากเข้าไปอีกเพราะ เพราะเราต้อง if err != nil กันทุกที่ที่มีการทำ multiple return อันนี้เป็นดั่ง กำแพงหินผาสำหรับงานบางประเภทที่ต้องการการอ่าน code ให้เป็นลำดับขั้นตอน แต่เราก็ if err != nil กันไปจนมือหงิกเพราะเขาว่ามันเป็น standard !!!!!!!! ไอ้การทำ multiple return ที่ไม่ใช้ tuple มันก็เลยเป็นงานยาก เพราะเราไม่สามารถทำ f(g(x)) = f.g(x) เลยเพราะ Go เขาเลือกที่จะให้ เป็น f(x, err) ดังนั้น Go เลยเป็นภาษาที่ โล้งเล้ง โหวกเหวก โวยมาก ... "เอ้ยทำนี้หน่อย เอ้า err ว่ะ จัดการดิ ตรงนี้ เดี๋ยวนี้เลยนะ เอาให้จบตรงนี้ เอ้าทำงานต่อ เห้ยยย err อีกแล้ว อ่านโค้ดแล้วเหมือนไปเดินตลาดสด และถ้า execution pipeline ยาว 7-8 ขั้นเราก็ต้องมานั่งอ่าน ความ โหวกเหวก โวยวาย แบบนี้ไปเรื่อยๆ ทั้งที่ บางที่เราอยากรู้ทีเดียวที่ปลายทางว่า ผล คืออะไร ดังนั้น สาย Mo Lang ก็เลยเขียน Wrapper เองบ้างได้แต่ก็ไม่ได้ ออกไปไกลมาก แต่ซ่อนความโหวกเหวกไว้ให้พองาม ผมอยากเดิน super market บ้างครับ เงียบๆ เก็บเงินทีเดียวตอนออก โลกนี้ไม่ต้องการตลาดสดทุกที่ครับพี่
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, error) {
return fmt.Sprintf("USER_%d: %s", u.ID, strings.ToUpper(u.Name)), nil
}
// Result represents a value of type T or an error.
type Result[T any] struct {
Value T
Err error
}
func AndThen[T any, U any](fn func(T) (U, error), r Result[T]) Result[U] {
if r.Err != nil {
return Result[U]{Err: r.Err} // Carry the error forward
}
val, err := fn(r.Value)
return Result[U]{Value: val, Err: err}
}
func ExampleCorrected() {
input := 123
initial := Result[int]{Value: input}
mayBeUsr := AndThen(FindUser, initial)
mightValid := AndThen(ValidateName, mayBeUsr)
seemFinal := AndThen(FormatOutput, mightValid)
if seemFinal.Err != nil {
fmt.Println("Error:", seemFinal.Err)
} else {
fmt.Println("Result:", seemFinal.Value)
}
}