A day with Elixir

A day with Elixir

จากวันเสาร์ - อาทิตย์นี้พี่ @zkan ชวนมาทำ Hackathon กันที่ Pronto Tools เลยเป็นโอกาสเหมาะว่าจะทำอะไรที่มัน Out of comfort zone ซักหน่อยแล้วส่วนตัวผมอยากจะเขียน Elixir มานานหลายปีมากเลยเป็นที่มาของ Blog นี้ครับ

เอาจริงๆ จนจบวันนี้ผมยังไม่รู้เลยว่าที่ผมเขียนไปมันถูก / ผิด idiom ของ Elixir ไปมากแค่ไหน แต่แค่อยากจะมาพูดถึงสิ่งที่ประทับใจในตัวภาษาจากมุมมองของคนที่เขียนแต่ Python เป็นหลักในชีวิต

Project Structure

mix.exs

อยากแรกเลยที่เจอพอคิดว่าจะใช้ Elixir Implement คือ จะวางโครงสร้างมันยังไง จะเขียนเป็น script.ex เลย หรือต้องทำยังไง หลังจากลองผิดลองถูกอยู่ซักแปป ถึงรู้ว่าภาษามีสิ่งที่เรียกว่า Mix ซึ่งเป็น Build tools หลักของภาษาสามารถสร้างได้โดยใช้คำสั่ง

$ mix new <name>

ซึ่งพอ Generate ออกมาแล้วมันจะสร้าง structure project ให้เลยตามชื่อที่เราตั้ง ซึ่งมี mix.exs เป็น Project description file อารมณ์น่าจะคล้ายๆ package.json ของ Node สร้าง module เราให้ด้วยจะอยู่ใน lib/<name>.ex สร้างไฟล์เทสของ module เราให้ด้วยที่ test/<name>_test.exs รวมถึงสร้าง README.md ให้เราด้วยอัตโนมัติเลย

Task Runner

lib/mix/tasks/main.ex

คำถามต่อมาหลังจากสร้าง Project ขึ้นมาได้แล้วก็คือ แล้วจะรันมันยังไง? ผมพอจะรู้มาบ้างว่ามันมี iex (Interactive Elixir) ซึ่งเทียบได้ว่าเป็น REPL ของภาษานี้ก็ได้ แต่จะให้ iex มาสั่งรัน module เราทุกครั้งมันก็ค่อนข้างจะพิมพ์เยอะ Requirement ผมเองคืออยากได้ Task runner มาไว้รันคำสั่งให้ซึ่ง มันมีวิธีทำครับ แล้วง่ายมากด้วย

วิธีคือไปสร้าง Directory ตามนี้ครับ /lib/mix/tasks/ เสร็จแล้วข้างในนั้นสร้าง module มาตัวนึงชื่ออะไรก็ได้ในที่นี้ผมตั้งชื่อมันว่า main.ex ข้างใน module ก็ตามภาพข้างบนครับ จะเห็นว่ามัน use Mix.Task ประกอบกับ method run หลังจากนั้นใน method run ให้เราใส่ Method ที่เราจะรันลงไป แค่นี้ก็เรียบร้อย วิธีรันก็คือ

$ mix main

Formatter

ความเจ๋งของ mix project อีกอย่างก็คือมันมี Formatter ในตัวครับ​ ซึ่งผมก็ไม่รู้ว่ามันทำงานยังไงนะ แต่แค่เราสั่งคำสั่งข้างล่าง โค้ดเราก็จะจัดระเบียบให้เป็นไปในแนวทางเดียวกันละ ไม่ต้องลง module แยกอย่าง YAPF อะไรแบบนี้เลย

$ mix format

Configuration

แน่นอนว่าเวลาเราทำ App ซักแอพถ้ามีการต้องติดต่อกับ 3rd Parties มันหลีกเลี่ยงไม่ได้ครับที่เราจะต้องหาทางใช้ Secret บางอย่างเช่น​ Username, Password หรือ Access Token กับโค้ดของเรา ซึ่งปกติตอนผมเขียน Python ก็เก็บมันไว้ใน .env ให้ Pipenv มันอ่านออกมาให้ แต่ Elixir มีวิธีต่างออกไปครับ

วิธีของ Elixir คือเราจะสร้าง config file ขึ้นมา แล้วสร้าง config เก็บไว้เป็นกลุ่มๆ (อันนี้ก็ไม่รู้จะเรียกว่าไงเหมือนกัน) เสร็จแล้วในโค้ดของเราก็เรียกผ่าน Application.get_env(<group>, <key>) ก็เป็นอันเรียบร้อยครับ

Module

มาดูที่ Module ที่มัน Generate มาให้บ้าง ผมยอมรับเลยครับว่าแว็บแรกที่มองนี่คือ อ่อ มันก็ class นี่หว่า จริงๆ มันน่าจะเทียบกันไม่ได้โนะ แต่ Concept หลายอย่างก็ใกล้เคียงกัน อย่างแรกเลยที่ชอบคือ Attribute ครับถ้าเป็น Python เราก็เขียนมันไปโต้งๆ ใน class นะแหละเหมือนเป็นตัวแปรตัวนึง แต่กับ Elixir ตัว Module attribute เราต้องเพิ่ม @ ข้างหน้ามันครับ

Anonymous function

เอาจริงๆ ก็ไม่เคยคิดว่าจะได้เขียน Anonymous function บ่อยๆ เพราะส่วนตัวไม่ชอบเพราะอ่านไม่ค่อยรู้เรื่อง (ใน Python หนะ) แต่ใน Elixir นี่ผมค่อนข้างชอบทีเดียว เพราะมันคล้ายๆ กับ Arrow function ที่เขียนบ่อยๆ ใน JS เพียงแต่ว่ามันจะมี keyword fn เอาไว้ข้างหน้าไว้ประกาศว่ามันคือ function นะ แล้วมี end ปิดตรงท้ายด้วย หน้าตาก็ประมาณนี้ครับ

Pipe Operator ✨

Pipe Operator (|>) นี่น่าจะเป็นสิ่งที่ผมชอบที่สุดในตัวภาษานี้แล้ว และเป็นเหตุผลหลักให้อยากเขียนมาตลอดด้วย หลักการของมันง่ายๆ มากครับคือมันจะรับ return value ของ function ทางซ้าย (หรือก่อนหน้า) ของ Pipe แล้วเอามาเป็น First argument ของ function ทางขวา หรือถ้า function ตัวที่รับต้องรับ arguments มากกว่าหนึ่งตัว เราก็สามารถเขียน arguments ตัวต่อๆ ไปใส่ได้เลยครับ มันจะรู้เองว่า arguments ตัวแรกจะถูกยัดมาผ่าน Pipe

ผมพบว่าพอเราเริ่มใช้ Pipe บ่อยขึ้นเรื่อยๆ เราจะเริ่มมองทุกอย่างให้เป็น function ที่เล็กที่สุดครับ ที่เราทำแบบนั้นเพราะอยากจะ Chain ให้มันอยู่ใน Pipe แล้วมันรู้สึกดีมาก อ่านง่ายด้วย

Signils

อันนี้เป็นสิ่งใหม่มากในชีวิตครับ ความอยากเขียน Elixir มาหลายปีก็ไม่เคยทำให้เรามาพบเจอกันจนกระทั่งวันนี้ Signils นี่เป็นอะไรที่ทำให้เราเขียน string, regex ฯลฯ ได้ง่ายขึ้นมากครับ หน้าตาก็ประมาณนี้

String and IO

ขอบันทึกเก็บไว้หน่อยละกันว่า String ของ Elixir มีทุกอย่างครบเครื่องอย่างที่ String ในภาษาอะไรก็ตามควรจะมีเช่น Multiline string, String interpolation ให้ครบเครื่อง

ส่วน IO น่าจะเป็นวิธี basic ที่สุดในการ debug โนะ ซึ่งตอนแรกสุดเนี่ยผมนึกว่า IO.puts มันจะ support ทุกอย่างแต่พอเขียนไป อ่าวเห้ย ถ้าไม่ใช่ string ต้องใช้ IO.inspect ละก็ละเอียดไปอย่าง

Debugger

ถึงแม้ IO.inspect จะเป็นวิธี debug ที่ง่ายที่สุด แต่ผมยอมรับเลยว่าผมติด debugger ใน Python มาคือ import ipdb; ipdb.set_trace() แล้วพยายามอยากหาวิธีที่จะใช้ท่าเดียวกันได้ โชคดีที่ Elixir ก็มีอะไรบางอย่างคล้ายๆ กันคือ IEx.pry

วิธีการของมันก็คือ เราไปแปะ Snippet ไว้ตรงโค้ดที่เราต้องการจะหยุดมันเสร็จแล้วเราสั่ง iex -S <task> มันก็จะสั่งรัน task ที่เราสร้างไว้แล้วหยุดตรงที่เราแปะ debugger ไว้ครับ มีประโยชน์มากๆ

Conclusion

ถ้าจะมีอะไรที่รู้สึกชอบ จากการที่ใช้ภาษาใหม่ๆ Implement อะไรบางอย่างก็คือ มันได้แนวคิดใหม่ๆ ในการแก้ปัญหานี่แหละครับ มีประโยชน์มากๆ ตอนนี้เลยชอบเขียน utility function เล็กๆ เอาไว้แก้ปัญหาย่อยๆ แล้วเอามารวมกันเป็น Pipeline ก็แก้ปัญหาอะไรได้เร็วดี (เขียนเทสง่ายด้วย)

อ่อเกือบลืมบอก Hackathon โปรเจ็คเล็กๆ ผมนี่แค่ต้องการสร้างอีเมล์ Sprint Summary ตอนจบ Sprint นะครับ ปกติเราใช้ Milestone + Issue ในการทำงานที่ Pronto อยู่แล้ว แต่ถ้ามี Script นี้ก็ทำให้ลดเวลาการเขียนอีเมล์ลงไปได้บ้าง โค้ดของโปรเจ็คอยู่ที่นี่นะครับ สนใจก็เปิด PR มาแก้ได้ครับผมหรือใครจะ Fork ไปพัฒนาต่อผมก็ยินดีครับ

GitHub - yothinix/mssum: Milestone Summary - A Pronto’s hackathon project
Milestone Summary - A Pronto’s hackathon project. Contribute to yothinix/mssum development by creating an account on GitHub.

Original post at: https://yothinix.medium.com/a-day-with-elixir-dc41e21d68c6