Elixir - คู่มือฉบับย่อ
Elixir เป็นภาษาแบบไดนามิกที่ใช้งานได้ซึ่งออกแบบมาเพื่อสร้างแอปพลิเคชันที่ปรับขนาดได้และบำรุงรักษาได้ มันใช้ประโยชน์จาก Erlang VM ซึ่งเป็นที่รู้จักในการใช้งานระบบที่มีความหน่วงต่ำระบบกระจายและทนต่อข้อผิดพลาดในขณะเดียวกันก็ใช้ในการพัฒนาเว็บและโดเมนซอฟต์แวร์แบบฝัง
Elixir เป็นภาษาแบบไดนามิกที่ใช้งานได้ซึ่งสร้างขึ้นจาก Erlang และ Erlang VM Erlang เป็นภาษาที่เขียนขึ้นครั้งแรกในปี 1986 โดย Ericsson เพื่อช่วยแก้ปัญหาโทรศัพท์เช่นการกระจายการยอมรับข้อผิดพลาดและการทำงานพร้อมกัน Elixir เขียนโดยJosé Valim ขยาย Erlang และให้ไวยากรณ์ที่เป็นมิตรกับ Erlang VM มันทำสิ่งนี้ในขณะที่รักษาประสิทธิภาพในระดับเดียวกับ Erlang
คุณสมบัติของ Elixir
ให้เราพูดถึงคุณสมบัติที่สำคัญบางประการของ Elixir -
Scalability - รหัส Elixir ทั้งหมดทำงานภายในกระบวนการที่มีน้ำหนักเบาซึ่งแยกและแลกเปลี่ยนข้อมูลผ่านข้อความ
Fault Tolerance- Elixir ให้ผู้บังคับบัญชาซึ่งอธิบายวิธีการรีสตาร์ทส่วนต่างๆของระบบของคุณเมื่อเกิดสิ่งผิดปกติกลับไปสู่สถานะเริ่มต้นที่ทราบซึ่งรับประกันว่าจะทำงาน สิ่งนี้ทำให้มั่นใจได้ว่าแอปพลิเคชัน / แพลตฟอร์มของคุณจะไม่ล่ม
Functional Programming - การเขียนโปรแกรมเชิงฟังก์ชันส่งเสริมรูปแบบการเข้ารหัสที่ช่วยให้นักพัฒนาเขียนโค้ดที่สั้นรวดเร็วและบำรุงรักษาได้
Build tools- Elixir มาพร้อมกับชุดเครื่องมือพัฒนา Mix เป็นเครื่องมือชนิดหนึ่งที่ทำให้ง่ายต่อการสร้างโครงการจัดการงานเรียกใช้การทดสอบ ฯลฯ นอกจากนี้ยังมีตัวจัดการแพ็คเกจของตัวเอง - Hex
Erlang Compatibility - Elixir ทำงานบน Erlang VM ให้นักพัฒนาเข้าถึงระบบนิเวศของ Erlang ได้อย่างสมบูรณ์
ในการเรียกใช้ Elixir คุณต้องตั้งค่าภายในระบบของคุณ
ในการติดตั้ง Elixir คุณจะต้องใช้ Erlang ก่อน ในบางแพลตฟอร์มแพ็คเกจ Elixir จะมาพร้อมกับ Erlang ในนั้น
การติดตั้ง Elixir
ตอนนี้ให้เราเข้าใจการติดตั้ง Elixir ในระบบปฏิบัติการต่างๆ
การตั้งค่า Windows
ในการติดตั้ง Elixir บน windows ให้ดาวน์โหลดตัวติดตั้งจาก https://repo.hex.pm/elixirwebsetup.exe แล้วคลิก Nextเพื่อดำเนินการตามขั้นตอนทั้งหมด คุณจะมีในระบบท้องถิ่นของคุณ
หากคุณมีปัญหาใด ๆ ในขณะติดตั้งคุณสามารถตรวจสอบหน้านี้เพื่อดูข้อมูลเพิ่มเติม
การตั้งค่า Mac
หากคุณติดตั้ง Homebrew ตรวจสอบให้แน่ใจว่าเป็นเวอร์ชันล่าสุด สำหรับการอัปเดตให้ใช้คำสั่งต่อไปนี้ -
brew update
ตอนนี้ติดตั้ง Elixir โดยใช้คำสั่งด้านล่าง -
brew install elixir
การตั้งค่า Ubuntu / Debian
ขั้นตอนในการติดตั้ง Elixir ในการตั้งค่า Ubuntu / Debian มีดังต่อไปนี้ -
เพิ่ม Erlang Solutions repo -
wget https://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb && sudo
dpkg -i erlang-solutions_1.0_all.deb
sudo apt-get update
ติดตั้งแพลตฟอร์ม Erlang / OTP และแอปพลิเคชันทั้งหมด -
sudo apt-get install esl-erlang
ติดตั้ง Elixir -
sudo apt-get install elixir
Linux Distros อื่น ๆ
หากคุณมีการแจกจ่าย Linux อื่น ๆ โปรดไปที่หน้านี้เพื่อตั้งค่ายาอมฤตในระบบท้องถิ่นของคุณ
กำลังทดสอบการตั้งค่า
หากต้องการทดสอบการตั้งค่า Elixir ในระบบของคุณให้เปิดเทอร์มินัลของคุณและป้อน iex ในนั้น มันจะเปิดเปลือก Elixir แบบโต้ตอบดังต่อไปนี้ -
Erlang/OTP 19 [erts-8.0] [source-6dc93c1] [64-bit]
[smp:4:4] [async-threads:10] [hipe] [kernel-poll:false]
Interactive Elixir (1.3.1) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>
ตอนนี้ Elixir ได้รับการติดตั้งบนระบบของคุณเรียบร้อยแล้ว
เราจะเริ่มต้นด้วยโปรแกรม 'Hello World' ตามธรรมเนียม
ในการเริ่มเชลล์แบบโต้ตอบ Elixir ให้ป้อนคำสั่งต่อไปนี้
iex
หลังจากเปลือกเริ่มทำงานให้ใช้ไฟล์ IO.putsฟังก์ชันเพื่อ "ใส่" สตริงบนเอาต์พุตคอนโซล ป้อนสิ่งต่อไปนี้ในเปลือก Elixir ของคุณ -
IO.puts "Hello world"
ในบทช่วยสอนนี้เราจะใช้โหมดสคริปต์ Elixir ซึ่งเราจะเก็บรหัส Elixir ไว้ในไฟล์ที่มีนามสกุล .ex. ให้เราเก็บรหัสด้านบนไว้ในไฟล์test.exไฟล์. ในขั้นตอนที่ประสบความสำเร็จเราจะดำเนินการโดยใช้elixirc-
IO.puts "Hello world"
ตอนนี้ให้เราลองเรียกใช้โปรแกรมข้างต้นดังนี้ -
$elixirc test.ex
โปรแกรมข้างต้นสร้างผลลัพธ์ต่อไปนี้ -
Hello World
ที่นี่เรากำลังเรียกใช้ฟังก์ชัน IO.putsเพื่อสร้างสตริงไปยังคอนโซลของเราเป็นเอาต์พุต ฟังก์ชันนี้ยังสามารถเรียกได้ว่าเป็นวิธีที่เราทำใน C, C ++, Java และอื่น ๆ โดยให้อาร์กิวเมนต์ในวงเล็บตามชื่อฟังก์ชัน -
IO.puts("Hello world")
ความคิดเห็น
ความคิดเห็นบรรทัดเดียวเริ่มต้นด้วยสัญลักษณ์ '#' ไม่มีความคิดเห็นหลายบรรทัด แต่คุณสามารถซ้อนความคิดเห็นหลายรายการได้ ตัวอย่างเช่น -
#This is a comment in Elixir
การสิ้นสุดบรรทัด
ไม่มีการลงท้ายบรรทัดที่ต้องการเช่น ';' ใน Elixir อย่างไรก็ตามเราสามารถมีหลายคำสั่งในบรรทัดเดียวกันได้โดยใช้ ";" ตัวอย่างเช่น,
IO.puts("Hello"); IO.puts("World!")
โปรแกรมข้างต้นสร้างผลลัพธ์ต่อไปนี้ -
Hello
World!
ตัวระบุ
ตัวระบุเช่นตัวแปรชื่อฟังก์ชันใช้เพื่อระบุตัวแปรฟังก์ชัน ฯลฯ ใน Elixir คุณสามารถตั้งชื่อตัวระบุของคุณโดยเริ่มจากตัวอักษรตัวพิมพ์เล็กพร้อมตัวเลขขีดล่างและตัวอักษรตัวพิมพ์ใหญ่หลังจากนั้น หลักการตั้งชื่อนี้เรียกกันโดยทั่วไปว่า snake_case ตัวอย่างเช่นต่อไปนี้เป็นตัวระบุที่ถูกต้องใน Elixir -
var1 variable_2 one_M0r3_variable
โปรดทราบว่าตัวแปรสามารถตั้งชื่อด้วยเครื่องหมายขีดล่าง ค่าที่ไม่ได้ตั้งใจจะใช้ต้องถูกกำหนดให้กับ _ หรือให้กับตัวแปรที่ขึ้นต้นด้วยขีดล่าง -
_some_random_value = 42
นอกจากนี้ elixir ยังอาศัยขีดล่างเพื่อทำให้ฟังก์ชันเป็นส่วนตัวกับโมดูล หากคุณตั้งชื่อฟังก์ชันด้วยเครื่องหมายขีดล่างนำหน้าในโมดูลและนำเข้าโมดูลนั้นฟังก์ชันนี้จะไม่ถูกนำเข้า
มีความซับซ้อนอื่น ๆ อีกมากมายที่เกี่ยวข้องกับการตั้งชื่อฟังก์ชันใน Elixir ซึ่งเราจะพูดถึงในบทต่อ ๆ ไป
คำสงวน
คำต่อไปนี้สงวนไว้และไม่สามารถใช้เป็นชื่อตัวแปรโมดูลหรือฟังก์ชันได้
after and catch do inbits inlist nil else end
not or false fn in rescue true when xor
__MODULE__ __FILE__ __DIR__ __ENV__ __CALLER__
สำหรับการใช้ภาษาใด ๆ คุณต้องเข้าใจประเภทข้อมูลพื้นฐานที่ภาษารองรับ ในบทนี้เราจะพูดถึงประเภทข้อมูลพื้นฐาน 7 ประเภทที่สนับสนุนโดยภาษา Elixir ได้แก่ จำนวนเต็มลอยบูลีนอะตอมสตริงลิสต์และทูเปิล
ประเภทตัวเลข
Elixir เช่นเดียวกับภาษาโปรแกรมอื่น ๆ รองรับทั้งจำนวนเต็มและจำนวนลอย หากคุณเปิดเปลือก elixir ของคุณและป้อนจำนวนเต็มหรือลอยเป็นอินพุตมันจะคืนค่า ตัวอย่างเช่น,
42
เมื่อรันโปรแกรมข้างต้นจะให้ผลลัพธ์ดังนี้ -
42
คุณยังสามารถกำหนดตัวเลขเป็นฐานแปดฐานสิบหกและฐานสองได้
ฐานแปด
ในการกำหนดตัวเลขในฐานแปดให้นำหน้าด้วย '0o' ตัวอย่างเช่น 0o52 ในรูปฐานแปดจะเท่ากับ 42 ในฐานสิบ
เลขฐานสิบหก
ในการกำหนดตัวเลขเป็นฐานสิบให้นำหน้าด้วย '0x' ตัวอย่างเช่น 0xF1 ในฐานสิบหกเทียบเท่ากับ 241 ในฐานสิบ
ไบนารี่
ในการกำหนดตัวเลขในฐานสองให้นำหน้าด้วย '0b' ตัวอย่างเช่น 0b1101 ในไบนารีเทียบเท่ากับ 13 ในฐานสิบ
Elixir รองรับความแม่นยำสองเท่า 64 บิตสำหรับตัวเลขทศนิยม และยังสามารถกำหนดได้โดยใช้รูปแบบการยกกำลัง ตัวอย่างเช่น 10145230000 สามารถเขียนเป็น 1.014523e10
อะตอม
อะตอมคือค่าคงที่ซึ่งมีชื่อเป็นค่าของมัน สามารถสร้างได้โดยใช้สัญลักษณ์สี (:) ตัวอย่างเช่น,
:hello
บูลีน
รองรับ Elixir true และ falseเป็นบูลีน ค่าทั้งสองนี้แนบมากับอะตอมจริงและ: เท็จตามลำดับ
สตริง
สตริงใน Elixir ถูกแทรกระหว่างเครื่องหมายคำพูดคู่และมีการเข้ารหัสเป็น UTF-8 สามารถขยายได้หลายบรรทัดและมีการแก้ไข ในการกำหนดสตริงเพียงแค่ใส่ในเครื่องหมายคำพูดคู่ -
"Hello world"
ในการกำหนดสตริงหลายบรรทัดเราใช้ไวยากรณ์ที่คล้ายกับ python โดยมีเครื่องหมายคำพูดคู่สามคำ -
"""
Hello
World!
"""
เราจะเรียนรู้เกี่ยวกับสตริงไบนารีและรายการอักขระ (คล้ายกับสตริง) ในเชิงลึกในบทสตริง
ไบนารี
ไบนารีคือลำดับของไบต์ที่อยู่ใน << >> คั่นด้วยเครื่องหมายจุลภาค ตัวอย่างเช่น,
<< 65, 68, 75>>
ไบนารีส่วนใหญ่จะใช้เพื่อจัดการกับข้อมูลที่เกี่ยวข้องกับบิตและไบต์หากคุณมี โดยค่าเริ่มต้นสามารถจัดเก็บ 0 ถึง 255 ในแต่ละค่าได้ ขีด จำกัด ขนาดนี้สามารถเพิ่มได้โดยใช้ฟังก์ชันขนาดที่ระบุว่าควรใช้กี่บิตในการจัดเก็บค่านั้น ตัวอย่างเช่น,
<<65, 255, 289::size(15)>>
รายการ
Elixir ใช้วงเล็บเหลี่ยมเพื่อระบุรายการค่า ค่าสามารถเป็นประเภทใดก็ได้ ตัวอย่างเช่น,
[1, "Hello", :an_atom, true]
รายการมาพร้อมกับฟังก์ชัน inbuilt สำหรับส่วนหัวและส่วนท้ายของรายการที่ชื่อ hd และ tl ซึ่งจะส่งคืนส่วนหัวและส่วนท้ายของรายการตามลำดับ บางครั้งเมื่อคุณสร้างรายการระบบจะส่งคืนรายการถ่าน เนื่องจากเมื่อ elixir เห็นรายการอักขระ ASCII ที่พิมพ์ได้ก็จะพิมพ์เป็นรายการถ่าน โปรดทราบว่าสตริงและรายการอักขระไม่เท่ากัน เราจะพูดถึงรายการต่อไปในบทต่อ ๆ ไป
ทูเปิล
Elixir ใช้วงเล็บปีกกาเพื่อกำหนดสิ่งที่เพิ่มขึ้น เช่นเดียวกับรายการ tuples สามารถเก็บค่าใดก็ได้
{ 1, "Hello", :an_atom, true
มีคำถามเกิดขึ้นที่นี่ - ทำไมต้องระบุทั้งสองอย่าง lists และ tuplesเมื่อทั้งคู่ทำงานในลักษณะเดียวกัน? พวกเขามีการใช้งานที่แตกต่างกัน
รายการจะถูกจัดเก็บเป็นรายการที่เชื่อมโยงกันดังนั้นการแทรกการลบจึงทำได้อย่างรวดเร็วในรายการ
ในทางกลับกัน Tuples จะถูกเก็บไว้ในบล็อกหน่วยความจำที่อยู่ติดกันซึ่งทำให้เข้าถึงได้เร็วขึ้น แต่จะเพิ่มค่าใช้จ่ายเพิ่มเติมในการแทรกและการลบ
ตัวแปรช่วยให้เรามีพื้นที่จัดเก็บที่มีชื่อซึ่งโปรแกรมของเราสามารถจัดการได้ ตัวแปรแต่ละตัวใน Elixir มีประเภทเฉพาะซึ่งกำหนดขนาดและรูปแบบของหน่วยความจำของตัวแปร ช่วงของค่าที่สามารถเก็บไว้ในหน่วยความจำนั้น และชุดของการดำเนินการที่สามารถนำไปใช้กับตัวแปร
ประเภทของตัวแปร
Elixir รองรับตัวแปรพื้นฐานประเภทต่อไปนี้
จำนวนเต็ม
สิ่งเหล่านี้ใช้สำหรับจำนวนเต็ม มีขนาด 32 บิตบนสถาปัตยกรรม 32 บิตและ 64 บิตบนสถาปัตยกรรม 64 บิต จำนวนเต็มจะถูกลงนามในยาอายุวัฒนะเสมอ หากจำนวนเต็มเริ่มขยายขนาดเกินขีด จำกัด Elixir จะแปลงเป็นจำนวนเต็มขนาดใหญ่ซึ่งจะใช้หน่วยความจำในช่วง 3 ถึง n คำแล้วแต่ว่าคำใดสามารถใส่ลงในหน่วยความจำได้
ลอย
Floats มีความแม่นยำ 64 บิตในยาอายุวัฒนะ นอกจากนี้ยังเป็นเหมือนจำนวนเต็มในแง่ของหน่วยความจำ เมื่อกำหนดค่าลอยสามารถใช้สัญกรณ์เอกซ์โพเนนเชียลได้
บูลีน
สามารถรับค่าได้ 2 ค่าซึ่งเป็นจริงหรือเท็จ
สตริง
สตริงเป็น utf-8 ที่เข้ารหัสใน elixir พวกเขามีโมดูลสตริงซึ่งมีฟังก์ชันมากมายให้กับโปรแกรมเมอร์เพื่อจัดการกับสตริง
ฟังก์ชั่นที่ไม่ระบุชื่อ / Lambdas
ฟังก์ชันเหล่านี้เป็นฟังก์ชันที่สามารถกำหนดและกำหนดให้กับตัวแปรซึ่งสามารถใช้เพื่อเรียกใช้ฟังก์ชันนี้ได้
คอลเลกชัน
มีคอลเลกชันมากมายใน Elixir บางส่วนเป็นรายการสิ่งที่เพิ่มขึ้นแผนที่ไบนารี ฯลฯ สิ่งเหล่านี้จะกล่าวถึงในบทต่อ ๆ ไป
การประกาศตัวแปร
การประกาศตัวแปรจะบอกล่ามว่าจะสร้างหน่วยเก็บข้อมูลสำหรับตัวแปรไว้ที่ใดและเท่าใด Elixir ไม่อนุญาตให้เราประกาศตัวแปร ต้องประกาศตัวแปรและกำหนดค่าพร้อมกัน ตัวอย่างเช่นในการสร้างตัวแปรชื่อ life และกำหนดค่า 42 เราทำสิ่งต่อไปนี้ -
life = 42
สิ่งนี้จะผูกชีวิตตัวแปรกับค่า 42 หากเราต้องการกำหนดค่าใหม่ให้กับตัวแปรนี้เราสามารถทำได้โดยใช้ไวยากรณ์เดียวกันกับด้านบนกล่าวคือ
life = "Hello world"
การตั้งชื่อตัวแปร
การตั้งชื่อตัวแปรตาม snake_caseอนุสัญญาใน Elixir กล่าวคือตัวแปรทั้งหมดต้องขึ้นต้นด้วยตัวอักษรพิมพ์เล็กตามด้วย 0 ตัวอักษรขึ้นไป (ทั้งตัวพิมพ์ใหญ่และตัวพิมพ์เล็ก) ตามด้วยตัวเลือก '?' หรือ '!'.
ชื่อตัวแปรสามารถเริ่มต้นด้วยขีดล่างนำหน้าได้ แต่ต้องใช้เมื่อละเว้นตัวแปรเท่านั้นกล่าวคือตัวแปรนั้นจะไม่ถูกใช้อีก แต่จำเป็นต้องกำหนดให้กับบางสิ่ง
ตัวแปรการพิมพ์
ในเชลล์แบบโต้ตอบตัวแปรจะพิมพ์หากคุณเพียงแค่ป้อนชื่อตัวแปร ตัวอย่างเช่นหากคุณสร้างตัวแปร -
life = 42
และป้อน 'ชีวิต' ในเปลือกของคุณคุณจะได้ผลลัพธ์เป็น -
42
แต่ถ้าคุณต้องการส่งออกตัวแปรไปยังคอนโซล (เมื่อเรียกใช้สคริปต์ภายนอกจากไฟล์) คุณต้องระบุตัวแปรเป็นอินพุตให้ IO.puts ฟังก์ชัน -
life = 42
IO.puts life
หรือ
life = 42
IO.puts(life)
สิ่งนี้จะให้ผลลัพธ์ต่อไปนี้ -
42
ตัวดำเนินการคือสัญลักษณ์ที่บอกให้คอมไพเลอร์ดำเนินการจัดการทางคณิตศาสตร์หรือตรรกะเฉพาะ มีโอเปอเรเตอร์จำนวนมากที่จัดหาโดยยาอายุวัฒนะ แบ่งออกเป็นหมวดหมู่ดังต่อไปนี้ -
- ตัวดำเนินการเลขคณิต
- ตัวดำเนินการเปรียบเทียบ
- ตัวดำเนินการบูลีน
- ตัวดำเนินการอื่น ๆ
ตัวดำเนินการเลขคณิต
ตารางต่อไปนี้แสดงตัวดำเนินการทางคณิตศาสตร์ทั้งหมดที่สนับสนุนโดยภาษา Elixir สมมติตัวแปรA ถือ 10 และตัวแปร B ถือ 20 แล้ว -
แสดงตัวอย่าง
ตัวดำเนินการ | คำอธิบาย | ตัวอย่าง |
---|---|---|
+ | เพิ่ม 2 หมายเลข | A + B จะให้ 30 |
- | ลบตัวเลขที่สองจากตัวแรก | AB จะให้ -10 |
* | คูณสองจำนวน | A * B จะให้ 200 |
/ | หารตัวเลขแรกจากวินาที สิ่งนี้จะทำให้ตัวเลขลอยตัวและให้ผลลัพธ์แบบลอยตัว | A / B จะให้ 0.5 |
div | ฟังก์ชันนี้ใช้เพื่อหาผลหาร | div (10,20) จะให้ 0 |
rem | ฟังก์ชันนี้ใช้เพื่อรับส่วนที่เหลือจากการหาร | rem (A, B) จะให้ 10 |
ตัวดำเนินการเปรียบเทียบ
ตัวดำเนินการเปรียบเทียบใน Elixir ส่วนใหญ่มักใช้กับตัวดำเนินการในภาษาอื่น ๆ ส่วนใหญ่ ตารางต่อไปนี้สรุปตัวดำเนินการเปรียบเทียบใน Elixir สมมติตัวแปรA ถือ 10 และตัวแปร B ถือ 20 แล้ว -
แสดงตัวอย่าง
ตัวดำเนินการ | คำอธิบาย | ตัวอย่าง |
---|---|---|
== | ตรวจสอบว่าค่าทางซ้ายเท่ากับค่าทางขวาหรือไม่ (Type casts values ถ้าไม่ใช่ประเภทเดียวกัน) | A == B จะให้เท็จ |
! = | ตรวจสอบว่าค่าทางซ้ายไม่เท่ากับค่าทางขวาหรือไม่ | A! = B จะให้จริง |
=== | ตรวจสอบว่าประเภทของค่าทางซ้ายเท่ากับประเภทของค่าทางด้านขวาหรือไม่ถ้าใช่ให้ตรวจสอบค่าเดียวกัน | A === B จะให้เท็จ |
! == | เหมือนกับข้างต้น แต่ตรวจสอบความไม่เท่าเทียมกันแทนที่จะเป็นความเท่าเทียมกัน | A! == B จะให้จริง |
> | ตรวจสอบว่าค่าของตัวถูกดำเนินการด้านซ้ายมากกว่าค่าของตัวถูกดำเนินการด้านขวาหรือไม่ ถ้าใช่เงื่อนไขจะกลายเป็นจริง | A> B จะให้เท็จ |
< | ตรวจสอบว่าค่าของตัวถูกดำเนินการด้านซ้ายน้อยกว่าค่าของตัวถูกดำเนินการด้านขวาหรือไม่ ถ้าใช่เงื่อนไขจะกลายเป็นจริง | A <B จะให้จริง |
> = | ตรวจสอบว่าค่าของตัวถูกดำเนินการด้านซ้ายมากกว่าหรือเท่ากับค่าของตัวถูกดำเนินการด้านขวาหรือไม่ ถ้าใช่เงื่อนไขจะกลายเป็นจริง | A> = B จะให้เท็จ |
<= | ตรวจสอบว่าค่าของตัวถูกดำเนินการด้านซ้ายน้อยกว่าหรือเท่ากับค่าของตัวถูกดำเนินการด้านขวาหรือไม่ ถ้าใช่เงื่อนไขจะกลายเป็นจริง | A <= B จะให้จริง |
ตัวดำเนินการทางตรรกะ
Elixir มีตัวดำเนินการเชิงตรรกะ 6 ตัว: และหรือไม่ใช่ &&, || และ!. สามคนแรกand or notเป็นตัวดำเนินการบูลีนที่เข้มงวดซึ่งหมายความว่าพวกเขาคาดหวังว่าอาร์กิวเมนต์แรกของพวกเขาจะเป็นบูลีน อาร์กิวเมนต์ที่ไม่ใช่บูลีนจะทำให้เกิดข้อผิดพลาด ในขณะที่สามคนถัดไป&&, || and !ไม่เข้มงวดไม่ต้องการให้เรามีค่าแรกอย่างเคร่งครัดเหมือนบูลีน พวกเขาทำงานในลักษณะเดียวกับคู่หูที่เข้มงวด สมมติตัวแปรA ถือเป็นจริงและตัวแปร B ถือ 20 แล้ว -
แสดงตัวอย่าง
ตัวดำเนินการ | คำอธิบาย | ตัวอย่าง |
---|---|---|
และ | ตรวจสอบว่าค่าทั้งสองที่ระบุเป็นจริงหรือไม่ถ้าใช่จะส่งคืนค่าของตัวแปรที่สอง (ตรรกะและ). | A และ B จะให้ 20 |
หรือ | ตรวจสอบว่าค่าที่ระบุเป็นจริงหรือไม่ ส่งคืนค่าใดก็ตามที่เป็นจริง ผลตอบแทนที่เป็นเท็จ (ตรรกะหรือ) | A หรือ B จะให้จริง |
ไม่ | ตัวดำเนินการยูนารีซึ่งเปลี่ยนค่าของอินพุตที่กำหนด | ไม่ใช่ A จะให้เท็จ |
&& | ไม่เข้มงวด and. ทำงานเช่นเดียวกับand แต่ไม่คาดว่าอาร์กิวเมนต์แรกจะเป็นบูลีน | B && A จะให้ 20 |
|| | ไม่เข้มงวด or. ทำงานเช่นเดียวกับor แต่ไม่คาดว่าอาร์กิวเมนต์แรกจะเป็นบูลีน | B || A จะให้จริง |
! | ไม่เข้มงวด not. ทำงานเช่นเดียวกับnot แต่ไม่คาดหวังว่าอาร์กิวเมนต์จะเป็นบูลีน | ! A จะให้เท็จ |
NOTE −และ , หรือ , &&และ|| || เป็นตัวดำเนินการไฟฟ้าลัดวงจร ซึ่งหมายความว่าหากอาร์กิวเมนต์แรกของandเป็นเท็จจากนั้นจะไม่ตรวจสอบรายการที่สองอีก และถ้าอาร์กิวเมนต์แรกของorเป็นจริงแล้วจะไม่ตรวจสอบข้อที่สอง ตัวอย่างเช่น,
false and raise("An error")
#This won't raise an error as raise function wont get executed because of short
#circuiting nature of and operator
ตัวดำเนินการ Bitwise
ตัวดำเนินการ Bitwise ทำงานบนบิตและดำเนินการทีละบิต Elixir มีโมดูล bitwise เป็นส่วนหนึ่งของแพ็คเกจBitwiseดังนั้นในการใช้สิ่งเหล่านี้คุณต้องใช้โมดูลบิต ในการใช้งานให้ป้อนคำสั่งต่อไปนี้ในเชลล์ของคุณ -
use Bitwise
สมมติว่า A เป็น 5 และ B เป็น 6 สำหรับตัวอย่างต่อไปนี้ -
แสดงตัวอย่าง
ตัวดำเนินการ | คำอธิบาย | ตัวอย่าง |
---|---|---|
&&& | Bitwise และตัวดำเนินการจะคัดลอกบิตเพื่อให้ได้ผลลัพธ์หากมีอยู่ในตัวถูกดำเนินการทั้งสอง | A &&& B จะให้ 4 |
||| | Bitwise หรือตัวดำเนินการจะคัดลอกบิตเพื่อให้ได้ผลลัพธ์หากมีอยู่ในตัวถูกดำเนินการอย่างใดอย่างหนึ่ง | ก ||| B จะให้ 7 |
>>> | ตัวดำเนินการ shift ขวาแบบ Bitwise จะเลื่อนบิตตัวถูกดำเนินการตัวแรกไปทางขวาตามจำนวนที่ระบุในตัวถูกดำเนินการที่สอง | A >>> B จะให้ 0 |
<<< | ตัวดำเนินการ Shift ด้านซ้ายแบบ Bitwise จะเลื่อนบิตตัวถูกดำเนินการแรกไปทางซ้ายตามจำนวนที่ระบุในตัวถูกดำเนินการที่สอง | A <<< B จะให้ 320 |
^^^ | ตัวดำเนินการ Bitwise XOR จะคัดลอกบิตเพื่อให้ได้ผลลัพธ์เฉพาะในกรณีที่ตัวถูกดำเนินการทั้งสองต่างกัน | A ^^^ B จะให้ 3 |
~~~ | Unary bitwise จะไม่กลับด้านบิตของตัวเลขที่กำหนด | ~~~ A จะให้ -6 |
ตัวดำเนินการอื่น ๆ
นอกเหนือจากตัวดำเนินการข้างต้น Elixir ยังมีตัวดำเนินการอื่น ๆ อีกมากมายเช่น Concatenation Operator, Match Operator, Pin Operator, Pipe Operator, String Match Operator, Code Point Operator, Capture Operator, Ternary Operator ซึ่งทำให้ภาษานี้มีประสิทธิภาพมาก
แสดงตัวอย่าง
การจับคู่รูปแบบเป็นเทคนิคที่ Elixir สืบทอดมาจาก Erlang เป็นเทคนิคที่มีประสิทธิภาพมากที่ช่วยให้เราสามารถแยกโครงสร้างย่อยที่ง่ายกว่าออกจากโครงสร้างข้อมูลที่ซับซ้อนเช่นรายการสิ่งที่เพิ่มขึ้นแผนที่ ฯลฯ
การแข่งขันมี 2 ส่วนหลักก left และก rightด้านข้าง ด้านขวาเป็นโครงสร้างข้อมูลชนิดใดก็ได้ ด้านซ้ายพยายามจับคู่โครงสร้างข้อมูลทางด้านขวาและผูกตัวแปรใด ๆ ทางด้านซ้ายเข้ากับโครงสร้างย่อยทางด้านขวา หากไม่พบการจับคู่ตัวดำเนินการจะทำให้เกิดข้อผิดพลาด
การจับคู่ที่ง่ายที่สุดคือตัวแปรเดี่ยวทางด้านซ้ายและโครงสร้างข้อมูลทางด้านขวา This variable will match anything. ตัวอย่างเช่น,
x = 12
x = "Hello"
IO.puts(x)
คุณสามารถวางตัวแปรไว้ในโครงสร้างเพื่อให้คุณจับภาพโครงสร้างย่อยได้ ตัวอย่างเช่น,
[var_1, _unused_var, var_2] = [{"First variable"}, 25, "Second variable" ]
IO.puts(var_1)
IO.puts(var_2)
สิ่งนี้จะจัดเก็บค่า {"First variable"}ในvar_1และ"Second variable"ในVAR_2 นอกจากนี้ยังมีความพิเศษ_ ตัวแปร (หรือตัวแปรที่ขึ้นต้นด้วย '_') ที่ทำงานเหมือนกับตัวแปรอื่น ๆ แต่บอก elixir "Make sure something is here, but I don't care exactly what it is.". ในตัวอย่างก่อนหน้านี้_unused_varเป็นหนึ่งในตัวแปรดังกล่าว
เราสามารถจับคู่รูปแบบที่ซับซ้อนมากขึ้นโดยใช้เทคนิคนี้ สำหรับexample หากคุณต้องการแกะและรับหมายเลขในทูเปิลซึ่งอยู่ในรายการที่ตัวเองอยู่ในรายการคุณสามารถใช้คำสั่งต่อไปนี้ -
[_, [_, {a}]] = ["Random string", [:an_atom, {24}]]
IO.puts(a)
โปรแกรมข้างต้นสร้างผลลัพธ์ต่อไปนี้ -
24
สิ่งนี้จะผูกมัด a ถึง 24 ค่าอื่น ๆ จะถูกละเว้นเมื่อเราใช้ '_'
ในการจับคู่รูปแบบหากเราใช้ตัวแปรบน rightค่าของมันถูกใช้ หากคุณต้องการใช้ค่าของตัวแปรทางด้านซ้ายคุณจะต้องใช้ตัวดำเนินการพิน
ตัวอย่างเช่นหากคุณมีตัวแปร "a" ที่มีค่า 25 และคุณต้องการจับคู่กับตัวแปรอื่น "b" ที่มีค่า 25 คุณจะต้องป้อน -
a = 25
b = 25
^a = b
บรรทัดสุดท้ายตรงกับค่าปัจจุบันของ aแทนที่จะกำหนดให้เป็นค่าของ b. หากเรามีชุดด้านซ้ายและด้านขวาที่ไม่ตรงกันผู้ดำเนินการจับคู่จะทำให้เกิดข้อผิดพลาด ตัวอย่างเช่นหากเราพยายามจับคู่ทูเปิลกับรายการหรือรายการขนาด 2 กับรายการขนาด 3 ข้อผิดพลาดจะปรากฏขึ้น
โครงสร้างการตัดสินใจกำหนดให้โปรแกรมเมอร์ระบุเงื่อนไขอย่างน้อยหนึ่งเงื่อนไขที่จะประเมินหรือทดสอบโดยโปรแกรมพร้อมกับคำสั่งหรือคำสั่งที่จะดำเนินการหากเงื่อนไขถูกกำหนดให้เป็น trueและเป็นทางเลือกที่จะเรียกใช้คำสั่งอื่น ๆ หากเงื่อนไขถูกกำหนดให้เป็น false.
ต่อไปนี้เป็นข้อมูลทั่วไปจากโครงสร้างการตัดสินใจทั่วไปที่พบในภาษาโปรแกรมส่วนใหญ่ -
Elixir มีโครงสร้างเงื่อนไข if / else เหมือนกับภาษาโปรแกรมอื่น ๆ นอกจากนี้ยังมีไฟล์condคำสั่งที่เรียกค่าจริงแรกที่พบ Case เป็นคำสั่งโฟลว์การควบคุมอื่นที่ใช้การจับคู่รูปแบบเพื่อควบคุมโฟลว์ของโปรแกรม มาดูรายละเอียดกันดีกว่า
Elixir มีข้อความประกอบการตัดสินใจประเภทต่อไปนี้ คลิกลิงก์ต่อไปนี้เพื่อตรวจสอบรายละเอียด
ซีเนียร์ | คำชี้แจงและคำอธิบาย |
---|---|
1 | ถ้าคำสั่ง คำสั่ง if ประกอบด้วยนิพจน์บูลีนตามด้วย doหนึ่งหรือมากกว่าคำสั่งปฏิบัติการและสุดท้ายคือ endคำสำคัญ. รหัสในคำสั่ง if ดำเนินการต่อเมื่อเงื่อนไขบูลีนประเมินว่าเป็นจริง |
2 | if..else คำสั่ง คำสั่ง if สามารถตามด้วยคำสั่ง else ที่เป็นทางเลือก (ภายในบล็อก do..end) ซึ่งดำเนินการเมื่อนิพจน์บูลีนเป็นเท็จ |
3 | เว้นแต่จะมีคำสั่ง คำสั่งยกเว้นมีเนื้อความเดียวกับคำสั่ง if รหัสภายในคำสั่งเว้นแต่จะดำเนินการเมื่อเงื่อนไขที่ระบุเป็นเท็จเท่านั้น |
4 | เว้นแต่.. คำสั่ง คำสั่งเว้นแต่ .. else มีเนื้อความเหมือนกับคำสั่ง if..else รหัสภายในคำสั่งเว้นแต่จะดำเนินการเมื่อเงื่อนไขที่ระบุเป็นเท็จเท่านั้น |
5 | เงื่อนไข คำสั่ง cond ถูกใช้ในกรณีที่เราต้องการรันโค้ดบนพื้นฐานของเงื่อนไขต่างๆ มันทำงานเหมือน if ... else if … .else สร้างในภาษาโปรแกรมอื่น ๆ |
6 | กรณี Case statement ถือได้ว่าเป็นการแทนที่คำสั่ง switch ในภาษาที่จำเป็น Case รับตัวแปร / ลิเทอรัลและใช้การจับคู่รูปแบบกับเคสที่แตกต่างกัน หากตรงกับกรณีใด ๆ Elixir จะรันโค้ดที่เกี่ยวข้องกับเคสนั้นและออกจาก case statement |
สตริงใน Elixir ถูกแทรกระหว่างเครื่องหมายคำพูดคู่และมีการเข้ารหัสเป็น UTF-8 แตกต่างจาก C และ C ++ ที่สตริงเริ่มต้นเข้ารหัส ASCII และมีเพียง 256 อักขระที่แตกต่างกัน UTF-8 ประกอบด้วยจุดรหัส 1,112,064 จุด ซึ่งหมายความว่าการเข้ารหัส UTF-8 ประกอบด้วยอักขระต่างๆที่เป็นไปได้เหล่านั้น เนื่องจากสตริงใช้ utf-8 เราจึงสามารถใช้สัญลักษณ์เช่นö, łเป็นต้น
สร้างสตริง
ในการสร้างตัวแปรสตริงเพียงกำหนดสตริงให้กับตัวแปร -
str = "Hello world"
หากต้องการพิมพ์ลงในคอนโซลของคุณเพียงโทร IO.puts ฟังก์ชั่นและส่งผ่านตัวแปร str -
str = str = "Hello world"
IO.puts(str)
โปรแกรมข้างต้นสร้างผลลัพธ์ต่อไปนี้ -
Hello World
สตริงว่าง
คุณสามารถสร้างสตริงว่างโดยใช้สตริงลิเทอรัล "". ตัวอย่างเช่น,
a = ""
if String.length(a) === 0 do
IO.puts("a is an empty string")
end
โปรแกรมข้างต้นสร้างผลลัพธ์ต่อไปนี้
a is an empty string
การแก้ไขสตริง
การแก้ไขสตริงเป็นวิธีสร้างค่าสตริงใหม่จากการผสมของค่าคงที่ตัวแปรลิเทอรัลและนิพจน์โดยการรวมค่าไว้ในลิเทอรัลสตริง Elixir รองรับการแก้ไขสตริงเพื่อใช้ตัวแปรในสตริงเมื่อเขียนมันห่อด้วยวงเล็บปีกกาและใส่เครื่องหมายวงเล็บปีกกาไว้ข้างหน้า'#' ลงชื่อ.
ตัวอย่างเช่น,
x = "Apocalypse"
y = "X-men #{x}"
IO.puts(y)
สิ่งนี้จะรับค่าของ x และแทนที่ด้วย y โค้ดด้านบนจะสร้างผลลัพธ์ต่อไปนี้ -
X-men Apocalypse
การต่อสตริง
เราได้เห็นการใช้การต่อสตริงในบทก่อน ๆ แล้ว ตัวดำเนินการ '<>' ใช้เพื่อต่อสายอักขระใน Elixir ในการเชื่อม 2 สตริงเข้าด้วยกัน
x = "Dark"
y = "Knight"
z = x <> " " <> y
IO.puts(z)
โค้ดด้านบนสร้างผลลัพธ์ต่อไปนี้ -
Dark Knight
ความยาวสตริง
เพื่อให้ได้ความยาวของสตริงเราใช้ String.lengthฟังก์ชัน ส่งสตริงเป็นพารามิเตอร์และจะแสดงขนาดของมัน ตัวอย่างเช่น,
IO.puts(String.length("Hello"))
เมื่อรันโปรแกรมด้านบนจะให้ผลลัพธ์ดังนี้ -
5
การย้อนกลับสตริง
ในการย้อนกลับสตริงให้ส่งผ่านไปยังฟังก์ชัน String.reverse ตัวอย่างเช่น,
IO.puts(String.reverse("Elixir"))
โปรแกรมข้างต้นสร้างผลลัพธ์ต่อไปนี้ -
rixilE
การเปรียบเทียบสตริง
ในการเปรียบเทียบ 2 สตริงเราสามารถใช้ตัวดำเนินการ == หรือ === ตัวอย่างเช่น,
var_1 = "Hello world"
var_2 = "Hello Elixir"
if var_1 === var_2 do
IO.puts("#{var_1} and #{var_2} are the same")
else
IO.puts("#{var_1} and #{var_2} are not the same")
end
โปรแกรมข้างต้นสร้างผลลัพธ์ต่อไปนี้ -
Hello world and Hello elixir are not the same.
การจับคู่สตริง
เราได้เห็นการใช้ตัวดำเนินการจับคู่สตริง = ~ แล้ว ในการตรวจสอบว่าสตริงตรงกับนิพจน์ทั่วไปหรือไม่เรายังสามารถใช้ตัวดำเนินการจับคู่สตริงหรือ String.match? ฟังก์ชัน ตัวอย่างเช่น,
IO.puts(String.match?("foo", ~r/foo/))
IO.puts(String.match?("bar", ~r/foo/))
โปรแกรมข้างต้นสร้างผลลัพธ์ต่อไปนี้ -
true
false
สิ่งเดียวกันนี้สามารถทำได้โดยใช้ตัวดำเนินการ = ~ ตัวอย่างเช่น,
IO.puts("foo" =~ ~r/foo/)
โปรแกรมข้างต้นสร้างผลลัพธ์ต่อไปนี้ -
true
ฟังก์ชันสตริง
Elixir รองรับฟังก์ชั่นจำนวนมากที่เกี่ยวข้องกับสตริงซึ่งบางส่วนที่ใช้มากที่สุดแสดงอยู่ในตารางต่อไปนี้
ซีเนียร์ | ฟังก์ชั่นและวัตถุประสงค์ |
---|---|
1 | at(string, position) ส่งคืนกราฟที่ตำแหน่งของสตริง utf8 ที่กำหนด หากตำแหน่งมีค่ามากกว่าความยาวสตริงจะส่งกลับค่า nil |
2 | capitalize(string) แปลงอักขระตัวแรกในสตริงที่กำหนดให้เป็นตัวพิมพ์ใหญ่และส่วนที่เหลือเป็นตัวพิมพ์เล็ก |
3 | contains?(string, contents) ตรวจสอบว่าสตริงมีเนื้อหาที่กำหนดหรือไม่ |
4 | downcase(string) แปลงอักขระทั้งหมดในสตริงที่กำหนดเป็นตัวพิมพ์เล็ก |
5 | ends_with?(string, suffixes) ส่งคืนจริงหากสตริงลงท้ายด้วยส่วนต่อท้ายที่กำหนด |
6 | first(string) ส่งคืน grapheme แรกจากสตริง utf8 ไม่มีถ้าสตริงว่าง |
7 | last(string) ส่งคืน grapheme สุดท้ายจากสตริง utf8 ไม่มีถ้าสตริงว่าง |
8 | replace(subject, pattern, replacement, options \\ []) ส่งคืนสตริงใหม่ที่สร้างขึ้นโดยแทนที่การเกิดขึ้นของรูปแบบในหัวเรื่องด้วยการแทนที่ |
9 | slice(string, start, len) ส่งคืนสตริงย่อยที่เริ่มต้นที่จุดเริ่มต้นออฟเซ็ตและของความยาวเลน |
10 | split(string) แบ่งสตริงออกเป็นสตริงย่อยที่ช่องว่าง Unicode แต่ละรายการโดยละเว้นช่องว่างนำหน้าและต่อท้าย กลุ่มของช่องว่างจะถือว่าเป็นเหตุการณ์เดียว การแบ่งแยกจะไม่เกิดขึ้นบนช่องว่างที่ไม่ทำลาย |
11 | upcase(string) แปลงอักขระทั้งหมดในสตริงที่กำหนดเป็นตัวพิมพ์ใหญ่ |
ไบนารี
ไบนารีเป็นเพียงลำดับของไบต์ ไบนารีถูกกำหนดโดยใช้<< >>. ตัวอย่างเช่น:
<< 0, 1, 2, 3 >>
แน่นอนว่าไบต์เหล่านั้นสามารถจัดระเบียบด้วยวิธีใดก็ได้แม้จะอยู่ในลำดับที่ไม่ได้ทำให้เป็นสตริงที่ถูกต้องก็ตาม ตัวอย่างเช่น,
<< 239, 191, 191 >>
สตริงยังเป็นไบนารี และตัวดำเนินการต่อสตริง<> เป็นตัวดำเนินการต่อไบนารี:
IO.puts(<< 0, 1 >> <> << 2, 3 >>)
โค้ดด้านบนสร้างผลลัพธ์ต่อไปนี้ -
<< 0, 1, 2, 3 >>
สังเกตอักขระł เนื่องจากมีการเข้ารหัส utf-8 การแสดงอักขระนี้จึงใช้เวลา 2 ไบต์
เนื่องจากตัวเลขแต่ละตัวที่แสดงในไบนารีมีความหมายว่าเป็นไบต์เมื่อค่านี้เพิ่มขึ้นจาก 255 จึงถูกตัดทอน เพื่อป้องกันสิ่งนี้เราใช้ตัวปรับขนาดเพื่อระบุจำนวนบิตที่เราต้องการให้ตัวเลขนั้นใช้ ตัวอย่างเช่น -
IO.puts(<< 256 >>) # truncated, it'll print << 0 >>
IO.puts(<< 256 :: size(16) >>) #Takes 16 bits/2 bytes, will print << 1, 0 >>
โปรแกรมข้างต้นจะสร้างผลลัพธ์ต่อไปนี้ -
<< 0 >>
<< 1, 0 >>
นอกจากนี้เรายังสามารถใช้ตัวปรับแต่ง utf8 หากอักขระเป็นจุดรหัสมันจะถูกสร้างในเอาต์พุต อื่นเป็นไบต์ -
IO.puts(<< 256 :: utf8 >>)
โปรแกรมข้างต้นสร้างผลลัพธ์ต่อไปนี้ -
Ā
เรายังมีฟังก์ชันที่เรียกว่า is_binaryที่ตรวจสอบว่าตัวแปรที่กำหนดเป็นไบนารีหรือไม่ โปรดทราบว่าเฉพาะตัวแปรที่จัดเก็บเป็นทวีคูณของ 8 บิตเท่านั้นที่เป็นไบนารี
บิตสตริง
ถ้าเรากำหนดไบนารีโดยใช้ตัวปรับขนาดและส่งผ่านค่าที่ไม่ใช่ผลคูณของ 8 เราจะจบลงด้วย bitstring แทนที่จะเป็นไบนารี ตัวอย่างเช่น,
bs = << 1 :: size(1) >>
IO.puts(bs)
IO.puts(is_binary(bs))
IO.puts(is_bitstring(bs))
โปรแกรมข้างต้นสร้างผลลัพธ์ต่อไปนี้ -
<< 1::size(1) >>
false
true
ซึ่งหมายความว่าตัวแปรนั้น bsไม่ใช่ไบนารี แต่เป็นบิตสตริง นอกจากนี้เรายังสามารถพูดได้ว่าไบนารีเป็นบิตสตริงที่จำนวนบิตหารด้วย 8 การจับคู่รูปแบบจะทำงานบนไบนารีและบิตสตริงในลักษณะเดียวกัน
รายการถ่านไม่ได้เป็นอะไรมากไปกว่ารายการอักขระ พิจารณาโปรแกรมต่อไปนี้เพื่อให้เข้าใจตรงกัน
IO.puts('Hello')
IO.puts(is_list('Hello'))
โปรแกรมข้างต้นสร้างผลลัพธ์ต่อไปนี้ -
Hello
true
แทนที่จะประกอบด้วยไบต์รายการอักขระจะมีจุดรหัสของอักขระระหว่างเครื่องหมายคำพูดเดี่ยว So while the double-quotes represent a string (i.e. a binary), singlequotes represent a char list (i.e. a list). โปรดทราบว่า IEx จะสร้างเฉพาะจุดรหัสเป็นเอาต์พุตหากอักขระใด ๆ อยู่นอกช่วง ASCII
รายการ Char ถูกใช้เป็นส่วนใหญ่เมื่อเชื่อมต่อกับ Erlang โดยเฉพาะอย่างยิ่งไลบรารีเก่าที่ไม่ยอมรับไบนารีเป็นอาร์กิวเมนต์ คุณสามารถแปลงรายการ char เป็นสตริงและย้อนกลับโดยใช้ฟังก์ชัน to_string (char_list) และto_char_list (สตริง) -
IO.puts(is_list(to_char_list("hełło")))
IO.puts(is_binary(to_string ('hełło')))
โปรแกรมข้างต้นสร้างผลลัพธ์ต่อไปนี้ -
true
true
NOTE - ฟังก์ชั่น to_string และ to_char_list เป็น polymorphic กล่าวคือสามารถรับอินพุตได้หลายประเภทเช่นอะตอมจำนวนเต็มและแปลงเป็นสตริงและรายการถ่านตามลำดับ
(เชื่อมโยง) รายการ
รายการที่เชื่อมโยงคือรายการองค์ประกอบที่แตกต่างกันซึ่งถูกเก็บไว้ในตำแหน่งต่างๆในหน่วยความจำและถูกติดตามโดยใช้การอ้างอิง รายการที่เชื่อมโยงเป็นโครงสร้างข้อมูลที่ใช้โดยเฉพาะในการเขียนโปรแกรมเชิงฟังก์ชัน
Elixir ใช้วงเล็บเหลี่ยมเพื่อระบุรายการค่า ค่าสามารถเป็นประเภทใดก็ได้ -
[1, 2, true, 3]
เมื่อ Elixir เห็นรายการหมายเลข ASCII ที่พิมพ์ได้ Elixir จะพิมพ์เป็นรายการถ่าน (ตามตัวอักษรรายการ) เมื่อใดก็ตามที่คุณเห็นค่าใน IEx และไม่แน่ใจว่าคืออะไรคุณสามารถใช้ไฟล์i เพื่อดึงข้อมูลเกี่ยวกับมัน
IO.puts([104, 101, 108, 108, 111])
อักขระด้านบนในรายการสามารถพิมพ์ได้ทั้งหมด เมื่อรันโปรแกรมข้างต้นจะให้ผลลัพธ์ดังนี้ -
hello
คุณยังสามารถกำหนดรายการในทางกลับกันโดยใช้เครื่องหมายคำพูดเดี่ยว -
IO.puts(is_list('Hello'))
เมื่อรันโปรแกรมข้างต้นจะให้ผลลัพธ์ดังนี้ -
true
โปรดจำไว้ว่าการเป็นตัวแทนที่ยกมาเพียงครั้งเดียวและการยกมาสองครั้งนั้นไม่สามารถเทียบเท่ากันได้ใน Elixir เนื่องจากเป็นตัวแทนประเภทต่างๆ
ความยาวของรายการ
ในการค้นหาความยาวของรายการเราใช้ฟังก์ชัน length ดังในโปรแกรมต่อไปนี้ -
IO.puts(length([1, 2, :true, "str"]))
โปรแกรมข้างต้นสร้างผลลัพธ์ต่อไปนี้ -
4
การต่อและการลบ
สองรายการสามารถต่อกันและลบได้โดยใช้ ++ และ --ตัวดำเนินการ พิจารณาตัวอย่างต่อไปนี้เพื่อทำความเข้าใจฟังก์ชัน
IO.puts([1, 2, 3] ++ [4, 5, 6])
IO.puts([1, true, 2, false, 3, true] -- [true, false])
สิ่งนี้จะทำให้คุณมีสตริงที่ต่อกันในกรณีแรกและสตริงที่ลบออกในตัวที่สอง โปรแกรมข้างต้นสร้างผลลัพธ์ต่อไปนี้ -
[1, 2, 3, 4, 5, 6]
[1, 2, 3, true]
หัวและท้ายรายการ
ส่วนหัวเป็นองค์ประกอบแรกของรายการและส่วนท้ายคือส่วนที่เหลือของรายการ สามารถเรียกดูได้ด้วยฟังก์ชันhd และ tl. ให้เรากำหนดรายการให้กับตัวแปรและดึงหัวและหาง
list = [1, 2, 3]
IO.puts(hd(list))
IO.puts(tl(list))
สิ่งนี้จะทำให้เรามีส่วนหัวและส่วนท้ายของรายการเป็นผลลัพธ์ โปรแกรมข้างต้นสร้างผลลัพธ์ต่อไปนี้ -
1
[2, 3]
Note - การได้รับส่วนหัวหรือส่วนท้ายของรายการว่างเป็นข้อผิดพลาด
ฟังก์ชั่นรายการอื่น ๆ
ไลบรารีมาตรฐาน Elixir มีฟังก์ชันมากมายสำหรับจัดการกับรายการ เราจะดูบางส่วนของที่นี่ คุณสามารถตรวจสอบส่วนที่เหลือนี่รายการ
ส. | ชื่อฟังก์ชันและคำอธิบาย |
---|---|
1 | delete(list, item) ลบรายการที่กำหนดออกจากรายการ ส่งคืนรายการที่ไม่มีรายการ หากรายการเกิดขึ้นมากกว่าหนึ่งครั้งในรายการเพียงเหตุการณ์แรกจะถูกลบออก |
2 | delete_at(list, index) สร้างรายการใหม่โดยการลบค่าที่ดัชนีที่ระบุ ดัชนีเชิงลบหมายถึงการหักล้างจากส่วนท้ายของรายการ หากดัชนีอยู่นอกขอบเขตรายการเดิมจะถูกส่งกลับ |
3 | first(list) ส่งคืนองค์ประกอบแรกในรายการหรือศูนย์หากรายการว่างเปล่า |
4 | flatten(list) แบนรายการที่ซ้อนกันที่กำหนด |
5 | insert_at(list, index, value) ส่งคืนรายการที่มีค่าแทรกที่ดัชนีที่ระบุ โปรดทราบว่าดัชนีถูกต่อยอดตามความยาวของรายการ ดัชนีเชิงลบหมายถึงการหักล้างจากส่วนท้ายของรายการ |
6 | last(list) ส่งคืนองค์ประกอบสุดท้ายในรายการหรือศูนย์หากรายการว่างเปล่า |
ทูเปิล
Tuples เป็นโครงสร้างข้อมูลที่เก็บโครงสร้างอื่น ๆ ไว้ภายใน ต่างจากรายการที่เก็บองค์ประกอบไว้ในบล็อกหน่วยความจำที่ต่อเนื่องกัน ซึ่งหมายความว่าการเข้าถึงองค์ประกอบทูเพิลต่อดัชนีหรือการได้ขนาดทูเพิลเป็นการดำเนินการที่รวดเร็ว ดัชนีเริ่มต้นจากศูนย์
Elixir ใช้วงเล็บปีกกาเพื่อกำหนดสิ่งที่เพิ่มขึ้น เช่นเดียวกับรายการ tuples สามารถเก็บค่าใดก็ได้ -
{:ok, "hello"}
ความยาวของทูเพิล
ในการรับความยาวของทูเปิลให้ใช้ tuple_size ฟังก์ชันในโปรแกรมต่อไปนี้ -
IO.puts(tuple_size({:ok, "hello"}))
โปรแกรมข้างต้นสร้างผลลัพธ์ต่อไปนี้ -
2
การต่อท้ายค่า
ในการผนวกค่าเข้ากับทูเปิลให้ใช้ฟังก์ชัน Tuple.append -
tuple = {:ok, "Hello"}
Tuple.append(tuple, :world)
สิ่งนี้จะสร้างและส่งคืนทูเพิลใหม่: {: ok, "Hello",: world}
การแทรกค่า
ในการแทรกค่าในตำแหน่งที่กำหนดเราสามารถใช้ Tuple.insert_at ฟังก์ชันหรือ put_elemฟังก์ชัน พิจารณาตัวอย่างต่อไปนี้เพื่อทำความเข้าใจสิ่งเดียวกัน -
tuple = {:bar, :baz}
new_tuple_1 = Tuple.insert_at(tuple, 0, :foo)
new_tuple_2 = put_elem(tuple, 1, :foobar)
สังเกตว่า put_elem และ insert_atส่งคืนสิ่งที่เพิ่มขึ้นใหม่ ทูเปิลดั้งเดิมที่เก็บไว้ในตัวแปรทูเปิลไม่ได้รับการแก้ไขเนื่องจากชนิดข้อมูล Elixir ไม่เปลี่ยนรูป ด้วยการไม่เปลี่ยนรูปรหัส Elixir จึงให้เหตุผลได้ง่ายขึ้นโดยที่คุณไม่ต้องกังวลว่ารหัสใดจะเปลี่ยนโครงสร้างข้อมูลของคุณหรือไม่
Tuples กับ Lists
ความแตกต่างระหว่างลิสต์และทูเปิลคืออะไร?
รายการจะถูกเก็บไว้ในหน่วยความจำเป็นรายการที่เชื่อมโยงซึ่งหมายความว่าแต่ละองค์ประกอบในรายการจะเก็บค่าและชี้ไปยังองค์ประกอบต่อไปนี้จนกว่าจะถึงจุดสิ้นสุดของรายการ เราเรียกแต่ละคู่ของมูลค่าและตัวชี้เซลล์ข้อเสีย ซึ่งหมายความว่าการเข้าถึงความยาวของรายการเป็นการดำเนินการเชิงเส้นเราจำเป็นต้องสำรวจรายการทั้งหมดเพื่อหาขนาดของรายการ การอัปเดตรายการทำได้รวดเร็วตราบเท่าที่เรากำลังเตรียมองค์ประกอบไว้ล่วงหน้า
ในทางกลับกัน Tuples จะถูกจัดเก็บไว้ในหน่วยความจำอย่างต่อเนื่อง ซึ่งหมายความว่าการรับขนาดทูเพิลหรือการเข้าถึงองค์ประกอบโดยดัชนีนั้นรวดเร็ว อย่างไรก็ตามการอัปเดตหรือเพิ่มองค์ประกอบให้กับทูเพิลมีราคาแพงเนื่องจากต้องคัดลอกทูเพิลทั้งหมดในหน่วยความจำ
จนถึงตอนนี้เรายังไม่ได้พูดถึงโครงสร้างข้อมูลที่เชื่อมโยงกันเช่นโครงสร้างข้อมูลที่สามารถเชื่อมโยงค่าบางค่า (หรือหลายค่า) กับคีย์ได้ ภาษาต่างๆเรียกคุณลักษณะเหล่านี้ด้วยชื่อที่แตกต่างกันเช่นพจนานุกรมแฮชอาร์เรย์ที่เชื่อมโยงเป็นต้น
ใน Elixir เรามีโครงสร้างข้อมูลที่เชื่อมโยงหลักสองแบบ ได้แก่ รายการคีย์เวิร์ดและแผนที่ ในบทนี้เราจะเน้นไปที่รายการคำหลัก
ในภาษาโปรแกรมที่ใช้งานได้หลายภาษาเป็นเรื่องปกติที่จะใช้รายการสิ่ง 2 รายการเป็นตัวแทนของโครงสร้างข้อมูลที่เชื่อมโยงกัน ใน Elixir เมื่อเรามีรายการทูเปิลและรายการแรกของทูเปิล (เช่นกุญแจ) คืออะตอมเราเรียกมันว่ารายการคีย์เวิร์ด พิจารณาตัวอย่างต่อไปนี้เพื่อทำความเข้าใจสิ่งเดียวกัน -
list = [{:a, 1}, {:b, 2}]
Elixir รองรับไวยากรณ์พิเศษสำหรับกำหนดรายการดังกล่าว เราสามารถวางโคลอนไว้ที่ส่วนท้ายของแต่ละอะตอมและกำจัดสิ่งทูเปิลทั้งหมด ตัวอย่างเช่น,
list_1 = [{:a, 1}, {:b, 2}]
list_2 = [a: 1, b: 2]
IO.puts(list_1 == list_2)
โปรแกรมข้างต้นจะสร้างผลลัพธ์ต่อไปนี้ -
true
ทั้งสองอย่างนี้แสดงถึงรายการคำหลัก เนื่องจากรายการคำหลักยังเป็นรายการเราจึงสามารถใช้การดำเนินการทั้งหมดที่เราใช้ในรายการ
ในการดึงค่าที่เกี่ยวข้องกับอะตอมในรายการคำหลักให้ส่งอะตอมเป็น [] หลังชื่อรายการ -
list = [a: 1, b: 2]
IO.puts(list[:a])
โปรแกรมข้างต้นสร้างผลลัพธ์ต่อไปนี้ -
1
รายการคำหลักมีลักษณะพิเศษสามประการ -
- กุญแจต้องเป็นอะตอม
- คีย์จะเรียงลำดับตามที่ผู้พัฒนาระบุ
- สามารถให้คีย์ได้มากกว่าหนึ่งครั้ง
เพื่อที่จะจัดการกับรายการคำหลัก Elixir ให้โมดูลคำหลัก อย่างไรก็ตามโปรดจำไว้ว่ารายการคำหลักเป็นเพียงรายการและด้วยเหตุนี้จึงมีลักษณะการทำงานเชิงเส้นเช่นเดียวกับรายการ ยิ่งรายการยาวเท่าไรก็จะต้องใช้เวลานานขึ้นในการค้นหาคีย์เพื่อนับจำนวนรายการและอื่น ๆ ด้วยเหตุนี้รายการคำหลักจึงถูกใช้ใน Elixir เป็นหลัก หากคุณต้องการจัดเก็บหลายรายการหรือรับประกันการเชื่อมโยงแบบคีย์เดียวด้วยค่าสูงสุดหนึ่งค่าคุณควรใช้แผนที่แทน
การเข้าถึงคีย์
ในการเข้าถึงค่าที่เกี่ยวข้องกับคีย์ที่กำหนดเราใช้ไฟล์ Keyword.getฟังก์ชัน ส่งคืนค่าแรกที่เกี่ยวข้องกับคีย์ที่กำหนด ในการรับค่าทั้งหมดเราใช้ฟังก์ชัน Keyword.get_values ตัวอย่างเช่น -
kl = [a: 1, a: 2, b: 3]
IO.puts(Keyword.get(kl, :a))
IO.puts(Keyword.get_values(kl))
โปรแกรมข้างต้นจะสร้างผลลัพธ์ต่อไปนี้ -
1
[1, 2]
การใส่คีย์
หากต้องการเพิ่มค่าใหม่ให้ใช้ Keyword.put_new. หากมีคีย์อยู่แล้วค่าจะยังคงไม่เปลี่ยนแปลง -
kl = [a: 1, a: 2, b: 3]
kl_new = Keyword.put_new(kl, :c, 5)
IO.puts(Keyword.get(kl_new, :c))
เมื่อเรียกใช้โปรแกรมข้างต้นโปรแกรมจะสร้างรายการคำหลักใหม่พร้อมด้วยคีย์เพิ่มเติม c และสร้างผลลัพธ์ต่อไปนี้ -
5
การลบคีย์
หากคุณต้องการลบรายการทั้งหมดสำหรับคีย์ให้ใช้ Keyword.delete; หากต้องการลบเฉพาะรายการแรกสำหรับคีย์ให้ใช้ Keyword.delete_first.
kl = [a: 1, a: 2, b: 3, c: 0]
kl = Keyword.delete_first(kl, :b)
kl = Keyword.delete(kl, :a)
IO.puts(Keyword.get(kl, :a))
IO.puts(Keyword.get(kl, :b))
IO.puts(Keyword.get(kl, :c))
การดำเนินการนี้จะลบรายการแรก b ในรายการและไฟล์ aในรายการ เมื่อเรียกใช้โปรแกรมข้างต้นโปรแกรมจะสร้างผลลัพธ์ดังต่อไปนี้ -
0
รายการคำหลักเป็นวิธีที่สะดวกในการจัดการเนื้อหาที่จัดเก็บในรายการตามคีย์ แต่ภายใต้ Elixir ยังคงดำเนินการตามรายการ สิ่งนี้อาจเหมาะสมหากคุณมีแผนอื่น ๆ สำหรับรายการนั้นที่ต้องดำเนินการทั้งหมด แต่อาจเป็นค่าใช้จ่ายที่ไม่จำเป็นหากคุณวางแผนที่จะใช้คีย์เป็นแนวทางเดียวในการเข้าถึงข้อมูล
นี่คือจุดที่แผนที่ช่วยคุณได้ เมื่อใดก็ตามที่คุณต้องการที่เก็บคีย์ - ค่าแผนที่คือโครงสร้างข้อมูล“ ไปที่” ใน Elixir
การสร้างแผนที่
แผนที่ถูกสร้างขึ้นโดยใช้ไวยากรณ์% {} -
map = %{:a => 1, 2 => :b}
เมื่อเทียบกับรายการคำหลักเราจะเห็นความแตกต่างสองประการ -
- แผนที่อนุญาตให้มีค่าใด ๆ เป็นกุญแจสำคัญ
- คีย์ของแผนที่ไม่เป็นไปตามลำดับใด ๆ
การเข้าถึงคีย์
ในการเข้าถึงค่าที่เชื่อมโยงกับคีย์ Maps จะใช้ไวยากรณ์เดียวกับรายการคำหลัก -
map = %{:a => 1, 2 => :b}
IO.puts(map[:a])
IO.puts(map[2])
เมื่อรันโปรแกรมข้างต้นจะสร้างผลลัพธ์ต่อไปนี้ -
1
b
การใส่คีย์
ในการแทรกคีย์ในแผนที่เราใช้ไฟล์ Dict.put_new ฟังก์ชันที่ใช้แผนที่คีย์ใหม่และค่าใหม่เป็นอาร์กิวเมนต์ -
map = %{:a => 1, 2 => :b}
new_map = Dict.put_new(map, :new_val, "value")
IO.puts(new_map[:new_val])
สิ่งนี้จะแทรกคู่คีย์ - ค่า :new_val - "value"ในแผนที่ใหม่ เมื่อรันโปรแกรมข้างต้นจะสร้างผลลัพธ์ต่อไปนี้ -
"value"
การอัปเดตค่า
ในการอัปเดตค่าที่มีอยู่แล้วในแผนที่คุณสามารถใช้ไวยากรณ์ต่อไปนี้ -
map = %{:a => 1, 2 => :b}
new_map = %{ map | a: 25}
IO.puts(new_map[:a])
เมื่อรันโปรแกรมข้างต้นจะสร้างผลลัพธ์ต่อไปนี้ -
25
การจับคู่รูปแบบ
ในทางตรงกันข้ามกับรายการคำหลักแผนที่มีประโยชน์มากในการจับคู่รูปแบบ เมื่อใช้แผนที่ในรูปแบบแผนที่จะจับคู่กับชุดย่อยของค่าที่กำหนดเสมอ -
%{:a => a} = %{:a => 1, 2 => :b}
IO.puts(a)
โปรแกรมข้างต้นสร้างผลลัพธ์ต่อไปนี้ -
1
สิ่งนี้จะตรงกัน a ด้วย 1. และด้วยเหตุนี้มันจะสร้างผลลัพธ์เป็น1.
ดังที่แสดงไว้ด้านบนแผนที่จะจับคู่ตราบเท่าที่มีคีย์ในรูปแบบอยู่ในแผนที่ที่กำหนด ดังนั้นแผนที่ว่างจะตรงกับแผนที่ทั้งหมด
สามารถใช้ตัวแปรเมื่อเข้าถึงจับคู่และเพิ่มคีย์แผนที่ -
n = 1
map = %{n => :one}
%{^n => :one} = %{1 => :one, 2 => :two, 3 => :three}
โมดูลแผนที่มี API ที่คล้ายกันมากกับโมดูลคำหลักพร้อมฟังก์ชันอำนวยความสะดวกในการจัดการแผนที่ คุณสามารถใช้ฟังก์ชันต่างๆเช่นไฟล์Map.get, Map.deleteเพื่อจัดการแผนที่
แผนที่ด้วยปุ่ม Atom
แผนที่มาพร้อมกับคุณสมบัติที่น่าสนใจบางประการ เมื่อคีย์ทั้งหมดในแผนที่เป็นอะตอมคุณสามารถใช้ไวยากรณ์คำหลักเพื่อความสะดวก -
map = %{:a => 1, 2 => :b}
IO.puts(map.a)
คุณสมบัติที่น่าสนใจอีกอย่างของแผนที่คือมีไวยากรณ์ของตัวเองสำหรับการอัปเดตและการเข้าถึงคีย์อะตอม -
map = %{:a => 1, 2 => :b}
IO.puts(map.a)
โปรแกรมข้างต้นสร้างผลลัพธ์ต่อไปนี้ -
1
โปรดทราบว่าในการเข้าถึงคีย์อะตอมด้วยวิธีนี้ควรมีอยู่มิฉะนั้นโปรแกรมจะไม่สามารถทำงานได้
ใน Elixir เราจัดกลุ่มฟังก์ชันต่างๆเป็นโมดูล เราได้ใช้โมดูลที่แตกต่างกันไปแล้วในบทก่อนหน้าเช่นโมดูล String, โมดูล Bitwise, โมดูล Tuple เป็นต้น
ในการสร้างโมดูลของเราเองใน Elixir เราใช้ไฟล์ defmoduleมาโคร เราใช้ไฟล์def มาโครเพื่อกำหนดฟังก์ชันในโมดูลนั้น -
defmodule Math do
def sum(a, b) do
a + b
end
end
ในส่วนต่อไปนี้ตัวอย่างของเราจะมีขนาดที่ยาวขึ้นและการพิมพ์ทั้งหมดในเชลล์อาจเป็นเรื่องยาก เราจำเป็นต้องเรียนรู้วิธีการรวบรวมรหัส Elixir และวิธีเรียกใช้สคริปต์ Elixir
การรวบรวม
สะดวกในการเขียนโมดูลลงในไฟล์เพื่อให้สามารถคอมไพล์และนำกลับมาใช้ใหม่ได้ สมมติว่าเรามีไฟล์ชื่อ math.ex ที่มีเนื้อหาดังต่อไปนี้ -
defmodule Math do
def sum(a, b) do
a + b
end
end
เราสามารถรวบรวมไฟล์โดยใช้คำสั่ง -elixirc :
$ elixirc math.ex
สิ่งนี้จะสร้างไฟล์ชื่อ Elixir.Math.beamมี bytecode สำหรับโมดูลที่กำหนด ถ้าเราเริ่มiexอีกครั้งคำจำกัดความโมดูลของเราจะพร้อมใช้งาน (โดยมีการเริ่มต้น iex ในไดเร็กทอรีเดียวกันกับไฟล์ bytecode) ตัวอย่างเช่น,
IO.puts(Math.sum(1, 2))
โปรแกรมข้างต้นจะสร้างผลลัพธ์ต่อไปนี้ -
3
โหมดสคริปต์
นอกเหนือจากนามสกุลไฟล์ Elixir .ex, Elixir ยังรองรับ .exsไฟล์สำหรับการเขียนสคริปต์ Elixir ปฏิบัติต่อทั้งสองไฟล์ในลักษณะเดียวกันข้อแตกต่างเพียงประการเดียวคือวัตถุประสงค์.ex ไฟล์มีไว้เพื่อคอมไพล์ในขณะที่ใช้ไฟล์. exs สำหรับ scripting. เมื่อดำเนินการส่วนขยายทั้งสองจะรวบรวมและโหลดโมดูลลงในหน่วยความจำแม้ว่าจะมีเพียง.ex ไฟล์เขียน bytecode ไปยังดิสก์ในรูปแบบของไฟล์. beam
ตัวอย่างเช่นหากเราต้องการเรียกใช้ไฟล์ Math.sum ในไฟล์เดียวกันเราสามารถใช้. exs ได้ดังต่อไปนี้ -
Math.exs
defmodule Math do
def sum(a, b) do
a + b
end
end
IO.puts(Math.sum(1, 2))
เราสามารถเรียกใช้โดยใช้คำสั่ง Elixir -
$ elixir math.exs
โปรแกรมข้างต้นจะสร้างผลลัพธ์ต่อไปนี้ -
3
ไฟล์จะถูกคอมไพล์ในหน่วยความจำและดำเนินการโดยพิมพ์“ 3” ออกมา จะไม่มีการสร้างไฟล์ bytecode
การซ้อนโมดูล
โมดูลสามารถซ้อนกันได้ใน Elixir คุณลักษณะของภาษานี้ช่วยให้เราจัดระเบียบรหัสของเราได้ดีขึ้น ในการสร้างโมดูลที่ซ้อนกันเราใช้ไวยากรณ์ต่อไปนี้ -
defmodule Foo do
#Foo module code here
defmodule Bar do
#Bar module code here
end
end
ตัวอย่างที่ให้ไว้ข้างต้นจะกำหนดสองโมดูล: Foo และ Foo.Bar. ที่สองสามารถเข้าถึงได้ในรูปแบบBar ภายใน Fooตราบเท่าที่อยู่ในขอบเขตคำศัพท์เดียวกัน ถ้าในภายหลังไฟล์Bar โมดูลถูกย้ายออกนอกนิยามโมดูล Foo ต้องอ้างอิงโดยชื่อเต็ม (Foo.Bar) หรือต้องตั้งนามแฝงโดยใช้คำสั่งนามแฝงที่กล่าวถึงในบทนามแฝง
Note- ใน Elixir ไม่จำเป็นต้องกำหนดโมดูล Foo เพื่อกำหนดโมดูล Foo.Bar เนื่องจากภาษาจะแปลชื่อโมดูลทั้งหมดเป็นอะตอม คุณสามารถกำหนดโมดูลที่ถูกควบคุมโดยพลการโดยไม่ต้องกำหนดโมดูลใด ๆ ในห่วงโซ่ ตัวอย่างเช่นคุณสามารถกำหนดFoo.Bar.Baz โดยไม่ต้องกำหนด Foo หรือ Foo.Bar.
เพื่ออำนวยความสะดวกในการนำซอฟต์แวร์มาใช้ซ้ำ Elixir มีคำสั่งสามประการ - alias, require และ import. นอกจากนี้ยังมีมาโครที่เรียกว่าการใช้งานซึ่งสรุปไว้ด้านล่าง -
# Alias the module so it can be called as Bar instead of Foo.Bar
alias Foo.Bar, as: Bar
# Ensure the module is compiled and available (usually for macros)
require Foo
# Import functions from Foo so they can be called without the `Foo.` prefix
import Foo
# Invokes the custom code defined in Foo as an extension point
use Foo
ตอนนี้ให้เราเข้าใจรายละเอียดเกี่ยวกับแต่ละคำสั่ง
นามแฝง
คำสั่งนามแฝงช่วยให้คุณตั้งค่านามแฝงสำหรับชื่อโมดูลที่กำหนด ตัวอย่างเช่นหากคุณต้องการตั้งชื่อแทน'Str' ไปที่โมดูล String คุณสามารถเขียน -
alias String, as: Str
IO.puts(Str.length("Hello"))
โปรแกรมข้างต้นสร้างผลลัพธ์ต่อไปนี้ -
5
มีการมอบนามแฝงให้กับไฟล์ String โมดูลเป็น Str. ตอนนี้เมื่อเราเรียกใช้ฟังก์ชันใด ๆ โดยใช้ตัวอักษร Str มันอ้างอิงถึงไฟล์Stringโมดูล. สิ่งนี้มีประโยชน์มากเมื่อเราใช้ชื่อโมดูลที่ยาวมากและต้องการแทนที่ชื่อโมดูลที่สั้นกว่าในขอบเขตปัจจุบัน
NOTE - นามแฝง MUST เริ่มต้นด้วยอักษรตัวใหญ่
นามแฝงใช้ได้เฉพาะภายในไฟล์ lexical scope จะถูกเรียกเข้ามาตัวอย่างเช่นหากคุณมี 2 โมดูลในไฟล์และสร้างนามแฝงภายในโมดูลใดโมดูลหนึ่งนามแฝงนั้นจะไม่สามารถเข้าถึงได้ในโมดูลที่สอง
หากคุณตั้งชื่อโมดูลในตัวเช่น String หรือ Tuple เป็นนามแฝงของโมดูลอื่นในการเข้าถึงโมดูล inbuilt คุณจะต้องนำหน้าด้วย "Elixir.". ตัวอย่างเช่น,
alias List, as: String
#Now when we use String we are actually using List.
#To use the string module:
IO.puts(Elixir.String.length("Hello"))
เมื่อรันโปรแกรมข้างต้นจะสร้างผลลัพธ์ต่อไปนี้ -
5
จำเป็นต้อง
Elixir จัดเตรียมมาโครเป็นกลไกสำหรับการเขียนโปรแกรมเมตา (การเขียนโค้ดที่สร้างโค้ด)
มาโครเป็นส่วนของโค้ดที่เรียกใช้และขยายในเวลาคอมไพล์ ซึ่งหมายความว่าในการใช้มาโครเราจำเป็นต้องรับประกันว่าโมดูลและการนำไปใช้งานพร้อมใช้งานในระหว่างการคอมไพล์ สิ่งนี้ทำได้ด้วยไฟล์require คำสั่ง
Integer.is_odd(3)
เมื่อเรียกใช้โปรแกรมข้างต้นโปรแกรมจะสร้างผลลัพธ์ดังต่อไปนี้ -
** (CompileError) iex:1: you must require Integer before invoking the macro Integer.is_odd/1
ใน Elixir Integer.is_odd ถูกกำหนดให้เป็นไฟล์ macro. มาโครนี้สามารถใช้เป็นยามได้ ซึ่งหมายความว่าในการเรียกใช้Integer.is_oddเราจะต้องใช้โมดูลจำนวนเต็ม
ใช้ require Integer และรันโปรแกรมดังที่แสดงด้านล่าง
require Integer
Integer.is_odd(3)
คราวนี้โปรแกรมจะทำงานและสร้างผลลัพธ์เป็น: true.
โดยทั่วไปไม่จำเป็นต้องใช้โมดูลก่อนการใช้งานยกเว้นว่าเราต้องการใช้มาโครที่มีอยู่ในโมดูลนั้น ความพยายามในการเรียกใช้แมโครที่ไม่ได้โหลดจะทำให้เกิดข้อผิดพลาด โปรดทราบว่าเช่นเดียวกับคำสั่งนามแฝงต้องมีการกำหนดขอบเขตคำศัพท์ด้วย เราจะพูดคุยเพิ่มเติมเกี่ยวกับมาโครในบทต่อไป
นำเข้า
เราใช้ไฟล์ importสั่งให้เข้าถึงฟังก์ชันหรือมาโครจากโมดูลอื่น ๆ ได้อย่างง่ายดายโดยไม่ต้องใช้ชื่อที่มีคุณสมบัติครบถ้วน ตัวอย่างเช่นหากเราต้องการใช้ไฟล์duplicate ฟังก์ชั่นจากโมดูลรายการหลาย ๆ ครั้งเราสามารถนำเข้าได้
import List, only: [duplicate: 2]
ในกรณีนี้เรากำลังนำเข้าเฉพาะฟังก์ชันที่ซ้ำกัน (ที่มีความยาวรายการอาร์กิวเมนต์ 2) จากรายการ แม้ว่า:only เป็นทางเลือกแนะนำให้ใช้เพื่อหลีกเลี่ยงการนำเข้าฟังก์ชันทั้งหมดของโมดูลที่กำหนดภายในเนมสเปซ :except นอกจากนี้ยังสามารถให้เป็นตัวเลือกเพื่อนำเข้าทุกอย่างในโมดูลยกเว้นรายการฟังก์ชัน
import คำสั่งยังรองรับ :macros และ :functions ที่จะมอบให้ :only. ตัวอย่างเช่นในการนำเข้ามาโครทั้งหมดผู้ใช้สามารถเขียน -
import Integer, only: :macros
โปรดทราบว่าการนำเข้าก็เช่นกัน Lexically scopedเช่นเดียวกับข้อกำหนดและคำสั่งนามแฝง โปรดทราบว่า'import'ing a module also 'require's it.
ใช้
แม้ว่าจะไม่ใช่คำสั่ง use เป็นมาโครที่เกี่ยวข้องอย่างแน่นหนา requireที่อนุญาตให้คุณใช้โมดูลในบริบทปัจจุบัน นักพัฒนามักใช้มาโครการใช้งานเพื่อนำฟังก์ชันภายนอกเข้าสู่ขอบเขตคำศัพท์ปัจจุบันซึ่งมักเป็นโมดูล ให้เราเข้าใจคำสั่งการใช้งานผ่านตัวอย่าง -
defmodule Example do
use Feature, option: :value
end
การใช้งานเป็นมาโครที่แปลงด้านบนเป็น -
defmodule Example do
require Feature
Feature.__using__(option: :value)
end
use Module ก่อนอื่นต้องใช้โมดูลจากนั้นเรียกใช้ __using__มาโครบนโมดูล Elixir มีความสามารถในการเขียนโปรแกรม metaprogram ที่ยอดเยี่ยมและมีมาโครสำหรับสร้างโค้ดในเวลาคอมไพล์ มาโคร_ _using__ถูกเรียกในอินสแตนซ์ด้านบนและโค้ดจะถูกแทรกลงในบริบทท้องถิ่นของเรา บริบทเฉพาะที่คือที่ที่มีการเรียกใช้มาโครการใช้งานในช่วงเวลาของการคอมไพล์
ฟังก์ชันคือชุดของคำสั่งที่จัดระเบียบร่วมกันเพื่อดำเนินงานเฉพาะ ฟังก์ชันในการเขียนโปรแกรมส่วนใหญ่เหมือนกับฟังก์ชันในคณิตศาสตร์ คุณให้ฟังก์ชั่นอินพุตบางอย่างซึ่งจะสร้างเอาต์พุตตามอินพุตที่ให้มา
มีฟังก์ชั่น 2 ประเภทใน Elixir -
ฟังก์ชันไม่ระบุชื่อ
ฟังก์ชันที่กำหนดโดยใช้ fn..end constructเป็นฟังก์ชันที่ไม่ระบุชื่อ ฟังก์ชันเหล่านี้บางครั้งเรียกว่า lambdas ใช้โดยกำหนดให้กับชื่อตัวแปร
ฟังก์ชันที่มีชื่อ
ฟังก์ชันที่กำหนดโดยใช้ def keywordเป็นชื่อฟังก์ชัน สิ่งเหล่านี้เป็นฟังก์ชันดั้งเดิมที่มีให้ใน Elixir
ฟังก์ชันที่ไม่ระบุตัวตน
เช่นเดียวกับชื่อที่แสดงถึงฟังก์ชันที่ไม่ระบุตัวตนจะไม่มีชื่อ สิ่งเหล่านี้มักถูกส่งผ่านไปยังฟังก์ชันอื่น ๆ ในการกำหนดฟังก์ชันนิรนามใน Elixir เราจำเป็นต้องมีfn และ endคำหลัก ภายในสิ่งเหล่านี้เราสามารถกำหนดพารามิเตอร์และส่วนของฟังก์ชันจำนวนเท่าใดก็ได้โดยคั่นด้วย->. ตัวอย่างเช่น,
sum = fn (a, b) -> a + b end
IO.puts(sum.(1, 5))
เมื่อรันโปรแกรมด้านบนรันจะสร้างผลลัพธ์ต่อไปนี้ -
6
โปรดทราบว่าฟังก์ชันเหล่านี้ไม่ได้ถูกเรียกเหมือนฟังก์ชันที่มีชื่อ เรามี '.'ระหว่างชื่อฟังก์ชันและอาร์กิวเมนต์
การใช้ตัวดำเนินการจับภาพ
เรายังสามารถกำหนดฟังก์ชันเหล่านี้โดยใช้ตัวดำเนินการจับภาพ นี่เป็นวิธีที่ง่ายกว่าในการสร้างฟังก์ชัน ตอนนี้เราจะกำหนดฟังก์ชันผลรวมข้างต้นโดยใช้ตัวดำเนินการจับภาพ
sum = &(&1 + &2)
IO.puts(sum.(1, 2))
เมื่อรันโปรแกรมข้างต้นจะสร้างผลลัพธ์ต่อไปนี้ -
3
ในเวอร์ชันจดชวเลขพารามิเตอร์ของเราจะไม่มีการตั้งชื่อ แต่มีให้เราเป็น & 1, & 2, & 3 และอื่น ๆ
ฟังก์ชันการจับคู่รูปแบบ
การจับคู่รูปแบบไม่ได้ จำกัด เฉพาะตัวแปรและโครงสร้างข้อมูลเท่านั้น เราสามารถใช้การจับคู่รูปแบบเพื่อทำให้ฟังก์ชันของเรามีความหลากหลาย ตัวอย่างเช่นเราจะประกาศฟังก์ชันที่สามารถรับอินพุต 1 หรือ 2 อินพุต (ภายในทูเพิล) และพิมพ์ไปยังคอนโซล
handle_result = fn
{var1} -> IO.puts("#{var1} found in a tuple!")
{var_2, var_3} -> IO.puts("#{var_2} and #{var_3} found!")
end
handle_result.({"Hey people"})
handle_result.({"Hello", "World"})
เมื่อรันโปรแกรมข้างต้นจะให้ผลลัพธ์ดังนี้ -
Hey people found in a tuple!
Hello and World found!
ฟังก์ชันที่มีชื่อ
เราสามารถกำหนดฟังก์ชันด้วยชื่อเพื่อให้อ้างอิงได้ง่ายในภายหลัง ฟังก์ชันที่กำหนดชื่อถูกกำหนดภายในโมดูลโดยใช้คำสำคัญ def ฟังก์ชันที่กำหนดชื่อจะถูกกำหนดไว้ในโมดูลเสมอ ในการเรียกใช้ฟังก์ชันที่มีชื่อเราจำเป็นต้องอ้างอิงโดยใช้ชื่อโมดูล
ต่อไปนี้เป็นไวยากรณ์สำหรับฟังก์ชันที่มีชื่อ -
def function_name(argument_1, argument_2) do
#code to be executed when function is called
end
ตอนนี้ให้เรากำหนดผลรวมฟังก์ชันที่มีชื่อของเราภายในโมดูลคณิตศาสตร์
defmodule Math do
def sum(a, b) do
a + b
end
end
IO.puts(Math.sum(5, 6))
เมื่อรันโปรแกรมด้านบนจะให้ผลลัพธ์ดังนี้ -
11
สำหรับฟังก์ชั่น 1 ซับมีสัญกรณ์ชวเลขเพื่อกำหนดฟังก์ชันเหล่านี้โดยใช้ do:. ตัวอย่างเช่น -
defmodule Math do
def sum(a, b), do: a + b
end
IO.puts(Math.sum(5, 6))
เมื่อรันโปรแกรมด้านบนจะให้ผลลัพธ์ดังนี้ -
11
ฟังก์ชั่นส่วนตัว
Elixir ช่วยให้เราสามารถกำหนดฟังก์ชันส่วนตัวที่สามารถเข้าถึงได้จากภายในโมดูลที่กำหนดไว้ ในการกำหนดฟังก์ชันส่วนตัวให้ใช้defp แทน def. ตัวอย่างเช่น,
defmodule Greeter do
def hello(name), do: phrase <> name
defp phrase, do: "Hello "
end
Greeter.hello("world")
เมื่อรันโปรแกรมข้างต้นจะให้ผลลัพธ์ดังนี้ -
Hello world
แต่ถ้าเราพยายามเรียกใช้ฟังก์ชันวลีอย่างชัดเจนโดยใช้ Greeter.phrase() มันจะเพิ่มข้อผิดพลาด
อาร์กิวเมนต์เริ่มต้น
หากเราต้องการค่าเริ่มต้นสำหรับอาร์กิวเมนต์เราใช้ argument \\ value ไวยากรณ์ -
defmodule Greeter do
def hello(name, country \\ "en") do
phrase(country) <> name
end
defp phrase("en"), do: "Hello, "
defp phrase("es"), do: "Hola, "
end
Greeter.hello("Ayush", "en")
Greeter.hello("Ayush")
Greeter.hello("Ayush", "es")
เมื่อรันโปรแกรมข้างต้นจะให้ผลลัพธ์ดังนี้ -
Hello, Ayush
Hello, Ayush
Hola, Ayush
การเรียกซ้ำเป็นวิธีการที่การแก้ปัญหาขึ้นอยู่กับวิธีการแก้ปัญหาในกรณีเดียวกันที่มีขนาดเล็กกว่า ภาษาโปรแกรมคอมพิวเตอร์ส่วนใหญ่สนับสนุนการเรียกซ้ำโดยอนุญาตให้ฟังก์ชันเรียกตัวเองภายในข้อความโปรแกรม
ฟังก์ชันเรียกซ้ำตามอุดมคติมีเงื่อนไขสิ้นสุด เงื่อนไขสิ้นสุดนี้หรือที่เรียกว่ากรณีฐานหยุดการป้อนฟังก์ชันซ้ำและเพิ่มการเรียกฟังก์ชันไปยังสแตก นี่คือจุดที่หยุดการเรียกฟังก์ชันแบบเรียกซ้ำ ให้เราพิจารณาตัวอย่างต่อไปนี้เพื่อทำความเข้าใจเกี่ยวกับฟังก์ชันเรียกซ้ำ
defmodule Math do
def fact(res, num) do
if num === 1 do
res
else
new_res = res * num
fact(new_res, num-1)
end
end
end
IO.puts(Math.fact(1,5))
เมื่อรันโปรแกรมข้างต้นจะสร้างผลลัพธ์ต่อไปนี้ -
120
ดังนั้นในฟังก์ชันข้างต้น Math.factเรากำลังคำนวณแฟกทอเรียลของตัวเลข โปรดทราบว่าเรากำลังเรียกใช้ฟังก์ชันภายในตัวเอง ตอนนี้ให้เราเข้าใจว่ามันทำงานอย่างไร
เราได้ใส่ 1 และจำนวนที่เราต้องการคำนวณแฟกทอเรียล ฟังก์ชันจะตรวจสอบว่าตัวเลขเป็น 1 หรือไม่และส่งคืน res หากเป็น 1(Ending condition). หากไม่เป็นเช่นนั้นมันจะสร้างตัวแปร new_res และกำหนดค่าของ res * current num ก่อนหน้านี้ ก็จะส่งกลับค่าที่ส่งกลับโดยการเรียกฟังก์ชั่นของเราเป็นจริง (new_res, Num-1) สิ่งนี้จะวนซ้ำจนกว่าเราจะได้ num เป็น 1 เมื่อสิ่งนั้นเกิดขึ้นเราจะได้ผลลัพธ์
ให้เราพิจารณาอีกตัวอย่างหนึ่งคือการพิมพ์แต่ละองค์ประกอบของรายการทีละรายการ ในการทำเช่นนี้เราจะใช้ไฟล์hd และ tl ฟังก์ชันของรายการและรูปแบบที่ตรงกันในฟังก์ชัน -
a = ["Hey", 100, 452, :true, "People"]
defmodule ListPrint do
def print([]) do
end
def print([head | tail]) do
IO.puts(head)
print(tail)
end
end
ListPrint.print(a)
ฟังก์ชันการพิมพ์ครั้งแรกถูกเรียกใช้เมื่อเรามีรายการว่าง(ending condition). ถ้าไม่เช่นนั้นฟังก์ชันการพิมพ์ที่สองจะถูกเรียกใช้ซึ่งจะแบ่งรายการออกเป็น 2 และกำหนดองค์ประกอบแรกของรายการเป็นส่วนหัวและส่วนที่เหลือของรายการเป็นส่วนท้าย จากนั้นส่วนหัวจะถูกพิมพ์และเราเรียกใช้ฟังก์ชันการพิมพ์อีกครั้งพร้อมกับส่วนที่เหลือของรายการนั่นคือ tail เมื่อรันโปรแกรมข้างต้นจะให้ผลลัพธ์ดังนี้ -
Hey
100
452
true
People
เนื่องจากความไม่เปลี่ยนรูปลูปใน Elixir (เช่นเดียวกับในภาษาโปรแกรมที่ใช้งานได้) จึงเขียนแตกต่างจากภาษาที่จำเป็น ตัวอย่างเช่นในภาษาที่จำเป็นเช่น C คุณจะเขียน -
for(i = 0; i < 10; i++) {
printf("%d", array[i]);
}
ในตัวอย่างที่ให้ไว้ข้างต้นเรากำลังกลายพันธุ์ทั้งอาร์เรย์และตัวแปร i. ไม่สามารถกลายพันธุ์ได้ใน Elixir ภาษาที่ใช้งานได้จะขึ้นอยู่กับการเรียกซ้ำ: ฟังก์ชันถูกเรียกซ้ำจนกว่าจะถึงเงื่อนไขที่หยุดการดำเนินการแบบเรียกซ้ำไม่ให้ดำเนินต่อไป ไม่มีข้อมูลที่กลายพันธุ์ในกระบวนการนี้
ตอนนี้ให้เราเขียนลูปง่ายๆโดยใช้การเรียกซ้ำที่พิมพ์สวัสดี n ครั้ง.
defmodule Loop do
def print_multiple_times(msg, n) when n <= 1 do
IO.puts msg
end
def print_multiple_times(msg, n) do
IO.puts msg
print_multiple_times(msg, n - 1)
end
end
Loop.print_multiple_times("Hello", 10)
เมื่อรันโปรแกรมข้างต้นจะให้ผลลัพธ์ดังนี้ -
Hello
Hello
Hello
Hello
Hello
Hello
Hello
Hello
Hello
Hello
เราได้ใช้เทคนิคการจับคู่รูปแบบของฟังก์ชันและการเรียกซ้ำเพื่อใช้งานลูปได้สำเร็จ คำจำกัดความแบบวนซ้ำเป็นเรื่องยากที่จะเข้าใจ แต่การแปลงลูปเป็นการเรียกซ้ำนั้นทำได้ง่าย
Elixir ให้เรา Enum module. โมดูลนี้ใช้สำหรับการโทรวนซ้ำที่วนซ้ำบ่อยที่สุดเนื่องจากใช้งานได้ง่ายกว่าการพยายามหาคำจำกัดความซ้ำสำหรับสิ่งเดียวกัน เราจะคุยกันในบทถัดไป ควรใช้คำจำกัดความแบบเรียกซ้ำของคุณเองก็ต่อเมื่อคุณไม่พบวิธีแก้ปัญหาโดยใช้โมดูลนั้น ฟังก์ชั่นเหล่านี้เหมาะสำหรับการโทรหางและค่อนข้างเร็ว
แจงนับเป็นวัตถุที่สามารถแจกแจงได้ "แจกแจง" หมายถึงการนับจำนวนสมาชิกของชุด / คอลเลกชัน / หมวดหมู่ทีละคน (โดยปกติจะเรียงตามชื่อ)
Elixir ให้แนวคิดของการแจงนับและโมดูล Enumเพื่อใช้กับพวกเขา ฟังก์ชั่นในโมดูล Enum ถูก จำกัด ไว้ที่ชื่อกล่าวคือการแจกแจงค่าในโครงสร้างข้อมูล ตัวอย่างโครงสร้างข้อมูลที่แจกแจงได้คือรายการทูเปิลแผนที่และอื่น ๆ โมดูล Enum มีฟังก์ชันมากกว่า 100 รายการเพื่อจัดการกับ enums เราจะพูดถึงหน้าที่สำคัญบางประการในบทนี้
ฟังก์ชันทั้งหมดเหล่านี้ใช้เวลานับเป็นองค์ประกอบแรกและฟังก์ชันที่สองและทำงานกับพวกเขา ฟังก์ชั่นอธิบายไว้ด้านล่าง
ทั้งหมด?
เมื่อเราใช้ allเหรอ? ฟังก์ชันคอลเลกชันทั้งหมดต้องประเมินเป็นจริงมิฉะนั้นเท็จจะถูกส่งกลับ ตัวอย่างเช่นเพื่อตรวจสอบว่าองค์ประกอบทั้งหมดในรายการเป็นจำนวนคี่หรือไม่
res = Enum.all?([1, 2, 3, 4], fn(s) -> rem(s,2) == 1 end)
IO.puts(res)
เมื่อรันโปรแกรมข้างต้นจะให้ผลลัพธ์ดังนี้ -
false
เนื่องจากองค์ประกอบทั้งหมดของรายการนี้ไม่ได้แปลก
ใด ๆ ?
ตามชื่อที่แนะนำฟังก์ชันนี้จะคืนค่าจริงหากองค์ประกอบใด ๆ ของคอลเล็กชันประเมินว่าเป็นจริง ตัวอย่างเช่น -
res = Enum.any?([1, 2, 3, 4], fn(s) -> rem(s,2) == 1 end)
IO.puts(res)
เมื่อรันโปรแกรมข้างต้นจะให้ผลลัพธ์ดังนี้ -
true
ก้อน
ฟังก์ชันนี้แบ่งคอลเล็กชันของเราออกเป็นชิ้นเล็ก ๆ ตามขนาดที่ระบุไว้เป็นอาร์กิวเมนต์ที่สอง ตัวอย่างเช่น -
res = Enum.chunk([1, 2, 3, 4, 5, 6], 2)
IO.puts(res)
เมื่อรันโปรแกรมข้างต้นจะให้ผลลัพธ์ดังนี้ -
[[1, 2], [3, 4], [5, 6]]
แต่ละ
อาจจำเป็นต้องทำซ้ำในคอลเลคชันโดยไม่สร้างค่าใหม่สำหรับกรณีนี้เราใช้ไฟล์ each ฟังก์ชัน -
Enum.each(["Hello", "Every", "one"], fn(s) -> IO.puts(s) end)
เมื่อรันโปรแกรมข้างต้นจะให้ผลลัพธ์ดังนี้ -
Hello
Every
one
แผนที่
ในการใช้ฟังก์ชันของเรากับแต่ละรายการและสร้างคอลเล็กชันใหม่เราใช้ฟังก์ชันแผนที่ เป็นโครงสร้างที่มีประโยชน์ที่สุดอย่างหนึ่งในการเขียนโปรแกรมเชิงฟังก์ชันเนื่องจากค่อนข้างแสดงออกและสั้น ให้เราพิจารณาตัวอย่างเพื่อทำความเข้าใจสิ่งนี้ เราจะเพิ่มค่าที่เก็บไว้ในรายการเป็นสองเท่าและจัดเก็บไว้ในรายการใหม่res -
res = Enum.map([2, 5, 3, 6], fn(a) -> a*2 end)
IO.puts(res)
เมื่อรันโปรแกรมข้างต้นจะให้ผลลัพธ์ดังนี้ -
[4, 10, 6, 12]
ลด
reduceฟังก์ชันช่วยให้เราลดการแจกแจงของเราให้เหลือเพียงค่าเดียว ในการทำเช่นนี้เราจัดหาตัวสะสมเสริม (5 ในตัวอย่างนี้) เพื่อส่งผ่านไปยังฟังก์ชันของเรา หากไม่มีการจัดเตรียมตัวสะสมจะใช้ค่าแรก -
res = Enum.reduce([1, 2, 3, 4], 5, fn(x, accum) -> x + accum end)
IO.puts(res)
เมื่อรันโปรแกรมข้างต้นจะให้ผลลัพธ์ดังนี้ -
15
ตัวสะสมคือค่าเริ่มต้นที่ส่งไปยังไฟล์ fn. ตั้งแต่การโทรครั้งที่สองเป็นต้นไปค่าที่ส่งคืนจากการโทรครั้งก่อนจะถูกส่งต่อแบบสะสม นอกจากนี้เรายังสามารถใช้การลดโดยไม่ต้องใช้ตัวสะสม -
res = Enum.reduce([1, 2, 3, 4], fn(x, accum) -> x + accum end)
IO.puts(res)
เมื่อรันโปรแกรมข้างต้นจะให้ผลลัพธ์ดังนี้ -
10
uniq
ฟังก์ชัน uniq จะลบรายการที่ซ้ำกันออกจากคอลเลกชันของเราและส่งคืนเฉพาะชุดขององค์ประกอบในคอลเล็กชัน ตัวอย่างเช่น -
res = Enum.uniq([1, 2, 2, 3, 3, 3, 4, 4, 4, 4])
IO.puts(res)
เมื่อรันโปรแกรมด้านบนจะให้ผลลัพธ์ดังนี้ -
[1, 2, 3, 4]
การประเมินอย่างกระตือรือร้น
ฟังก์ชั่นทั้งหมดในโมดูล Enum มีความกระตือรือร้น ฟังก์ชั่นจำนวนมากคาดหวังว่าจะสามารถแจงนับและส่งคืนรายการกลับ ซึ่งหมายความว่าเมื่อดำเนินการหลายอย่างกับ Enum แต่ละการดำเนินการจะสร้างรายการกลางจนกว่าเราจะไปถึงผลลัพธ์ ให้เราพิจารณาตัวอย่างต่อไปนี้เพื่อทำความเข้าใจสิ่งนี้ -
odd? = &(odd? = &(rem(&1, 2) != 0)
res = 1..100_000 |> Enum.map(&(&1 * 3)) |> Enum.filter(odd?) |> Enum.sum
IO.puts(res)
เมื่อรันโปรแกรมข้างต้นจะให้ผลลัพธ์ดังนี้ -
7500000000
ตัวอย่างข้างต้นมีท่อของการดำเนินงาน เราเริ่มต้นด้วยช่วงจากนั้นคูณแต่ละองค์ประกอบในช่วงด้วย 3 การดำเนินการแรกนี้จะสร้างและส่งคืนรายการที่มีรายการ 100_000 รายการ จากนั้นเราเก็บองค์ประกอบแปลก ๆ ทั้งหมดจากรายการสร้างรายการใหม่ตอนนี้มี 50_000 รายการจากนั้นเราจะรวมรายการทั้งหมด
|> สัญลักษณ์ที่ใช้ในตัวอย่างด้านบนคือ pipe operator: เพียงแค่รับเอาต์พุตจากนิพจน์ทางด้านซ้ายและส่งผ่านเป็นอาร์กิวเมนต์แรกไปยังการเรียกใช้ฟังก์ชันทางด้านขวา คล้ายกับ Unix | ตัวดำเนินการ มีวัตถุประสงค์เพื่อเน้นการไหลของข้อมูลที่ถูกแปลงโดยชุดฟังก์ชัน
ปราศจาก pipe ตัวดำเนินการรหัสดูซับซ้อน -
Enum.sum(Enum.filter(Enum.map(1..100_000, &(&1 * 3)), odd?))
อย่างไรก็ตามเรามีฟังก์ชั่นอื่น ๆ อีกมากมาย แต่มีคำอธิบายที่สำคัญเพียงไม่กี่อย่างเท่านั้น
ฟังก์ชั่นจำนวนมากคาดหวังว่าจะสามารถแจกแจงและส่งคืนไฟล์ listกลับ. หมายความว่าในขณะที่ดำเนินการหลายอย่างกับ Enum แต่ละการดำเนินการจะสร้างรายการกลางจนกว่าเราจะไปถึงผลลัพธ์
สตรีมสนับสนุนการดำเนินการที่ขี้เกียจตรงข้ามกับการดำเนินการที่กระตือรือร้นโดย enums ในระยะสั้นstreams are lazy, composable enumerables. สิ่งนี้หมายความว่า Streams จะไม่ดำเนินการเว้นแต่ว่าจำเป็นอย่างยิ่ง ให้เราพิจารณาตัวอย่างเพื่อทำความเข้าใจสิ่งนี้ -
odd? = &(rem(&1, 2) != 0)
res = 1..100_000 |> Stream.map(&(&1 * 3)) |> Stream.filter(odd?) |> Enum.sum
IO.puts(res)
เมื่อรันโปรแกรมข้างต้นจะให้ผลลัพธ์ดังนี้ -
7500000000
ในตัวอย่างที่ให้ไว้ข้างต้น 1..100_000 |> Stream.map(&(&1 * 3))ส่งคืนชนิดข้อมูลซึ่งเป็นสตรีมจริงที่แสดงถึงการคำนวณแผนที่ในช่วง 1..100_000 ยังไม่ได้ประเมินการเป็นตัวแทนนี้ แทนที่จะสร้างรายการระดับกลางสตรีมจะสร้างชุดการคำนวณที่เรียกใช้เฉพาะเมื่อเราส่งสตรีมพื้นฐานไปยังโมดูล Enum สตรีมมีประโยชน์เมื่อทำงานกับคอลเล็กชันขนาดใหญ่ซึ่งอาจไม่มีที่สิ้นสุด
สตรีมและอีนัมมีฟังก์ชันหลายอย่างที่เหมือนกัน สตรีมส่วนใหญ่มีฟังก์ชันเดียวกับที่จัดเตรียมโดยโมดูล Enum ซึ่งสร้างรายการเป็นค่าส่งคืนหลังจากดำเนินการคำนวณเกี่ยวกับการแจงนับอินพุต บางส่วนมีรายชื่ออยู่ในตารางต่อไปนี้ -
ซีเนียร์ | ฟังก์ชันและคำอธิบาย |
---|---|
1 | chunk(enum, n, step, leftover \\ nil) สตรีมที่แจกแจงเป็นกลุ่มโดยมี n รายการแต่ละรายการโดยที่แต่ละกลุ่มใหม่จะเริ่มองค์ประกอบขั้นตอนในการแจงนับ |
2 | concat(enumerables) สร้างสตรีมที่แจกแจงแต่ละรายการในแบบแจกแจง |
3 | each(enum, fun) เรียกใช้ฟังก์ชันที่กำหนดสำหรับแต่ละรายการ |
4 | filter(enum, fun) สร้างสตรีมที่กรององค์ประกอบตามฟังก์ชันที่กำหนดในการแจงนับ |
5 | map(enum, fun) สร้างสตรีมที่จะใช้ฟังก์ชันที่กำหนดในการแจงนับ |
6 | drop(enum, n) ทิ้ง n รายการถัดไปอย่างเกียจคร้านจากตัวนับ |
โครงสร้างคือส่วนขยายที่สร้างขึ้นบนแผนที่ที่ให้การตรวจสอบเวลาคอมไพล์และค่าเริ่มต้น
การกำหนดโครงสร้าง
ในการกำหนดโครงสร้างจะใช้โครงสร้างป้องกัน -
defmodule User do
defstruct name: "John", age: 27
end
รายการคีย์เวิร์ดที่ใช้กับ Defstruct จะกำหนดฟิลด์ที่โครงสร้างจะมีพร้อมกับค่าดีฟอลต์ โครงสร้างใช้ชื่อของโมดูลที่กำหนดไว้ในตัวอย่างที่ให้ไว้ข้างต้นเราได้กำหนดโครงสร้างชื่อผู้ใช้ ตอนนี้เราสามารถสร้างโครงสร้างผู้ใช้โดยใช้ไวยากรณ์ที่คล้ายกับที่ใช้ในการสร้างแผนที่ -
new_john = %User{})
ayush = %User{name: "Ayush", age: 20}
megan = %User{name: "Megan"})
โค้ดด้านบนจะสร้างโครงสร้างที่แตกต่างกันสามแบบด้วยค่า -
%User{age: 27, name: "John"}
%User{age: 20, name: "Ayush"}
%User{age: 27, name: "Megan"}
โครงสร้างให้การรับประกันเวลาคอมไพล์ว่าเฉพาะฟิลด์ (และทั้งหมด) ที่กำหนดผ่าน defstruct เท่านั้นที่จะได้รับอนุญาตให้มีอยู่ในโครงสร้าง ดังนั้นคุณจึงไม่สามารถกำหนดฟิลด์ของคุณเองได้เมื่อคุณสร้างโครงสร้างในโมดูลแล้ว
การเข้าถึงและอัปเดตโครงสร้าง
เมื่อเราพูดถึงแผนที่เราได้แสดงให้เห็นว่าเราสามารถเข้าถึงและอัปเดตฟิลด์ของแผนที่ได้อย่างไร เทคนิคเดียวกัน (และไวยากรณ์เดียวกัน) นำไปใช้กับโครงสร้างเช่นกัน ตัวอย่างเช่นหากเราต้องการอัปเดตผู้ใช้ที่เราสร้างไว้ในตัวอย่างก่อนหน้านี้ -
defmodule User do
defstruct name: "John", age: 27
end
john = %User{}
#john right now is: %User{age: 27, name: "John"}
#To access name and age of John,
IO.puts(john.name)
IO.puts(john.age)
เมื่อรันโปรแกรมข้างต้นจะให้ผลลัพธ์ดังนี้ -
John
27
ในการอัปเดตค่าในโครงสร้างเราจะใช้ขั้นตอนเดียวกับที่เราใช้ในบทแผนที่อีกครั้ง
meg = %{john | name: "Meg"}
โครงสร้างยังสามารถใช้ในการจับคู่รูปแบบทั้งสำหรับการจับคู่กับค่าของคีย์ที่เฉพาะเจาะจงและเพื่อให้แน่ใจว่าค่าที่ตรงกันเป็นโครงสร้างประเภทเดียวกับค่าที่ตรงกัน
โปรโตคอลเป็นกลไกในการบรรลุความหลากหลายใน Elixir การจัดส่งโปรโตคอลสามารถใช้ได้กับข้อมูลทุกประเภทตราบใดที่ใช้โปรโตคอล
ให้เราพิจารณาตัวอย่างของการใช้โปรโตคอล เราใช้ฟังก์ชันที่เรียกว่าto_stringในบทก่อนหน้าเพื่อแปลงจากประเภทอื่นเป็นประเภทสตริง นี่เป็นโปรโตคอลจริงๆ ทำหน้าที่ตามอินพุตที่กำหนดโดยไม่เกิดข้อผิดพลาด สิ่งนี้อาจดูเหมือนว่าเรากำลังพูดถึงฟังก์ชันการจับคู่รูปแบบ แต่เมื่อเราดำเนินการต่อไปมันกลับกลายเป็นว่าแตกต่าง
พิจารณาตัวอย่างต่อไปนี้เพื่อทำความเข้าใจกลไกโปรโตคอลเพิ่มเติม
ให้เราสร้างโปรโตคอลที่จะแสดงว่าอินพุตที่ระบุว่างเปล่าหรือไม่ เราจะเรียกโปรโตคอลนี้ว่าblank?.
การกำหนดโปรโตคอล
เราสามารถกำหนดโปรโตคอลใน Elixir ได้ด้วยวิธีต่อไปนี้ -
defprotocol Blank do
def blank?(data)
end
อย่างที่คุณเห็นเราไม่จำเป็นต้องกำหนดเนื้อความสำหรับฟังก์ชัน หากคุณคุ้นเคยกับอินเทอร์เฟซในภาษาโปรแกรมอื่น ๆ คุณสามารถคิดว่า Protocol เป็นสิ่งเดียวกัน
ดังนั้นพิธีสารนี้จึงบอกว่าสิ่งใดก็ตามที่ใช้ต้องมี empty?แม้ว่ามันจะขึ้นอยู่กับตัวดำเนินการว่าฟังก์ชันตอบสนองอย่างไร เมื่อกำหนดโปรโตคอลแล้วให้เราเข้าใจวิธีเพิ่มการใช้งานสองสามอย่าง
การดำเนินการตามพิธีสาร
เนื่องจากเราได้กำหนดโปรโตคอลตอนนี้เราจึงต้องบอกวิธีจัดการอินพุตต่างๆที่อาจได้รับ ให้เราสร้างตัวอย่างที่เราได้ทำก่อนหน้านี้ เราจะใช้โปรโตคอลเปล่าสำหรับรายการแผนที่และสตริง สิ่งนี้จะแสดงว่าสิ่งที่เราผ่านมานั้นว่างเปล่าหรือไม่
#Defining the protocol
defprotocol Blank do
def blank?(data)
end
#Implementing the protocol for lists
defimpl Blank, for: List do
def blank?([]), do: true
def blank?(_), do: false
end
#Implementing the protocol for strings
defimpl Blank, for: BitString do
def blank?(""), do: true
def blank?(_), do: false
end
#Implementing the protocol for maps
defimpl Blank, for: Map do
def blank?(map), do: map_size(map) == 0
end
IO.puts(Blank.blank? [])
IO.puts(Blank.blank? [:true, "Hello"])
IO.puts(Blank.blank? "")
IO.puts(Blank.blank? "Hi")
คุณสามารถใช้โปรโตคอลของคุณได้มากหรือน้อยตามที่คุณต้องการสิ่งที่เหมาะสมสำหรับการใช้งานโปรโตคอลของคุณ นี่เป็นกรณีการใช้งานขั้นพื้นฐานของโปรโตคอล เมื่อรันโปรแกรมข้างต้นจะให้ผลลัพธ์ดังนี้ -
true
false
true
false
Note - หากคุณใช้สิ่งนี้กับประเภทอื่น ๆ นอกเหนือจากที่คุณกำหนดโปรโตคอลไว้จะทำให้เกิดข้อผิดพลาด
File IO เป็นส่วนหนึ่งของภาษาโปรแกรมใด ๆ เนื่องจากอนุญาตให้ภาษาโต้ตอบกับไฟล์ในระบบไฟล์ ในบทนี้เราจะพูดถึงสองโมดูล - เส้นทางและไฟล์
โมดูลเส้นทาง
pathโมดูลเป็นโมดูลขนาดเล็กมากที่ถือได้ว่าเป็นโมดูลตัวช่วยสำหรับการทำงานของระบบไฟล์ ฟังก์ชันส่วนใหญ่ในโมดูลไฟล์คาดว่าพา ธ เป็นอาร์กิวเมนต์ โดยทั่วไปเส้นทางเหล่านั้นจะเป็นไบนารีปกติ โมดูลพา ธ จัดเตรียมสิ่งอำนวยความสะดวกสำหรับการทำงานกับพา ธ ดังกล่าว แนะนำให้ใช้ฟังก์ชันจากโมดูล Path ซึ่งต่างจากการจัดการไบนารีเนื่องจากโมดูล Path ดูแลระบบปฏิบัติการที่แตกต่างกันอย่างโปร่งใส เป็นที่สังเกตได้ว่า Elixir จะแปลงเครื่องหมายทับ (/) เป็นแบ็กสแลช (\) บน Windows โดยอัตโนมัติเมื่อดำเนินการกับไฟล์
ให้เราพิจารณาตัวอย่างต่อไปนี้เพื่อทำความเข้าใจโมดูลเส้นทางเพิ่มเติม -
IO.puts(Path.join("foo", "bar"))
เมื่อรันโปรแกรมข้างต้นจะให้ผลลัพธ์ดังนี้ -
foo/bar
มีวิธีการมากมายที่โมดูลพา ธ มีให้ คุณสามารถดูได้ที่วิธีการที่แตกต่างกันที่นี่ วิธีการเหล่านี้มักใช้หากคุณดำเนินการจัดการไฟล์จำนวนมาก
โมดูลไฟล์
โมดูลไฟล์มีฟังก์ชันที่อนุญาตให้เราเปิดไฟล์เป็นอุปกรณ์ IO โดยค่าเริ่มต้นไฟล์จะเปิดในโหมดไบนารีซึ่งนักพัฒนาต้องใช้ไฟล์IO.binread และ IO.binwriteฟังก์ชันจากโมดูล IO ให้เราสร้างไฟล์ชื่อnewfile และเขียนข้อมูลลงไป
{:ok, file} = File.read("newfile", [:write])
# Pattern matching to store returned stream
IO.binwrite(file, "This will be written to the file")
หากคุณไปเปิดไฟล์ที่เราเพิ่งเขียนลงไปเนื้อหาจะแสดงในลักษณะต่อไปนี้ -
This will be written to the file
ตอนนี้ให้เราเข้าใจวิธีใช้โมดูลไฟล์
การเปิดไฟล์
ในการเปิดไฟล์เราสามารถใช้หนึ่งใน 2 ฟังก์ชันต่อไปนี้ -
{:ok, file} = File.open("newfile")
file = File.open!("newfile")
ตอนนี้ให้เราเข้าใจความแตกต่างระหว่างไฟล์ File.open ฟังก์ชันและ File.open!() ฟังก์ชัน
File.openฟังก์ชันจะส่งคืนทูเพิลเสมอ หากเปิดไฟล์สำเร็จระบบจะส่งคืนค่าแรกในทูเพิลเป็น:okและค่าที่สองคือลิเทอรัลของประเภท io_device หากเกิดข้อผิดพลาดจะส่งคืนทูเพิลที่มีค่าแรกเป็น:error และค่าที่สองเป็นเหตุผล
File.open!() ในทางกลับกันฟังก์ชันจะส่งกลับไฟล์ io_deviceหากเปิดไฟล์สำเร็จมิฉะนั้นจะทำให้เกิดข้อผิดพลาด หมายเหตุ: นี่คือรูปแบบที่ตามมาในฟังก์ชันโมดูลไฟล์ทั้งหมดที่เราจะพูดถึง
เราสามารถระบุโหมดที่เราต้องการเปิดไฟล์นี้ได้ด้วย ในการเปิดไฟล์เป็นแบบอ่านอย่างเดียวและในโหมดการเข้ารหัส utf-8 เราใช้รหัสต่อไปนี้ -
file = File.open!("newfile", [:read, :utf8])
การเขียนไฟล์
เรามีสองวิธีในการเขียนลงไฟล์ ให้เราดูอันแรกโดยใช้ฟังก์ชันเขียนจากโมดูลไฟล์
File.write("newfile", "Hello")
แต่ไม่ควรใช้หากคุณทำการเขียนหลายรายการในไฟล์เดียวกัน ทุกครั้งที่เรียกใช้ฟังก์ชันนี้ตัวอธิบายไฟล์จะถูกเปิดขึ้นและกระบวนการใหม่จะถูกสร้างขึ้นเพื่อเขียนลงในไฟล์ หากคุณกำลังเขียนหลายรายการในลูปให้เปิดไฟล์ผ่านFile.openและเขียนโดยใช้วิธีการในโมดูล IO ให้เราพิจารณาตัวอย่างเพื่อให้เข้าใจตรงกัน -
#Open the file in read, write and utf8 modes.
file = File.open!("newfile_2", [:read, :utf8, :write])
#Write to this "io_device" using standard IO functions
IO.puts(file, "Random text")
คุณสามารถใช้วิธีการโมดูล IO อื่น ๆ เช่น IO.write และ IO.binwrite เพื่อเขียนไปยังไฟล์ที่เปิดเป็น io_device
อ่านจากไฟล์
เรามีสองวิธีในการอ่านจากไฟล์ ให้เราดูอันแรกโดยใช้ฟังก์ชั่นอ่านจากโมดูลไฟล์
IO.puts(File.read("newfile"))
เมื่อเรียกใช้โค้ดนี้คุณควรได้ทูเปิลที่มีองค์ประกอบแรกเป็น :ok และอันที่สองเป็นเนื้อหาของไฟล์ใหม่
เรายังสามารถใช้ไฟล์ File.read! ฟังก์ชั่นเพื่อรับเนื้อหาของไฟล์กลับมาให้เรา
การปิดไฟล์เปิด
เมื่อใดก็ตามที่คุณเปิดไฟล์โดยใช้ฟังก์ชัน File.open หลังจากใช้งานเสร็จแล้วคุณควรปิดโดยใช้ไฟล์ File.close ฟังก์ชัน -
File.close(file)
ใน Elixir โค้ดทั้งหมดจะทำงานภายในกระบวนการ กระบวนการจะแยกออกจากกันทำงานพร้อมกันและสื่อสารผ่านข้อความ ไม่ควรสับสนกระบวนการของ Elixir กับกระบวนการของระบบปฏิบัติการ กระบวนการใน Elixir มีน้ำหนักเบามากในแง่ของหน่วยความจำและ CPU (ไม่เหมือนกับเธรดในภาษาโปรแกรมอื่น ๆ ) ด้วยเหตุนี้จึงไม่ใช่เรื่องแปลกที่จะมีกระบวนการทำงานพร้อมกันเป็นหมื่นหรือหลายแสน
ในบทนี้เราจะเรียนรู้เกี่ยวกับโครงสร้างพื้นฐานสำหรับการวางไข่กระบวนการใหม่ตลอดจนการส่งและรับข้อความระหว่างกระบวนการต่างๆ
ฟังก์ชัน Spawn
วิธีที่ง่ายที่สุดในการสร้างกระบวนการใหม่คือการใช้ไฟล์ spawnฟังก์ชัน spawnยอมรับฟังก์ชันที่จะทำงานในกระบวนการใหม่ ตัวอย่างเช่น -
pid = spawn(fn -> 2 * 2 end)
Process.alive?(pid)
เมื่อรันโปรแกรมข้างต้นจะให้ผลลัพธ์ดังนี้ -
false
ค่าส่งกลับของฟังก์ชันวางไข่คือ PID นี่คือตัวระบุที่ไม่ซ้ำกันสำหรับกระบวนการดังนั้นหากคุณเรียกใช้โค้ดเหนือ PID ของคุณก็จะแตกต่างออกไป ดังที่คุณเห็นในตัวอย่างนี้กระบวนการนี้จะตายเมื่อเราตรวจสอบว่ายังมีชีวิตอยู่หรือไม่ เนื่องจากกระบวนการนี้จะออกทันทีที่รันฟังก์ชันที่กำหนดเสร็จสิ้น
ดังที่ได้กล่าวไปแล้วรหัส Elixir ทั้งหมดจะทำงานภายในกระบวนการ หากคุณเรียกใช้ฟังก์ชัน self คุณจะเห็น PID สำหรับเซสชันปัจจุบันของคุณ -
pid = self
Process.alive?(pid)
เมื่อรันโปรแกรมข้างต้นจะให้ผลลัพธ์ดังนี้ -
true
ข้อความผ่าน
เราสามารถส่งข้อความไปยังกระบวนการด้วย send และรับด้วย receive. ให้เราส่งข้อความไปยังกระบวนการปัจจุบันและรับในขั้นตอนเดียวกัน
send(self(), {:hello, "Hi people"})
receive do
{:hello, msg} -> IO.puts(msg)
{:another_case, msg} -> IO.puts("This one won't match!")
end
เมื่อรันโปรแกรมข้างต้นจะให้ผลลัพธ์ดังนี้ -
Hi people
เราส่งข้อความไปยังกระบวนการปัจจุบันโดยใช้ฟังก์ชันส่งและส่งต่อไปยัง PID ของตนเอง จากนั้นเราจัดการข้อความขาเข้าโดยใช้ไฟล์receive ฟังก์ชัน
เมื่อข้อความถูกส่งไปยังกระบวนการข้อความจะถูกเก็บไว้ในไฟล์ process mailbox. บล็อกการรับจะผ่านกล่องจดหมายกระบวนการปัจจุบันที่ค้นหาข้อความที่ตรงกับรูปแบบที่กำหนด บล็อกการรับรองรับยามและหลายมาตราเช่นกรณี
หากไม่มีข้อความในกล่องเมลที่ตรงกับรูปแบบใด ๆ กระบวนการปัจจุบันจะรอจนกว่าข้อความที่ตรงกันจะมาถึง นอกจากนี้ยังสามารถระบุระยะหมดเวลาได้ ตัวอย่างเช่น,
receive do
{:hello, msg} -> msg
after
1_000 -> "nothing after 1s"
end
เมื่อรันโปรแกรมข้างต้นจะให้ผลลัพธ์ดังนี้ -
nothing after 1s
NOTE - สามารถกำหนดระยะหมดเวลาเป็น 0 ได้เมื่อคุณคาดหวังว่าข้อความจะอยู่ในกล่องเมลแล้ว
ลิงค์
รูปแบบการวางไข่ที่พบบ่อยที่สุดใน Elixir เกิดขึ้นจริงผ่านทาง spawn_linkฟังก์ชัน ก่อนที่จะดูตัวอย่าง spawn_link ให้เราเข้าใจว่าเกิดอะไรขึ้นเมื่อกระบวนการล้มเหลว
spawn fn -> raise "oops" end
เมื่อรันโปรแกรมข้างต้นจะเกิดข้อผิดพลาดดังต่อไปนี้ -
[error] Process #PID<0.58.00> raised an exception
** (RuntimeError) oops
:erlang.apply/2
บันทึกข้อผิดพลาด แต่กระบวนการวางไข่ยังคงทำงานอยู่ เนื่องจากกระบวนการต่างๆถูกแยกออก หากเราต้องการให้ความล้มเหลวในกระบวนการหนึ่งเผยแพร่ไปยังอีกกระบวนการหนึ่งเราจำเป็นต้องเชื่อมโยงสิ่งเหล่านี้ ซึ่งสามารถทำได้ด้วยไฟล์spawn_linkฟังก์ชัน ให้เราพิจารณาตัวอย่างเพื่อให้เข้าใจตรงกัน -
spawn_link fn -> raise "oops" end
เมื่อรันโปรแกรมข้างต้นจะเกิดข้อผิดพลาดดังต่อไปนี้ -
** (EXIT from #PID<0.41.0>) an exception was raised:
** (RuntimeError) oops
:erlang.apply/2
หากคุณกำลังเรียกใช้สิ่งนี้ใน iexจากนั้นเชลล์จะจัดการกับข้อผิดพลาดนี้และไม่ออก แต่ถ้าคุณเรียกใช้โดยสร้างไฟล์สคริปต์ก่อนแล้วใช้elixir <file-name>.exsกระบวนการพาเรนต์จะถูกปิดลงเนื่องจากความล้มเหลวนี้
กระบวนการและการเชื่อมโยงมีบทบาทสำคัญในการสร้างระบบป้องกันความผิดพลาด ในแอปพลิเคชัน Elixir เรามักจะเชื่อมโยงกระบวนการของเรากับหัวหน้างานซึ่งจะตรวจจับเมื่อกระบวนการตายและเริ่มกระบวนการใหม่แทน สิ่งนี้เป็นไปได้ก็ต่อเมื่อกระบวนการต่างๆถูกแยกออกจากกันและจะไม่แชร์อะไรโดยปริยาย และเนื่องจากกระบวนการต่างๆถูกแยกออกจึงไม่มีทางที่ความล้มเหลวในกระบวนการจะขัดข้องหรือทำให้สถานะอื่นเสียหาย ในขณะที่ภาษาอื่นต้องการให้เราจับ / จัดการข้อยกเว้น ใน Elixir เราสบายดีที่ปล่อยให้กระบวนการล้มเหลวเพราะเราคาดหวังว่าหัวหน้างานจะรีสตาร์ทระบบของเราอย่างถูกต้อง
สถานะ
หากคุณกำลังสร้างแอปพลิเคชันที่ต้องใช้สถานะตัวอย่างเช่นเพื่อเก็บการกำหนดค่าแอปพลิเคชันของคุณไว้หรือคุณต้องการแยกวิเคราะห์ไฟล์และเก็บไว้ในหน่วยความจำคุณจะเก็บไว้ที่ไหน ฟังก์ชันกระบวนการของ Elixir จะมีประโยชน์เมื่อทำสิ่งนั้น
เราสามารถเขียนกระบวนการที่วนซ้ำไม่สิ้นสุดรักษาสถานะและส่งและรับข้อความ ตัวอย่างเช่นให้เราเขียนโมดูลที่เริ่มกระบวนการใหม่ที่ทำงานเป็นที่เก็บคีย์ - ค่าในไฟล์ที่ชื่อkv.exs.
defmodule KV do
def start_link do
Task.start_link(fn -> loop(%{}) end)
end
defp loop(map) do
receive do
{:get, key, caller} ->
send caller, Map.get(map, key)
loop(map)
{:put, key, value} ->
loop(Map.put(map, key, value))
end
end
end
โปรดทราบว่าไฟล์ start_link ฟังก์ชันเริ่มกระบวนการใหม่ที่รันไฟล์ loopฟังก์ชันเริ่มต้นด้วยแผนที่ว่างเปล่า loopจากนั้นจะรอข้อความและดำเนินการตามความเหมาะสมสำหรับแต่ละข้อความ ในกรณีของก:getข้อความจะส่งข้อความกลับไปยังผู้โทรและโทรวนซ้ำอีกครั้งเพื่อรอข้อความใหม่ ในขณะที่:put ข้อความเรียกใช้จริง loop ด้วยแผนที่เวอร์ชันใหม่พร้อมคีย์และค่าที่กำหนดไว้
ให้เราดำเนินการต่อไปนี้ -
iex kv.exs
ตอนนี้คุณควรอยู่ในไฟล์ iexเปลือก. ในการทดสอบโมดูลของเราให้ลองทำดังต่อไปนี้ -
{:ok, pid} = KV.start_link
# pid now has the pid of our new process that is being
# used to get and store key value pairs
# Send a KV pair :hello, "Hello" to the process
send pid, {:put, :hello, "Hello"}
# Ask for the key :hello
send pid, {:get, :hello, self()}
# Print all the received messages on the current process.
flush()
เมื่อรันโปรแกรมข้างต้นจะให้ผลลัพธ์ดังนี้ -
"Hello"
ในบทนี้เราจะสำรวจสัญลักษณ์ซึ่งเป็นกลไกที่จัดทำโดยภาษาสำหรับการทำงานกับการแสดงข้อความ เครื่องหมายเริ่มต้นด้วยอักขระเครื่องหมายทิลเดอ (~) ซึ่งตามด้วยตัวอักษร (ซึ่งระบุเครื่องหมาย) แล้วตัวคั่น คุณสามารถเพิ่มตัวปรับแต่งได้หลังจากตัวคั่นสุดท้าย
Regex
Regexes ใน Elixir คือ sigils เราได้เห็นการใช้งานของพวกเขาในบท String ขอยกตัวอย่างอีกครั้งเพื่อดูว่าเราสามารถใช้ regex ใน Elixir ได้อย่างไร
# A regular expression that matches strings which contain "foo" or
# "bar":
regex = ~r/foo|bar/
IO.puts("foo" =~ regex)
IO.puts("baz" =~ regex)
เมื่อรันโปรแกรมข้างต้นจะให้ผลลัพธ์ดังนี้ -
true
false
เครื่องหมายรองรับ 8 ตัวคั่นที่แตกต่างกัน -
~r/hello/
~r|hello|
~r"hello"
~r'hello'
~r(hello)
~r[hello]
~r{hello}
~r<hello>
เหตุผลที่สนับสนุนตัวคั่นที่แตกต่างกันก็คือตัวคั่นที่แตกต่างกันสามารถเหมาะสำหรับเครื่องหมายต่างๆ ตัวอย่างเช่นการใช้วงเล็บสำหรับนิพจน์ทั่วไปอาจเป็นตัวเลือกที่สับสนเนื่องจากสามารถผสมกับวงเล็บภายใน regex ได้ อย่างไรก็ตามวงเล็บอาจมีประโยชน์สำหรับเครื่องหมายอื่น ๆ ดังที่เราจะเห็นในส่วนถัดไป
Elixir รองรับ regexes ที่เข้ากันได้กับ Perl และยังรองรับตัวดัดแปลง คุณสามารถอ่านมากขึ้นเกี่ยวกับการใช้ regexes ที่นี่
สตริงรายการ Char และรายการ Word
นอกเหนือจาก regexes แล้ว Elixir ยังมีสัญลักษณ์ในตัวอีก 3 ตัว ให้เราดูที่ sigils
สตริง
เครื่องหมาย ~ s ใช้ในการสร้างสตริงเช่นเครื่องหมายคำพูดคู่คือ เครื่องหมาย ~ s มีประโยชน์ตัวอย่างเช่นเมื่อสตริงมีทั้งเครื่องหมายคำพูดคู่และเดี่ยว -
new_string = ~s(this is a string with "double" quotes, not 'single' ones)
IO.puts(new_string)
เครื่องหมายนี้สร้างสตริง เมื่อรันโปรแกรมข้างต้นจะให้ผลลัพธ์ดังนี้ -
"this is a string with \"double\" quotes, not 'single' ones"
รายการ Char
เครื่องหมาย ~ c ใช้เพื่อสร้างรายการถ่าน -
new_char_list = ~c(this is a char list containing 'single quotes')
IO.puts(new_char_list)
เมื่อรันโปรแกรมข้างต้นจะให้ผลลัพธ์ดังนี้ -
this is a char list containing 'single quotes'
รายการคำ
เครื่องหมาย ~ w ใช้เพื่อสร้างรายการคำ (คำเป็นเพียงสตริงปกติ) ภายในเครื่องหมาย ~ w คำจะถูกคั่นด้วยช่องว่าง
new_word_list = ~w(foo bar bat)
IO.puts(new_word_list)
เมื่อรันโปรแกรมข้างต้นจะให้ผลลัพธ์ดังนี้ -
foobarbat
เครื่องหมาย ~ w ยังยอมรับไฟล์ c, s และ a ตัวปรับแต่ง (สำหรับรายการถ่านสตริงและอะตอมตามลำดับ) ซึ่งระบุประเภทข้อมูลขององค์ประกอบของรายการผลลัพธ์ -
new_atom_list = ~w(foo bar bat)a
IO.puts(new_atom_list)
เมื่อรันโปรแกรมข้างต้นจะให้ผลลัพธ์ดังนี้ -
[:foo, :bar, :bat]
Interpolation and Escaping in Sigils
นอกจากตัวพิมพ์เล็กแล้ว Elixir ยังรองรับสัญลักษณ์ตัวพิมพ์ใหญ่เพื่อจัดการกับอักขระที่หลบหนีและการแก้ไข ในขณะที่ทั้งสอง ~ s และ ~ S จะส่งคืนสตริง แต่ก่อนหน้านี้อนุญาตให้ใช้รหัสหลบหนีและการแก้ไขในขณะที่ค่าหลังไม่ได้ ให้เราพิจารณาตัวอย่างเพื่อทำความเข้าใจสิ่งนี้ -
~s(String with escape codes \x26 #{"inter" <> "polation"})
# "String with escape codes & interpolation"
~S(String without escape codes \x26 without #{interpolation})
# "String without escape codes \\x26 without \#{interpolation}"
เครื่องหมายกำหนดเอง
เราสามารถสร้าง sigils เองได้ง่ายๆ ในตัวอย่างนี้เราจะสร้างเครื่องหมายเพื่อแปลงสตริงเป็นตัวพิมพ์ใหญ่
defmodule CustomSigil do
def sigil_u(string, []), do: String.upcase(string)
end
import CustomSigil
IO.puts(~u/tutorials point/)
เมื่อเรารันโค้ดด้านบนจะให้ผลลัพธ์ดังนี้ -
TUTORIALS POINT
ก่อนอื่นเรากำหนดโมดูลที่เรียกว่า CustomSigil และภายในโมดูลนั้นเราได้สร้างฟังก์ชันที่เรียกว่า sigil_u เนื่องจากไม่มีเครื่องหมาย ~ u ในช่องว่างที่มีอยู่เราจะใช้มัน _u ระบุว่าเราต้องการใช้ u เป็นอักขระหลังเครื่องหมายทิลเดอ นิยามฟังก์ชันต้องใช้สองอาร์กิวเมนต์อินพุตและรายการ
ความเข้าใจในรายการเป็นน้ำตาลเชิงวากยสัมพันธ์สำหรับการวนรอบตัวนับใน Elixir ในบทนี้เราจะใช้ความเข้าใจในการทำซ้ำและการสร้าง
พื้นฐาน
เมื่อเราดูโมดูล Enum ในบทแจงนับเราเจอฟังก์ชันแผนที่
Enum.map(1..3, &(&1 * 2))
ในตัวอย่างนี้เราจะส่งผ่านฟังก์ชันเป็นอาร์กิวเมนต์ที่สอง แต่ละรายการในช่วงจะถูกส่งไปยังฟังก์ชันจากนั้นรายการใหม่จะถูกส่งกลับซึ่งมีค่าใหม่
การทำแผนที่การกรองและการแปลงร่างเป็นการกระทำทั่วไปใน Elixir ดังนั้นจึงมีวิธีที่แตกต่างกันเล็กน้อยในการบรรลุผลลัพธ์เช่นเดียวกับตัวอย่างก่อนหน้านี้ -
for n <- 1..3, do: n * 2
เมื่อเรารันโค้ดด้านบนจะให้ผลลัพธ์ดังนี้ -
[2, 4, 6]
ตัวอย่างที่สองคือความเข้าใจและอย่างที่คุณเห็นมันเป็นเพียงแค่น้ำตาลที่เป็นประโยคสำหรับสิ่งที่คุณสามารถทำได้หากคุณใช้ Enum.mapฟังก์ชัน อย่างไรก็ตามไม่มีประโยชน์ที่แท้จริงในการใช้ความเข้าใจเกี่ยวกับฟังก์ชันจากโมดูล Enum ในแง่ของประสิทธิภาพ
ความเข้าใจไม่ จำกัด เฉพาะรายการ แต่สามารถใช้ได้กับตัวนับทั้งหมด
กรอง
คุณอาจคิดว่าตัวกรองเป็นเครื่องป้องกันความเข้าใจ เมื่อค่าที่กรองกลับมาfalse หรือ nilไม่รวมอยู่ในรายการสุดท้าย ให้เราวนซ้ำช่วงหนึ่งและกังวลเฉพาะเลขคู่ เราจะใช้ไฟล์is_even ฟังก์ชันจากโมดูลจำนวนเต็มเพื่อตรวจสอบว่าค่าเท่ากันหรือไม่
import Integer
IO.puts(for x <- 1..10, is_even(x), do: x)
เมื่อรันโค้ดด้านบนจะให้ผลลัพธ์ดังนี้ -
[2, 4, 6, 8, 10]
เรายังสามารถใช้ตัวกรองหลายตัวในความเข้าใจเดียวกัน เพิ่มตัวกรองอื่นที่คุณต้องการหลังจากis_even ตัวกรองคั่นด้วยลูกน้ำ
: เป็นตัวเลือก
ในตัวอย่างข้างต้นความเข้าใจทั้งหมดจะแสดงรายการเป็นผลลัพธ์ อย่างไรก็ตามผลของความเข้าใจสามารถแทรกลงในโครงสร้างข้อมูลที่แตกต่างกันได้โดยส่งไฟล์:into ตัวเลือกเพื่อความเข้าใจ
ตัวอย่างเช่นก bitstring เครื่องกำเนิดไฟฟ้าสามารถใช้กับตัวเลือก: into เพื่อลบช่องว่างทั้งหมดในสตริงได้อย่างง่ายดาย -
IO.puts(for <<c <- " hello world ">>, c != ?\s, into: "", do: <<c>>)
เมื่อรันโค้ดด้านบนจะให้ผลลัพธ์ดังนี้ -
helloworld
โค้ดด้านบนจะลบช่องว่างทั้งหมดออกจากสตริงโดยใช้ c != ?\s กรองจากนั้นใช้ตัวเลือก: into ซึ่งจะทำให้อักขระที่ส่งคืนทั้งหมดอยู่ในสตริง
Elixir เป็นภาษาที่พิมพ์แบบไดนามิกดังนั้นทุกประเภทใน Elixir จะอนุมานโดยรันไทม์ อย่างไรก็ตาม Elixir มาพร้อมกับ typespecs ซึ่งเป็นสัญกรณ์ที่ใช้สำหรับdeclaring custom data types and declaring typed function signatures (specifications).
ข้อมูลจำเพาะของฟังก์ชัน (รายละเอียด)
ตามค่าเริ่มต้น Elixir จะจัดเตรียมประเภทพื้นฐานบางประเภทเช่นจำนวนเต็มหรือ pid และประเภทที่ซับซ้อนเช่น roundฟังก์ชันที่ปัดเศษทศนิยมให้เป็นจำนวนเต็มที่ใกล้เคียงที่สุดรับตัวเลขเป็นอาร์กิวเมนต์ (จำนวนเต็มหรือจำนวนทศนิยม) และส่งกลับจำนวนเต็ม ในเอกสารที่เกี่ยวข้องลายเซ็นที่พิมพ์รอบเขียนว่า -
round(number) :: integer
คำอธิบายข้างต้นหมายความว่าฟังก์ชันทางด้านซ้ายใช้เป็นอาร์กิวเมนต์ที่ระบุไว้ในวงเล็บและส่งคืนสิ่งที่อยู่ทางขวาของ :: นั่นคือจำนวนเต็ม รายละเอียดฟังก์ชันเขียนด้วย@specคำสั่งวางไว้หน้านิยามฟังก์ชัน ฟังก์ชันรอบสามารถเขียนเป็น -
@spec round(number) :: integer
def round(number), do: # Function implementation
...
typespecs สนับสนุนประเภทที่ซับซ้อนเช่นกันเช่นหากคุณต้องการส่งคืนรายการจำนวนเต็มคุณสามารถใช้ [Integer]
ประเภทที่กำหนดเอง
แม้ว่า Elixir จะมีประเภทในตัวที่มีประโยชน์มากมาย แต่ก็สะดวกในการกำหนดประเภทที่กำหนดเองตามความเหมาะสม ซึ่งสามารถทำได้เมื่อกำหนดโมดูลผ่านคำสั่ง @type ให้เราพิจารณาตัวอย่างเพื่อให้เข้าใจตรงกัน -
defmodule FunnyCalculator do
@type number_with_joke :: {number, String.t}
@spec add(number, number) :: number_with_joke
def add(x, y), do: {x + y, "You need a calculator to do that?"}
@spec multiply(number, number) :: number_with_joke
def multiply(x, y), do: {x * y, "It is like addition on steroids."}
end
{result, comment} = FunnyCalculator.add(10, 20)
IO.puts(result)
IO.puts(comment)
เมื่อรันโปรแกรมข้างต้นจะให้ผลลัพธ์ดังนี้ -
30
You need a calculator to do that?
NOTE - ประเภทที่กำหนดเองที่กำหนดผ่าน @type จะถูกส่งออกและพร้อมใช้งานภายนอกโมดูลที่กำหนดไว้หากคุณต้องการเก็บประเภทที่กำหนดเองไว้เป็นส่วนตัวคุณสามารถใช้ @typep คำสั่งแทน @type.
พฤติกรรมใน Elixir (และ Erlang) เป็นวิธีแยกและแยกส่วนทั่วไปของส่วนประกอบ (ซึ่งกลายเป็นโมดูลพฤติกรรม) ออกจากส่วนเฉพาะ (ซึ่งกลายเป็นโมดูลเรียกกลับ) พฤติกรรมเป็นหนทางในการ -
- กำหนดชุดของฟังก์ชันที่โมดูลต้องนำไปใช้
- ตรวจสอบให้แน่ใจว่าโมดูลใช้ฟังก์ชันทั้งหมดในชุดนั้น
หากคุณต้องการคุณสามารถนึกถึงพฤติกรรมเช่นอินเทอร์เฟซในภาษาเชิงวัตถุเช่น Java: ชุดลายเซ็นฟังก์ชันที่โมดูลต้องใช้
การกำหนดพฤติกรรม
ให้เราพิจารณาตัวอย่างเพื่อสร้างพฤติกรรมของเราเองจากนั้นใช้พฤติกรรมทั่วไปนี้เพื่อสร้างโมดูล เราจะกำหนดพฤติกรรมที่ทักทายผู้คนสวัสดีและลาก่อนในภาษาต่างๆ
defmodule GreetBehaviour do
@callback say_hello(name :: string) :: nil
@callback say_bye(name :: string) :: nil
end
@callbackคำสั่งใช้เพื่อแสดงรายการฟังก์ชันที่ต้องใช้โมดูล นอกจากนี้ยังระบุเลขที่ ของอาร์กิวเมนต์ประเภทและค่าที่ส่งคืน
การนำพฤติกรรมมาใช้
เรากำหนดพฤติกรรมสำเร็จแล้ว ตอนนี้เราจะนำมาใช้และนำไปใช้ในหลายโมดูล ให้เราสร้างโมดูลสองโมดูลที่ใช้พฤติกรรมนี้เป็นภาษาอังกฤษและสเปน
defmodule GreetBehaviour do
@callback say_hello(name :: string) :: nil
@callback say_bye(name :: string) :: nil
end
defmodule EnglishGreet do
@behaviour GreetBehaviour
def say_hello(name), do: IO.puts("Hello " <> name)
def say_bye(name), do: IO.puts("Goodbye, " <> name)
end
defmodule SpanishGreet do
@behaviour GreetBehaviour
def say_hello(name), do: IO.puts("Hola " <> name)
def say_bye(name), do: IO.puts("Adios " <> name)
end
EnglishGreet.say_hello("Ayush")
EnglishGreet.say_bye("Ayush")
SpanishGreet.say_hello("Ayush")
SpanishGreet.say_bye("Ayush")
เมื่อรันโปรแกรมข้างต้นจะให้ผลลัพธ์ดังนี้ -
Hello Ayush
Goodbye, Ayush
Hola Ayush
Adios Ayush
ดังที่คุณได้เห็นแล้วเรานำพฤติกรรมมาใช้โดยใช้ไฟล์ @behaviourคำสั่งในโมดูล เราต้องกำหนดฟังก์ชันทั้งหมดที่ใช้ในลักษณะการทำงานสำหรับโมดูลลูกทั้งหมด สิ่งนี้ถือได้ว่าเทียบเท่ากับอินเทอร์เฟซในภาษา OOP
Elixir มีกลไกข้อผิดพลาดสามประการ: ข้อผิดพลาดการขว้างและการออก ให้เราสำรวจกลไกแต่ละอย่างโดยละเอียด
ข้อผิดพลาด
ข้อผิดพลาด (หรือข้อยกเว้น) ใช้เมื่อมีสิ่งพิเศษเกิดขึ้นในโค้ด สามารถเรียกดูข้อผิดพลาดตัวอย่างได้โดยพยายามเพิ่มตัวเลขลงในสตริง -
IO.puts(1 + "Hello")
เมื่อรันโปรแกรมข้างต้นจะเกิดข้อผิดพลาดดังต่อไปนี้ -
** (ArithmeticError) bad argument in arithmetic expression
:erlang.+(1, "Hello")
นี่เป็นตัวอย่างข้อผิดพลาดในตัว
เพิ่มข้อผิดพลาด
เราทำได้ raiseข้อผิดพลาดในการใช้ฟังก์ชันเพิ่ม ให้เราพิจารณาตัวอย่างเพื่อให้เข้าใจตรงกัน -
#Runtime Error with just a message
raise "oops" # ** (RuntimeError) oops
ข้อผิดพลาดอื่น ๆ สามารถเกิดขึ้นได้โดยใช้ Raise / 2 ส่งผ่านชื่อข้อผิดพลาดและรายการอาร์กิวเมนต์คำหลัก
#Other error type with a message
raise ArgumentError, message: "invalid argument foo"
คุณยังสามารถกำหนดข้อผิดพลาดของคุณเองและเพิ่มข้อผิดพลาดเหล่านั้น ลองพิจารณาตัวอย่างต่อไปนี้ -
defmodule MyError do
defexception message: "default message"
end
raise MyError # Raises error with default message
raise MyError, message: "custom message" # Raises error with custom message
การช่วยเหลือข้อผิดพลาด
เราไม่ต้องการให้โปรแกรมของเราหยุดทำงานทันที แต่จำเป็นต้องจัดการข้อผิดพลาดอย่างระมัดระวัง สำหรับสิ่งนี้เราใช้การจัดการข้อผิดพลาด เราrescue ข้อผิดพลาดโดยใช้ไฟล์ try/rescueสร้าง. ให้เราพิจารณาตัวอย่างต่อไปนี้เพื่อทำความเข้าใจเช่นเดียวกัน -
err = try do
raise "oops"
rescue
e in RuntimeError -> e
end
IO.puts(err.message)
เมื่อรันโปรแกรมข้างต้นจะให้ผลลัพธ์ดังนี้ -
oops
เราได้จัดการข้อผิดพลาดในคำสั่งการช่วยเหลือโดยใช้การจับคู่รูปแบบ หากเราไม่ได้ใช้ข้อผิดพลาดใด ๆ และเพียงต้องการใช้เพื่อวัตถุประสงค์ในการระบุตัวตนเราสามารถใช้แบบฟอร์ม -
err = try do
1 + "Hello"
rescue
RuntimeError -> "You've got a runtime error!"
ArithmeticError -> "You've got a Argument error!"
end
IO.puts(err)
เมื่อรันโปรแกรมด้านบนจะให้ผลลัพธ์ดังนี้ -
You've got a Argument error!
NOTE- ฟังก์ชั่นส่วนใหญ่ในไลบรารีมาตรฐาน Elixir จะถูกใช้สองครั้งเมื่อคืนค่าทูเปิลและเวลาอื่น ๆ เพิ่มข้อผิดพลาด ตัวอย่างเช่นไฟล์File.read และ File.read!ฟังก์ชั่น. อันแรกส่งคืนทูเพิลหากอ่านไฟล์สำเร็จและหากพบข้อผิดพลาดทูเปิลนี้จะถูกใช้เพื่อให้เหตุผลของข้อผิดพลาด ข้อที่สองทำให้เกิดข้อผิดพลาดหากพบข้อผิดพลาด
หากเราใช้แนวทางฟังก์ชันแรกเราจำเป็นต้องใช้ case สำหรับรูปแบบที่ตรงกับข้อผิดพลาดและดำเนินการตามนั้น ในกรณีที่สองเราใช้วิธีการลองช่วยเหลือสำหรับรหัสที่มีแนวโน้มเกิดข้อผิดพลาดและจัดการข้อผิดพลาดตามนั้น
พ่น
ใน Elixir สามารถโยนมูลค่าและจับได้ในภายหลัง Throw and Catch สงวนไว้สำหรับสถานการณ์ที่ไม่สามารถดึงค่าได้เว้นแต่จะใช้ Throw and Catch
อินสแตนซ์ค่อนข้างผิดปกติในทางปฏิบัติยกเว้นเมื่อเชื่อมต่อกับไลบรารี ตัวอย่างเช่นตอนนี้สมมติว่าโมดูล Enum ไม่ได้ให้ API ใด ๆ สำหรับการค้นหาค่าและเราจำเป็นต้องค้นหาตัวคูณ 13 ตัวแรกในรายการตัวเลข -
val = try do
Enum.each 20..100, fn(x) ->
if rem(x, 13) == 0, do: throw(x)
end
"Got nothing"
catch
x -> "Got #{x}"
end
IO.puts(val)
เมื่อรันโปรแกรมข้างต้นจะให้ผลลัพธ์ดังนี้ -
Got 26
ออก
เมื่อกระบวนการตายด้วย“ สาเหตุตามธรรมชาติ” (เช่นข้อยกเว้นที่ไม่สามารถจัดการได้) ระบบจะส่งสัญญาณออก กระบวนการสามารถตายได้โดยการส่งสัญญาณออกอย่างชัดเจน ให้เราพิจารณาตัวอย่างต่อไปนี้ -
spawn_link fn -> exit(1) end
ในตัวอย่างข้างต้นกระบวนการที่เชื่อมโยงเสียชีวิตโดยการส่งสัญญาณออกที่มีค่า 1 โปรดทราบว่าทางออกสามารถ "จับ" ได้โดยใช้ try / catch ตัวอย่างเช่น -
val = try do
exit "I am exiting"
catch
:exit, _ -> "not really"
end
IO.puts(val)
เมื่อรันโปรแกรมข้างต้นจะให้ผลลัพธ์ดังนี้ -
not really
หลังจาก
บางครั้งจำเป็นต้องตรวจสอบให้แน่ใจว่ามีการล้างทรัพยากรหลังจากการดำเนินการบางอย่างที่อาจทำให้เกิดข้อผิดพลาด โครงสร้าง try / after ช่วยให้คุณทำเช่นนั้นได้ ตัวอย่างเช่นเราสามารถเปิดไฟล์และใช้คำสั่ง after เพื่อปิดได้แม้ว่าจะมีบางอย่างผิดพลาด
{:ok, file} = File.open "sample", [:utf8, :write]
try do
IO.write file, "olá"
raise "oops, something went wrong"
after
File.close(file)
end
เมื่อเรารันโปรแกรมนี้มันจะทำให้เรามีข้อผิดพลาด แต่after คำสั่งจะทำให้แน่ใจว่า file descriptor ถูกปิดเมื่อเกิดเหตุการณ์ดังกล่าว
มาโครเป็นหนึ่งในคุณสมบัติขั้นสูงและทรงพลังที่สุดของ Elixir เช่นเดียวกับคุณสมบัติขั้นสูงของภาษาใด ๆ ควรใช้มาโครให้เพียงพอ ทำให้สามารถทำการแปลงรหัสที่มีประสิทธิภาพได้ในเวลารวบรวม ตอนนี้เราจะเข้าใจว่ามาโครคืออะไรและใช้งานอย่างไรโดยสังเขป
ใบเสนอราคา
ก่อนที่เราจะเริ่มพูดถึงมาโครเรามาดู Elixir ภายในกันก่อน โปรแกรม Elixir สามารถแสดงด้วยโครงสร้างข้อมูลของมันเอง ส่วนประกอบพื้นฐานของโปรแกรม Elixir คือทูเพิลที่มีสามองค์ประกอบ ตัวอย่างเช่นผลรวมการเรียกฟังก์ชัน (1, 2, 3) จะแสดงภายในเป็น -
{:sum, [], [1, 2, 3]}
องค์ประกอบแรกคือชื่อฟังก์ชันส่วนที่สองคือรายการคำหลักที่มีข้อมูลเมตาและองค์ประกอบที่สามคือรายการอาร์กิวเมนต์ คุณจะได้รับสิ่งนี้เป็นผลลัพธ์ในเชลล์ iex หากคุณเขียนสิ่งต่อไปนี้ -
quote do: sum(1, 2, 3)
โอเปอเรเตอร์จะแสดงเป็นทูเพิลเช่นกัน ตัวแปรจะแสดงโดยใช้ triplets เช่นกันยกเว้นว่าองค์ประกอบสุดท้ายคืออะตอมแทนที่จะเป็นรายการ เมื่ออ้างถึงนิพจน์ที่ซับซ้อนมากขึ้นเราจะเห็นว่าโค้ดนั้นแสดงเป็นทูเปิลซึ่งมักจะซ้อนกันภายในโครงสร้างที่คล้ายกับต้นไม้ หลายภาษาจะเรียกการแสดงเช่นนี้ว่าAbstract Syntax Tree (AST). Elixir เรียกสำนวนที่ยกมาเหล่านี้
ไม่ต้องพูด
ตอนนี้เราสามารถดึงโครงสร้างภายในของโค้ดของเราได้แล้วเราจะแก้ไขได้อย่างไร? ในการฉีดรหัสหรือค่าใหม่เราใช้unquote. เมื่อเรายกเลิกการอ้างถึงนิพจน์จะถูกประเมินและฉีดเข้าไปใน AST ให้เราพิจารณาตัวอย่าง (ใน iex shell) เพื่อทำความเข้าใจแนวคิด -
num = 25
quote do: sum(15, num)
quote do: sum(15, unquote(num))
เมื่อรันโปรแกรมข้างต้นจะให้ผลลัพธ์ดังนี้ -
{:sum, [], [15, {:num, [], Elixir}]}
{:sum, [], [15, 25]}
ในตัวอย่างของนิพจน์ใบเสนอราคาไม่ได้แทนที่ num ด้วย 25 โดยอัตโนมัติเราจำเป็นต้องยกเลิกการอ้างสิทธิ์ตัวแปรนี้หากเราต้องการแก้ไข AST
มาโคร
ตอนนี้เราคุ้นเคยกับ quote และ unquote แล้วเราสามารถสำรวจ metaprogramming ใน Elixir โดยใช้มาโครได้
ในคำที่ง่ายที่สุดคือฟังก์ชันพิเศษที่ออกแบบมาเพื่อส่งคืนนิพจน์ที่ยกมาซึ่งจะถูกแทรกลงในโค้ดแอปพลิเคชันของเรา ลองนึกภาพมาโครถูกแทนที่ด้วยนิพจน์ที่ยกมาแทนที่จะเรียกว่าเหมือนฟังก์ชัน ด้วยมาโครเรามีทุกสิ่งที่จำเป็นในการขยาย Elixir และเพิ่มโค้ดลงในแอปพลิเคชันของเราแบบไดนามิก
ให้เรานำไปใช้เว้นแต่เป็นมาโคร เราจะเริ่มต้นด้วยการกำหนดมาโครโดยใช้defmacroมาโคร โปรดจำไว้ว่ามาโครของเราจำเป็นต้องส่งคืนนิพจน์ที่ยกมา
defmodule OurMacro do
defmacro unless(expr, do: block) do
quote do
if !unquote(expr), do: unquote(block)
end
end
end
require OurMacro
OurMacro.unless true, do: IO.puts "True Expression"
OurMacro.unless false, do: IO.puts "False expression"
เมื่อรันโปรแกรมข้างต้นจะให้ผลลัพธ์ดังนี้ -
False expression
สิ่งที่เกิดขึ้นที่นี่คือโค้ดของเราจะถูกแทนที่ด้วยโค้ดที่ยกมาที่ส่งคืนโดยมาโครยกเว้น เราได้ยกเลิกการอ้างสิทธิ์ของนิพจน์เพื่อประเมินในบริบทปัจจุบันและยังไม่ได้ใส่เครื่องหมายคำสั่ง do block เพื่อดำเนินการในบริบทของมัน ตัวอย่างนี้แสดงให้เราเห็นการเขียนโปรแกรม metaprogramming โดยใช้มาโครใน elixir
มาโครสามารถใช้ในงานที่ซับซ้อนกว่านี้ได้มาก แต่ควรใช้อย่าง จำกัด เนื่องจากโดยทั่วไปแล้ว metaprogramming ถือเป็นแนวทางปฏิบัติที่ไม่ดีและควรใช้เมื่อจำเป็นเท่านั้น
Elixir มอบความสามารถในการทำงานร่วมกันที่ยอดเยี่ยมกับห้องสมุด Erlang ให้เราพูดคุยสั้น ๆ เกี่ยวกับห้องสมุด
โมดูลไบนารี
โมดูล Elixir String ในตัวจัดการไบนารีที่เข้ารหัส UTF-8 โมดูลไบนารีมีประโยชน์เมื่อคุณจัดการกับข้อมูลไบนารีที่ไม่จำเป็นต้องเข้ารหัส UTF-8 ให้เราพิจารณาตัวอย่างเพื่อทำความเข้าใจเพิ่มเติมเกี่ยวกับโมดูลไบนารี -
# UTF-8
IO.puts(String.to_char_list("Ø"))
# binary
IO.puts(:binary.bin_to_list "Ø")
เมื่อรันโปรแกรมข้างต้นจะให้ผลลัพธ์ดังนี้ -
[216]
[195, 152]
ตัวอย่างข้างต้นแสดงความแตกต่าง โมดูล String ส่งคืนจุดรหัส UTF-8 ในขณะที่: ข้อตกลงไบนารีกับไบต์ข้อมูลดิบ
โมดูล Crypto
โมดูลการเข้ารหัสลับประกอบด้วยฟังก์ชันการแฮชลายเซ็นดิจิทัลการเข้ารหัสและอื่น ๆ โมดูลนี้ไม่ได้เป็นส่วนหนึ่งของไลบรารีมาตรฐาน Erlang แต่รวมอยู่ในการแจกแจง Erlang ซึ่งหมายความว่าคุณต้องแสดงรายการ: crypto ในรายการแอปพลิเคชันของโครงการเมื่อใดก็ตามที่คุณใช้งาน ให้เราดูตัวอย่างการใช้โมดูลการเข้ารหัส -
IO.puts(Base.encode16(:crypto.hash(:sha256, "Elixir")))
เมื่อรันโปรแกรมข้างต้นจะให้ผลลัพธ์ดังนี้ -
3315715A7A3AD57428298676C5AE465DADA38D951BDFAC9348A8A31E9C7401CB
โมดูล Digraph
โมดูล digraph มีฟังก์ชันสำหรับจัดการกับกราฟกำกับที่สร้างจากจุดยอดและขอบ หลังจากสร้างกราฟแล้วอัลกอริทึมในนั้นจะช่วยในการค้นหาเช่นเส้นทางที่สั้นที่สุดระหว่างจุดยอดสองจุดหรือการวนซ้ำในกราฟ สังเกตว่าฟังก์ชันต่างๆin :digraph ปรับเปลี่ยนโครงสร้างกราฟโดยอ้อมเป็นผลข้างเคียงในขณะที่ส่งกลับจุดยอดหรือขอบที่เพิ่มเข้ามา
digraph = :digraph.new()
coords = [{0.0, 0.0}, {1.0, 0.0}, {1.0, 1.0}]
[v0, v1, v2] = (for c <- coords, do: :digraph.add_vertex(digraph, c))
:digraph.add_edge(digraph, v0, v1)
:digraph.add_edge(digraph, v1, v2)
for point <- :digraph.get_short_path(digraph, v0, v2) do
{x, y} = point
IO.puts("#{x}, #{y}")
end
เมื่อรันโปรแกรมข้างต้นจะให้ผลลัพธ์ดังนี้ -
0.0, 0.0
1.0, 0.0
1.0, 1.0
โมดูลคณิตศาสตร์
โมดูลคณิตศาสตร์ประกอบด้วยการดำเนินการทางคณิตศาสตร์ทั่วไปซึ่งครอบคลุมฟังก์ชันตรีโกณมิติเลขชี้กำลังและลอการิทึม ให้เราพิจารณาตัวอย่างต่อไปนี้เพื่อทำความเข้าใจว่าโมดูลคณิตศาสตร์ทำงานอย่างไร -
# Value of pi
IO.puts(:math.pi())
# Logarithm
IO.puts(:math.log(7.694785265142018e23))
# Exponentiation
IO.puts(:math.exp(55.0))
#...
เมื่อรันโปรแกรมข้างต้นจะให้ผลลัพธ์ดังนี้ -
3.141592653589793
55.0
7.694785265142018e23
โมดูลคิว
คิวเป็นโครงสร้างข้อมูลที่ใช้คิว FIFO (เข้าก่อนออกก่อน) อย่างมีประสิทธิภาพ ตัวอย่างต่อไปนี้แสดงวิธีการทำงานของโมดูลคิว -
q = :queue.new
q = :queue.in("A", q)
q = :queue.in("B", q)
{{:value, val}, q} = :queue.out(q)
IO.puts(val)
{{:value, val}, q} = :queue.out(q)
IO.puts(val)
เมื่อรันโปรแกรมข้างต้นจะให้ผลลัพธ์ดังนี้ -
A
B