อะไรคือ“ แนวคิดที่ยิ่งใหญ่” เบื้องหลังเส้นทางการเขียนคอมโพชเชอร์


109

ฉันเพิ่งเริ่มใช้ Clojure และใช้ Compojure เพื่อเขียนเว็บแอปพลิเคชันพื้นฐาน defroutesแม้ว่าฉันจะชนกำแพงด้วยไวยากรณ์ของ Compojure และฉันคิดว่าฉันต้องเข้าใจทั้ง "อย่างไร" และ "ทำไม" ที่อยู่เบื้องหลังทั้งหมดนี้

ดูเหมือนว่าแอปพลิเคชัน Ring-style เริ่มต้นด้วยแผนที่คำขอ HTTP จากนั้นส่งคำขอผ่านชุดฟังก์ชันมิดเดิลแวร์จนกว่าจะเปลี่ยนเป็นแผนที่ตอบกลับซึ่งจะถูกส่งกลับไปยังเบราว์เซอร์ สไตล์นี้ดูเหมือน "ระดับต่ำ" เกินไปสำหรับนักพัฒนาดังนั้นจึงจำเป็นต้องมีเครื่องมือเช่น Compojure ฉันสามารถมองเห็นความต้องการสิ่งนี้สำหรับนามธรรมเพิ่มเติมในระบบนิเวศซอฟต์แวร์อื่น ๆ เช่นกันโดยเฉพาะอย่างยิ่งกับ WSGI ของ Python

ปัญหาคือฉันไม่เข้าใจแนวทางของ Compojure ลองใช้defroutesS-expression ต่อไปนี้:

(defroutes main-routes
  (GET "/"  [] (workbench))
  (POST "/save" {form-params :form-params} (str form-params))
  (GET "/test" [& more] (str "<pre>" more "</pre>"))
  (GET ["/:filename" :filename #".*"] [filename]
    (response/file-response filename {:root "./static"}))
  (ANY "*"  [] "<h1>Page not found.</h1>"))

ฉันรู้ว่ากุญแจสำคัญในการทำความเข้าใจทั้งหมดนี้อยู่ในมาโครวูดู แต่ฉันยังไม่เข้าใจมาโครทั้งหมด (ยัง) ฉันจ้องที่defroutesมานานแล้ว แต่ก็ไม่เข้าใจ! เกิดอะไรขึ้นที่นี่? การทำความเข้าใจกับ "แนวคิดที่ยิ่งใหญ่" อาจช่วยให้ฉันตอบคำถามเหล่านี้ได้:

  1. ฉันจะเข้าถึงสภาพแวดล้อม Ring จากภายในฟังก์ชันที่กำหนดเส้นทาง (เช่นworkbenchฟังก์ชัน) ได้อย่างไร ตัวอย่างเช่นสมมติว่าฉันต้องการเข้าถึงส่วนหัว HTTP_ACCEPT หรือส่วนอื่น ๆ ของคำขอ / มิดเดิลแวร์?
  2. ข้อตกลงกับการทำลาย ( {form-params :form-params}) คืออะไร? มีคำหลักอะไรบ้างสำหรับฉันเมื่อทำลายโครงสร้าง

ฉันชอบ Clojure มาก แต่ฉันนิ่งงัน!

คำตอบ:


212

อธิบาย Compojure (ในระดับหนึ่ง)

NB. ฉันกำลังทำงานกับ Compojure 0.4.1 ( นี่คือรุ่น 0.4.1 ที่กระทำบน GitHub)

ทำไม?

ที่ด้านบนสุดcompojure/core.cljมีข้อมูลสรุปที่เป็นประโยชน์เกี่ยวกับวัตถุประสงค์ของ Compojure:

ไวยากรณ์ที่กระชับสำหรับการสร้างเครื่องจัดการแหวน

ในระดับผิวเผินนั่นคือทั้งหมดสำหรับคำถาม "ทำไม" หากต้องการเจาะลึกลงไปอีกสักหน่อยเรามาดูกันว่าแอพรูปแบบ Ring ทำงานอย่างไร:

  1. คำขอมาถึงและเปลี่ยนเป็นแผนที่ Clojure ตามข้อกำหนดของ Ring

  2. แผนที่นี้รวมเข้ากับสิ่งที่เรียกว่า "ฟังก์ชันตัวจัดการ" ซึ่งคาดว่าจะตอบสนอง (ซึ่งก็คือแผนที่ Clojure เช่นกัน)

  3. แผนที่ตอบกลับจะถูกแปลงเป็นการตอบกลับ HTTP จริงและส่งกลับไปยังไคลเอนต์

ขั้นตอนที่ 2 ในข้างต้นเป็นสิ่งที่น่าสนใจที่สุดเนื่องจากเป็นความรับผิดชอบของผู้ดำเนินการในการตรวจสอบ URI ที่ใช้ในคำขอตรวจสอบคุกกี้ ฯลฯ และในที่สุดก็ได้รับคำตอบที่เหมาะสม เห็นได้ชัดว่างานทั้งหมดนี้จำเป็นต้องรวมอยู่ในกลุ่มของชิ้นงานที่กำหนดไว้อย่างดี โดยปกติจะเป็นฟังก์ชันตัวจัดการ "ฐาน" และชุดของฟังก์ชันมิดเดิลแวร์ที่ห่อหุ้มไว้ จุดประสงค์ของ Compojure คือการทำให้การสร้างฟังก์ชันตัวจัดการพื้นฐานง่ายขึ้น

อย่างไร?

Compojure สร้างขึ้นตามแนวคิดของ "เส้นทาง" สิ่งเหล่านี้ถูกนำไปใช้ในระดับที่ลึกกว่าโดยไลบรารีClout (สปินออฟของโครงการ Compojure - หลายสิ่งถูกย้ายไปยังไลบรารีแยกต่างหากที่การเปลี่ยนแปลง 0.3.x -> 0.4.x) เส้นทางถูกกำหนดโดย (1) เมธอด HTTP (GET, PUT, HEAD ... ), (2) รูปแบบ URI (ระบุด้วยไวยากรณ์ซึ่งดูเหมือนจะคุ้นเคยกับ Webby Rubyists), (3) รูปแบบการทำลายโครงสร้างที่ใช้ใน การเชื่อมโยงส่วนต่างๆของแผนผังคำขอกับชื่อที่มีอยู่ในเนื้อหา (4) เนื้อหาของนิพจน์ที่จำเป็นต้องสร้างการตอบสนองของ Ring ที่ถูกต้อง (ในกรณีที่ไม่สำคัญซึ่งโดยปกติจะเป็นเพียงการเรียกใช้ฟังก์ชันแยกต่างหาก)

นี่อาจเป็นจุดที่ดีที่จะดูตัวอย่างง่ายๆ:

(def example-route (GET "/" [] "<html>...</html>"))

ลองทดสอบสิ่งนี้ที่ REPL (แผนที่คำขอด้านล่างเป็นแผนที่คำขอ Ring ที่ถูกต้องน้อยที่สุด):

user> (example-route {:server-port 80
                      :server-name "127.0.0.1"
                      :remote-addr "127.0.0.1"
                      :uri "/"
                      :scheme :http
                      :headers {}
                      :request-method :get})
{:status 200,
 :headers {"Content-Type" "text/html"},
 :body "<html>...</html>"}

ถ้า:request-methodเป็นแทนการตอบสนองจะเป็น:head nilเราจะกลับไปที่คำถามว่าความnilหมายที่นี่ในอีกไม่กี่นาที (แต่โปรดสังเกตว่ามันไม่ใช่การตอบสนองของ Ring ที่ถูกต้อง!)

ดังที่เห็นได้ชัดจากตัวอย่างนี้example-routeเป็นเพียงฟังก์ชันและฟังก์ชันที่เรียบง่ายมาก จะดูที่คำขอพิจารณาว่าสนใจที่จะจัดการหรือไม่ (โดยการตรวจสอบ:request-methodและ:uri) และหากเป็นเช่นนั้นจะส่งคืนแผนผังการตอบกลับพื้นฐาน

สิ่งที่เห็นได้ชัดก็คือเนื้อความของเส้นทางไม่จำเป็นต้องประเมินตามแผนที่ตอบสนองที่เหมาะสม Compojure ให้การจัดการดีฟอลต์สำหรับสตริง (ดังที่เห็นด้านบน) และอ็อบเจ็กต์ประเภทอื่น ๆ อีกจำนวนมาก ดูcompojure.response/renderรายละเอียดหลายวิธี (รหัสนี้จัดทำเอกสารด้วยตัวเองทั้งหมดที่นี่)

ลองใช้เลยdefroutes:

(defroutes example-routes
  (GET "/" [] "get")
  (HEAD "/" [] "head"))

การตอบสนองต่อคำขอตัวอย่างที่แสดงด้านบนและตัวแปรด้วย:request-method :headเป็นไปตามที่คาดไว้

การทำงานภายในexample-routesเป็นเช่นนั้นในแต่ละเส้นทางจะถูกพยายามในทางกลับกัน ทันทีที่หนึ่งในนั้นส่งคืนการไม่nilตอบกลับคำตอบนั้นจะกลายเป็นค่าส่งคืนของexample-routesตัวจัดการทั้งหมด เพื่อความสะดวกที่เพิ่มขึ้นdefroutesตัวจัดการที่กำหนดไว้จะถูกห่อหุ้มwrap-paramsและwrap-cookiesโดยปริยาย

นี่คือตัวอย่างของเส้นทางที่ซับซ้อนมากขึ้น:

(def echo-typed-url-route
  (GET "*" {:keys [scheme server-name server-port uri]}
    (str (name scheme) "://" server-name ":" server-port uri)))

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

การทดสอบข้างต้น:

user> (echo-typed-url-route {:server-port 80
                             :server-name "127.0.0.1"
                             :remote-addr "127.0.0.1"
                             :uri "/foo/bar"
                             :scheme :http
                             :headers {}
                             :request-method :get})
{:status 200,
 :headers {"Content-Type" "text/html"},
 :body "http://127.0.0.1:80/foo/bar"}

แนวคิดในการติดตามผลข้างต้นที่ยอดเยี่ยมคือเส้นทางที่ซับซ้อนมากขึ้นอาจให้assocข้อมูลเพิ่มเติมในคำขอในขั้นตอนการจับคู่:

(def echo-first-path-component-route
  (GET "/:fst/*" [fst] fst))

ตอบสนองนี้กับ:bodyของ"foo"การร้องขอจากตัวอย่างก่อนหน้านี้

มีสองสิ่งที่ใหม่ ๆ เกี่ยวกับตัวอย่างล่าสุดนี้คือและไม่ว่างเปล่าเวกเตอร์ที่มีผลผูกพัน"/:fst/*" [fst]ประการแรกคือไวยากรณ์ที่คล้ายกับ Rails-and-Sinatra ข้างต้นสำหรับรูปแบบ URI มันซับซ้อนกว่าที่เห็นได้ชัดจากตัวอย่างข้างต้นเล็กน้อยในการรองรับข้อ จำกัด regex บนเซ็กเมนต์ URI (เช่น["/:fst/*" :fst #"[0-9]+"]สามารถจัดหาเพื่อให้เส้นทางยอมรับเฉพาะค่าทั้งหมดที่เป็นตัวเลข:fstในด้านบน) วิธีที่สองคือวิธีที่ง่ายกว่าในการจับคู่:paramsรายการในแผนที่คำขอซึ่งก็คือแผนที่ มันมีประโยชน์สำหรับการแยกส่วน URI จากคำขอพารามิเตอร์สตริงการสืบค้นและพารามิเตอร์ฟอร์ม ตัวอย่างเพื่อแสดงประเด็นหลัง:

(defroutes echo-params
  (GET "/" [& more]
    (str more)))

user> (echo-params
       {:server-port 80
        :server-name "127.0.0.1"
        :remote-addr "127.0.0.1"
        :uri "/"
        :query-string "foo=1"
        :scheme :http
        :headers {}
        :request-method :get})
{:status 200,
 :headers {"Content-Type" "text/html"},
 :body "{\"foo\" \"1\"}"}

นี่เป็นช่วงเวลาที่ดีที่จะได้ดูตัวอย่างจากข้อความคำถาม:

(defroutes main-routes
  (GET "/"  [] (workbench))
  (POST "/save" {form-params :form-params} (str form-params))
  (GET "/test" [& more] (str "<pre>" more "</pre>"))
  (GET ["/:filename" :filename #".*"] [filename]
    (response/file-response filename {:root "./static"}))
  (ANY "*"  [] "<h1>Page not found.</h1>"))

มาวิเคราะห์แต่ละเส้นทางกัน:

  1. (GET "/" [] (workbench))- เมื่อจัดการกับGETคำขอ:uri "/"ให้เรียกใช้ฟังก์ชันworkbenchและแสดงสิ่งที่ส่งกลับไปยังแผนที่ตอบกลับ (โปรดจำไว้ว่าค่าที่ส่งคืนอาจเป็นแผนที่ แต่ยังรวมถึงสตริงเป็นต้น)

  2. (POST "/save" {form-params :form-params} (str form-params))- :form-paramsเป็นรายการในแผนที่คำขอที่จัดทำโดยwrap-paramsมิดเดิลแวร์ (โปรดจำไว้ว่ามีการรวมโดยปริยายโดยdefroutes) การตอบสนองจะเป็นมาตรฐาน{:status 200 :headers {"Content-Type" "text/html"} :body ...}ที่มีการเปลี่ยนแทน(str form-params) ...( POSTตัวจัดการที่ผิดปกติเล็กน้อยนี่ ... )

  3. (GET "/test" [& more] (str "<pre> more "</pre>"))- นี้จะสะท้อนกลับเช่นการเป็นตัวแทนสตริงของแผนที่ถ้าตัวแทนของผู้ใช้ถามหา{"foo" "1"}"/test?foo=1"

  4. (GET ["/:filename" :filename #".*"] [filename] ...)- :filename #".*"ส่วนนี้ไม่ทำอะไรเลย (เนื่องจาก#".*"ตรงกันเสมอ) เรียกใช้ฟังก์ชันยูทิลิตี้ Ring ring.util.response/file-responseเพื่อสร้างการตอบสนอง {:root "./static"}ส่วนหนึ่งบอกว่าที่จะมองหาไฟล์

  5. (ANY "*" [] ...)- เส้นทางรับทั้งหมด เป็นการดีที่จะรวมเส้นทางดังกล่าวไว้ที่ส่วนท้ายของdefroutesแบบฟอร์มเพื่อให้แน่ใจว่าตัวจัดการที่กำหนดจะส่งคืนแผนที่การตอบสนองของเสียงเรียกเข้าที่ถูกต้องเสมอ (โปรดจำไว้ว่าความล้มเหลวในการจับคู่เส้นทางส่งผลให้nil)

ทำไมต้องเป็นแบบนี้?

จุดประสงค์ประการหนึ่งของมิดเดิลแวร์ Ring คือการเพิ่มข้อมูลลงในแผนที่คำขอ ดังนั้นมิดเดิลแวร์จัดการคุกกี้จึงเพิ่ม:cookiesคีย์ให้กับคำขอwrap-paramsเพิ่ม:query-paramsและ / หรือ:form-paramsหากมีข้อมูลสตริง / ฟอร์มแบบสอบถามและอื่น ๆ (พูดอย่างเคร่งครัดข้อมูลทั้งหมดที่ฟังก์ชันมิดเดิลแวร์กำลังเพิ่มจะต้องมีอยู่แล้วในแผนที่คำขอเนื่องจากนั่นคือสิ่งที่พวกเขาได้รับงานของพวกเขาคือการเปลี่ยนมันให้สะดวกยิ่งขึ้นในการทำงานร่วมกับตัวจัดการที่พวกเขารวมไว้) ในที่สุดคำขอ "ที่เพิ่มประสิทธิภาพ" จะถูกส่งไปยังตัวจัดการพื้นฐานซึ่งจะตรวจสอบแผนผังคำขอพร้อมข้อมูลที่ประมวลผลล่วงหน้าอย่างดีทั้งหมดที่เพิ่มโดยมิดเดิลแวร์และสร้างการตอบกลับ (มิดเดิลแวร์สามารถทำสิ่งที่ซับซ้อนกว่านั้นได้เช่นการห่อตัวจัดการ "ภายใน" หลายตัวและเลือกระหว่างตัวจัดการตัดสินใจว่าจะเรียกตัวจัดการที่ถูกห่อหรือไม่เป็นต้นอย่างไรก็ตามนั่นคืออยู่นอกขอบเขตของคำตอบนี้)

ในทางกลับกันตัวจัดการพื้นฐานมักเป็นฟังก์ชัน (ในกรณีที่ไม่สำคัญ) ซึ่งมีแนวโน้มที่จะต้องการข้อมูลเพียงไม่กี่รายการเกี่ยวกับคำขอ (เช่นring.util.response/file-responseไม่สนใจคำขอส่วนใหญ่ต้องการเพียงแค่ชื่อไฟล์เท่านั้น) ดังนั้นความต้องการวิธีง่ายๆในการแยกเฉพาะส่วนที่เกี่ยวข้องของคำขอ Ring Compojure มีจุดมุ่งหมายเพื่อจัดหาเอ็นจิ้นการจับคู่รูปแบบที่มีวัตถุประสงค์พิเศษเหมือนเดิมซึ่งทำเช่นนั้น


3
"เพื่อความสะดวกที่เพิ่มขึ้นตัวจัดการที่กำหนดค่า defroutes จะถูกห่อด้วย wrap-params และ wrap-cookies โดยปริยาย" - ในเวอร์ชัน 0.6.0 คุณต้องเพิ่มสิ่งเหล่านี้อย่างชัดเจน Ref github.com/weavejester/compojure/commit/…
Dan Midwood

3
ใส่ได้ดีมาก คำตอบนี้ควรอยู่ในหน้าแรกของ Compojure
Siddhartha Reddy

2
การอ่านที่จำเป็นสำหรับทุกคนที่เพิ่งเริ่มใช้ Compojure ฉันหวังว่าทุกโพสต์ในวิกิและบล็อกในหัวข้อนี้จะเริ่มต้นด้วยลิงก์ไปยังสิ่งนี้
jemmons

7

มีบทความที่ยอดเยี่ยมที่booleanknot.comจาก James Reeves (ผู้เขียน Compojure) และการอ่านมันทำให้ "คลิก" สำหรับฉันดังนั้นฉันจึงได้แปลบางส่วนใหม่ที่นี่ (นั่นคือทั้งหมดที่ฉันทำจริงๆ)

นอกจากนี้ยังมีสไลด์จากผู้เขียนคนเดียวกันที่ตอบคำถามนี้อย่างแน่นอน

Compojure ขึ้นอยู่กับRingซึ่งเป็นนามธรรมสำหรับคำขอ http

A concise syntax for generating Ring handlers.

ดังนั้นสิ่งที่เป็นผู้ขนย้ายวัสดุแหวน ? สารสกัดจากเอกสาร:

;; Handlers are functions that define your web application.
;; They take one argument, a map representing a HTTP request,
;; and return a map representing the HTTP response.

;; Let's take a look at an example:

(defn what-is-my-ip [request]
  {:status 200
   :headers {"Content-Type" "text/plain"}
   :body (:remote-addr request)})

ค่อนข้างเรียบง่าย แต่ก็ค่อนข้างต่ำ ตัวจัดการข้างต้นสามารถกำหนดได้อย่างรัดกุมยิ่งขึ้นโดยใช้ring/utilไลบรารี

(use 'ring.util.response)

(defn handler [request]
  (response "Hello World"))

ตอนนี้เราต้องการเรียกตัวจัดการที่แตกต่างกันขึ้นอยู่กับคำขอ เราสามารถกำหนดเส้นทางแบบคงที่ได้ดังนี้:

(defn handler [request]
  (or
    (if (= (:uri request) "/a") (response "Alpha"))
    (if (= (:uri request) "/b") (response "Beta"))))

และ refactor เป็นดังนี้:

(defn a-route [request]
  (if (= (:uri request) "/a") (response "Alpha")))

(defn b-route [request]
  (if (= (:uri request) "/b") (response "Beta"))))

(defn handler [request]
  (or (a-route request)
      (b-route request)))

สิ่งที่น่าสนใจที่เจมส์ตั้งข้อสังเกตก็คือสิ่งนี้ช่วยให้สามารถสร้างเส้นทางการทำรังได้เนื่องจาก "ผลของการรวมเส้นทางตั้งแต่สองเส้นทางขึ้นไปเข้าด้วยกันนั้นเป็นเส้นทาง"

(defn ab-routes [request]
  (or (a-route request)
      (b-route request)))

(defn cd-routes [request]
  (or (c-route request)
      (d-route request)))

(defn handler [request]
  (or (ab-routes request)
      (cd-routes request)))

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

(defroutes ab-routes a-route b-route)

;; is identical to

(def ab-routes (routes a-route b-route))

Compojure มีมาโครอื่น ๆ เช่นGETมาโคร:

(GET "/a" [] "Alpha")

;; will expand to

(fn [request#]
  (if (and (= (:request-method request#) ~http-method)
           (= (:uri request#) ~uri))
    (let [~bindings request#]
      ~@body)))

ฟังก์ชันสุดท้ายที่สร้างขึ้นดูเหมือนตัวจัดการของเรา!

โปรดตรวจสอบโพสต์ของ Jamesเนื่องจากมีคำอธิบายโดยละเอียดเพิ่มเติม


4

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

จริงๆแล้วการอ่านเอกสารเพื่อletช่วยให้ชัดเจนขึ้น "ค่าเวทมนตร์มาจากไหน" คำถาม.

ฉันวางส่วนที่เกี่ยวข้องด้านล่าง:

Clojure สนับสนุนการผูกโครงสร้างแบบนามธรรมซึ่งมักเรียกว่าการทำลายโครงสร้างในรายการที่มีผลผูกพันรายการพารามิเตอร์ fn และมาโครใด ๆ ที่ขยายไปสู่การปล่อยหรือ fn แนวคิดพื้นฐานคือรูปแบบการผูกสามารถเป็นโครงสร้างข้อมูลตามตัวอักษรที่มีสัญลักษณ์ที่เชื่อมโยงกับส่วนต่างๆของ init-expr การเชื่อมโยงเป็นนามธรรมที่ลิเทอรัลเวกเตอร์สามารถผูกกับอะไรก็ได้ที่เป็นลำดับในขณะที่ลิเทอรัลแผนที่สามารถผูกกับอะไรก็ได้ที่เชื่อมโยงกัน

Vector Binding-exprs ช่วยให้คุณสามารถผูกชื่อกับส่วนต่างๆของสิ่งที่เรียงตามลำดับ (ไม่ใช่แค่เวกเตอร์) เช่นเวกเตอร์รายการ seqs สตริงอาร์เรย์และอะไรก็ตามที่รองรับ n รูปแบบลำดับพื้นฐานคือเวกเตอร์ของรูปแบบการผูกซึ่งจะถูกผูกไว้กับองค์ประกอบที่ต่อเนื่องกันจาก init-expr ซึ่งค้นหาผ่านทาง n นอกจากนี้และทางเลือกและตามด้วยรูปแบบการผูกจะทำให้รูปแบบการผูกนั้นถูกผูกไว้กับส่วนที่เหลือของลำดับกล่าวคือส่วนที่ยังไม่ถูกผูกไว้ค้นหาผ่านทาง nthnext สุดท้ายยังเป็นทางเลือก: ตามด้วยสัญลักษณ์จะทำให้สัญลักษณ์นั้นถูกผูกไว้กับ init-expr ทั้งหมด:

(let [[a b c & d :as e] [1 2 3 4 5 6 7]]
  [a b c d e])
->[1 2 3 (4 5 6 7) [1 2 3 4 5 6 7]]

Vector Binding-exprs ช่วยให้คุณสามารถผูกชื่อกับส่วนต่างๆของสิ่งที่เรียงตามลำดับ (ไม่ใช่แค่เวกเตอร์) เช่นเวกเตอร์รายการ seqs สตริงอาร์เรย์และอะไรก็ตามที่รองรับ n รูปแบบลำดับพื้นฐานคือเวกเตอร์ของรูปแบบการผูกซึ่งจะถูกผูกไว้กับองค์ประกอบที่ต่อเนื่องกันจาก init-expr ซึ่งค้นหาผ่านทาง n นอกจากนี้และทางเลือกและตามด้วยรูปแบบการผูกจะทำให้รูปแบบการผูกนั้นถูกผูกไว้กับส่วนที่เหลือของลำดับกล่าวคือส่วนที่ยังไม่ถูกผูกไว้ค้นหาผ่านทาง nthnext สุดท้ายยังเป็นทางเลือก: ตามด้วยสัญลักษณ์จะทำให้สัญลักษณ์นั้นถูกผูกไว้กับ init-expr ทั้งหมด:

(let [[a b c & d :as e] [1 2 3 4 5 6 7]]
  [a b c d e])
->[1 2 3 (4 5 6 7) [1 2 3 4 5 6 7]]

3

ฉันยังไม่ได้เริ่มใช้งานเว็บ clojure แต่ฉันจะทำนี่คือสิ่งที่ฉันบุ๊กมาร์กไว้


ขอบคุณลิงค์เหล่านี้มีประโยชน์อย่างแน่นอน ฉันได้แก้ไขปัญหานี้ให้ดีขึ้นในแต่ละวันและอยู่ในจุดที่ดีขึ้นด้วย ... ฉันจะพยายามโพสต์ติดตามผลในบางจุด
Sean Woods

1

อะไรคือข้อตกลงกับการทำลาย ({form-params: form-params}) มีคำหลักอะไรบ้างสำหรับฉันเมื่อทำลายโครงสร้าง

คีย์ที่ใช้ได้คือคีย์ที่อยู่ในแผนผังอินพุต การทำลายมีอยู่ในรูปแบบ let และ doseq หรือภายในพารามิเตอร์เป็น fn หรือ defn

หวังว่าโค้ดต่อไปนี้จะเป็นข้อมูล:

(let [{a :thing-a
       c :thing-c :as things} {:thing-a 0
                               :thing-b 1
                               :thing-c 2}]
  [a c (keys things)])

=> [0 2 (:thing-b :thing-a :thing-c)]

ตัวอย่างขั้นสูงที่แสดงการทำลายโครงสร้างแบบซ้อน:

user> (let [{thing-id :id
             {thing-color :color :as props} :properties} {:id 1
                                                          :properties {:shape
                                                                       "square"
                                                                       :color
                                                                       0xffffff}}]
            [thing-id thing-color (keys props)])
=> [1 16777215 (:color :shape)]

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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.