2010-11-03 35 views
13

SQL cung cấp một hàm có tên là coalesce(a, b, c, ...) trả về giá trị rỗng nếu tất cả các đối số của nó là null, nếu không nó sẽ trả về đối số không null đầu tiên.Chức năng phối hợp Clojure

Bạn sẽ viết một cái gì đó như thế này ở Clojure như thế nào?

Nó sẽ được gọi như sau: (coalesce f1 f2 f3 ...) trong đó fi là các hình thức chỉ nên được đánh giá nếu được yêu cầu. Nếu f1 không phải là không, thì không nên đánh giá f2 - nó có thể có tác dụng phụ.

Có thể Clojure đã cung cấp chức năng (hoặc macro) như vậy.

EDIT: Dưới đây là một giải pháp mà tôi đã đưa ra (sửa đổi từ trình của Stuart Halloway Clojure, (and ...) vĩ mô trên trang 206):

(defmacro coalesce 
    ([] nil) 
    ([x] x) 
    ([x & rest] `(let [c# ~x] (if c# c# (coalesce [email protected]))))) 

vẻ để làm việc.

(defmacro coalesce 
    ([] nil) 
    ([x] x) 
    ([x & rest] `(let [c# ~x] (if (not (nil? c#)) c# (coalesce [email protected]))))) 

Đã sửa lỗi.

Trả lời

5

Dựa trên câu trả lời nickik và "hay" vĩ mô clojure:

(defmacro coalesce 
    ([] nil) 
    ([x] x) 
    ([x & next] 
     `(let [v# ~x] 
      (if (not (nil? v#)) v# (coalesce [email protected]))))) 
+1

@ user128186: Không chắc bạn cần '(not (nil? V #))' trong câu lệnh '(if ...)', vì bất kỳ thứ gì không phải là 'false' hoặc' nil' đều đánh giá 'true'. Nếu không, các giải pháp của chúng tôi là như nhau. – Ralph

+3

tại sao bạn sẽ ghi lại 1: 1 của "hoặc" -macro? – nickik

+2

@Ralph: Vì vậy, bạn muốn đầu tiên không phải là nil hoặc đầu tiên không phải là giá trị sai? – Arjan

21

Điều bạn muốn là macro "hoặc".

Đánh giá từng lần một, từ trái sang phải. Nếu một biểu mẫu trả về giá trị thực sự hợp lý hoặc trả về giá trị đó và không đánh giá bất kỳ biểu thức nào khác, nếu không, giá trị này sẽ trả về giá trị của biểu thức cuối cùng. (hoặc) trả về nil.

http://clojuredocs.org/clojure_core/clojure.core/or

Nếu bạn chỉ muốn bằng không và không sai làm một viết lại và và đặt tên nó kết thành một khối.

Chỉnh sửa:

Không thể thực hiện chức năng này vì chức năng đánh giá tất cả các đối số của họ trước tiên. Điều này có thể được thực hiện trong Haskell vì chức năng là lười (không chắc chắn 100% về điều Haskell).

+0

Tôi muốn giá trị ** không phải là giá trị đầu tiên **, không phải giá trị cuối cùng, tuy nhiên '(hoặc ...)' thực hiện những gì tôi cần. Tôi đã không nhận ra rằng '(và ...)' và '(hoặc ...)' trả về các giá trị. Tôi nghĩ họ đã trở về sai hoặc đúng. Nhưng ngay cả những điều này không trả lại giá trị tôi muốn cho một đầu vào của 'false'. – Ralph

+0

oh chắc chắn im sẽ thay đổi điều đó. – nickik

3

Bạn có thể sử dụng tiếp tục giới thiệu trong 1.2:

EDIT: câu trả lời mở rộng một chút. Macro cho các cuộc xâm lược trực tiếp. Người trợ giúp cho ví dụ. áp dụng + lazy seq tạo ra các giá trị.

(defn coalesce* 
    [values] 
    (first (keep identity values))) 

(defmacro coalesce 
    [& values] 
    `(coalesce* (lazy-list [email protected]))) 

Tuy nhiên để ngăn chặn việc đánh giá giá trị, người ta cần một số cách phát triển tại nhà.

Ugly:

(lazy-cat [e1] [e2] [e3])

Một chút tham gia nhiều hơn nhưng đẹp hơn trong các mã:

(defn lazy-list* 
    [& delayed-values] 
    (when-let [delayed-values (seq delayed-values)] 
    (reify 
     clojure.lang.ISeq 
     (first [this] @(first delayed-values)) 
     (next [this] (lazy-list* (next delayed-values))) 
     (more [this] (or (next this)()))))) 

(defmacro lazy-list 
    [& values] 
    `(lazy-list* [email protected](map (fn [v] `(delay ~v)) values)) 
+0

Tôi có thể thấy tại sao giải pháp không phải macro có thể tốt hơn vì giải pháp macro không thể được tạo bằng các hàm khác. – Ralph

+0

@ralph: Tất nhiên giải pháp được chấp nhận nhanh hơn, nhưng giải pháp của tôi linh hoạt hơn. Những gì bạn nên chọn tùy thuộc vào nhu cầu của bạn. Nếu bạn không cần tốc độ, nhưng có một chuỗi được tạo ra một cách lười biếng mà bạn muốn kết hợp lại, giải pháp của tôi thực hiện thủ thuật. Nếu bạn cần xử lý nhanh vài giá trị đã biết. sau đó là giải pháp của arjan để giải cứu. YMMV. :) – kotarak

+0

Tôi không chỉ trích. Nó là nhiều hơn một bài tập học tập anyway. Tôi đã suy nghĩ về việc làm thế nào để thực hiện toán tử "elvis" ở Scala, và nó khiến tôi nghĩ về điều gì đó tương tự trong Clojure. – Ralph

0

Có lẽ tôi đang hiểu sai câu hỏi, nhưng không phải đây chỉ là phần tử được lọc đầu tiên?

Ví dụ:

 
user=> (first (filter (complement nil?) [nil false :foo])) 
false 
user=> (first (filter (complement nil?) [nil :foo])) 
:foo 
user=> (first (filter (complement nil?) [])) 
nil 
user=> (first (filter (complement nil?) nil)) 
nil 

Nó có thể được rút ngắn đến:

 
(defn coalesce [& vals] 
    (first (filter (complement nil?) vals))) 
 
user=> (coalesce nil false :foo) 
false 
user=> (coalesce nil :foo) 
:foo 
user=> (coalesce nil) 
nil 
user=> (coalesce) 
nil 
+0

Đây là một cách khác. Một thứ ba sẽ là '(đầu tiên (loại bỏ nil? ...))'. Tuy nhiên điều này không giải quyết được vấn đề rằng các biểu thức được kết hợp lại chỉ nên được đánh giá trên cơ sở theo nhu cầu. Với những thứ như '(liên tụC# (tạo ra một cái gì đó))' điều này làm việc tại hộp, nhưng không phải cho các giá trị "theo nghĩa đen": '[(do-something) (do-otherthing) (do-thứ ba)]'. Ở đây mọi thứ được đánh giá trước khi bộ lọc nhìn thấy nó. – kotarak

+1

Vâng, tôi đã bỏ lỡ sự đánh giá lười biếng về yêu cầu của arg. –

1

Một số phiên bản chức năng của liên hiệp, nếu bạn muốn tránh macro:

(defn coalesce 
    "Returns first non-nil argument." 
    [& args] 
    (first (keep identity args))) 

(defn coalesce-with 
    "Returns first argument which passes f." 
    [f & args] 
    (first (filter f args))) 

Cách sử dụng:

=> (coalesce nil "a" "b") 
"a" 
=> (coalesce-with not-empty nil "" "123") 
"123" 

Không giống như thông số kỹ thuật, điều này sẽ đánh giá tất cả các arg. Sử dụng or hoặc giải pháp macro thích hợp khác nếu bạn muốn đánh giá ngắn mạch.

Các vấn đề liên quan