简体   繁体   English

Clojure:如何返回在函数的 for 循环内计算的值

[英]Clojure: How to return a value computed inside a for-loop of a function

I am new to clojure: Let's say I have a function in which something must be computed inside a for-loop.我是 clojure 的新手:假设我有一个函数,必须在 for 循环中计算某些内容。 How to I make the function return that something?如何让函数返回一些东西?

The following doesn't work:以下不起作用:

(defn foo [] (for [i [1 2 3]] (def something 4)) something)
(foo)
me.core=> #object[clojure.lang.Var$Unbound 0x4808dacc "Unbound: #'me.core/something"]

The background of this question is that I wanted to write a function bracket that receives a string and a brackettype as parameters and return the string surrounded by that brackettype, for instance (bracket "hello" "]") or (bracket "hello" "[") should return the string "[hello]" .这个问题的背景是我想写一个函数bracket ,它接收一个字符串和一个括号类型作为参数并返回由该括号类型包围的字符串,例如(bracket "hello" "]")(bracket "hello" "[")应该返回字符串"[hello]"

Of course, if anybody will provide a more clojure-way to write such a function I will be greatful as well.当然,如果有人能提供一种更clojure 的方式来编写这样的函数,我也会很高兴。

Here's what I've come up with so far这是我到目前为止想出的

(def brackets ["()" "[]" "{}" "<>"])

(defn bracket [string brackettype] 
  (for [b brackets] 
    [(let [f (str (first b)) l (str (last b))]
       (if (or (= brackettype f) (= brackettype l))
         (def fstringl (str f string l))))])
  bstringb)

(bracket "hello" "[")

=> #object[clojure.lang.Var$Unbound 0x6401b490 "Unbound: #'me.core/fstringl"]

Here's one way that tries to align with your approach of finding the "f" and "l" of the brackets and then using them to surround the given string.这是一种尝试与您找到括号的“f”和“l”然后使用它们包围给定字符串的方法保持一致的方法。 (As mentioned in the comments, it's rare that you want to explicitly loop as you would in a procedural language. If it helps, you can think of higher order functions such as filter , map and reduce as functions that do the looping for you.) (如评论中所述,您很少想像在过程语言中那样显式循环。如果有帮助,您可以将高阶函数(例如filtermapreduce视为为您执行循环的函数。 )

(def brackets ["()" "[]" "{}" "<>"])

(defn bracket [string bracket]
  (let [[[f l]] (filter #(.contains % bracket) brackets)]
    (str f string l)))

As strings are themselves sequences, sequential destructuring comes in handy here as the filter step will return (depending on which bracket) something like ("[]") .由于字符串本身就是序列,因此顺序解构在这里很方便,因为filter步骤将返回(取决于哪个括号)类似("[]") By assigning this to [[fl]] , f and l get the values we want.通过将其分配给[[fl]]fl得到我们想要的值。

A more verbose version without the destructuring could be没有解构的更详细的版本可能是

(defn bracket [string bracket]
  (let [bracket-entry (first (filter #(.contains % bracket) brackets))
        f (first bracket-entry)
        l (last bracket-entry)]
    (str f string l)))

You haven't said what should happen if the bracket is not found in brackets .您还没有说如果在括号中找不到brackets会发生什么。 If you call (bracket "hello" "|") , in the versions above, f and l will be nil and the original string is returned.如果您调用(bracket "hello" "|") ,在上述版本中, fl将为nil并返回原始字符串。

Here's a version that tests for that case and allows you to act accordingly:这是一个测试该案例并允许您采取相应行动的版本:

(defn bracket [string bracket]
  (if-let [[[f l]] (seq (filter #(.contains % bracket) brackets))]
    (str f string l)
    "unknown bracket type"))

seq is really useful here because it returns nil in the case of an empty sequence but the original sequence otherwise. seq在这里非常有用,因为它在空序列的情况下返回nil ,否则返回原始序列。 Hence if none of the brackets pass through the filter, you go to the "else" part of the if-let .因此,如果没有任何括号通过过滤器,您将转到if-let的“else”部分。

A simple code pattern would be:一个简单的代码模式是:

(1) Detect and branch on the type of bracket and, (1) 检测和分支上支架的类型和,

(2) Create a result string consisting of: Open-Bracket + string + Close-Bracket) (2) 创建一个结果字符串组成:Open-Bracket + string + Close-Bracket)

(defn bracket [s brackettype]
    (cond  ; Branch on type of bracket
           ; (.contains string substring) identifies if substring
           ; is in string
        (.contains "()" brackettype) (str "(" s ")") 
        (.contains "[]" brackettype)  (str "[" s "]")
        (.contains "{}" brackettype)  (str "{" s "}")
        (.contains "<>" brackettype)  (str "<" s ">")
        :else s))

; Test cases (test different type of brackets on word "Hello")
(doseq [b ["(" "]" "{" ")" ">"]]
    (println "Bracket type" b "-> "(bracket "Hello" b)))

Output输出

Bracket type ( ->  (Hello)
Bracket type ] ->  [Hello]
Bracket type { ->  {Hello}
Bracket type ) ->  (Hello)
Bracket type > ->  <Hello>

Further Explanation进一步说明

(.contains "()" brackettype)

This uses Java's contain method to check if string contains another.这使用 Java 的包含方法来检查字符串是否包含另一个。

Other options for this functionality are:此功能的其他选项是:

(or (= brackettype "(") (= brackettype ")")))  ; check letter combinations

Or:或者:

(contains? #{"(", ")"} brackettype )  ; If brackettype is set of brackets

Here is my version of a Clojure-y solution, using my favorite library .这是我使用我最喜欢的 library的 Clojure-y 解决方案版本。

I would submit that using a keyword to signal the desired output is more "canonical" than passing in a single character.我认为使用关键字来表示所需的输出比传入单个字符更“规范”。 Unit tests show the intended usage:单元测试显示预期用途:

(dotest
  (is= bracket-types #{:curly :angle :round :square})
  (is= (add-brackets "hello" :angle) "<hello>")
  (is= (add-brackets "hello" :curly) "{hello}")
  (is= (add-brackets "hello" :round) "(hello)")
  (is= (add-brackets "hello" :square) "[hello]")

  ; error message if you try `(add-brackets "hello" :exotic)`:
  ;   clojure.lang.ExceptionInfo: Illegal bracket-type
  ;   {:bracket-type :exotic, :bracket-types #{:curly :angle :round :square}}
  (throws? (add-brackets "hello" :exotic)))

and the code:和代码:

(ns tst.demo.core
  (:use demo.core tupelo.core tupelo.test)
  (:require
    [schema.core :as s]))

(def brackets->chars
  "Translates between a bracket type and the display chars"
  {:angle  "<>"
   :curly  "{}"
   :round  "()"
   :square "[]"})

(def bracket-types (set (keys brackets->chars)))

(s/defn add-brackets :- s/Str
  [str-val :- s/Str
   bracket-type :- s/Keyword]
  (when-not (contains? bracket-types bracket-type)
    (throw (ex-info "Illegal bracket-type" (vals->map bracket-type bracket-types))))
  (let [bracket-str (get brackets->chars bracket-type)
        [open-char close-char] bracket-str
        result      (str open-char str-val close-char)]
    result))

The above uses the Plumatic Schema lib to describe the expected inputs & outputs of the functions.以上使用Plumatic Sc​​hema库来描述函数的预期输入和输出。

If you only have a character of the desired bracket type, then write a "bracket detection" function to help out:如果您只有所需括号类型的字符,那么编写一个“括号检测”函数来帮助您:

;-----------------------------------------------------------------------------
; we could auto-generate this from the `brackets->chars` map, but that is another exercise
(def chars->brackets
  "Translates between a bracket char and the bracket type keyword"
  {\> :angle
   \< :angle
   \} :curly
   \{ :curly
   \) :round
   \( :round
   \] :square
   \[ :square })

(s/defn bracket-type :- s/Keyword
  "Returns the bracket type given a character or string input"
  [bracket-sample :- (s/cond-pre Character s/Str)] ; allow either a character or string input
  (let [bracket-chars (filterv #(contains? all-bracket-chars %) ; discard non-bracket-chars
                        (seq (str bracket-sample)))
        bracket-types (set (mapv chars->brackets bracket-chars))]
    ; error msg sample for `(bracket-type "[hello>")`:
    ;    clojure.lang.ExceptionInfo: Too many bracket types found in sample!
    ;    {:bracket-sample "[hello>", :bracket-types #{:angle :square}}

    (when-not (= 1 (count bracket-types))
      (throw (ex-info "Too many bracket types found in sample!" (vals->map bracket-sample bracket-types))))
    (first bracket-types)))

and some unit tests to show it in action:和一些单元测试来展示它的实际效果:

(dotest
  (is= #{\< \> \{ \} \( \) \[ \]} all-bracket-chars)
  (is= [ \a \b \c] (seq "abc")) ; seq of a string yields java.lang.Character result, not len-1 string
  (is= :square (bracket-type \[)) ; can accept a Character input
  (is= :square (bracket-type "[")) ; or a len-1 string
  (is= :square (bracket-type "[hello")) ; and can filter out non-bracket chars
  (is= :square (bracket-type "[hello]")) ; and accepts multiple brackets
  (throws? (bracket-type "[hello>")) ; unless clashing brackets are found

  (is= :round (bracket-type "(hello"))
  (is= :curly (bracket-type "{hello"))
  (is= :angle (bracket-type "<hello")))

Running the unit tests (with the help of lein test-refresh ) shows that everything works as desired:运行单元测试(在lein test-refresh的帮助下)显示一切正常:

-------------------------------
   Clojure 1.10.1    Java 13
-------------------------------

Testing tst.demo.core

Ran 3 tests containing 15 assertions.
0 failures, 0 errors.

Passed all tests

If you want to make your function open for others to extend/use, I will suggest you look at defmulti .如果您想让您的功能开放供其他人扩展/使用,我建议您查看defmulti


;; Here you define a dispatch method, which takes 2 args
;; where s - the string, t - bracket type
;; the dispatch method return t
(defmulti bracket (fn [s t] t))

;; first create a default method when t is unknown
(defmethod bracket :default
  [s t]
  (str "*" s "*"))

;; when t is a "[", returns s with square bracket
(defmethod bracket "["
  [s t]
  (str "[" s "]"))

;; here is how to use it
(bracket "hello" "[")
;; => "[hello]"

;; "?" is not defined in any multi-methods (yet),
;; hence the default implementation is used
(bracket "hello" "?")
;; => "*hello*"

You might not want to repeat your multi-methods implementation for every type of brackets - defmacro comes to the rescue:您可能不想为每种类型的括号重复您的多方法实现 - defmacro来救援:


(defmacro make-bracket [[lb rb]]
  (let [lb (str lb)
        rb (str rb)]
    `(do
       (defmethod bracket ~lb
         [s# t#]
         (str ~lb s# ~rb))
       (defmethod bracket ~rb
         [s# t#]
         (str ~lb s# ~rb)))))

;; now create the multi-methods per bracket type
(make-bracket "<>")
(make-bracket "()")
(make-bracket "{}")

;; try it
(bracket "hello" "<")
;; => "<hello>"

(bracket "hello" ">")
;; => "<hello>"

(bracket "hello" "{")
;; => "{hello}"

;; You can even make your own asymmetric bracket!
(make-bracket "!?")

(bracket "hello" "?")
;; => "!hello?"

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM