Can anyone explain this code?
1: user=> (def some-account {:number :any-number :balance :any-balance :bank :any-bank})
2: #'user/some-account
3: user=> (contains? some-account :bank)
4: true
5: user=> (assoc some-account :owner :any-owner)
6: {:owner :any-owner, :number :any-number, :balance :any-balance, :bank :any-bank}
7: user=> (contains? some-account :owner)
8: false
9: user=> (def some-account (assoc some-account :owner :any-owner))
10: #'user/some-account
11: user=> (cont开发者_运维技巧ains? some-account :owner)
12: true
13: user=> (dissoc some-account :owner)
14: {:number :any-number, :balance :any-balance, :bank :any-bank}
15: user=> (contains? some-account :owner)
16: true
Can anyone explain this code?
Why, after (assoc some-account :owner :any-owner)
, does (contains? some-account :owner)
return false
?
Why, only after (def some-account (assoc some-account :owner :any-owner))
does (contains? some-account :owner)
return true
?
Why, after (dissoc some-account :owner)
, does (contains? some-account :owner)
returns true
?
I tried saying (def some-account (assoc some-account :owner :any-owner))
out of instinct. But why does it work this way?
Maps in Clojure are immutable.
That is, the updation functions instead of modifying the original map, return a new updated map.
Try the following:
user=> (def some-account {:number :any-number :balance :any-balance :bank :any-bank})
#'user/some-account
user=> (contains? some-account :bank)
true
user=> (def updated-map (assoc some-account :owner :any-owner))
#'user/updated-map
user=> (contains? updated-map :owner)
true
user=>
It's always good to note that when the update functions produce a new map, they do not copy the old one they produce a new one that shares all the parts that did not change with the old one and only replaces the parts required by the changes.
This structural sharing is very important to all functional languages with [immutable data structures][1]
[1]: http://clojure.org/functional_programming#Functional Programming--Immutable Data Structures
In Clojure all the data types are immutable. Hence the assoc
operation on the some-account
map does not change it in place (unlike the put
operation on java.util.Map
in Java) and produces a new map. That is why the countain?
operation returns false. When you do
(def some-account (assoc some-account :owner :any-owner))
you are catching the return value of the assoc
operation and assigning it to the some-account
variable. In a sense you are redefining it. So later the contain?
operation returns true.
because assoc and dissoc return new objekts and don't change some-account
Imagine that you were using a number instead of a map:
user> (def s 3)
#'user/s
user> (= s 3)
true
user> (+ 1 s)
4
user> (= s 4)
false
user> (def s (+ 1 s)) ;;don't do this! changing definitions shouldn't be used in a program, only at the REPL for trying things out!
#'user/s
user> (= s 4)
true
user> (- s 1)
3
user> (= s 4)
In Clojure, most values behave like numbers. There are some mutable things, but they are hidden behind cryptic interfaces. An awful lot of programming can be done without them.
But how can we program without mutating variables? You may have seen the factorial function
(defn factorial [n]
(if (< n 2) 1
(* n (factorial (dec n)))))
user> (factorial 5)
120
Here is a similar function that builds up a map in the same way
(defn char-map [n]
(if (< n 0) {}
(assoc (char-map (dec n)) n (char n))))
user> (char-map 10)
{various control characters..}
This style is strange at first, but eventually becomes natural. When I think of something to program these days, I often think of the recursive solution before the imperative loop.
They are two different ways of looking at the same thing.
It is usually easy to translate between them, so that if you can think of one way to do it, you have already thought of the other. But this does take practice, like learning to speak Latin.
There are some advantages in clarity of thought and thread safety to having things not change after you've created them. Mathematicians particularly seem to prefer it.
精彩评论