lazy-seq #Clojure

เรื่องนี้ออกตัวก่อนว่าจริง ๆ ผมก็ยังงง ๆ อยู่ แต่เขียนบันทึกสิ่งที่คิด ๆ ไว้ก่อน

เมื่อวานผมอ่าน lazy sequence มาจากไฟล์แล้วเอามา process ต่อ loop-recur ใน loop recur ก็เอาผลลัพธ์มาต่อ ๆ กันด้วย cons รู้สึกแบบนี้มันไม่ค่อยมีประโยชน์เพราะว่า ผลลัพธ์ทั้งหมดก็จะอยู่ใน ram หมดเลย

ก็เลยคิดว่าน่าจะทำเป็น lazy sequence ได้ แต่พอไม่ใช่ filter map เรื่องมันก็เริ่มจะไม่ตรงไปตรงมาแล้ว ผมพยายามจะลดรูปปัญหาให้ง่าย ๆ อย่างนี้ มีไฟล์ csv ประมาณนี้โดยที่เรียง (sort) ตาม column แรกไว้

1,cat
1,man
1,can
3,ruby
3,prolog
4,perl

สิ่งที่ต้องการคือถ้ามี id ซ้ำกันเอาแค่ row แรกพอ เช่นจากด้านบนผลที่จะได้คือ [[1 cat] [3 ruby] [4 perl]] แต่ต้องจิตนาการว่าไฟล์ใหญ่สัก 20GB อะไรแบบนี้นะครับโหลดขึ้น ram หมดไม่ได้

อ่านไปอ่านมาก็พอว่าต้องใช้ macro ชื่อ lazy-seq พอเขียน code ก็ออกมาได้อย่างนี้

(defn distinct-id [rows]
  (lazy-seq
   (when-let [s (seq rows)]
     (let [row (first s)]
       (cons row
             (distinct-id 
                (drop-while
                     #(= (first row) (first %))
                     (rest s))))))))

ไม่รู้ว่ามันใช้ได้จริงหรือเปล่าเลยลองยัด data ที่มันไม่สิ้นสุดใส่เข้าไป

(take 3 (distinct-id (map list (range) (range))))

ถ้าหากว่ามันไม่ lazy จะ stack overflow เลย เพราะมันหาไปเรื่อยไม่เสร็จสักที แต่ปรากฎว่าก็ใช้ได้

เพื่อความชัวร์ก็ลองเอา lazy-seq ออก

(defn distinct-id [rows]
  ;;(lazy-seq
   (when-let [s (seq rows)]
     (let [row (first s)]
       (cons row
             (distinct-id 
                 (drop-while 
                     #(= (first row) (first %))
                     (rest s)))))))

แล้วรัน (take 3 (distinct-id (map list (range) (range)))) อีกทีก็บึ้มตามคาด

when-let นี้ผมไม่รู้เอามาจากไหนก็เลยลองเอาออกดู แก้เป็นแบบนี้

(defn distinct-id [rows]
  (lazy-seq
   (when rows
     (let [row (first rows)]
       (cons row
             (distinct-id 
                 (drop-while 
                     #(= (first row) (first %))
                     (rest rows))))))))

 

ก็ทำงานได้ปกติ

ตอนนี้ตามที่เข้าใจคือ lazy-seq มันจะสร้าง object มาหุ้ม body คือ

 

 ;; body
 (when rows
     (let [row (first rows)]
       (cons row
             (distinct-id 
               (drop-while 
                   #(= (first row) (first %))
                   (rest rows))))))

 

ไว้

พอมีการเรียกใช้งาน เช่น (first (distinct-id (map list (range) (range)))) มันก็จะมาเรียกใช้ body 1 ที แล้ว body มันจะคืน sequence หรือจริง ๆ แล้วก็คือ Cons ออกไป โดยจะมี 2 ส่วนคือส่วน first ได้แก่ row ที่คำนวณเสร็จเรียบร้อยแล้ว และ rest ที่เป็น lazy-seq อีกตัว ประมาณนี้

(cons row (lazy-seq …))

เพราะว่าส่วนหลัง (rest) มันเป็น lazy sequence อยู่ก็เลยยังไม่ได้คำนวณจริง ๆ ก็เลยทำให้โปรแกรมมันกลายเป็นแบบ lazy ไปแล้ว (take 3 (distinct-id (map list (range) (range)))) มันก็ทำงานได้โดยเรียก body แค่ 3 ที

ใส่ความเห็น

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / เปลี่ยนแปลง )

Twitter picture

You are commenting using your Twitter account. Log Out / เปลี่ยนแปลง )

Facebook photo

You are commenting using your Facebook account. Log Out / เปลี่ยนแปลง )

Google+ photo

You are commenting using your Google+ account. Log Out / เปลี่ยนแปลง )

Connecting to %s