In Java, I can do the following to format a floating point number for display:
String output = String.format("%2f" 5.0);
System.out.println(output);
In theory, I should be able to do the same thing with this Clojure:
(let [output (String/format "%2f" 5.0)]
(println output))
However, when I run the above Clojure snippet in the REPL, I get the following exception:
java.lang.Double cannot be cast to [Ljava.lang.Object;
[Thrown class java.lang.ClassCastException
What am I doing wrong?
Java's String.format takes an Object[] (or Object...), to use String.format in Clojure you need to wrap your arguments in an array:
(String/format "%2f" (into-array [5.0]))
Clojure provides a wrapper for format that's easier to use:
(format "%2f" 5.0)
Kyle
Related
I know that there's a lot of questions about converting string to float/number/decimal... but my case is quite different cause I need to convert the string number (representing a dollar value) but I must keep the cents in this conversion, here is my case.
I receive this values
"96,26"
"1.296,26"
And I expect to convert to this follow:
96.26
1296.26
If I try to use clojure.edn it escape cents
(edn/read-string "1.296,26")
=> 1.296
(edn/read-string "96,26")
=> 96
If I try to use another approach like bugdec I get NumberFormatException
I know that we can do some string replace but it looks like a big work around, like this:
(-> "1.296,87"
(clojure.string/replace #"\." "")
(clojure.string/replace #"," ".")
(edn/read-string))
what you can do, is to use java's formatting facilities:
(defn read-num [s]
(let [format (java.text.NumberFormat/getNumberInstance java.util.Locale/GERMANY)]
(.parse format s)))
user> (read-num "1.296,26")
;;=> 1296.26
user> (read-num "96,26")
;;=> 96.26
Just use straight Java interop:
(let [nf (java.text.NumberFormat/getInstance java.util.Locale/FRENCH)]
(.parse nf "12,6")) => 12.6
See the Oracle docs: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/text/NumberFormat.html
and this posting: https://www.baeldung.com/java-decimalformat
You can also get a BigDecimal to avoid any rounding errors. See https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/text/DecimalFormat.html#%3Cinit%3E(java.lang.String,java.text.DecimalFormatSymbols)
(let [nf (DecimalFormat. "" (DecimalFormatSymbols. Locale/ITALIAN))
>> (.setParseBigDecimal nf true)
result (.parse nf "123.45,9")]
result => <#java.math.BigDecimal 12345.9M>
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.
Given the following piece of code:
(map Integer/parseInt ["1" "2" "3" "4"])
Why do I get the following exception unless I wrap Integer/parseInt in an anonymous function and call it manually (#(Integer/parseInt %))?
clojure.lang.Compiler$CompilerException: java.lang.RuntimeException: Unable to find static field: parseInt in class java.lang.Integer
the documentation on java interop says the following:
The preferred idiomatic forms for accessing field or method members
are given above. The instance member form works for both fields and
methods. The instanceField form is preferred for fields and required
if both a field and a 0-argument method of the same name exist. They
all expand into calls to the dot operator (described below) at
macroexpansion time. The expansions are as follows:
...
(Classname/staticMethod
args*) ==> (. Classname staticMethod args*) Classname/staticField ==>
(. Classname staticField)
so you should remember, that Class/fieldName is just a sugar for getting a static field, neither static method call, nor reference to the static method (java method is not a clojure function really), so there is no static field parseInt in Integer class, while (Class/fieldName arg) calls a static method, they are two totally different operations, using the similar sugary syntax.
so when you do (map #(Integer/parseInt %) ["1" "2" "3" "4"]) it expands to
(map #(. Integer parseInt %) ["1" "2" "3" "4"])
(you can easily see it yourself with macroexpansion),
and (map Integer/parseInt ["1" "2" "3"]) expands to
(map (. Integer parseInt) ["1" "2" "3"])
It fails when it is trying to get a field (which you think is getting a reference to a method).
Integer/parseInt is a static method of Integer class, not a clojure function. Each clojure function is compiled to java class which implements clojure.lang.IFn interface. map expects clojure function (which implements IFn interface) as a first argument, however, Integer/parseInt is not.
You can check that in the clojure repl.
user=> (type map)
clojure.core$map
user=> (type Integer)
java.lang.Class
user=> (type Integer/parseInt)
CompilerException java.lang.RuntimeException: Unable to find static field: parseInt in class java.lang.Integer, compiling:(/private/var/folders/p_/psdvlp_12sdcxq07pp07p_ycs_v5qf/T/form-init4110003279275246905.clj:1:1)
user=> (defn f [] 1)
#'user/f
user=> (type f)
user$f
user=> (type #(1))
user$eval9947$fn__9948
Perhaps reading this stackoverflow question will help you understand what is going on.
You might prefer to do it without the Java interop:
(map read-string ["1" "2"])
or for a more safe variant:
(map clojure.edn/read-string ["1" "2"])
I personally prefer to minimize the use of Java as much as possible in Clojure code.
As for why you can't just pass the Java function, because map expects a function in Clojure, not a function in Java.
I want to get following results when I evaluate edit-url and (edit-url 1).
edit-url --> "/articles/:id/edit"
(edit-url 1) --> "/articles/1/edit"
Is it possible to define such a Var or something?
Now, I use following function, but I don't want to write (edit-url) to get const string.
(defn edit-url
([] "/articles/:id/edit")
([id] (str "/articles/" id "/edit")))
Thanks in advance.
If those behaviors are exactly what you want, print-method and tagged literals may be used to imitate them.
(defrecord Path [path]
clojure.lang.IFn
(invoke [this n]
(clojure.string/replace path ":id" (str n))))
(defmethod print-method Path [o ^java.io.Writer w]
(.write w (str "#path\"" (:path o) "\"")))
(set! *data-readers* (assoc *data-readers* 'path ->Path))
(comment
user=> (def p #path"/articles/:id/edit")
#'user/p
user=> p
#path"/articles/:id/edit"
user=> (p 1)
"/articles/1/edit"
user=>
)
edit-url will either have the value of an immutable string or function. Not both.
The problem will fade when you write a function with better abstraction that takes a string and a map of keywords to replace with words. It should work like this
(generate-url "/articles/:id/edit" {:id 1})
Clojure is a "Lisp 1" which means that is has a single namespace for all symbols, including both data scalars and functions. What you have written shows the functionally of both a string and a function but for a single name, which you can do in Common Lisp but not Clojure (not that a "Lisp 2" has its own inconveniences as well).
In general this type of "problem" is a non issue if you organize your vars better. Why not just make edit-url a function with variable arity? Without arguments it returns something, with arguments it returns something else. Really the possibilities are endless, even more so when you consider making a macro instead of a function (not that I'm advocating that).
I'd like to call java.nio.file.Files/readAttributes, but doing so always fails with No matching method. For example:
user=> (java.nio.file.Files/readAttributes (-> "/etc/passwd" clojure.java.io/file .toPath) "posix:group")
CompilerException java.lang.IllegalArgumentException: No matching method: readAttributes, compiling:(NO_SOURCE_PATH:1:1)
user=> (java.lang.System/getProperty "java.version")
"1.7.0_25"
For a sanity check, doing the equivalent works fine from the scala repl:
scala> java.nio.file.Files.readAttributes((new java.io.File("/etc/passwd")).toPath, "posix:group")
res11: java.util.Map[String,Object] = {group=root}
It turns out that to call java variadic methods, you have to explicitly pass the vararg part as an array, even if you don't care about it. (see the question Java Interop — Netty + Clojure)
The following works:
user=> (java.nio.file.Files/readAttributes (-> "/etc/passwd" clojure.java.io/file .toPath) "posix:group" (into-array java.nio.file.LinkOption []))
{"group" #<Group root>}