Monthly Archives: July 2016

How Clojure teaches me about side-effects

ดัดแปลงมาจากคอมเมนต์นี้ https://www.facebook.com/groups/919377878100706/permalink/1121787907859701/?comment_id=1121803797858112&reply_comment_id=1121826181189207

หลังจากที่ผมโพสว่า Clojure สอนผม ว่าผมจะอยู่ร่วมกับ side-effects ได้อย่างไร

พี่มาคอมเมนต์ Patraphong Sukhonpitumart ถามว่า

clojure สอนยังไง มีตัวอย่างมั้ยครับ

ผมเลยตอบว่า

ที่ผมพอจะนึกออกตอนนี้ ไม่ได้ exclusive สำหรับ clojure นะครับ

  • สอนให้ model domain เป็น value ไม่ใช่ object มันเหมาะกับแนวคิด bounderies (Functional Core, Imperative Shell) มาก
  • แยกที่เก็บ mutable data (atom) ให้ชัดเจน เราจะเรียกใช้แก้ไขมันเมื่อไหร่ก็ได้ แต่ก็จะบอกว่าพยายามให้มีให้น้อยที่สุด
  • ไม่มีคนทำ ORM แล้วประสบความสำเร็จ
  • Referential transparency จะใช้ไม่ได้ถ้ามี side-effects ฟังก์ชันอย่าง memoize ก็จะทำงานผิด ถ้าไม่สนใจเรื่องนี้
  • ความที่เราสามารถทำ side-effect ในฟังก์ชันได้ตามใจ แต่เค้าก็จะบอกว่าไม่ควรทำ เช่น ฟังก์ชันที่ใช้ update ค่าให้ atom (เพราะมันไม่การันตีว่าจะถูกเรียกแค่ครั้งเดียว)
  • ไม่สร้างความยุ่งยากให้เราหากเราจะทำ side-effects ง่ายๆ เช่น เราอยากจะ println เมื่อไหร่ก็ได้ ไม่โดน type บังคับ
  • พวก React wrapper ก็จะบอกให้เราระวังเรื่อง state โดยที่บางตัวก็ strict บางตัวก็ปล่อยหลวมๆ
  • def เป็น mutable เพราะมันไม่จำเป็นต้อง immutable
  • do, doseq ทำให้เรารู้ว่าเราทำ side-effects อยู่นะคือ ผมมีโอกาสเลือกอยู่เสมอว่าผมจะทำ side-effects ได้ แต่ผมก็จะเห็นชัดเจนว่าถ้าผมทำแล้วปัญหาที่ตามมาคืออะไร
Advertisements

Blending functional programming with imperative. Higher-level of abstraction or functional core.

ดัดแปลงและเพิ่มเติมจากคอมเมนต์คำถามในโพสนี้ https://www.facebook.com/nuttanart/posts/1015437440718608 เป็นคำถามจาก Supanat IBoss Potiwarakorn

ผมได้ยินคนพูดถึง functional core, imperative shell มาซักพักและ แต่ผมก็มักจะได้ยินว่า functional เนี่ยเป็น higher level abstraction เราแอบเอาลำดับขั้นตอนการทำงานต่างๆ ไปแอบไว้ภายใต้ declarative code ผมก็เลยยังรู้สึกสับสนอยู่เล็กน้อย (รู้สึกว่าเห้ย ถ้าเป็น shell มันต้องอยู่บนกว่าสิ)

เท่าที่ผมเข้าใจคือ monad เนี่ยมันคือเหมือน imperative shell ซึ่งจริงๆ แล้วมันก็คือ เรา แอบ state mutation เอาไว้ ไอสองเรื่องที่ผมพูดอ้างถึงบนนี่มันไม่ได้ขัดแย้งกัน แต่มันแค่อธิบายในคนละมุมกันถูกมั้ยฮะ

คำตอบของผม

จริงๆ เราคิดอะไรคล้ายๆ กันนะเนี้ย ผมก็เคยสงสัยเรื่องเดียวกันนี้เลย จำไม่ได้แล้วว่าตอนนั้นตอบตัวเองว่ายังไง แต่ถ้าพยายามตอบคำถามนี้ด้วยความเข้าใจในปัจจุบันจะตอบแบบนี้

ตอนนี้มีสองเรื่อง
a) functional เป็น declarative (higher-level abstraction) อยู่ด้านบน ส่วน imperative (lower-level abstraction) อยู่ด้านล่าง อย่างเช่นเราเขียน map,reduce โดยไม่ต้องวนลูปเอง
b) imperative อยู่ด้านบน functional อยู่ด้านล่าง ตามแนวคิด Functional Core, Imperative Shell เช่น เอา UI, http request, database ไว้ด้านบน หรืออย่าง Actor  model

เวลาเราเขียนโปรแกรมจริงๆ เราจะไม่ได้คำนึงถึงข้อ a) อะเพราะไม่ใช่หน้าที่เราจะต้องไปสนใจการทำงานตรงนั้น platform มีหน้าที่การันตีให้เรา ด้านล่างของเรามันไม่ functional อยู่แล้ว แต่ platform ทำให้เรารู้สึกว่าเราควบคุมทุกอย่างได้ในลักษณะ functional (input – output) แต่สำหรับ b) เราต้องสนใจเพราะเป็นสิ่งที่เราต้องเป็นคนสั่งให้มันทำ เพราะฉะนั้นแล้วในชั้นของเราที่ใช้ภาษาเขียนเพื่อแก้ปัญหาอื่นๆ เราคงโฟกัสที่ b) มากกว่า ส่วน a) สำหรับเราน่าจะสำคัญแค่ตอนที่เราอธิบายให้คนอื่นฟังว่า functional มันทำงานยังไงแค่นั้นมั้ง

จริงๆ แล้วเราอาจจะต้องไปเขียน imperative ด้านล่างบ้างก็ได้ แต่เราก็ควรห่อให้ imperative นั้นเรียบร้อยอยู่ภายในแล้ว expose ออกมาเป็นแค่ functional ให้ด้านบนใช้ ผมเชื่อว่าการที่ด้านล่างของเราเรียบง่ายและตรงไปตรงมาจะทำให้เราสามารถเข้าใจระบบโดยรวมของเราได้ง่ายกว่า จะเพิ่มฟีเจอร์ก็มั่นใจ จะ debug ก็ไล่ได้ง่าย

ผมชอบประโยคนี้ของ José Valim (ผู้สร้าง Elixir)

I often summarize functional programming as a paradigm that forces us to make the complex parts of our system explicit

ผมคิดว่าการที่เราเอา imperative ซึ่งเป็นส่วนที่ยากขึ้นมาไว้ด้านบน (ไม่ซ่อนอยู่ด้านล่าง) เป็นตัวอย่างที่ดีมากของการทำให้ complex parts explicit

ส่วนเรื่อง monad นี่ไม่รู้เลยอะ(เขียน clojure ไม่ต้องรู้จัก monad 😜) แต่ก็เคยได้ยินมาบ้างเหมือนกันที่ว่ามองมันเป็น imperative shell ได้

เรื่องสำคัญที่หนังสือ OOP ทั่วไปไม่ได้สอน

ดัดแปลงจาก Facebook โพสนี้ https://www.facebook.com/nuttanart/posts/10154374407186081

ซึ่งเป็นโพสต่อเนื่องจากโพสแนะนำหนังสือ 99 Bottles of OOP อันนี้ https://www.facebook.com/nuttanart/posts/10154374324641081

ถึงแม้ผมจะเชียร์หนังสือ 99 Bottles of OOP อย่างไร ผมยังรู้สึกว่ายังมีปัญหาสำคัญมากๆ อันหนึ่งที่ทำให้โค้ดเราเน่าและหนังสือเล่มนี้คงจะไม่ได้พูดถึง หนังสือเล่มอื่นๆที่ผมเคยได้อ่านมาก็ไม่ได้สอน คือ การอยู่ร่วมกันระหว่าง OO กับสิ่งเหล่านี้
– การเชื่อมต่อ database
– การทำ external call
– logging
– อ่าน/เขียนไฟล์
– UI
– ฯลฯ

อาจจะมีแนวคิด Clean Architecture ที่พูดถึงอยู่บ้าง เพียงแต่ว่างบางครั้งเราไปโฟกัสที่ตัว pattern และแต่ละ component มากเกินไปจนลืมว่าจริงๆ แล้วต้นเหตุของปัญหาของเราคืออะไร

ผมคิดว่าตราบใดที่สิ่งเหล่านี้ยังปะปนกันอยู่ในโค้ดของเราอยู่ เราจะรู้สึกว่าสิ่งที่หนังสือสอนเขียนโค้ดดีๆ เค้าสอนเนี้ย มันทำไม่เห็นจะทำได้จริงเลย

บางคนอาจจะคิดในใจว่า นี่มัน side-effects นี่หว่า ใช่แล้วครับ ผมคิดว่าอีกปัจจัยหนึ่งที่จะทำให้เราเขียนโค้ด OO ได้ดี ดันเป็นสิ่งที่ OO ไม่ได้สอน แต่มันอยู่ในแนวคิด functional programming ถ้ารู้ว่าจะจัดสิ่งเหล่านี้ให้อยู่ถูกที่ถูกทางได้อย่างไร มันจะทำให้เราจะมีพื้นที่ให้จัดโค้ด OO ให้ดีได้

ลองไปศึกษาเรื่อง side-effects ดูนะครับ แล้วจะพบว่าหลายๆ ปัญหาที่เราเจอกันอยู่ สาเหตุเกิดจากพวกมันนี่แหละ

Update: talk นี้อธิบายเพิ่มเติมได้ดีว่า side-effects คืออะไร และมันสำคัญอย่างไร