I need advice,
I try to make function :
(def user-map [new-name new-phone new-email]
{:name new-name
:phone new-phone
:email new-email})
With new-name, new-phone, new-email are user input. But when i try to compile it, it says too many arguments to def, after change def to defn, when i try to execute user-map in REPL i get something like
#<temp_atom$user_address zenedu.mm.dbase.temp_atom$user_address#714924b5
instead of actual map.
I need to get to the map, any advice?
It sounds like perhaps you are conceptually combining the value that will be returned from calling user-map as a function with some arguments and evaluating the symbol user-map on its own.
Evaluating
(user-map "me" "123456789" "me#here.com")
Which will return a map, by looking up the var user-map in the current namespace and calling the function stored in that var with these arguments. Where evaluating just
user-map
Will simply look up the var user-map in the current namespace and return the contents of that var, which in the case where you used defn, will be the function it's self. The REPL then prints the object I'd of that function.
In your use case, you need defn to define a builder (like a constructor in Java) for the object you want. The log
#<temp_atom$user_address zenedu.mm.dbase.temp_atom$user_address#714924b5
suggests that you are using another structure user-address somewhere in the application and it looks like there is confusion between a user-map object and this user-address.
Anyway, you may be interested to have a look at defrecord that provides a convenient way to build objects with a constructor (and potentially other functions related to this object), e.g.
(defrecord user [name phone email])
defrecord provides 2 constructors ->user and map->user:
(def me (->user "abb" "0102030405" "abb#mail.com"))
(def you (map->user {:email "das#mail.com" :phone "9090909090" :name "das"}))
And you can access the properties of a user through keywords exactly like a map:
user> (:name me)
"abb"
user> (:phone you)
"9090909090"
OK, you should use defn instead of def.
But what information really varies here? The number and order of the map keys, in this case [:name :phone :email].
A generic function that will build - from a key sequence - a function that will build the map from the value sequence is
(defn map-builder [keys]
(fn [& values] (zipmap keys values)))
You can then define
(def user-map (map-builder [:name :phone :email]))
... which works as required:
(user-map "me" "123456789" "me#here.com")
;{:email "me#here.com", :phone "123456789", :name "me"}
If performance is pressing, by all means use records instead of maps, as #AbbéRésina suggests.
Putting it simply...
The error you are receiving is due to essentially passing a vector as a second value to def. If you want to use def in this instance go with...
(def user-map-other
(fn [new-name new-phone new-email]
{:name new-name
:phone new-phone
:email new-email}))
Here we are using an anonymous function that accepts your three parameters. Here is a link to learn more about them => http://clojuredocs.org/clojure.core/fn
To gain access to the values contained in your function we can use get in this instance.
(get (user-map-other "Ben" "999" "bbb#mail.com") :phone) => "999"
(get (user-map-other "Ben" "999" "bbb#mail.com") :name) => "Ben"
(get (user-map-other "Ben" "999" "bbb#mail.com") :email) => "bbb#mail.com"
A more concise method would be to use defn as represented below.
(defn user-map [new-name new-phone new-email]
{:name new-name
:phone new-phone
:email new-email})
(get (user-map "Ben" "999" "bbb#mail.com") :phone) => "999"
(get (user-map "Ben" "999" "bbb#mail.com") :name) => "Ben"
(get (user-map "Ben" "999" "bbb#mail.com") :email) => "bbb#mail.com"
Related
(def p {:name "James" :age 26})
I'm trying update method, like
(update p :name "David")
which does not work since the second argument has to be a function.
Try this:
(assoc p :name "David")
Please see this list of documentation, especially the Clojure CheatSheet! See also assoc-in and update-in as described under
Collections -> Maps
P.S. What you have there is a Clojure map value, which is different than an object in JavaScript or a JSON string.
I am trying to understund this piece of code from Web Development With Clojure book. Its about clojure script:
(defn message-form []
(let [fields (atom {})]
(fn []
[:div.content
[:div.form-group
[:p "Name:"
[:input.form-control
{:type :text
:name :name
:on-change #(swap! fields assoc :name (-> % .-target .-value))
:value (:name #fields)}]]]
[:p "Message:"
[:textarea.form-control
{:rows 4
:cols 50
:name :message
:on-change #(swap! fields assoc :message (-> % .-target .-value))}
(:message #fields)]]
[:input.btn.btn-primary {:type :submit :value "comment"}]])))
Can anybody explain this part:
#(swap! fields assoc :name (-> % .-target .-value)
especially this:
...(-> % .-target .-value)
[:input {:on-change #(swap! fields assoc :name (-> % .-target .-value)}]
This defines an input and a function that will be called each time the user changes the input field, i. e. types in it. The function will be called with the change event object as argument, which is then bound to %. The event target is the input field DOM element, which has as value its text content. The function then changes fields to contain as :name that content.
The #(…) and the % belong together: #(…) creates an anonymous function and % is the name of its parameter.
The -> is a macro that inverts the usual lispy prefix composition of forms into something resembling postfix composition or "piping": (-> % .-target .-value) is expanded to (.-value (.-target %)).
The .-value notation is JavaScript interop in ClojureScript. It denotes the access of a field "value" of a JavaScript object; (.-value foo) in ClojureScript is essentially the same as writing foo.value or foo["value"] in JavaScript.
In your question swap! is taking an atom, a function (assoc) that updates the value the atom stores and subsequent arguments that are given to the function.
Here is another way of achieving the same result:
#(swap! fields (fn [prev] (assoc prev :name (-> % .-target .-value)))
See that :name and (-> % .-target .-value) would be the two subsequent arguments in the swap! invocation of your question, where apply is used internally to apply these arguments to assoc. Here there is just an update function - no subsequent arguments.
As you can see prev is the internal value of the atom that we are going to change and swap! just takes an 'updating' function, which returns the next value for swap! to store.
As far as (-> % .-target .-value) goes, remember you are already in the reader macro, so % is the first argument to it. The -> threading macro threads into the first argument of each following function, so % is given to .-target and then the result of that is given to .-value, giving us: (.-value (.-target %)).
:name in the map stored at fields is being set to the target value of some value that is being provided!
Going out a little wider % is a JavaScript event that is provided whenever the text input field is changed - basically whenever the user types text. You rarely want the whole of the event - just the text that has been typed, which is where getting the target of the event (as opposed to the source) and then that target's value, comes in. The .- part means you are getting a JavaScript property - this is 'interop', as opposed to normal ClojureScript syntax.
#() is a reader macro. It is a short form for an anonymous function (fn [args] ...), where % is the first argument of this function.
In this particular case #(swap! fields assoc :name (-> % .-target .-value) is equal to (fn [X] (swap! fields assoc :name (-> X .-target .-value))
-> is a threading macro and (-> X .-target .-value) is equivalent to (.-value (.-target X)).
(.-target X) means get property target of object X.
So you define an anonymous function which receives single argument. This function will change the fields atom so that its key :name will be set to the value of the property value of the object in property target of the object X.
I start learn Clojure and need help with task.
I have to write this function:
(data-table student-tbl)
;; => ({:surname "Ivanov", :year "1996", :id "1"}
;; {:surname "Petrov", :year "1996", :id "2"}
;; {:surname "Sidorov", :year "1997", :id "3"})
I must use let, map, next, table-keys and data-record functions.
In this case:
student-tbl => (["id" "surname" "year" "group_id"] ["1" "Ivanov" "1998"] ["2" "Petrov" "1997"] ["3" "Sidorov" "1996"])
(table-keys student-tbl) => [:id :surname :year :group_id]
(data-record [:id :surname :year :group_id] ["1" "Ivanov" "1996"]) => {:surname "Ivanov", :year "1996", :id "1"}
I wrote this:
(defn data-table [tbl]
(let [[x] (next tbl)]
(data-record (table-keys tbl) x)
))
(data-table student-tbl) => {:surname "Ivanov", :year "1998", :id "1"}
How I can use map for right result?
First, here is how you should probably write this in practice. Then I'll show you your mistake so you can learn for your homework.
One way:
(defn data-table
[[headers & data]]
(let [headers (map keyword headers)
data-record (partial zipmap headers)]
(map data-record data)))
The key takeaways here are:
destructure the input to go ahead and separate headers from data
build the headers once, using the core keyword function
compose a function which always takes the same set of headers, and then map that function over our data
note that there are no external functions, which is always a nice thing when we can get away with it
Now, to make your way work, what you need to do is map the data-record function over x. First, the let binding should bind (next tbl) to x, not [x] (the way you're doing it, you only get the first element of the data set (Ivanov, 1998, 1).
In this example, ignore the data-record zipmap and table-keys binding in the let. They're there to make this example work, and you can remove them safely.
(defn data-table-newb
[tbl]
(let [table-keys #(map keyword (first %))
headers (table-keys tbl)
data-record zipmap
x (next tbl)]
(map #(data-record headers %) x)))
Essentially, you compute your table headers at the beginning, then create a new anonymous function that calls data-record and gives it your computed headers and an individual vector of data. You apply that function over every element of your data list, which you have bound to x.
Removing the unnecessary functions which are defined elsewhere, you get:
(defn data-table-newb
[tbl]
(let [headers (table-keys tbl)
x (next tbl)]
(map #(data-record headers %) x)))
(defroutes my-routes
(GET "/:id" [id] (html/display-thing id)))
(def my-map
{:id 1 :title "One"
:id 2 :title "Two"})
Is there a nice way to check if the url parameter id exists in my-map else continue checking if the other routes match? I know you can do something similar with regex like so: ["/:id", :id #"[0-9]+"] and suspect it might be possible to plug in an arbitrary predicate function.
Not actually at a REPL, but isn't this as straightforward as returning nil from html/display-thing if there's no id element in my-map? Take a look at (source GET) to see how the macro passes control to the next route if the method or URL don't match.
I have a simple record definition, for example
(defrecord User [name email place])
What is the best way to make a record having it's values in a sequence
(def my-values ["John" "john#example.com" "Dreamland"])
I hoped for something like
(apply User. my-values)
but that won't work. I ended up doing:
(defn make-user [v]
(User. (nth v 0) (nth v 1) (nth v 2)))
But I'm sensing there is some better way for achieving this...
Warning: works only for literal sequables! (see Mihał's comment)
Try this macro:
(defmacro instantiate [klass values]
`(new ~klass ~#values))
If you expand it with:
(macroexpand '(instantiate User ["John" "john#example.com" "Dreamland"]))
you'll get this:
(new User "John" "john#example.com" "Dreamland")
which is basically what you need.
And you can use it for instantiating other record types, or Java classes. Basically, this is just a class constructor that takes a one sequence of parameters instead of many parameters.
the defrecord function creates a compiled class with some immutable fields in it. it's not a proper clojure functions (ie: not a class that implements iFn). If you want to call it's constructor with apply (which expects an iFun) you need to wrap it in an anonymous function so apply will be able to digest it.
(apply #(User. %1 %2 %3 %4) my-values)
it's closer to what you started with though your approach of defining a constructor with a good descriptive name has its own charm :)
from the API:
Note that method bodies are
not closures, the local environment includes only the named fields,
and those fields can be accessed directy.
Writing your own constructor function is probably the way to go. As Arthur Ulfeldt said, you then have a function you can use as a function (e.g. with apply) rather than a Java-interop constructor call.
With your own constructor function you can also do argument validation or supply default arguments. You gain another level of abstraction to work with; you can define make-user to return a hash-map for quick development, and if you later decide to change to records, you can do so without breaking everything. You can write constructors with multiple arities, or that take keyword arguments, or do any number of other things.
(defn- default-user [name]
(str (.toLowerCase name) "#example.com"))
(defn make-user
([name] (make-user name nil nil))
([name place] (make-user name nil place))
([name user place]
(when-not name
(throw (Exception. "Required argument `name` missing/empty.")))
(let [user (or user (default-user name))]
(User. name user place))))
(defn make-user-keyword-args [& {:keys [name user place]}]
(make-user name user place))
(defn make-user-from-hashmap [args]
(apply make-user (map args [:name :user :place])))
user> (apply make-user ["John" "john#example.com" "Somewhere"])
#:user.User{:name "John", :email "john#example.com", :place "Somewhere"}
user> (make-user "John")
#:user.User{:name "John", :email "john#example.com", :place nil}
user> (make-user-keyword-args :place "Somewhere" :name "John")
#:user.User{:name "John", :email "john#example.com", :place "Somewhere"}
user> (make-user-from-hashmap {:user "foo"})
; Evaluation aborted.
; java.lang.Exception: Required argument `name` missing/empty.
One simple thing you can do is to make use of destructuring.
(defn make-user [[name email place]]
(User. name email place))
Then you can just call it like this
(make-user ["John" "John#example.com" "Dreamland"])
Update for Clojure 1.4
defrecord now defines ->User and map->User thus following in Goran's footstaps, one can now
(defmacro instantiate [rec args] `(apply ~(symbol (str "->" rec)) ~args))
which also works with non-literal sequences as in (instantiate User my-values).
Alternatively, along the lines of map->User one can define a function seq->User
(defmacro def-seq-> [rec] `(defn ~(symbol (str "seq->" rec)) [arg#] (apply ~(symbol (str "->" rec)) arg#)))
(def-seq-> User)
which will allow (seq->User my-values).
The idiomatic way to call a Record constructor is with the Clojure symbol ->MyRecord and that works just fine with apply.
(def my-values ["John" "john#example.com" "Dreamland"])
(defrecord User [name email place])
(apply ->User my-values)
; => #my-ns.User{:name "John",
:email "john#example.com",
:place "Dreamland"}