6 ms·
I’m not sure I understand what you mean. I don’t consider Clojure to be imperative at all — everything is immutable by default, and you actually have to go thro
by stingraycharles 5y ago
I’m not sure I understand what you mean. I don’t consider Clojure to be imperative at all — everything is immutable by default, and you actually have to go through considerable efforts to write things in an imperative style.
When I compare Clojure to another functional language I know well, Haskell, one of the things I really feel it lacks is proper pattern matching and currying; yes there are libraries that you can use, but it’s just not idiomatic to do in Clojure. I would not, however, assert that it’s an imperative language.
Could you care to elaborate on what exactly you find is lacking in Clojure?
- davidrupp 5y agoIt's not difficult to write imperative code in Clojure; [1] is an example. Immutability-by-default makes you work to mutate things, for sure, but that in itself doesn't make the language inherently functional. [1] https://clojuredocs.org/clojure.core/let#example-542692c7c026201cdc3269c2 https://clojuredocs.org/clojure.core/let#example-542692c7c02...
- robthethird 5y agoYour link might be pointing to the wrong example. I assume you wanted to show an example of an atom.
- davidrupp 5y agoNope. The sequence of statements in the (let) block is imperative. Atoms are actually an example of how the values of references are updated functionally. Clojure has facilities for both imperative and functional style.
- didibus 5y agoLexical shadowing isn't an imperative thing. Imagine the form: (let [a 10 a (+ a a) a (+ a a a)] a) as being like so: (let [a 10] (let [a (+ a a)] (let [a (+ a a a)] a))) Which you can now re-imagine as the functional composition: (+ (+ 10 10) (+ 10 10) (+ 10 10)) So it has evaluation semantics which allow you to reduce it as described in lambda calculus using α-conversion and β-reduction. And this is different to the imperative style, because in imperative you can do: int a = 10; a = ++a + ++a; Where `a` is now equal to 23, but in Clojure: (let [a 10 a (+ (inc a) (inc a))] a) You now have `a` equal to 22 instead. This is the difference between the variable substitution evaluation semantic of lambda calculus and the imperative semantics.
- davidrupp 5y agoNice strawman. Now, how would you restructure the let bindings (without the println) at https://clojuredocs.org/clojure.core/let#example-542692c7c026201cdc3269c2 https://clojuredocs.org/clojure.core/let#example-542692c7c02... equivalently? Wouldn't the ordering of the function executions matter? Could you re-order the bindings arbitrarily and get the same result? I don't think you can. That makes it imperative-as-opposed-to-declarative, even though it may not be imperative-as-a-synonym-for-side-effecting.
- didibus 5y agoYou do the same thing: (let [a (take 5 (range))] (let [{:keys [b c d] :or {d 10 b 20 c 30}} {:c 50 :d 100}] (let [[e f g & h] ["a" "b" "c" "d" "e"]] (let [_ (println "I was here!")] (let [foo 12] (let [bar (+ foo 100)] [a b c d e f g h foo bar])))))) And now you can simply evaluate it using variable substitution again. The difference is that when you say `(let [a 10])` the variable `a` is a constant now, you can effectively replace all occurrence of it within the scope by `10`, and you can do this at compile time. That's why people say the symbol `a` is bound to the value 10, and not the variable `a` points to the value 10. Effectively you cannot change `a` within the scope anymore, all use of `a` in that scope will be equal, and so within that scope you can change their ordering freely. What I think is confusing you here is that `let` gives you syntax sugar so all the scopes are flattened, but each new binding pair is actually inside a nested scope, and so `a` is still immutable. In this case, it matters almost never, but as I showed, it still can, because in an imperative language you could still try to mutate the variable even in the same expression, and that is impossible to do in Clojure.
- davidrupp 5y ago> each new binding pair is actually inside a nested scope Can you point me to something that demonstrates this? I've both decompiled this form and traced its compilation: (defn foo [a] (let [b a b (+ b a)] b)) and it does create two different symbols with a name of "b", which I get has the same net effect of not mutating the original "b", but I would not consider that "nested scope". It feels more like SSA to me, which is typically quite sequential in nature, which is what I associate with the term "imperative".
- weavejester 5y ago"The sequence of statements in the (let) block is imperative." The println in the example is imperative, as it has side-effects, but a let block having an ordering of bindings is not inherently imperative. You can replace any let block with a set of nested functions: (let [foo 12 bar (+ foo 100)] [foo bar]) ((fn [foo] ((fn [bar] [foo bar]) (+ foo 100))) 12) Sequential ordering of 'statements' is not automatically imperative. On the other hand, atoms are imperative, as they are mutable and therefore side-effectful.
- davidrupp 5y agoHmm. I usually think of "imperative" as "as opposed to declarative", and sequential ordering is one of its hallmarks in that context. And for Clojure atoms specifically I think of "functional" as in Okasaki's "purely functional data structures". Language is hard.