Looking at the first code sample, I wouldn't call it declarative at all. For me, the defining feature of declarative code, is that it doesn't have a list of actions to be performed one after another. That code sample is such a list of actions, which for me makes it imperative code, meaning "first do this, then do that, then do the other thing."
The "list of actions" approach is what makes code complexity grow exponentially, because whenever you look at a series of 10 instructions, you have to think "what state was created by the combination of the first 7 instructions, and does the eighth instruction depend on that state?"
> That code sample is such a list of actions, which for me makes it imperative code, meaning "first do this, then do that, then do the other thing."
Animation at its core is a sequence of images. Imperative animation code would describe how those images change every frame. (For example, a for loop in which you multiply properties by i in order to change them over time).
Declarative animation code would let you define keyframes — how the images look at specific points in time — and the library would generate the images in between them. (This is called "tweening" [0]).
The sample code in this case isn't a list of actions to be performed one after another. It's a list of states (keyframes), which the library interpolates between. That sounds declarative to me!
Compared with SQL it's definitely imperative. It's definitely using higher-level abstractions than a `for` loop, but it still specifies the order of operations.
Actually it doesn't unless you're assuming eager evaluation and that 'arr' is something like an array, rather than something which monadically collects functions for evaluation later.
In C#, the functions being passed could be passed as analyzable syntax trees, and the implementation could actually be in SQL.
nope. Sequencing and imperative have an overlap, but they are not the same thing.
Sequencing is a fundamental computing construct. Data flow graphs allow you to specify sequences, just as shell pipelines do. SQL has sequentiality built-in with nested queries. None of these are imperative environments.
Depends on what language you're using. In math notation, given `y = x * x`, you can work backwards from `y = 4` to figure out the value of x, whereas in, say, Javascript, `y = x * x` means exactly "compute y as the value of x times itself" and only that. For illustration, we could also compute the square of x in a different imperative form, e.g. in terms of a loop over additions.
Similarly, in mathematical notation, `f(g(x))` can be a way of expressing the existence of some sort of law, e.g. maybe f and g are commutative. That means that if code were written as such in a 5th-gen language[1], the underlying engine is free to recompile the code into `g(f(x))` assuming the commutative property holds and the performance is better. By contrast, in a imperative language, `f(g(x))` generally would compile to that exact order of operations (unless you have a mythical sufficiently smart compiler)
I can see an argument about JIT compilers being smart in some cases, but the philosophical distinction between imperative and declarative paradigms is that with declarative style, the compiler can transparently swap units of arbitrary complexity. For example, given some CSS rules, a browser engine can decide to paint the screen buffer however it wants, be it top-to-bottom, edge-to-center, layer-over-layer, etc regardless of how the CSS was originally expressed.
Compiled programming languages are declarative ways of generating machine code. The source code describes what the output or final state (the executable) should consist of, but not how to construct it (that's in the compiler source).
Is the code "read x; print x + 5" declarative or imperative?
It's declarative because it doesn't specify how to read the number, how to print the result, or how to add numbers. It merely symbolically describes the IO and calculations to be performed.
It's imperative because it specifies in a step-wise fashion reading input, performing a calculation, and outputting the result.
Declarative code is imperative from the perspective of the next layer up in the abstraction stack. Declarative code elides implementation details; the we call the implementation details imperative, because they specify the "how" and not the "what", which is the domain of the higher level.
Under this lens, what can we say about this:
arr
.map(x => x + 2)
.filter(x => x % 3)
.map(x => other(x))
It's imperative if we understand map() and filter() to be imperative operations. If they're declarative - perfectly possible in C# - then the code is declarative, because `arr` could be quite abstract, and do something much more interesting.
The border is fuzzy, so there wouldn't be a yes or no answer. Here are a couple of things that make your example more declarative than OP's code:
1. While each line is a separate step that's done in order, it's done on the results of the previous step. So it's like "get me the sum of the products of the results of foo" rather than "do x, then do y, then do z".
Imperative and declarative seem to me to be a matter of degree and sometimes even syntax. Point(x=1,y=2,z=3) is pretty declarative, but "point, with x set to 1, with y set to 2, with z set to 3" is getting more imperative even though it's really the exact same thing. But the syntax makes our mental model a little different, so yay. From there, it's not to hard to go to "scope, with x set to scope(a), with y set to x+1, with z set to f(x,y)," which s the same as C-style imperative "{x = a; y = x+1; z = f(x, y)}." There's a reasonably smooth continuum between imperative and declarative. As soon as you introduce lambda functions, declarative gets absurdly flexible and can model stateful computation in a surprisingly ergonomic way, so it's not even a twisted pedantic equivalence.
It's not really about syntax. It's about the execution model. Your second example might almost feel normal to someone with a preference for SQL, which is a declarative language.
Declarative languages don't specify (or minimize the specification of) the execution, imperative languages specify the execution. You can look at the verbs used in describing or verbalizing the language. In declarative languages you don't talk about "assigning" as much as you talk about relationships: "x is y", "x is related to y by f(x,y)", "if x is predicate(x) then y is z else y is z'". In imperative languages you do things: "x is assigned y", "for x in y do ...", "if x is predicate(x) then do y is assigned z else do y is assigned z'".
Additionally, statements/expressions in declarative languages can be reordered more freely (the "purer" the declarative language the more true this is), given that it tends towards the relational version. In a constraint based system, for example, you could do these in either order:
x in 1..10
x % 3 == 2
;; => x \in {2, 5, 8}
x collects these constraints and so the order is irrelevant (though practically many declarative languages aren't this pure so the order may matter for various reasons).
Sequencing is a fundamental computing construct, not the exclusive preserve of imperative programming.
The 'do' notation in Haskell, function composition operators, shell pipes, data flow graphs are all expressions of explicit sequencing.
Imperative programming is about each statement altering a program's state, not the act of sequencing.
The "list of actions" approach is what makes code complexity grow exponentially, because whenever you look at a series of 10 instructions, you have to think "what state was created by the combination of the first 7 instructions, and does the eighth instruction depend on that state?"