Haskell is not fully declarative. It is purely functional, but that doesn’t necessarily imply fully or purely declarative. Even if it is widely considered to be declarative. In a way, it is imperative, without mutations, i.e. every function declaration is still a series of imperatives of how to compute A from B — now, in the human sense of the word ‘declarative’, compute A from B
is not declarative, unless you mean a declaration of imperatives. Its runtime doesn’t exhibit any behavior that adds on top of that basic declaration (of imperatives), it just makes sure side effects are pure, and makes some reorderdings to make evaluation lazy. Perhaps this laziness of evaluation makes it declarative in the world of imperative languages in the sense that the runtime figures out from the programmer’s declarations how to automagically make the code more efficient without the programmer having to specify evaluation order and eagerness. But I guess even compilers such as GCC can do this to an extent, and the JVM JIT.
However, if you look at a language such as Prolog (or Curry, which is Haskell with Prolog capabilities), when you declare how to compute A from B
, you typically also automatically declare how to compute B from A
, and how to generate all/some pairs of A and B, and how to verify that a given (A,B) belongs to the set/relation defined by the original declaration.
Now, if you look at it from a human perspective, that is far closer to being declarative in the truer sense of the word. In human reality, all instructions to perform something, are interpreted by humans more or less like Prolog does. In addition, sequences of imperatives are even re-interpreted as declarations, and declaratives are automatically converted into executable steps.
Curry, which is something like Haskell++, can perform in both the pure FP as well as declarative Prolog worlds. (shameless plug)