I cannot figure out how to compare strings in Clojure. Given this basic function:
(defn login [username]
(let [x (username)]
(if (identical? x "John")
"Welcome"
"incorrect username")))
I keep getting this error message: java.lang.String cannot be cast to clojure.lang.IFn when I call the function. What is the best way of comparing strings in Clojure?
The error message you are getting is because username that is coming into your function is a String, and you are trying to invoke it (without any params). You don't really need the let form at all. Instead of x put username.
All immutable values respond to using =:
(= username "John")
= actually checks the value, rather than some 'in memory pointer' notion.
Related
I have a small clojure function:
(defn split-legal-ref
"Highly specific function that expects one AssessPro map and a map key,
from which book and page will be extracted."
[assess-pro-acct extract-key]
(let [[book page] (cstr/split (extract-key assess-pro-acct) #"-")]
(list (cstr/trim book) (cstr/trim page))))
Given this: (extract-key assess-pro-acct) #"-"), the extract-key's value is :legal_ref. So, it is fetching a single value like 927-48 out of a map and splitting the value using '-'. I just need to catch when there isn't one of those nice values. That is where the split returns nil.
So, I am stuck having tried to replace the original function with the following.
(def missing-book 888)
(def missing-page 999)
.
.
.
(defn split-legal-ref
"Highly specific function that expects one AssessPro map and a map key,
from which book and page will be extracted."
[assess-pro-acct extract-key]
(let [[book page] (cstr/split (extract-key assess-pro-acct) #"-")]
(let [[trimBook trimPage] ((if book (cstr/trim book) (missing-book))
(if page (cstr/trim page) (missing-page)))]
(list (trimBook) (trimPage)))))
The problem is I keep getting the dreaded
String cannot be cast to clojure.lang.IFn From Small Clojure Function
error. How can I restructure this function to avoid the error?
Post Answers Edit:
Thank you for the answers:
I reworked the function to test for a "-" in a string. If it's not there, I use a dummy "888-99" as a value when none is there.
(def missing-book-page "888-99")
.
.
.
(defn split-legal-ref
"Highly specific function that expects one AssessPro map and a map key,
from which book and page will be extracted."
[assess-pro-acct extract-key]
(let [[book page]
(if (.contains "-" (extract-key assess-pro-acct))
(cstr/split (extract-key assess-pro-acct) #"-")
(cstr/split missing-book-page #"-"))]
(list (cstr/trim book) (cstr/trim page))))
You have an extra set of parentheses around the expression beginning with ((if book .... The if expression returns a string, and then since that string is in the first position of a list with the outer of those 2 parentheses, Clojure tries to invoke the string as a function.
Parentheses are very, very significant in Clojure. Unlike arithmetic expressions in languages like Fortran, C, C++, Java, Python, etc., where adding an extra set of parentheses around a subexpression is redundant, and maybe bad style, but harmless, it changes the meaning of Clojure expressions.
Can you add more information, like the function names and sample data? Also include more of the error message.
Somewhere in your code you are attempting to use a string as if it were a function. For example:
("hello" 3) ; should be (inc 3) or something. This is line #6
This generates the following error
ERROR in (dotest-line-5) (core.clj:6)
Uncaught exception, not in assertion.
expected: nil
actual: java.lang.ClassCastException: class java.lang.String cannot be cast to class clojure.lang.IFn (java.lang.String is in module java.base of loader 'bootstrap'; clojure.lang.IFn is in unnamed module of loader 'app')
at tst.demo.core$fn__18295.invokeStatic (core.clj:6)
<snip>
Note the last line of the error above refers to core.clj:6 which matches the namespace tst.demo.core and line number 6 where (hello 3) is found in the source code.
I've been trying to find an idiomatic way to convert user data to valid keywords in clojure.
A possible use case for this is when reading in an excel spreadsheet, I would like to dynamically build a map for each row besides the first where the first row contains headers that will be keywords . I need to account for the headers possibly containing spaces or other invalid characters. I have read that the keyword function will not complaim and will give you an invalid key that may be hard to work with or even harmful.
I could manually make the conversions or possibly use a framework like slugger to do this, but i wanted to know if there was anything already built-in that could handle this.
Also, I have read that at one point creating too many keys could overload the heap, but that was from 2010 and it may have been resolved in 1.3. Would it just be best for me to create my hash-map with string key instead of keywords? I have read that doing so is not idiomatic.
Unless you have good reason for doing otherwise, simply use the string itself as a key.
user=> (def my-db (atom {}))
#'user/my-db
user=> (swap! my-db assoc "New York" 1)
{"New York" 1}
user=> (swap! my-db assoc "Los Angeles" 2)
{"Los Angeles" 2, "New York" 1}
user=> (do (print "Which city do you want to rank?\n =>")
(flush)
(#my-db (read-line)))
Which city do you want to rank?
=>New York
1
If you encode/keywordize your map, you'll instead have to encode/keywordize or stringify/decode on each interaction with the user's conventions.
Hm, it does appear that keyword will spit out unreadable keywords:
user=> (keyword "foo bar")
:foo bar
user=> (keyword "foo:")
:foo:
Neither of these can be read in again.
I would just write a small function to clean your input (rules here) before passing it to the keyword function.
I'm trying to learn clojure.
I am calling a function which is returning me an array of strings..
If I do:
(let [items (get-all-items)]
(println (type items))
(items))
the type of items is shown as class clojure.lang.PersistentVector where as the items value is like so:
[["Dogs"] ["Cats"] ["Capybaras"] ["Pygmy Hedgehogs"]]
I would like to convert this to a map in a format like this:
{ "Dogs" "Cats" "Capybaras" "Pygmy Hedgehogs" }
Does that make sense? Clojure maps can contain list of strings right?
I am only doing this because if I have it as a map, I can check if I have a pet in the list like this:
(contains? pets "Dogs")
; assuming the map is stored in pets variable
that fails if pets is a vector.
So If I can convert that to maps, how do I convert it? if not, how do I search for something in the vector?
(I like working with maps so I'd rather have maps - unless there is a strong reason not to do so)
ps: I've tried converting with into but that doesn't work either.
I suspect what you really want is a set, not a map. Maps store a value associated with a specific key. Sets store a unique list of values.
If your only use case is testing for membership, then you definitely want a set. You want a map if you are also associating some data with that key.
It's very easy to produce a set from any sequence. In your case:
(set (flatten items))
;;=> #{"Pygmy Hedgehogs" "Dogs" "Cats" "Capybaras"}
flatten removes the nesting on your lists giving you a sequence of strings. set consumes the sequence and returns a set of the unique values in that sequence.
(apply assoc {} (flatten [["Dogs"] ["Cats"] ["Capybaras"] ["Pygmy Hedgehogs"]]))
;;=> {"Capybaras" "Pygmy Hedgehogs", "Dogs" "Cats"}
I have compojure based app where I need to parse a request and retrieve parameters that can be numbers. I want to be able to verify that the parameters exist and that they are numbers before actually processing the request. This is what I have so far:
(defn get-int [str]
"Returns nil if str is not a number"
(try (Integer/parseInt str)
(catch NumberFormatException _)))
(defn some-request [request]
(let [some-number (get-int (get-in request [:route-params :some-number])
other-number (get-int (get-in request [:route-params :other-number])]
(if (every? identity [some-number other-number])
(process-the-request)
(bad-request "The request was malformed")))
Is there a better way to do string -> number conversion?
Is there a better way to do request validation?
This question contains good examples for parsing numbers in Clojure. If you are not sure that the string contains a valid number, your approach looks good.
If you can pass the parameters as part of the query string, you could use a route with regex to retrieve the value, e.g.
(GET ["/user/:id", :id #"[0-9]+"] [id]
(let [num (read-string id)]
(str "The number is: " num)))
The route would only match if the regex conditions are met, therefore you could skip the Integer/parseInt check.
Use Long/parseLong instead of Integer/parseInteger. The latter supports only 32 bits, which is often insufficient; for instance, Datomic entity IDs don't fit into Integer.
Never use read-string for user input. In addition, you must sanitize user input, removing injected scripts and such. https://github.com/alxlit/autoclave is a good start, although the defaults are arguably too aggressive.
I'd thought I'd post this as I got it to work through guesswork without a real understanding of what's going on and I thought it might be helpful if someone explained it.
I understand how to get at an element of the :params map in a Compojure handler:
(GET "/something" [some_arg] "this is the response body")
or
(GET "/something" {{some_arg "some_arg"} :params} "this is the response body")
although I don't completely understand what the {some_arg "some_arg"} part is doing :(
I also wanted to access the :remote-addr part of the request as well as some_arg. And I ended up with
(GET "/something" {{some_arg "some_arg"} :params ip :remote-addr}
(do-something-with some_arg ip))
So, I get that the unquoted strings some_arg and ip are the names of variables to which I want the values bound but the map above isn't a valid Clojure map. How does it work?
I also get that this is evaluated against the Ring request map (which is somehow supplied by the defroutes macro) but the expression above isn't a function or macro definition so how can it 'exist' as a valid expression in my code? Is there some sort of suspension of the normal rules for macro arguments? I've been unable to find a definition of the syntax of destructuring forms comprehensible to this non-Lisp'er.
The map is a valid destructuring map. In any place where you bind names, you can use destructuring. You could do the same thing in a let, like this:
user=> (let [{{some-arg "some_arg"} :params ip :remote-addr} {:remote-addr "127.0.0.1" :params {"some_arg" "some_value"}}] [ip some-arg])
["127.0.0.1" "some_value"]
I wrote a post about map destructuring in the context of named arguments, but it applies here. You might find this useful: Clojure - named arguments
There are a lot of blog posts demonstrating destructuring, including this one. I'm not sure which one would be a canonical place to learn from.
I don't pretend to know what exactly compojure does with that map under the hood, but I presume it throws it in a let or something similar as I demonstrated above. GET is a macro, so it doesn't have to evaluate the map you pass it, which is why you wouldn't get an error unless it evaluated it.
user=> (defmacro blah [m])
#'user/blah
user=> (blah {a "b" c "d"})
nil
user=> (defn blah [m])
#'user/blah
user=> (blah {a "b" c "d"})
java.lang.Exception: Unable to resolve symbol: a in this context (NO_SOURCE_FILE:9)
Under the hood, magic happens to that map and it gets passed to a function called destructuring that does the destructuring magic.
There isn't really anything special going on here other than normal macro/special form foo and delayed evaluation.
Destructing takes place within a binding form, and for map destructuring the var to be bound is on the left, and the key is on the right:
user=> (let [{a :foo} {:foo :bar}]
user=* a)
:bar
Compojure is doing a binding form behind the scenes, so that map destructuring form you were using above is effectively turned into something like:
(let [{{some_arg "some_arg"} :params} request]
...)
Where request is an implicitly provided map.
The vector version (e.g., [some_arg]), is an alternative that just binds against the :params map contained in the request.