และเลือกข้าง functional อย่างชัดเจน (ไม่เหมือน scala ที่เลือกมันสองข้างเลย)
มาลองดู code ที่ตัดมาจากตัวอย่าง code ในตอนที่ 1
(ns test
(:import (java.util.regex Pattern)))
(defstruct pos :x :y)
(defn explode
"return a pos/type seq for each char in the line"
[y line]
(map (fn [x y type] (vector (struct pos x y) type))
(iterate inc 0) (repeat y) line))
(defn line-explode
"call explode on each line with the appropriate y"
[lines]
(let [linesplit (. Pattern compile "\n" (. Pattern MULTILINE))] ; must be a better way?
(reduce into
(map explode
(iterate inc 0)
(. linesplit split lines)))))
เริ่มด้วยบรรทัดบนสุด เป็นการระบุ namespace (ถ้าเทียบกับ java ก็คือ package)
(ns test
(:import (java.util.regex Pattern)))
บรรทัดที่สองบอกด้วยว่าให้ import class java.util.regex.Pattern เข้ามาด้วย
(note: ใน clojure มันมี syntax สำหรับ regex ให้ใช้เหมือนกัน แต่คนเขียนเลือกใช้แบบเรียกผ่าน java)
มาดู function line-explode ก่อน
จะเห็นเขาประกาศ function ด้วย form แบบนี้
(defn function-name description params body)
เข่น
(defn plus-9 "plus with 9" [x] (+ x 9))
โปรแกรมตระกูล lisp นี่, ตัว code ก็เป็น data structure ด้วย
ดังนั้นจะเห็นว่า params ก็คือ vector type, ส่วน body เป็น list type
จาก code จะเห็นว่า function explode รับ parameter ตัวเดียว ก็คือ lines
(defn line-explode
"call explode on each line with the appropriate y"
[lines]
ตัว body ของ function line-explode จะอยู่ในรูป let syntax
(let bindings body)
เช่น
(let [x 9, y 10] (+ x y))
run แล้วได้ผลลัพท์เป็น 19
ใน bindings ของ let นั้น, variable ที่ถูก bind ก่อน สามารถถูกอ้างถึงโดย variable ที่ bind ทีหลังได้
(let [x 9, y (+ x 7)] (+ x y))
run แล้วได้ผลลัพท์เป็น 25
ลองดู bindings ของโปรแกรมตัวอย่าง
(let [linesplit (. Pattern compile "\n" (. Pattern MULTILINE))] ; must be a better way?
ถ้าเทียบกับ java แล้วบรรทัดข้างบนก็คือ
Pattern linesplit = Pattern.compie("\n", Pattern.MULTILINE);
ที่นี้ก็มาถึง body ของ function line-explode
(reduce into
(map explode
(iterate inc 0)
(. linesplit split lines)))
เวลาอ่านก็ให้ไล่จากในออกมาข้างนอก
เริ่มจากในสุด
(. linesplit split lines)
ก็คือการเรียกใช้ method split บน Pattern instance (ในที่นี้ก็คือการตัดที่ตำแหน่ง new-line)
(iterate inc 0)
ตรงนี้เขา show ความสามารถของ infinite-sequence และ lazy behavior ของ Clojure
โดยผลลัพท์ที่ได้จะ เป็นไปตามกฎนี้
สมาชิกตัวแรกของผลลัพท์เกิดจากการเรียกใช้ (inc 0) ซึ่งจะได้ผลลัพท์เป็น 1
ผลลัพท์ตัวที่สองเกิดจากการเรียกใช้ (inc ผลลัพท์ตัวแรก) ซึ่งก็คือ (inc 1)
ผลลัพท์ตัวถัดๆไปก็คือ (inc ผลลัพท์ตัวก่อนหน้ามัน)
ดังนั้น (iterate inc 0) จะได้ผลลัพท์เป็น (1 2 3 4 5 6 ... infinity)
ส่วน function map นี้ เดี๋ยวนี้นิยมใช้กันมากแล้ว คงไม่ต้องอธิบาย
แต่ว่า map ของพวก lisp นี้เขารับ collection ได้หลาย collection พร้อมๆกัน
ลองดูตัวอย่างนี้
(map + '(1 2 3) '(3 4 5))
=> (4 6 8)
(map + '(1 2 3) '(3 4 5 6 7 8))
=> (4 6 8)
เนื่องจากเจ้า map function มันไปเรียกใช้ function
explode
ดังนั้นเราต้องตามไปดูว่ามันทำอะไร
(defn explode
"return a pos/type seq for each char in the line"
[y line]
(map (fn [x y type] (vector (struct pos x y) type))
(iterate inc 0) (repeat y) line))
ใน code ข้างบนจะเห็นว่ามี
fn
ในบรรทัดที่ 4 โดยมันคือการ define function อีกแบบหนึ่ง ซึ่งมีคุณสมบัติแบบ closure นั่นคือมันจะจับ ตัวแปร ณ scope ที่มันถูกประกาศ
test> (def foo
(let [y 10]
(fn [x] (+ x y))))
#=(var test/foo)
test> (foo 7)
17
ลองเปลี่ยน y ให้เป็น 12 ณ ขณะที่ run
test> (let [y 12] (foo 7))
17
ตัว
repeat
นั่นเป็น infinite list อีกตัวหนึ่ง(repeat 7)
=> (7 7 7 7 7 7 7 7 7 .....)
ดูจาก code แล้วจะเห็นว่า function
explode
มันก็คือการสร้าง list ของ (pos x y) char
นั่นเองทดลอง run ดู
test> (explode 0 "hello")
([{:x 0, :y 0} \h] [{:x 1, :y 0} \e] [{:x 2, :y 0} \l] [{:x 3, :y 0} \l] [{:x 4, :y 0} \o])
กลับมาที่ line-explode, function ตัวสุดท้ายก็คือ
(reduce into (map ..))
reduce รับ parameter เป็น function และ list
โดยมันจะเรียก function โดยมี parameter เป็นสมาชิกตัวที่ 1 และ 2 ของ list
จากนั้นก็จะเรียก function โดยมี parameter ตัวที่เป็นผลลัพท์ที่ได้จากครั้งก่อน กับ สมาชิกตัวที่ 3
ทำเช่นนี้ไปเรื่อยๆจนกว่าจะหมด
(reduce + [1 2 3 4 5])
=> 15
เขียนอีกอย่างก็คือ
(+ (+ (+ (+ 1 2) 3) 4) 5)
ส่วน
into
นั้นทำงานแบบนี้(into [1] [2])
=> [1 2]
(into [1 2 3] [2])
=> [1 2 3 2]
(into [1] [1 2 3])
=>[1 1 2 3]
มันคือ operation ที่จับ array บวกกันนั่นเอง
สรุปแล้วจะเห็นว่าการ explode ก็คือการเปลี่ยน multi-line string ให้อยู่ในรูปของ [(pos x y) char] นั่นเอง
test> (line-explode "hello\nworld\n")
([{:x 4, :y 1} \d] [{:x 3, :y 1} \l] [{:x 2, :y 1} \r] [{:x 1, :y 1} \o] [{:x 0, :y 1} \w] [{:x 0, :y 0} \h] [{:x 1, :y 0} \e] [{:x 2, :y 0} \l] [{:x 3, :y 0} \l] [{:x 4, :y 0} \o])
No comments:
Post a Comment