แรงจูงใจ

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

ในกระบวนการเรียนรู้ของฉัน ฉันพบว่าการเขียนคอมไพเลอร์/ล่ามสำหรับภาษาง่ายๆ บางอย่างจะเป็นวิธีที่ดีที่สุดในการเรียนรู้และประยุกต์ใช้โครงสร้างส่วนใหญ่ที่ Elixir มีให้ ฉันพบว่า "บทความ" ของ Peter Norvig เกี่ยวกับการเขียนล่าม Lisp ใน Python นั้นเป็นจุดเริ่มต้นที่ยอดเยี่ยมมาก Lisp เป็นภาษาที่ง่ายมากเนื่องจากมีโครงสร้างที่สอดคล้องกัน แต่มี Lisp หลากหลายรูปแบบ Scheme เป็นหนึ่งในรูปแบบที่ง่ายที่สุดและมีโครงสร้างน้อยมาก

หากคุณยังใหม่กับ Elixir ที่ได้อ่านบทความนี้ คุณจะเห็นว่าโครงสร้างส่วนใหญ่ใน Elixir เช่น Pattern Matching, Piping และ List Processing ทำให้ชีวิตของคุณง่ายขึ้นในฐานะโปรแกรมเมอร์ได้อย่างไร

คุณสามารถตรวจสอบ ซอร์สโค้ด และ เอกสารประกอบ เพื่อเป็นข้อมูลอ้างอิง

โครงสร้าง Lisp (Scheme Variant)

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

ตัวอย่างเช่น ลองค้นหาตัวเลขสูงสุดสองตัวในภาษาเช่น C และในภาษา Scheme

C

if( x > y) {
 max = x; 
}
else {
 max = y;
}

โครงการ

(begin (if (> x y) (set! max x) (set! max y)) 

C มีชุดโครงสร้างที่ใหญ่กว่าด้วยสิ่งต่างๆ เช่น ตัวยุติบรรทัด วงเล็บประเภทต่างๆ เป็นต้น ในขณะที่ไวยากรณ์ของ Scheme มีความสม่ำเสมอมากกว่ามาก

ใน Scheme ทุกอย่างคือนิพจน์ ตัวเลขและสัญลักษณ์เรียกว่านิพจน์อะตอมมิก ส่วนอย่างอื่นเป็นนิพจน์รายการ

กระบวนการล่าม

การเขียนล่ามสำหรับภาษาใดๆ มีสองขั้นตอนหลักดังนี้: การแยกวิเคราะห์และการประเมินผล

การแยกวิเคราะห์

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

การประเมินผล

ในขั้นตอนนี้ AST จะถูกประมวลผลตามกฎความหมายของภาษา ตัวอย่างเช่น โอเปอเรเตอร์ > หมายถึงค้นหาค่าที่มากกว่าจากโหนดย่อยทั้งสองใน AST

การใช้งาน Parser ใน Elixir

parser ประกอบด้วยสองส่วน ส่วน tokenizer จะแบ่งโค้ดออกเป็นคำหลัก และตัวสร้าง AST

  • โทเค็นไนเซอร์

ในตัวอย่างที่ให้ไว้ด้านล่าง ทุก Scheme Expression ที่ซ้อนกันหรือไม่ซ้อนกันจะถูกคั่นด้วย ( )

(begin (if (> x y) (set! max x) (set! max y))

เราต้องโทเค็นโปรแกรมต้นทางด้วย ( ) เรามาทำสิ่งนั้นกันเถอะ

มีอะไรเกิดขึ้นมากมายที่นี่ ดังนั้นเรามาแกะมันกันดีกว่า: def เป็นมาโคร Elixir เพื่อประกาศฟังก์ชันที่มีชื่อ มาโครเป็นโครงสร้างที่ทรงพลังมากซึ่งใช้สำหรับการเขียนโปรแกรมเมตาใน Elixir ซึ่งไม่อยู่ในขอบเขตของบทความนี้ ฟังก์ชั่นใน Elixir จะถูกระบุด้วยชื่อและความสมบูรณ์ Arity ของฟังก์ชันคือจำนวนอาร์กิวเมนต์ที่ใช้ ฟังก์ชัน tokenize รับอาร์กิวเมนต์เดียว str ดังนั้นโทเค็นจึงถูกระบุเป็น tokenize/1

Elixir ยังมีฟังก์ชันการจัดการสตริงที่เป็นประโยชน์ในโมดูล String โมดูลใน Elixir คือกลุ่มของฟังก์ชัน โมดูลสามารถกำหนดได้โดยใช้แมโคร defmodule หลังจากนั้นเราจะเพิ่มฟังก์ชันเพิ่มเติมในขั้นตอนการแยกวิเคราะห์ของเราและจัดกลุ่มฟังก์ชันเหล่านั้นในโมดูล Parse

สำหรับขั้นตอนการสร้างโทเค็น เราต้องแน่ใจว่ามีช่องว่างอย่างน้อยหนึ่งช่องระหว่างคีย์เวิร์ด ดังนั้นเราจึงเว้นวรรคในวงเล็บ |> เรียกว่าตัวดำเนินการไปป์ และทำงานคล้ายกับไปป์ Unix ผลลัพธ์ของฟังก์ชันแรกจะถูกส่งไปเป็นอาร์กิวเมนต์แรกของฟังก์ชันถัดไป ดังนั้น str จึงถูกส่งผ่านเป็นอาร์กิวเมนต์แรกไปยัง String.replace/3 ในที่สุด String.split/2 ได้รับสตริงที่เว้นระยะห่าง String.split/1 มีอาร์กิวเมนต์เริ่มต้นที่สองซึ่งเป็นช่องว่าง ' ' และส่งคืนรายการสตริง List เป็นหนึ่งในประเภทที่นับได้ใน Elixir และอีกประเภทหนึ่งคือ Tuple และ Map ดังนั้นสำหรับโค้ดตัวอย่างข้างต้น ผลลัพธ์ของ tokenize/1 จะเป็น

[ "(", "begin", "(", "if", "(", ">", "x", "y", ")", "(", "set!", "max", "x", ")", "(", "set!", "max", "y", ")", ")" ]
  • ตัวสร้าง AST

ก่อนที่เราจะเจาะลึกขั้นตอนนี้ เราจำเป็นต้องทราบข้อมูลเพิ่มเติมเกี่ยวกับ รายการ การจับคู่รูปแบบ และการเรียกซ้ำ ใน Elixir

รายการทำความเข้าใจ

รายการใน Elixir สามารถกำหนดได้โดยใส่ค่าที่คั่นด้วยเครื่องหมายจุลภาคในวงเล็บเหลี่ยม

list = [ 1, 2, 3]
[1, 2, 3]
length(list)
3

ภายใน Elixir รายการจะแสดงเป็นรายการที่เชื่อมโยง สิ่งนี้มีผลกระทบเล็กน้อยบางอย่าง เช่น การค้นหาความยาวของรายการเป็นแบบเชิงเส้นในขณะนี้

เราสามารถเชื่อมสองรายการเข้าด้วยกันโดยใช้ตัวดำเนินการ ++

[4] ++ list
[4, 1, 2, 3]
list ++ [4]
[1, 2, 3, 4]

การเติมหน้ารายการจะเกิดขึ้นในเวลาคงที่ ในขณะที่การต่อท้ายรายการจะต้องใช้เวลาเป็นเส้นตรง

รายการใน Elixir สามารถแบ่งออกเป็นส่วนหัวและส่วนท้ายได้คล้ายกับรายการที่เชื่อมโยงโดยใช้ตัวดำเนินการ |

[head | tail] = list
head
1
tail
[2, 3]

head จะมี 1 และ tail จะเป็นรายการของ 2 องค์ประกอบ [2, 3]

Elixir มีฟังก์ชันตัวช่วย hd/1 และ tl/1 ซึ่งจะค้นหาส่วนหัวและส่วนท้ายของรายการที่ระบุเป็นอาร์กิวเมนต์

hd(list)
1
tl(list)
[2, 3]

Elixir ยังมีโมดูล List เฉพาะซึ่งมีฟังก์ชันมากมายในการจัดการกับรายการต่างๆ

List.first(list)
1
List.last(list)
3

อะตอมในน้ำอมฤต

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

my_atom = :john
is_atom(my_atom)
true
is_atom(false)
true

การจับคู่รูปแบบไม่ชัดเจน

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

[1, 2, 3] = list
[1, 2, 3]
[1, 2, 5] = list
** (MatchError) no match of right hand side value: [1, 2, 3]

ซึ่งอาจดูคล้ายกับตัวดำเนินการ == แต่ตัวดำเนินการดังกล่าวจะทำการเปรียบเทียบเท่านั้น ตัวดำเนินการจับคู่ = ที่นี่จะทำการเปรียบเทียบและผูกค่ากับตัวแปร แต่การเชื่อมโยงตัวแปรจะเกิดขึ้นที่ด้านซ้ายของตัวดำเนินการจับคู่เท่านั้น สิ่งนี้ทำให้เกิดกรณีการใช้งานที่น่าสนใจมากในการลดโครงสร้างประเภทที่ซับซ้อน เช่น List หรือ Tuple

[first, mid, last ] = list
[1, 2, 3]
[first, mid, last]
[1, 2, 3]
[1, 2, _] = list
[1, 2, 3]

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

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

students = fetchCoolKids()
doCoolStuff(students)
def doCoolStuff(["Stu", "Alan", "_"]) do
   IO.puts "We don't need a third person."
end
def doCoolStuff(["Doug", _, _]) do
   IO.puts "I don't need nobody."
end
# Scenario 1
students = ["Doug", "Alan", "Mark"]
I don't need nobody
#Scenario 2
students = ["Stu", "Alan", "Mark"]
We don't need a third person
#Scenario 3
students = ["Mark", "Stu", "Alan"]
(FunctionClauseError) no function clause matching in doCoolStuff/1

คำจำกัดความประเภท

Scheme มีออบเจ็กต์อยู่ไม่กี่ประเภท นี่คือวิธีที่เราจะนำเสนอใน Elixir

  • สัญลักษณ์ -> นำไปใช้เป็น Atom begin => :begin
  • Atom -› นำไปใช้เป็น Atom หรือ Number
  • หมายเลข -› ดำเนินการเป็น Float หรือ Integer
  • รายการ -> นำไปใช้เป็น List (1, 2, 3) => [1, 2, 3]
  • นิพจน์ -> นำไปใช้เป็น Atom หรือ List

การสร้าง AST

ด้านล่างนี้คือโค้ดสำหรับสร้าง AST พร้อมความคิดเห็นที่เป็นประโยชน์

ฉันใช้การเรียกซ้ำเพื่อดูโทเค็นทั้งหมดเนื่องจากไม่มีแนวคิดเรื่อง while loop ใน Elixir เนื่องจากความไม่เปลี่ยนรูป parse/2 รับรายการโทเค็นเป็นอาร์กิวเมนต์แรกและตัวสะสม acc เป็นอาร์กิวเมนต์ที่สอง มีการใช้แบบฟอร์ม [head | tail] เพื่อแสดงรายการ

เพื่อให้เข้าใจโค้ดข้างต้นได้ดีขึ้น ลองใช้โค้ดเสียงกระเพื่อมตัวอย่าง

(begin (define r 10) (* r r))

รูปแบบโทเค็นของรหัสเสียงกระเพื่อมข้างต้นจะเป็นเช่นนี้

["(", "begin", "(", "define", "r", "10", ")", "(", "*", "r", "r", ")", ")"]

เราจะส่งรายการโทเค็นด้านบนและรายการตัวสะสมว่างไปยังฟังก์ชัน parse/2 ของเรา การจับคู่รูปแบบจะดูแลฟังก์ชันเฉพาะที่จะเรียกใช้ สิ่งที่เราพยายามทำให้สำเร็จคือการสร้างรายการโอเปอเรเตอร์และอาร์กิวเมนต์ทุกครั้งที่เราพบ '(' และ ') ในรูปแบบ AST โหนดที่ไม่ใช่ลีฟจะเป็นฟังก์ชันหรือตัวดำเนินการเสมอ และโหนดลีฟจะเป็นสัญลักษณ์ (ตัวเลขหรือสตริง)

ด้านล่างนี้คือผลลัพธ์ของขั้นตอนการแยกวิเคราะห์

[ :begin, [ :define, :r, 10 ], [ :*, :r, :r ] ]

การประเมิน AST ใน Elixir

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

แผนที่

Map เป็นประเภทที่นับได้ใน Elixir เป็นคู่ของค่าคีย์ ซึ่งไม่มีข้อจำกัดเกี่ยวกับประเภทคีย์ สามารถสร้างแผนที่ได้โดยใช้ไวยากรณ์ %{} และคู่ของค่าคีย์สามารถแสดงเป็น key => value ได้

id_map = %{"Name" => "John", "Age" => 45, "Citizen" => "USA"}
Map.get(id_map,"Name")
"John"
Map.put(id_map,"DateOfBirth","03-03-1973")
%{"Name" => "John",  "DateOfBirth" => "03-03-1973" "Age" => 45, "Citizen" => "USA"}

ฟังก์ชันที่ไม่ระบุชื่อ

ก่อนหน้านี้เราได้เห็นวิธีการกำหนดฟังก์ชันที่มีชื่อโดยใช้มาโคร def นอกจากนี้เรายังสามารถกำหนดฟังก์ชันที่ไม่มีชื่อเรียกว่าฟังก์ชัน Anonymous ในฟังก์ชัน Elixir และ lambda ในภาษาอื่นบางภาษาได้

multiply = fn (a, b) -> a * b end
multiply.(3,4)
12
toss = fn "heads" -> "England won the toss."
          "tails" -> "Australia won the toss."
               _  -> "Invalid toss."
       end
toss.("heads")
England won the toss

เรากำหนดฟังก์ชันที่ไม่ระบุชื่อโดยใช้คำหลัก fn และตัวดำเนินการ -> ฟังก์ชันเหล่านี้สามารถเรียกใช้ได้โดยใช้ตัวดำเนินการ . และยังสามารถเรียกใช้หลายเนื้อหาได้โดยใช้การจับคู่รูปแบบ

มีอีกวิธีหนึ่งในการแสดงฟังก์ชันที่ไม่ระบุชื่อใน Elixir

multiply = &( &1 * &2 )

คำจำกัดความฟังก์ชันประเภทนี้ไม่สามารถตั้งชื่อพารามิเตอร์หรือเนื้อหาของฟังก์ชันหลายรายการได้ & เรียกว่าตัวดำเนินการจับภาพ &1 และ &2 เป็นอาร์กิวเมนต์ที่หนึ่งและที่สองที่คล้ายคลึงกับ a และ b ในคำจำกัดความ fn

โครงสร้างโครงการที่นำไปใช้ใน Lispex

  • คำจำกัดความ : (define symbol exp)
  • งานที่ได้รับมอบหมาย : (set! symbol exp)
  • การอ้างอิงตัวแปร: symbol
  • ตัวอักษรคงที่: number
  • มีเงื่อนไข: ( if test conseq alt)
  • ขั้นตอน: (proc args…)

ขั้นตอนเป็นอย่างอื่นนอกเหนือจาก if,defense และ set! . ตัวอย่างได้แก่ ตัวดำเนินการทางคณิตศาสตร์และตรรกะ ฟังก์ชันทางคณิตศาสตร์ การดำเนินการรายการ เช่น car, cdr, cons

แผนที่สภาพแวดล้อม

โครงสร้างโครงร่างข้างต้นได้ถูกนำมาใช้ผ่านแผนที่ใน Elixir ส่วนสำคัญของแผนที่นั้นได้รับด้านล่าง

แมปสภาพแวดล้อมนี้ถูกกำหนดไว้ในโมดูล Env โมดูลประกอบด้วยฟังก์ชันสำหรับการแทรกและดึงค่าจากแผนที่

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

ดังนั้น เมื่อประเมินโค้ด Scheme ทุกครั้งที่เราพบ ( ) ใหม่ เราจะสร้างสภาพแวดล้อมใหม่ โดยอ้างอิงสภาพแวดล้อมหลักด้วยคีย์ an:outer ใน get/2 เราค้นหาคีย์ซ้ำๆ จนกระทั่งเข้าถึงสภาพแวดล้อมทั่วโลก

คำแถลงกรณีและตัวป้องกันฟังก์ชัน

คำสั่ง Case ช่วยให้เราสามารถจัดรูปแบบการจับคู่ค่าและดำเนินการคำสั่งที่สอดคล้องกับการจับคู่นั้น

case [1, 3, 5] do
   [_, 3, 5] -> 
             "Match at 1"
   [_, _, 5] ->
             "Match at 2"
    _ ->
        "No match found"
end

ตัวป้องกันฟังก์ชันเป็นส่วนเสริมของการจับคู่รูปแบบ การจับคู่รูปแบบจะเกิดขึ้นเฉพาะเมื่อตรงตามเงื่อนไขการป้องกันเท่านั้น

Guards สามารถใช้ในฟังก์ชันที่มีชื่อและไม่ระบุชื่อและคำสั่ง case Guards ถูกกำหนดโดยใช้คีย์เวิร์ด when ตามด้วยนิพจน์

def parse(x) when is_list(x) do
    IO.puts "List"
end

การตรวจสอบประเภท เช่น is_list/1 , is_atom/1 ฯลฯ สามารถใช้กับการ์ดได้ นอกเหนือจากโฮสต์ของโอเปอเรเตอร์อื่นๆ

การประเมิน AST

เราประเมิน AST ซ้ำ ๆ โดยประเมินแต่ละโครงสร้างและข้อโต้แย้งตามลำดับ

เมื่อนิพจน์โครงร่างคือ:

  • ไม่ใช่รายการและเป็นอะตอม ให้ดึงค่าสำหรับอะตอมนั้นจากสภาพแวดล้อม
  • ไม่ใช่รายการและเป็นตัวเลข ให้ส่งคืนหมายเลขนั้น
  • รายการและส่วนหัวคือ :if ให้ประเมินองค์ประกอบที่สอง หากเป็นจริง ให้ประเมินองค์ประกอบที่สาม หากเป็นเท็จ ให้ประเมินส่วนท้าย
  • รายการและส่วนหัวคือ :define จากนั้นองค์ประกอบที่สองคือสัญลักษณ์ ประเมินส่วนท้ายและใส่ค่าลงในแผนที่สิ่งแวดล้อมโดยมีสัญลักษณ์เป็นกุญแจ
  • รายการและส่วนหัวคือ :set! ดังนั้นพฤติกรรมจะคล้ายกับ :define ยกเว้นว่าตัวแปรจะต้องมีอยู่ในแผนผังสภาพแวดล้อม
  • รายการและส่วนหัวเป็นอย่างอื่นที่ไม่ใช่ :if, :set! หรือ :define ดังนั้น head คือโพรซีเดอร์ และส่วนท้ายคือรายการอาร์กิวเมนต์สำหรับโพรซีเดอร์นั้น ประเมินแต่ละองค์ประกอบในส่วนท้าย รับค่าของกระบวนการจากแผนผังสภาพแวดล้อม

การสร้าง REPL

การเรียกใช้ฟังก์ชัน การตีความ ด้วยตนเองนั้นน่าเบื่อ และเรายังต้องระบุสถานะแผนที่สภาพแวดล้อมด้วย ดังนั้นการสร้าง REPL จึงเป็นวิธีที่ดีที่สุดในการสะท้อนล่าม Scheme

นี่คือตัวอย่างของ Lispex REPL ที่ใช้งานจริง

iex> Lispex.repl
lispex> (begin (define a 4) (* a 5))
20
lispex> (if a<3 (* a 4) (* a 6))
24

รหัสทั้งหมดที่ให้ไว้ที่นี่รวมอยู่ในสี่โมดูลที่เรียกว่า Lispex , Parse , Env และ Eval

บทสรุป

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

ในบทความถัดไป ฉันจะพูดถึงเกี่ยวกับการเขียนโปรแกรมเมตาใน Elixir และ Erlang OTP ซึ่งจะมาพร้อมกับปัญหาในทางปฏิบัติ

อ้างอิง

https://hexdocs.pm/elixir

http://norvig.com/lispy.html