Just teaching myself clojure and wondering around:
I am trying to create a function that takes an argument and adds it to a string. Being a newbie, i dont know if i am doing it right or wrong but it is not working. I want it to say "Hello, Ron !"
(fn
[x]
((str "hello, " %1 " !") x) "Ron")
This might sound basic
java.lang.RuntimeException: Unable to resolve symbol: % in this
context, compiling:(NO_SOURCE_PATH:0)
The %1 syntax is for use with anonymous function literals, like this:
#(str "hello, " %1)
In your case, the argument is named, so you can use it directly:
(fn [x] (str "hello, " x "!"))
You can also name the function itself:
(defn hello [name] (str "hello, " name "!"))
You can either use a:
named function
(defn hello [name] (str "hello, " name " !"))
(hello "Ron")
anonymous function
((fn [name] (str "hello, " name " !")) "Ron")
http://clojure.org/functional_programming
Related
I am newbie in Clojure. I have following Expressions:
(= (__ "Dave") "Hello, Dave!")
(= (__ "Jenn") "Hello, Jenn!")
(= (__ "Rhea") "Hello, Rhea!")
In place of __, in all 3 places must be inserted same expression so that the equality check in all 3 cases are true. At this point i have come up with str "Hello, ". As i understand this should produce "Hello, Dave" "Hello, Jenn" "Hello, Rhea" How do i put "!" mark at the and of each string? (I can only "write" expression in place of __)
Thank you
You want to drop a function into the place of __.
This function shall take a string s and shall return a string that is based on s so as to satisfy the three test cases.
A possible function is
(fn [s] (str "Hello, " s "!"))
which can written using Clojure syntactic sugar
#(str "Hello, " % "!"))
Thus
(= (#(str "Hello, " % "!") "Dave") "Hello, Dave!")
Bonus: Using testing framework
Clojure comes with a nice testing library, clojure.test (I don't know why it is called an API, which would mean there is a component on the other side of the callable functions; it's just a library)
We can use the testing library for good effect:
(require '[clojure.test :as t]) ; make library visible
(def myfun (fn [s] (str "Hello, " s "!"))) ; our function as symbol myfun
(t/deftest test-stringmanip
(t/is (= (myfun "Dave") "Hello, Dave!"))
(t/is (= (myfun "Jenn") "Hello, Jenn!"))
(t/is (= (myfun "Rhea") "Hello, Rhea!")))
(t/run-tests) ; do it!
I have 3 lists ("Hello" "Hi" "Hey") ("How's it" "What's" "Hey") ("going?" "up?" "Hey!")
How can I join the lists to get Hello How's it going? Hi What's up? Hey Hey Hey!
I know I can use clojure.string/join " " to join the strings in a single list but I want to join the first elements in each list, the second elements in each list, the third elements in each list ...
Any help would be much appreciated. Thanks
You can use map. If you pass in multiple collections, it will take an element from each collection and pass it to the fn:
(def a ["Hello" "Hi" "Hey"])
(def b ["How's it" "What's" "Hey"])
(def c ["going?" "up?" "Hey!"])
(map (fn [& args] (clojure.string/join " " args)) a b c)
This will result in:
("Hello How's it going?" "Hi What's up?" "Hey Hey Hey!")
But I'm sure with a little more thought a more elegant solution can be thought of :-).
You can use map:
(map (fn [& args] (join " " args)) '("Hello" "Hi" "Hey") '("How's it" "What's" "Hey") '("going?" "up?" "Hey!"))
Just concatenate your lists into the whole one and them join it:
(def data '[("Hello" "Hi" "Hey")
("How's it" "What's" "Hey")
("going?" "up?" "Hey!")])
(clojure.string/join " " (apply concat data))
Hello Hi Hey How's it What's Hey going? up? Hey!
Here's the function
(#(
(println (str "first: " %1))
(println (str "second: " %2))
(println (str "rest: " (clojure.string/join ", " %&))))
"f" "s" "x" "y" "z")
When running this in cider I get the desired result but at the end I see that I am also getting a NullPointerException.
It seems that this form of anonymous function has some issues with destructuring.
Because, when I try the following form of anonymous function, it works.
((fn [f s & rest]
(println (str "first: " f))
(println (str "second: " s))
(println (str (clojure.string/join ", " rest))))
"f" "s" "x" "y" "z")
Can someone explain why is this happening ?
You need a do:
(#(do
(println (str "first: " %1))
(println (str "second: " %2))
(println (str "rest: " (clojure.string/join ", " %&))))
"f" "s" "x" "y" "z")
Without the do, you're trying to invoke the result of the first println, (i.e. nil) on the remaining elements of the list. fn has an implicit do.
For a minimal case, compare ((println)) and (do (println))
Input: "Michael" "Julia" "Joe" "Sam"
Output: Hi, Michael, Julia, Joe, and Sam. (pay attention to the commas and the word "and")
Input: nil
Output: Hi, world.
Here is my first attempt:
(defn say-hi [& name]
(print "Hi," name))
user> (say-hi "Michael")
Hi, (Michael)
nil
user> (say-hi "Michael" "Julia")
Hi, (Michael Julia)
nil
Question:
How to implement default: (no input, say "Hi World!")
How to get rid of the parents around names in output?
How to implement the commas separation and add the conjunction word "and"?
First off, Clojure supports multi-arity functions, so you could do something like this to achieve default behaviour:
(defn say-hi
([] (say-hi "World"))
([& names] ...))
Then, what you want is to take a seq and join all the strings it contains together, using ", " in between. The clojure.string namespaces contains lots of string manipulation functions, one of them being clojure.string/join:
(require '[clojure.string :as string])
(string/join ", " ["Michael", "Julia"])
;; => "Michael, Julia"
But the last element of the seq should be concatenated using " and " as a separator, so you'll end up with something like this:
(require '[clojure.string :as string])
(defn say-hi
([] (say-hi "World"))
([& names]
(if (next names)
(format "Hi, %s, and %s!"
(string/join ", " (butlast names))
(last names))
(format "Hi, %s!" (first names)))))
Note that you have to differentiate between the single- and multi-name cases and (next names) basically checks whether the seq contains more than one element. (You could achieve the same by adding another arity to the function.)
(say-hi)
;; => "Hi, World!"
(say-hi "Michael")
;; => "Hi, Michael!"
(say-hi "Michael" "Julia" "Joe" "Sam")
;; => "Hi, Michael, Julia, Joe, and Sam!"
You can use clojure.string/join:
(use '[clojure.string :only [join]])
(defn sentencify [& elems]
(->>
[(join ", " (butlast elems)) (last elems)]
(remove empty?)
(join " and ")))
(defn say-hi [& name]
(print "Hi," (if name
(sentencify name)
"World!")))
A concise solution:
(defn say-hi [& names]
(let [names (case (count names)
0 ["world"]
1 names
(concat (butlast names) (list (str "and " (last names)))))]
(->> names, (cons "Hi"), (interpose ", "), (apply str))))
(say-hi)
;"Hi, world"
(say-hi "Michael")
;"Hi, Michael"
(say-hi "Michael" "Julia" "Joe" "Sam")
;"Hi, Michael, Julia, Joe, and Sam"
For long lists of names, you would want to eschew count, last, and butlast, maybe by pouring names into a vector first.
To print (as the question does) rather than return the formatted string, append print to the final form:
(->> names, (cons "Hi"), (interpose ", "), (apply str), print)
What is the idomatic way to change a local depending on conditions like below? Here I am changing value of x depending on some conditions.
(defn person-story
[person]
(let [x (str "My name is " (:firstname person) " " (:lastname person) "." )
x (if (:showaddress person) (str x " I live in " (:address person) ".") x)
x (if (:showage person) (str x " I am " (:age person) " yrs old. ") x)
x (if (seq (:hobby person)) (str x " I like " (clojure.string/join ", " (:hobby person)) ".") x)]
x))
(person-story {:firstname "John" :lastname "Doe" :age 45 :showage false :address "67 Circe Ave" :showaddress true :hobby ["movie" "music" "money"]})
And that would output:
"My name is John Doe. I live in 67 Circe Ave. I like movie, music, money."
If I were to do it in java, i would have done something like:
StringBuilder sb = new StringBuilder("My name is ");
sb.append(person.get("firstname")).append(" ");
sb.append(person.get("lastname")).append(" ");
if (showaddress) sb.append("I live in ").append(person.get("address")).append(" ");
if (showage) sb.append("I am ").append(person.get("age")).append(" yrs old. ");
List<String> hobbies = person.get("hobby");
if ( hobbies != null && !hobbies.isEmpty()) sb.append("I like "). append(StringUtils.join(hobbies, ", "));
return sb.toString()
What is the best way to achieve the same thing that I am achieving above in clojure? Sorry for the title, I could not come up with a better one.
Solutions
Thank you xsc and amalloy, both answers were great. I accepted amalloy's answer since it showed a totally new way of solving the problem, but upvoted both.
Here are the snippets for solution in both suggested ways:
amalloy's method:
(defn person-story2 [person]
(let [tests [[:showaddress #(format " I live in %s." (:address %))]
[:showage #(format " I am %s yrs old." (:age %))]
[(comp seq :hobbies) #(format " I like %s." (clojure.string/join ", " (:hobbies %)))]]]
(apply str (format "My name is %s %s." (:firstname person) (:lastname person))
(for [[test f] tests
:when (test person)]
(f person)))))
(person-story2 {:firstname "John" :lastname "Doe" :showage true :age 50 :showaddress true :address "Universal Studios" :hobbies ["movies" "music" "money"]})
output:
"My name is John Doe. I live in Universal Studios. I am 50 yrs old. I like movies, music, money."
xsc's method:
(defn person-story
[{:keys [firstname lastname address showaddress age showage hobbies] :as person}]
(cond->
(str "My name is " firstname " " lastname ". ")
showaddress (str "I live in " address ". ")
showage (str "I am " age " yrs old. ")
(seq hobbies) (str "I like " (clojure.string/join ", " hobbies))))
(person-story {:firstname "John" :lastname "Doe" :showage false :age 50 :address "Universal Studios" :showaddress true :hobbies ["movies" "music" "money"]})
output:
"My name is John Doe. I live in Universal Studios. I like movies, music, money"
Since Clojure 1.5 there are cond->/cond->> that work just like ->/->> with conditionals determining whether a single step is performed:
(cond->
(str "My name is " (:firstname p) " " (:lastname p) ".")
(:showaddress p) (str "I live in " (:address p) ".")
(:showage p) (str "I am " (:age p) " yrs old.")
...)
This would be the idiomatic solution for conditional string building. Alternatively, you could use something along the lines of:
(clojure.string/join
[(str "My name is " (:firstname p) " " (:lastname p) ".")
(if (:showaddress p)
(str "I live in " (:address p) "."))
...])
This uses the fact that nil will be ignored when joining strings.
To avoid repeating anything useless (eg, if conditions, or the x you are threading through everything), define a list of the tests you want to run, and the functions to apply if the tests succeed. Then you can just reduce over that list. In the simple case of building a string via repeated calls to str, you can cut out the middleman a bit and just call str yourself, via apply:
(defn person-story [person]
(let [tests [[:showaddress #(format " I live in %s." (:address %))]
[:showage #(format " I am %s yrs old." (:age %))]
[(comp seq :hobby) #(format " I like %s." (clojure.string/join ", " (:hobby %)))]]]
(apply str (format "My name is %s %s." (:firstname person) (:lastname person))
(for [[test f] tests
:when (test person)]
(f person)))))