Hacker News new | ask | show | jobs
Clojure is Imperative (lispcast.com)
67 points by momo-reina 4189 days ago
9 comments

"imperative" does not seem like a good/valuable characterization. Yes, the "do" form is imperative. That's why you're not supposed to use it much, perhaps only for the top-level structure of your program, the part that acts as an interpreter to connect the pure program to the outside world. When you express business logic in a language like clojure, that expression of the business logic is not usually "a sequence of commands executed in order". It usually looks more like "an expression tree and rules for how nodes are evaluated".

The downside of a simple, transparent language like lisp is that it exposes you to the dangerous parts of the underlying. An opaque abstraction (e.g. expressing database operations as a tree of operations some of which define transaction boundaries, rather than as a series of SQL commands some of which open or close transactions) is vital for transforming dangerous things into safe things. Ultimately a program looks like an expression of business-level logic and then an opaque layer that pretends the computer is actually a machine that understands your business. Lisp forces you to provide more of that opacity yourself, whereas a language that already has a vocabulary for drawing distinctions between different kinds of thing provides you some of the scaffolding you need (the tradeoff being of course that it will be less specialized to your particular circumstances).

Just because Clojure has the DO form (or that Common Lisp has the PROG form) doesn't mean that the language is imperative. It means that it allows imperative programming.

This is like saying that a language with casts doesn't have a type system. No, it has a type system, it just also has an escape hatch that lets you break out from the type system when you need to. Lisps' functional-ness is generally the same sort of thing: They are designed primarily around function-oriented programming, but provide straightforward escape hatches into imperative programming. Common Lisp tends more towards being multi-paradigm than Clojure (i.e. provides more imperative accommodations), but both have strict evaluation, which the author seems to equate with imperative-ness.

The author spends more time talking about a small core and non-leaky abstractions than about imperative-ness. On these points, I totally agree and think it's a well-written article. I don't exactly see the connection with being imperative, and I think it's a shame that the title will draw attention away from those points. The thing that I really like about working in Clojure is how the abstractions are built with care: I feel like whenever I just use the base building blocks, they fit together seamlessly and edge cases usually don't come up.

Are you saying that languages aren't imperative or declarative but programs are?
Eh... somewhat.

I do think that it's meaningful to describe a program as being imperative or declarative or object-oriented or functional or strongly-typed or whatever, and that determination can be separate from the language that the program was written in (up to a point). See also, "writing COBOL in any language."

But just because those descriptions apply to programs, that doesn't mean that they are inapplicable to languages. At the least, those languages can be described in terms what types of programs they allow and what types they encourage, and how strongly. What programs they allow is a question mostly of features, but what they encourage is more complicated question that often involves the culture and ecosystem as much as the syntax or standard library.

E.g., Haskell encourages functional, strongly-typed programs. It's possible to write an imperative, weakly-typed program by using a lot of monads and passing data between modules as serialized strings. But that program is massively cutting against the grain, so to speak, so it's still meaningful to describe Haskell as functional and strongly-typed. Common Lisp functions naturally fit together into a functional program, but it's nowhere near as difficult or counter-intuitive to write an imperative program, so Common Lisp can lay better claim to being multi-paradigm.

I do think that programs are the primary things that are described as imperative or whatever. Languages are better-described by the programs they encourage rather than the features they have. For example, JavaScript and Ruby have all the features common to (dynamically-typed) functional languages (closures, lambdas, functions-as-values, etc), but that's not the type of program that is easiest to write in either language. That's why most people don't describe JavaScript as a functional language, even though there are certainly functional programs written in it.

From what I've read, the exact definitions of imperative and declarative vary a lot. The most useful rule of thumb I've come across is to consider imperative to be concerned with HOW something is to be done, while declarative concerns primarily WHAT is supposed to be done.

SQL is usually classified as declarative in that you're not telling the database how to retrieve the top ten entries from a table - you're merely informing it that you are interested in those entries.

It struck me at some point that this definition must be relative to the level of abstraction you're operating at. For example, when using (go …) blocks, I'm declaring that I want the code contained within the block to be processed asynchronously, but I'm not describing how that is to be done. Perhaps a callback might be described as being "more imperative" since it contains more implementation details?

So, am I correct in thinking that this discussion would hinge on what you consider to be "concrete" and not? That something executes in sequence is hardly enough to decide whether it is to be considered declarative or imperative in nature.

Declarative is what we'd like programming to be in the future. At the highest level we could say "Computer, make me a first person shooter set in WW2" and receive a ready to play game from that.
"Make me a..." is clearly imperative.
You would only want to execute an expression and throw away its value for its side effects.

Useful for IO as there's normally nothing I want to pass the value of a println expression to, evaluating already prints it to stdout. Also useful for mutation, but doing this in Clojure feels wrong since the immutable data structures are among the language's greatest strengths.

Do you not care if your output succeeded ?
It honestly hadn't occurred to me that printing to stdout could fail, or that I'd need to check its return value to ensure it was successful. Can you think of an situation where that might happen? And if so, why doesn't println throw an exception?

On the other hand, a side effect like writing to a file or inserting to a database can easily fail, but those of course throw exceptions.

Output redirected to a file and file being already closed, for one. Or anyway, stdout being closed midway due to many other possible reasons: this is (probably) why printf returns a number of bytes written or -1, for example: http://www.cplusplus.com/reference/cstdio/printf/
The question is whether there's anything that can be done about it. Much of the time, there is! And people overlook it more often than they should. But much of the time there isn't, and in that case it might as well be ignored.
"Imperative" isn't really a binary feature of programming languages, the imperative-declarative axes is more a continuous axes of variation describing code (which can be applied to languages, as well, as a general characterization of code written in the language.)

And, while its certainly possible to write very imperative code in clojure (or even Haskell!), idiomatic clojure is more declarative and less imperative than most popular languages, so, I think, on balance, "is imperative" is not a useful description.

That being said, in most "declarative" languages, there still remains the risk of being bitten by issues related to order of operations and structure of the code that aren't apparent if you view the code as purely declarative statements of intent rather than as to some degree imperative instructions. And if you call anything that presents that issue as "imperative", virtually all programming languages are "imperative".

'That being said, in most "declarative" languages, there still remains the risk of being bitten by issues related to order of operations and structure of the code that aren't apparent if you view the code as purely declarative statements of intent rather than as to some degree imperative instructions. And if you call anything that presents that issue as "imperative", virtually all programming languages are "imperative".'

Yeah, particularly painful is massaging something purportedly "declarative" in order to get particular operational semantics out of it.

I sometimes dream of a future language that will have a denotational layer separated from an operational one, with each playing off the other. In the limit, you can implement things entirely in the denotational and the compiler will fill in the operational details - but when that breaks down you have the option to specify how it works in a way that's built for specifying how it works (and, ideally, then checked against your declarative specification). Ideally here, you can express arbitrarily strong or weak constraints upon both and the compiler will say "done", or "I don't see how", or "I can prove that's impossible."

I just started learning Clojure, so I don't really understand this article very well. Is Clojure significantly different from other LISPs (e.g. Racket)?
The author of this article is straining logic to argue that Clojure is not a functional language. It's true that Clojure is not a purely functional language, but most people should already know that. Unlike Haskell, Clojure allows side affects in code. It exposes the underlying Java platform 'transparently' which is definitely imperative. In that respect it's not too much different from other Lisps which allow side affects and generally have an FFI of some sort for working with imperative code written in other languages.

Unlike other Lisps, though, Clojure's core semantics are very strongly functional. All of Clojure's core data structures and types are immutable and to have mutable state you need to resort to reference types with special semantics to have mutable state. It would be uncomfortable to write Clojure code in a primarily imperative fashion. Common Lisp by contrast has no problem with imperative code or mutable state.

Ah, thanks. It does look pretty functional from my limited experience so far, plus I think these debates are very academic. You can write purely functional Python if you want, it's just going to be neither easy nor practical.
No, and there's nothing wrong with this.

Actually - I didn't read the article very carefully, so I may be completely wrong here - it looks like the OP uses a bit incorrect term. To me it looks like the word he wants is "applicative", and the opposition to it would be "concatenative" (see: http://evincarofautumn.blogspot.com/2012/02/why-concatenativ...).

"Imperative" is usually contrasted with "declarative" (and not with "functional", which TFA seems to suggest) and in practice most languages are imperative to some degree, even the ones which are said to be "declarative" are most often not purely so (but see Datalog).

Not in this respect, no.
No.
Which language is not imperative then? The only program writtten in non-imperative language I can think of would return random values (of random type, also not known to mankind, including total destruction and creation of life) and crashed avery second function call.
Haskell is not imperative, though it allows you to express imperative programs within it via Monads. One way to think of Haskell is as a purely functional, lazy analogue to the C Preprocessor. Your program is then merely a function whose value is a set of instructions for performing the effects you desire on some particular machine. The cool thing about being lazy in this context is that preprocessing time (evaluation of the function) and runtime are interleaved rather than separate (as in the case of C).
There's a case to be made that Haskell (or Idris) is not imperative, but 'imperative', at it's core, describes an interface, and Haskell supports that interface, basically (and a limited version of the associated semantics -- all good interfaces reduce their detail and surface area, though, no good having an arbitrarily precise interface).
http://en.wikipedia.org/wiki/Datalog

There are probably more, I just don't know about them.

prolog, for one.
Prolog functions are a little more advanced 'if' statements. Flow of the execution is always the same. Data may be processed in a different order, but the rest is purely imperative. Logic never changes.
You're implying that the opposite of "imperative" is "nondeterministic." That's incorrect.

Prolog is declarative as opposed to imperative. User source code declares rules/constraints, and the Prolog compiler performs a search algorithm to arrive at a solution that satisfies the constraints.

I exaggerated a little with this randomness.

Let me explain:

Given the same input Prolog program and imperative (Pascal?) one will give the same results. Because Prolog is imperative.

Non-imparative languages give estimated results (actually I don't know any of these). Why? Because for example data is too large to do simple things like counting all elements. Thats non-imperative. Result is no longer based on what is in the data space, but based on its characteristics. For example, you may calculate a number of helium atoms in a cubic inch, but you will never do such thing like counting them one by one (imperative way).

aminit I am not sure if you know what imperative means.

Imperative programs have a state or virtual machine that is manipulated by the program. All programs, at heart, are imperative, but higher level constructs allow for procedural or declarative programming which abstracts away the lower-level imperative bits.

    // Imperative 
    if(foo == 'Tuesday') {
        print 'Taco Tuesday';
    }
    
    // Procedural
    if(isTacoTuesday(foo)) {
        writeTacoTuesday();
    }
   
    // Functional (but not monadic)
    (if (eq foo 'Tuesday') (print 'Taco Tuesday'))

    // Declarative
    taco_tuesday : foo == 'Tuesday'
    run : ( taco_tuesday -> write('Taco Tuesday') ; true )
    run;
No I have not had lunch yet.
He explain his intention was another thing, and this post make it more clear, I think:

http://www.lispcast.com/two-kinds-of-bootstrapping

He talks about how lisps are bootstrapped on a small core, and that is nice. Reminded me that this is the approach that Perl 6 is trying to take (NQP is the core) although on a larger and more complicated language