Reputation: 63
I am new to clojure: Let's say I have a function in which something must be computed inside a for-loop. 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]"
.
Of course, if anybody will provide a more clojure-way to write such a function I will be greatful as well.
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"]
Upvotes: 2
Views: 489
Reputation: 29984
Here is my version of a Clojure-y solution, using my favorite library.
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.
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:
-------------------------------
Clojure 1.10.1 Java 13
-------------------------------
Testing tst.demo.core
Ran 3 tests containing 15 assertions.
0 failures, 0 errors.
Passed all tests
Upvotes: 2
Reputation: 1976
If you want to make your function open for others to extend/use, I will suggest you look at 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 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?"
Upvotes: 1
Reputation: 10865
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. (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.)
(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 ("[]")
. By assigning this to [[f l]]
, f
and l
get the values we want.
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
. If you call (bracket "hello" "|")
, in the versions above, f
and l
will be nil
and the original string is returned.
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. Hence if none of the brackets pass through the filter, you go to the "else" part of the if-let
.
Upvotes: 3
Reputation: 17166
A simple code pattern would be:
(1) Detect and branch on the type of bracket and,
(2) Create a result string consisting of: 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.
Other options for this functionality are:
(or (= brackettype "(") (= brackettype ")"))) ; check letter combinations
Or:
(contains? #{"(", ")"} brackettype ) ; If brackettype is set of brackets
Upvotes: 2