เริ่มต้นใช้งาน Elm Programming Language

เผยแพร่แล้ว: 2022-03-11

เมื่อหัวหน้าผู้พัฒนาโครงการที่น่าสนใจและสร้างสรรค์มากแนะนำให้เปลี่ยนจาก AngularJS เป็น Elm ความคิดแรกของฉันคือ: ทำไม?

เรามีแอปพลิเคชัน AngularJS ที่เขียนอย่างดีซึ่งอยู่ในสถานะที่มั่นคง ได้รับการทดสอบอย่างดี และได้รับการพิสูจน์แล้วในการผลิต และ Angular 4 ซึ่งเป็นการอัพเกรดที่คุ้มค่าจาก AngularJS อาจเป็นทางเลือกที่เป็นธรรมชาติสำหรับการเขียนใหม่ — React หรือ Vue ก็สามารถทำได้เช่นกัน Elm ดูเหมือนภาษาเฉพาะโดเมนแปลก ๆ ที่ผู้คนแทบไม่เคยได้ยิน

ภาษาโปรแกรม Elm

นั่นคือก่อนที่ฉันจะรู้อะไรเกี่ยวกับเอล์ม ด้วยประสบการณ์บางอย่าง โดยเฉพาะหลังจากเปลี่ยนจาก AngularJS มาใช้ ฉันคิดว่าฉันสามารถให้คำตอบว่า "ทำไม"

ในบทความนี้ เราจะพูดถึงข้อดีและข้อเสียของ Elm และแนวคิดที่แปลกใหม่บางส่วนนั้นเหมาะกับความต้องการของนักพัฒนาเว็บส่วนหน้าอย่างไร สำหรับบทความเกี่ยวกับภาษา Elm แบบกวดวิชาเพิ่มเติม คุณสามารถดูโพสต์บล็อกนี้

Elm: ภาษาการเขียนโปรแกรมที่ใช้งานได้จริง

หากคุณเคยชินกับการเขียนโปรแกรมด้วย Java หรือ JavaScript และรู้สึกว่ามันเป็นวิธีการเขียนโค้ดที่เป็นธรรมชาติ การเรียนรู้ Elm ก็เหมือนกับการล้มลงในโพรงกระต่าย

สิ่งแรกที่คุณจะสังเกตเห็นคือรูปแบบไวยากรณ์แปลก ๆ ไม่มีเครื่องหมายปีกกา ลูกศรและสามเหลี่ยมจำนวนมาก

คุณอาจเรียนรู้ที่จะใช้ชีวิตโดยปราศจากเครื่องหมายวงเล็บปีกกา แต่คุณจะกำหนดและวางบล็อกของโค้ดได้อย่างไร หรือ for loop อยู่ที่ไหนหรือลูปอื่นสำหรับเรื่องนั้น? แม้ว่าขอบเขต let ชัดเจนสามารถกำหนดได้ด้วยบล็อกการอนุญาต แต่ไม่มีบล็อกในความหมายแบบคลาสสิกและไม่มีการวนซ้ำเลย

Elm เป็นภาษาเว็บไคลเอ็นต์ที่ทำงานได้อย่างหมดจด มีการพิมพ์ที่ชัดเจน มีปฏิกิริยาตอบสนอง และขับเคลื่อนด้วยเหตุการณ์

คุณอาจเริ่มสงสัยว่าการเขียนโปรแกรมสามารถทำได้ด้วยวิธีนี้หรือไม่

ในความเป็นจริง คุณสมบัติเหล่านี้รวมกันเพื่อให้คุณมีกระบวนทัศน์อันน่าทึ่งในการเขียนโปรแกรมและพัฒนาซอฟต์แวร์ที่ดี

ใช้งานได้จริง

คุณอาจคิดว่าโดยใช้ Java หรือ ECMAScript 6 เวอร์ชันใหม่กว่า คุณสามารถเขียนโปรแกรมเชิงฟังก์ชันได้ แต่นั่นเป็นเพียงพื้นผิวของมัน

ในภาษาการเขียนโปรแกรมเหล่านั้น คุณยังคงสามารถเข้าถึงคลังแสงของโครงสร้างภาษาและการล่อลวงให้หันไปใช้ส่วนที่ไม่ทำงานของมัน จุดที่คุณสังเกตเห็นความแตกต่างคือเมื่อคุณไม่สามารถทำอะไรได้นอกจากการเขียนโปรแกรมเชิงฟังก์ชัน เมื่อถึงจุดนั้นในที่สุดคุณจะเริ่มรู้สึกว่าการเขียนโปรแกรมฟังก์ชั่นที่เป็นธรรมชาติสามารถทำได้

ใน Elm เกือบทุกอย่างเป็นฟังก์ชัน ชื่อเรกคอร์ดคือฟังก์ชัน ค่าประเภทยูเนี่ยนคือฟังก์ชัน ทุกฟังก์ชันประกอบด้วยฟังก์ชันบางส่วนที่ใช้กับอาร์กิวเมนต์ แม้แต่ตัวดำเนินการเช่นบวก (+) และลบ (-) ก็เป็นฟังก์ชัน

เพื่อประกาศให้ภาษาโปรแกรมทำงานได้อย่างหมดจด มากกว่าการมีอยู่ของโครงสร้างดังกล่าว การไม่มีสิ่งอื่นใดมีความสำคัญยิ่ง เมื่อนั้นคุณสามารถเริ่มคิดได้อย่างมีประสิทธิภาพ

Elm ถูกจำลองตามแนวคิดที่เป็นผู้ใหญ่ของการเขียนโปรแกรมเชิงฟังก์ชัน และคล้ายกับภาษาที่ใช้งานได้อื่นๆ เช่น Haskell และ OCaml

พิมพ์อย่างยิ่ง

หากคุณเขียนโปรแกรมด้วย Java หรือ TypeScript คุณจะรู้ว่าสิ่งนี้หมายถึงอะไร ตัวแปรทุกตัวต้องมีประเภทเดียวเท่านั้น

แน่นอนว่ามีความแตกต่างบางอย่าง เช่นเดียวกับ TypeScript การประกาศประเภทเป็นทางเลือก หากไม่ปรากฏก็จะมีการอนุมาน แต่ไม่มีประเภท "ใด ๆ "

Java รองรับประเภททั่วไป แต่ในทางที่ดีขึ้น Generics ใน Java ถูกเพิ่มในภายหลัง ดังนั้นประเภทจึงไม่เป็นแบบทั่วไป เว้นแต่จะระบุไว้เป็นอย่างอื่น และเพื่อที่จะใช้มัน เราต้องการไวยากรณ์ <> ที่น่าเกลียด

ใน Elm ประเภทเป็นแบบทั่วไปเว้นแต่จะระบุไว้เป็นอย่างอื่น มาดูตัวอย่างกัน สมมติว่าเราต้องการวิธีการที่ใช้รายการประเภทใดประเภทหนึ่งและส่งกลับตัวเลข ใน Java มันจะเป็น:

 public static <T> int numFromList(List<T> list){ return list.size(); }

และในภาษาเอล์ม:

 numFromList list = List.length list

แม้ว่าจะเป็นทางเลือก ฉันขอแนะนำอย่างยิ่งให้คุณเพิ่มการประกาศประเภทเสมอ คอมไพเลอร์ Elm จะไม่ยอมให้ดำเนินการกับประเภทที่ไม่ถูกต้อง สำหรับมนุษย์ การทำผิดพลาดนั้นง่ายกว่ามาก โดยเฉพาะอย่างยิ่งในขณะที่เรียนภาษา ดังนั้นโปรแกรมด้านบนที่มีคำอธิบายประกอบประเภทจะเป็น:

 numFromList: List a -> Int numFromList list = List.length list

อาจดูผิดปกติในตอนแรกที่จะประกาศประเภทในบรรทัดที่แยกจากกัน แต่หลังจากผ่านไประยะหนึ่งก็เริ่มดูเป็นธรรมชาติ

ภาษาไคลเอ็นต์ของเว็บ

ความหมายง่ายๆ คือ Elm คอมไพล์เป็น JavaScript ดังนั้นเบราว์เซอร์จึงสามารถดำเนินการได้บนหน้าเว็บ

เนื่องจากไม่ใช่ภาษาวัตถุประสงค์ทั่วไปเช่น Java หรือ JavaScript ที่มี Node.js แต่เป็นภาษาเฉพาะโดเมนสำหรับเขียนส่วนไคลเอ็นต์ของเว็บแอปพลิเคชัน ยิ่งไปกว่านั้น Elm ยังรวมทั้งการเขียนตรรกะทางธุรกิจ (สิ่งที่ JavaScript ทำ) และส่วนการนำเสนอ (สิ่งที่ HTML ทำ) ทั้งหมดนี้ในภาษาที่ใช้งานได้ภาษาเดียว

ทั้งหมดนี้ทำในลักษณะเดียวกับเฟรมเวิร์กที่เรียกว่า The Elm Architecture

ปฏิกิริยา

สถาปัตยกรรม Elm เป็นเฟรมเวิร์กเว็บแบบโต้ตอบ การเปลี่ยนแปลงใดๆ ในโมเดลจะแสดงขึ้นทันทีบนเพจ โดยไม่มีการปรับเปลี่ยน DOM อย่างชัดเจน

ด้วยวิธีนี้จะคล้ายกับ Angular หรือ React แต่เอล์มก็ทำในแบบของตัวเองเช่นกัน กุญแจสำคัญในการทำความเข้าใจพื้นฐานของมันอยู่ในลายเซ็นของฟังก์ชั่น view และ update :

 view : Model -> Html Msg update : Msg -> Model -> Model

มุมมอง Elm ไม่ใช่แค่มุมมอง HTML ของโมเดลเท่านั้น เป็น HTML ที่สามารถสร้างข้อความประเภท Msg โดยที่ Msg เป็นประเภทสหภาพที่คุณกำหนด

เหตุการณ์หน้ามาตรฐานสามารถสร้างข้อความได้ และเมื่อมีการสร้างข้อความ Elm จะเรียกใช้ฟังก์ชันการอัปเดตภายในด้วยข้อความนั้น ซึ่งจะอัปเดตโมเดลตามข้อความและรูปแบบปัจจุบัน และโมเดลที่อัปเดตจะแสดงผลภายในไปยังมุมมองอีกครั้ง

ขับเคลื่อนด้วยเหตุการณ์

เช่นเดียวกับ JavaScript Elm นั้นขับเคลื่อนด้วยเหตุการณ์ แต่ต่างจาก ตัวอย่างเช่น Node.js ซึ่งมีการเรียกกลับแต่ละรายการสำหรับการดำเนินการแบบอะซิงโครนัส เหตุการณ์ Elm จะถูกจัดกลุ่มเป็นชุดข้อความที่ไม่ต่อเนื่อง ซึ่งกำหนดไว้ในประเภทข้อความเดียว และเช่นเดียวกับประเภทสหภาพใด ๆ ข้อมูลที่ค่าประเภทแยกกันสามารถเป็นอะไรก็ได้

มีแหล่งที่มาของเหตุการณ์สามแหล่งที่สามารถสร้างข้อความได้: การกระทำของผู้ใช้ในมุมมอง Html การดำเนินการคำสั่ง และเหตุการณ์ภายนอกที่เราสมัครรับข้อมูล นั่นเป็นเหตุผลที่ทั้งสามประเภท Html , Cmd และ Sub มี msg เป็นอาร์กิวเมนต์ และประเภท msg ทั่วไปต้องเหมือนกันในคำจำกัดความทั้งสาม—แบบเดียวกับที่ให้มากับฟังก์ชันการอัพเดท (ในตัวอย่างก่อนหน้านี้ เป็นประเภท Msg ด้วยตัวพิมพ์ใหญ่ M) โดยที่การประมวลผลข้อความทั้งหมดจะถูกรวมศูนย์

ซอร์สโค้ดของตัวอย่างที่สมจริง

คุณสามารถค้นหาตัวอย่างเว็บแอปพลิเคชัน Elm ฉบับสมบูรณ์ได้ในที่เก็บ GitHub นี้ แม้ว่าจะเรียบง่าย แต่แสดงฟังก์ชันส่วนใหญ่ที่ใช้ในการเขียนโปรแกรมไคลเอ็นต์ทุกวัน: การดึงข้อมูลจากปลายทาง REST การถอดรหัสและการเข้ารหัสข้อมูล JSON การใช้มุมมอง ข้อความ และโครงสร้างอื่นๆ การสื่อสารด้วย JavaScript และทั้งหมดที่จำเป็นในการคอมไพล์และแพ็กเกจ รหัส Elm พร้อม Webpack

ตัวอย่างเว็บ ELM

แอปพลิเคชั่นแสดงรายการผู้ใช้ที่ดึงมาจากเซิร์ฟเวอร์

สำหรับกระบวนการติดตั้ง/สาธิตที่ง่ายขึ้น เซิร์ฟเวอร์ dev ของ Webpack จะใช้สำหรับทั้งการบรรจุทุกอย่าง รวมทั้ง Elm และการแสดงรายชื่อของผู้ใช้

ฟังก์ชันบางอย่างอยู่ใน Elm และบางส่วนใน JavaScript สิ่งนี้ทำโดยเจตนาด้วยเหตุผลสำคัญประการหนึ่ง: เพื่อแสดงความสามารถในการทำงานร่วมกัน คุณอาจต้องการลองใช้ Elm เพื่อเริ่มต้น หรือค่อยๆ เปลี่ยนโค้ด JavaScript ที่มีอยู่หรือเพิ่มฟังก์ชันการทำงานใหม่ในภาษา Elm ด้วยความสามารถในการทำงานร่วมกัน แอปของคุณยังคงทำงานกับทั้งโค้ด Elm และ JavaScript นี่อาจเป็นแนวทางที่ดีกว่าการเริ่มต้นแอปทั้งหมดตั้งแต่เริ่มต้นใน Elm

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

โค้ดนี้ยังใช้แนวคิดและเทคนิคบางอย่างที่อธิบายไว้ในหัวข้อถัดไป

การประยุกต์ใช้แนวคิด Elm

ให้เราพูดถึงแนวคิดที่แปลกใหม่ของภาษาโปรแกรม Elm ในสถานการณ์จริง

ประเภทสหภาพ

นี่คือทองคำบริสุทธิ์ของภาษาเอล์ม จำสถานการณ์เหล่านั้นทั้งหมดเมื่อจำเป็นต้องใช้ข้อมูลที่แตกต่างกันเชิงโครงสร้างกับอัลกอริธึมเดียวกันหรือไม่ เป็นเรื่องยากเสมอที่จะจำลองสถานการณ์เหล่านั้น

นี่คือตัวอย่าง: ลองนึกภาพว่าคุณกำลังสร้างเลขหน้าสำหรับรายการของคุณ ในตอนท้ายของแต่ละหน้า ควรมีลิงก์ไปยังหน้าก่อนหน้า ถัดไป และทุกหน้าตามหมายเลข คุณจัดโครงสร้างเพื่อเก็บข้อมูลของลิงค์ที่ผู้ใช้คลิกได้อย่างไร?

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

ใน Elm มันง่ายมาก เราจะกำหนดประเภทของสหภาพ:

 type NextPage = Prev | Next | ExactPage Int

และเราใช้เป็นพารามิเตอร์สำหรับหนึ่งในข้อความ:

 type Msg = ... | ChangePage NextPage

สุดท้าย เราอัปเดตฟังก์ชันให้มี case ให้ตรวจสอบประเภทของ nextPage :

 update msg model = case msg of ChangePage nextPage -> case nextPage of Prev -> ... Next -> ... ExactPage newPage -> ...

มันทำให้สิ่งต่าง ๆ สง่างามมาก

การสร้างฟังก์ชันแผนที่หลายรายการด้วย <|

โมดูลจำนวนมากมีฟังก์ชัน map โดยมีตัวแปรหลายแบบเพื่อใช้กับอาร์กิวเมนต์จำนวนต่างกัน ตัวอย่างเช่น List มี map , map2 , … , สูงสุด map5 แต่ถ้าเรามีฟังก์ชันที่รับอาร์กิวเมนต์ได้ 6 อาร์กิวเมนต์ล่ะ? ไม่มี map6 แต่มีเทคนิคที่จะเอาชนะสิ่งนั้น มันใช้ <| ทำหน้าที่เป็นพารามิเตอร์ และฟังก์ชันบางส่วน โดยมีการใช้อาร์กิวเมนต์บางส่วนเป็นผลลัพธ์ระดับกลาง

เพื่อความง่าย สมมติว่า List มีเฉพาะ map และ map2 และเราต้องการใช้ฟังก์ชันที่รับสามอาร์กิวเมนต์ในสามรายการ

นี่คือลักษณะการใช้งาน:

 map3 foo list1 list2 list3 = let partialResult = List.map2 foo list1 list2 in List.map2 (<|) partialResult list3

สมมติว่าเราต้องการใช้ foo ซึ่งเพียงแค่คูณอาร์กิวเมนต์ที่เป็นตัวเลข กำหนดดังนี้:

 foo abc = a * b * c

ดังนั้นผลลัพธ์ของ map3 foo [1,2,3,4,5] [1,2,3,4,5] [1,2,3,4,5] คือ [1,8,27,64,125] : List number .

ลองแยกแยะสิ่งที่เกิดขึ้นที่นี่

อย่างแรก ใน partialResult = List.map2 foo list1 list2 foo ถูกนำไปใช้กับทุกคู่ใน list1 และ list2 บางส่วน ผลลัพธ์คือ [foo 1 1, foo 2 2, foo 3 3, foo 4 4, foo 5 5] รายการฟังก์ชันที่ใช้พารามิเตอร์เดียว (เนื่องจากสองตัวแรกถูกใช้แล้ว) และส่งคืนตัวเลข

ถัดไปใน List.map2 (<|) partialResult list3 มันคือ List.map2 (<|) [foo 1 1, foo 2 2, foo 3 3, foo 4 4, foo 5 5] list3 จริงๆ สำหรับทุกคู่ของสองรายการนี้ เรากำลังเรียกใช้ฟังก์ชัน (<|) ตัวอย่างเช่น สำหรับคู่แรก มันคือ (<|) (foo 1 1) 1 ซึ่งเหมือนกับ foo 1 1 <| 1 foo 1 1 <| 1 ซึ่งเหมือนกับ foo 1 1 1 ซึ่งสร้าง 1 สำหรับวินาทีจะเป็น (<|) (foo 2 2) 2 ซึ่งก็คือ foo 2 2 2 ซึ่งประเมินเป็น 8 เป็นต้น

วิธีนี้มีประโยชน์อย่างยิ่งกับฟังก์ชัน mapN สำหรับการถอดรหัสออบเจ็กต์ JSON ที่มีหลายฟิลด์ เนื่องจาก Json.Decode มีมากถึง map8

ลบค่าไม่มีอะไรทั้งหมดออกจากรายการของ Maybes

สมมติว่าเรามีรายการค่า Maybe และเราต้องการแยกเฉพาะค่าจากองค์ประกอบที่มีค่าเดียว ตัวอย่างเช่น รายการคือ:

 list : List (Maybe Int) list = [ Just 1, Nothing, Just 3, Nothing, Nothing, Just 6, Just 7 ]

และเราต้องการได้ [1,3,6,7] : List Int วิธีแก้ปัญหาคือนิพจน์หนึ่งบรรทัด:

 List.filterMap identity list

มาดูกันว่าทำไมสิ่งนี้ถึงได้ผล

List.filterMap คาดว่าอาร์กิวเมนต์แรกจะเป็นฟังก์ชัน (a -> Maybe b) ซึ่งใช้กับองค์ประกอบของรายการที่ให้มา (อาร์กิวเมนต์ที่สอง) และรายการผลลัพธ์จะถูกกรองเพื่อละเว้นค่า Nothing ทั้งหมด และค่าจริง ค่าถูกดึงออกมาจาก Maybe s

ในกรณีของเรา เราได้ระบุ identity ดังนั้นรายการผลลัพธ์จึงเป็นอีกครั้ง [ Just 1, Nothing, Just 3, Nothing, Nothing, Just 6, Just 7 ] หลังจากกรองแล้ว เราจะได้ [ Just 1, Just 3, Just 6, Just 7 ] และหลังจากการดึงค่าออกมา ได้ค่า [1,3,6,7] อย่างที่เราต้องการ

การถอดรหัส JSON แบบกำหนดเอง

เนื่องจากความต้องการของเราในการถอดรหัส JSON (หรือดีซีเรียลไลซ์) เริ่มมีมากกว่าที่เปิดเผยในโมดูล Json.Decode เราอาจประสบปัญหาในการสร้างตัวถอดรหัสแปลกใหม่ใหม่ เนื่องจากตัวถอดรหัสเหล่านี้ถูกเรียกใช้จากตรงกลางของกระบวนการถอดรหัส เช่น ภายในเมธอด Http และไม่ชัดเจนเสมอไปว่าอินพุตและเอาต์พุตคืออะไร โดยเฉพาะอย่างยิ่งหากมีฟิลด์จำนวนมากใน JSON ที่ให้มา

ต่อไปนี้คือตัวอย่างสองตัวอย่างเพื่อแสดงวิธีจัดการกับกรณีดังกล่าว

ในอันแรก เรามีสองฟิลด์ใน JSON ขาเข้า a และ b แสดงถึงด้านของพื้นที่สี่เหลี่ยม แต่ในวัตถุ Elm เราเพียงต้องการเก็บพื้นที่ของมันเท่านั้น

 import Json.Decode exposing (..) areaDecoder = map2 (*) (field "a" int) (field "b" int) result = decodeString areaDecoder """{ "a":7,"b":4 }""" -- Ok 28 : Result.Result String Int

ฟิลด์ต่างๆ จะถูกถอดรหัสแยกกันด้วยตัวถอดรหัส field int จากนั้นค่าทั้งสองจะถูกส่งไปยังฟังก์ชันที่ให้ไว้ใน map2 เนื่องจากการคูณ ( * ) ก็เป็นฟังก์ชันเช่นกัน และต้องใช้พารามิเตอร์สองตัว เราจึงใช้มันแบบนั้นได้ areaDecoder ที่เป็นผลลัพธ์เป็นตัวถอดรหัสที่ส่งคืนผลลัพธ์ของฟังก์ชันเมื่อใช้ ในกรณีนี้คือ a*b

ในตัวอย่างที่สอง เราได้รับฟิลด์สถานะที่ยุ่งเหยิงซึ่งอาจเป็นค่าว่างหรือสตริงใด ๆ รวมถึงค่าว่าง แต่เรารู้ว่าการดำเนินการสำเร็จก็ต่อเมื่อเป็น "ตกลง" ในกรณีนั้น เราต้องการเก็บเป็น True และสำหรับกรณีอื่นๆ ทั้งหมดเป็น False ตัวถอดรหัสของเรามีลักษณะดังนี้:

 okDecoder = nullable string |> andThen (\ms -> case ms of Nothing -> succeed False Just s -> if s == "OK" then succeed True else succeed False )

ลองใช้กับ JSON บางตัว:

 decodeString (field "status" okDecoder) """{ "a":7, "status":"OK" }""" -- Ok True decodeString (field "status" okDecoder) """{ "a":7, "status":"NOK" }""" -- Ok False decodeString (field "status" okDecoder) """{ "a":7, "status":null }""" -- Ok False

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

ที่สำคัญ Takeaway

ดังที่เห็นได้จากตัวอย่างเหล่านี้ การเขียนโปรแกรมในลักษณะที่ใช้งานได้อาจไม่ง่ายนักสำหรับนักพัฒนา Java และ JavaScript ต้องใช้เวลาพอสมควรในการทำความคุ้นเคยกับมัน โดยมีการลองผิดลองถูกมากมาย เพื่อช่วยให้เข้าใจได้ คุณสามารถใช้ elm-repl เพื่อออกกำลังกายและตรวจสอบประเภทการส่งคืนของนิพจน์ของคุณ

โครงการตัวอย่างที่เชื่อมโยงกับตอนต้นในบทความนี้ประกอบด้วยตัวอย่างเพิ่มเติมของตัวถอดรหัสและตัวเข้ารหัสแบบกำหนดเองซึ่งอาจช่วยให้คุณเข้าใจได้

แต่ทำไมถึงเลือกเอล์ม?

แตกต่างจากเฟรมเวิร์กไคลเอนต์อื่น ๆ ภาษา Elm ไม่ใช่ "ไลบรารี JavaScript อื่น" อย่างแน่นอน ด้วยเหตุนี้ จึงมีลักษณะหลายอย่างที่สามารถพิจารณาได้ว่าเป็นบวกหรือลบเมื่อเทียบกับพวกเขา

เริ่มจากด้านบวกกันก่อน

การเขียนโปรแกรมไคลเอนต์โดยไม่ใช้ HTML และ JavaScript

ในที่สุด คุณมีภาษาที่ทำได้ทุกอย่าง ไม่มีการแยกจากกันอีกต่อไปและการผสมผสานที่น่าอึดอัดใจของการผสมของพวกเขา ไม่มีการสร้าง HTML ใน JavaScript และไม่มีภาษาเทมเพลตที่กำหนดเองด้วยกฎตรรกะแบบแยกส่วน

ด้วย Elm คุณจะมีเพียงหนึ่งรูปแบบและหนึ่งภาษาเท่านั้น

ความสม่ำเสมอ

เนื่องจากแนวคิดเกือบทั้งหมดมีพื้นฐานมาจากฟังก์ชัน และมีโครงสร้างบางส่วน ไวยากรณ์จึงกระชับมาก คุณไม่ต้องกังวลหากมีการกำหนดวิธีการบางอย่างบนอินสแตนซ์หรือระดับคลาส หรือหากเป็นเพียงฟังก์ชัน เป็นฟังก์ชันทั้งหมดที่กำหนดไว้ในระดับโมดูล และไม่มีวิธีต่างๆ หลายร้อยวิธีในการวนซ้ำรายการ

ในภาษาส่วนใหญ่ มีอาร์กิวเมนต์อยู่เสมอว่าโค้ดนั้นเขียนในลักษณะของภาษาหรือไม่ ต้องใช้สำนวนมากมาย

ใน Elm ถ้าคอมไพล์ก็น่าจะเป็นแบบ "Elm" ถ้าไม่อย่างนั้นก็ไม่แน่นอน

การแสดงออก

แม้ว่าจะกระชับ แต่ไวยากรณ์ของ Elm ก็แสดงออกได้ดีมาก

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

ไม่มีค่าว่าง

เมื่อคุณใช้ Java หรือ JavaScript เป็นเวลานาน null จะกลายเป็นเรื่องปกติสำหรับคุณ ซึ่งเป็นส่วนที่หลีกเลี่ยงไม่ได้ของการเขียนโปรแกรม และแม้ว่าเราจะเห็น NullPointerException และ TypeError ต่างๆ อยู่ตลอดเวลา แต่เราก็ยังไม่คิดว่าปัญหาที่แท้จริงคือการมีอยู่ของ null มันเป็นเรื่อง ธรรมชาติ มาก

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

ความมั่นใจว่าจะได้ผล

การสร้างโปรแกรม JavaScript ที่ถูกต้องตามวากยสัมพันธ์สามารถทำได้อย่างรวดเร็ว แต่มันจะได้ผลจริงหรือ? มาดูกันว่าหลังจากโหลดหน้าใหม่และทดสอบอย่างละเอียดแล้ว

กับ Elm เป็นสิ่งที่ตรงกันข้าม ด้วยการตรวจสอบประเภทสแตติกและการตรวจสอบ null ที่บังคับใช้ จึงต้องใช้เวลาในการรวบรวม โดยเฉพาะอย่างยิ่งเมื่อผู้เริ่มต้นเขียนโปรแกรม แต่เมื่อมันคอมไพล์แล้ว มีโอกาสที่ดีที่มันจะทำงานได้อย่างถูกต้อง

เร็ว

นี่อาจเป็นปัจจัยสำคัญในการเลือกกรอบงานลูกค้า การตอบสนองของเว็บแอปที่ครอบคลุมมักจะมีความสำคัญต่อประสบการณ์ของผู้ใช้ และทำให้ผลิตภัณฑ์ทั้งหมดประสบความสำเร็จ และจากการทดสอบพบว่า Elm นั้นเร็วมาก

ข้อดีของ Elm กับ Frameworks แบบดั้งเดิม

เว็บเฟรมเวิร์กแบบดั้งเดิมส่วนใหญ่มีเครื่องมือที่มีประสิทธิภาพสำหรับการสร้างเว็บแอป แต่พลังนั้นมาพร้อมกับราคา: สถาปัตยกรรมที่ซับซ้อนเกินไปพร้อมแนวคิดและกฎเกณฑ์ที่แตกต่างกันมากมายเกี่ยวกับวิธีการใช้งานและเวลา ต้องใช้เวลามากในการควบคุมทุกอย่าง มีตัวควบคุมส่วนประกอบและคำสั่ง จากนั้นมีขั้นตอนการคอมไพล์และการกำหนดค่าและเฟสรัน จากนั้น มีบริการ โรงงาน และภาษาเทมเพลตแบบกำหนดเองทั้งหมดที่ใช้ในคำสั่งที่ให้มา—สถานการณ์ทั้งหมดเหล่านี้เมื่อเราจำเป็นต้องเรียก $scope.$apply() โดยตรงเพื่อทำให้หน้ารีเฟรช และอื่นๆ

การรวบรวม Elm ไปยัง JavaScript นั้นซับซ้อนมากเช่นกัน แต่นักพัฒนาได้รับการปกป้องจากการต้องรู้ถึงถั่วและสลักเกลียวทั้งหมดของมัน แค่เขียน Elm แล้วปล่อยให้คอมไพเลอร์ทำงานแทน

และทำไมไม่เลือกเอล์ม?

เพียงพอกับการสรรเสริญเอล์ม ทีนี้ มาดูด้านที่ไม่ดีกันบ้าง

เอกสาร

นี่เป็นประเด็นสำคัญจริงๆ ภาษาเอล์มไม่มีคู่มือโดยละเอียด

บทแนะนำอย่างเป็นทางการเพียงแค่อ่านผ่าน ๆ ภาษาและทิ้งคำถามที่ยังไม่ได้ตอบไว้มากมาย

การอ้างอิง API อย่างเป็นทางการนั้นแย่ยิ่งกว่า ฟังก์ชันจำนวนมากขาดคำอธิบายหรือตัวอย่าง แล้วก็มีประโยคที่มีประโยคที่ว่า “ถ้าสิ่งนี้ทำให้สับสน ให้ทำงานผ่าน Elm Architecture Tutorial มันช่วยได้จริงๆ!” ไม่ใช่บรรทัดที่ยิ่งใหญ่ที่สุดที่คุณต้องการเห็นในเอกสาร API อย่างเป็นทางการ

หวังว่าสิ่งนี้จะเปลี่ยนไปในไม่ช้า

ฉันไม่เชื่อว่า Elm สามารถนำมาใช้กันอย่างแพร่หลายกับเอกสารที่ไม่ดีเช่นนี้ โดยเฉพาะอย่างยิ่งกับผู้ที่มาจาก Java หรือ JavaScript ซึ่งแนวคิดและฟังก์ชันดังกล่าวไม่ได้ใช้งานง่ายเลย เพื่อทำความเข้าใจพวกเขา จำเป็นต้องมีเอกสารประกอบที่ดีขึ้นมากพร้อมตัวอย่างมากมาย

รูปแบบและช่องว่าง

การกำจัดเหล็กดัดหรือวงเล็บปีกกาและใช้ช่องว่างสีขาวเพื่อเยื้องอาจดูดี ตัวอย่างเช่น โค้ด Python ดูเรียบร้อยมาก แต่สำหรับผู้สร้าง elm-format ยังไม่เพียงพอ

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

นี้อาจฟังดูดีถ้าคุณได้รับเงินโดยบรรทัดของรหัสที่เขียน แต่ถ้าคุณต้องการจัดแนวบางสิ่งด้วยนิพจน์ที่เริ่ม 150 บรรทัดก่อนหน้านี้ ขอให้โชคดีกับการค้นหาการเยื้องที่ถูกต้อง

การจัดการบันทึก

เป็นการยากที่จะทำงานร่วมกับพวกเขา ไวยากรณ์ในการแก้ไขฟิลด์ของเรกคอร์ดนั้นน่าเกลียด ไม่มีวิธีง่าย ๆ ในการแก้ไขฟิลด์ที่ซ้อนกันหรืออ้างอิงฟิลด์ตามอำเภอใจตามชื่อ และถ้าคุณใช้ฟังก์ชัน accessor ในลักษณะทั่วไป จะมีปัญหามากมายในการพิมพ์อย่างถูกต้อง

ใน JavaScript บันทึกหรือวัตถุเป็นโครงสร้างกลางที่สามารถสร้าง เข้าถึง และแก้ไขได้หลายวิธี แม้แต่ JSON ก็เป็นเพียงเวอร์ชันต่อเนื่องของเร็กคอร์ด นักพัฒนาคุ้นเคยกับการทำงานกับเร็กคอร์ดในการเขียนโปรแกรมเว็บ ดังนั้นปัญหาในการจัดการ Elm จึงสามารถสังเกตเห็นได้ชัดเจนหากใช้เป็นโครงสร้างข้อมูลหลัก

พิมพ์ดีดเพิ่มเติม

Elm ต้องการโค้ดในการเขียนมากกว่า JavaScript

ไม่มีการแปลงประเภทโดยนัยสำหรับการดำเนินการสตริงและตัวเลข ดังนั้นจึงจำเป็นต้องมีการแปลงแบบ int-float จำนวนมาก และโดยเฉพาะอย่างยิ่งการเรียก toString ซึ่งต้องใช้วงเล็บหรือสัญลักษณ์แอปพลิเคชันฟังก์ชันเพื่อให้ตรงกับจำนวนอาร์กิวเมนต์ที่ถูกต้อง นอกจากนี้ ฟังก์ชัน Html.text ต้องการสตริงเป็นอาร์กิวเมนต์ จำเป็นต้องมีนิพจน์ตัวพิมพ์จำนวนมากสำหรับ Maybe s, Results , types เหล่านั้นทั้งหมด

เหตุผลหลักสำหรับเรื่องนี้คือระบบประเภทที่เข้มงวด และนั่นอาจเป็นราคาที่ยุติธรรมที่จะต้องจ่าย

ตัวถอดรหัสและตัวเข้ารหัส JSON

พื้นที่หนึ่งที่การพิมพ์มีความโดดเด่นมากขึ้นคือการจัดการ JSON JSON.parse() เรียกใน JavaScript ว่าสามารถขยายบรรทัดในภาษา Elm ได้หลายร้อยบรรทัด

แน่นอนว่าจำเป็นต้องมีการทำแผนที่ระหว่างโครงสร้าง JSON และ Elm แต่ความจำเป็นในการเขียนทั้งตัวถอดรหัสและตัวเข้ารหัสสำหรับ JSON ชิ้นเดียวกันนั้นเป็นปัญหาร้ายแรง หาก REST API ของคุณถ่ายโอนอ็อบเจ็กต์ที่มีหลายร้อยฟิลด์ นี่จะเป็นงานมาก

สรุป

เราได้เห็นเกี่ยวกับ Elm แล้ว ถึงเวลาตอบคำถามที่เป็นที่รู้จัก ซึ่งน่าจะเก่าพอๆ กับภาษาโปรแกรมเอง ดีกว่าคู่แข่งไหม? เราควรใช้ในโครงการของเราหรือไม่?

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

สำหรับคำถามที่สอง เพื่อหลีกเลี่ยงไม่ให้ตอบแบบโบราณว่า “มันขึ้นอยู่กับ” คำตอบง่ายๆ คือ: ใช่ แม้จะมีข้อเสียทั้งหมดที่กล่าวถึง แต่ความมั่นใจที่ Elm มอบให้คุณเกี่ยวกับความถูกต้องของโปรแกรมก็เป็นเหตุผลที่เพียงพอต่อการใช้งาน

การเข้ารหัสใน Elm ก็สนุกเช่นกัน เป็นมุมมองใหม่สำหรับทุกคนที่เคยชินกับกระบวนทัศน์การเขียนโปรแกรมเว็บ "แบบธรรมดา"

ในการใช้งานจริง คุณไม่จำเป็นต้องเปลี่ยนแอปทั้งหมดเป็น Elm ทันทีหรือเริ่มแอปใหม่ทั้งหมด คุณสามารถใช้ความสามารถในการทำงานร่วมกันกับ JavaScript เพื่อทดลองใช้ โดยเริ่มจากส่วนต่อประสานบางส่วนหรือฟังก์ชันบางอย่างที่เขียนด้วยภาษา Elm คุณจะทราบได้อย่างรวดเร็วว่าเหมาะกับความต้องการของคุณหรือไม่ จากนั้นจึงขยายการใช้งานหรือปล่อยทิ้งไว้ และใครจะรู้ คุณอาจตกหลุมรักโลกของการเขียนโปรแกรมเว็บที่ใช้งานได้จริง

ที่เกี่ยวข้อง: Unearthing ClojureScript สำหรับการพัฒนาส่วนหน้า