Clojure is Imperative(lispcast.com) |
Clojure is Imperative(lispcast.com) |
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).
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.
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.
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.
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.
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.
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".
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."
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.
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).
There are probably more, I just don't know about them.
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.
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).
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.I like this more for a functional idiom:
case foo of
TUESDAY => "Taco Tuesday"
| _ => "Not Taco Tuesday"
Or this: (cond (= foo TUESDAY)
("Taco Tuesday")
("Not Taco Tuesday"))
All programs, at heart, are imperativeYes, but only because the underlying machine architecture is imperative, right? If you had a chip that could evaluate pure functions with no mutable state (like a Lisp Machine that used a purely functional dialect of Lisp) then you could have programs with no underlying imperativeness.
But in a perhaps more relevant sense, in languages like Haskell or Prolog it is possible to write a program with no imperative state manipulation happening at the programmer's working level of abstraction.
Still, this really has nothing to do with determinism.
Also, I like SQL as an example of declarative programming.
SELECT * FROM USERS
WHERE JOIN_DATE = "20150106"
That is a declarative program; the SQL engine it's run on decides the underlying imperative implementation of the solution at runtime. It is also deterministic in that when it's run on the same database containing the same data multiple times it will return the same result.From what I know about Prolog, I understand it's similar in this regard.
An actual declarative language would choose TUESDAY over _ because it's the most specific match, and not because it matched it first.
// Imperative: (essence of it) x = 0; x = x + 1; y = x;
// Procedural: x = 0; inc(x); return x;
// Functional: return inc(x);
// Declarative: select x + 1;
--- My argument is that all of these above are imperative. Down below sits imperative CPU which will give exactly the same result. Every time.
Wiki definition of imperative programming in my opinion is too broad: "Imperative programming is focused on describing how a program operates"
This applies to every program in every language. Because you as a programmer are always focused on describing how program operates. Prolog is only another higher abstraction layer.
Wiki: "... imperative programming is a programming paradigm that describes computation in terms of statements that change a program state."
Lets reverse the logic here. Non-imperative programming will not describe computation in terms of statements that change program state. So, non-imperative program will not use statements.
If you're authoring programs for an abstract virtual machine in a declarative and/or functional style, it doesn't necessarily matter to you that that the underlying CPU architecture is imperative in nature. That's the point of an abstraction layer.
When we say a language is declarative vs. imperative, we're talking about the language level itself, not the full stack all the way down to the metal. A programming language and an implementation are different things.
// Imperative: (essence of it) x = 0; x = x + 1; return x;
// Procedural x = 0; inc(x); return x;
// Functional return inc(x);
// declarative select x + 1;
--- My argument is that all of these above are imperative. Down below sits imperative CPU which will give exactly the same result. Every time.
Wiki definition of imperative programming is wrong: "Imperative programming is focused on describing how a program operates"
This applies to every program in every language. Because you as a programmer are always focused on describing how program operates. Prolog is only another higher abstraction layer.
// Imperative: (essence of it) x = 0; x = x + 1; return x;
// Procedural x = 0; inc(x); return x;
// Functional return inc(x);
// declarative select x + 1;
--- My argument is that all of these above are imperative. Down below sits imperative CPU which will give exactly the same result. Every time.
Wiki definition of imperative programming is wrong: "Imperative programming is focused on describing how a program operates"
This applies to every program in every language. Because you as a programmer are always focused on describing how program operates. Prolog is only another higher abstraction layer.