问题
I was reading an article on Declarative Programming Languages.
If I don't understand the qualities of this type/paradigm of programming languages and it's contrast to Imperative languages, should I just read up on programming in this type of programming language, like Haskell, and then read up that article later?
回答1:
The point of the declarative paradigm is to be lazy. We, declarative programmers, like to let compilers do all the work. As often as we can, we do not specify the algorithm we want to use but only the definition of our wanted results.
For example, if in an imperative setup you want to calculate the sum of the integers up until n
, you could write (in C):
int f(int n) {
int result = 0, i = 1;
for(;i <= n; i ++)
result += i;
return result;
}
in a declarative setup, you would just declare what this sum is (in Haskell):
f 0 = 0
f n = n + f (n - 1)
This is not an algorithm, it's merely a definition. But the Haskell compiler is clever enough to get us the result anyway.
It's even more seeable when you switch to Prolog, with the canonical example that follows:
We declare some relationships between various people:
father(luke, anakin). father(anakin, a_slave_on_tattouin). father(a_slave_on_tattouin, someone). father(someone, adam).
Then we state that someone's ancestor is his father or his father's ancestor:
ancestor(Young, Old) :- father(Young, Old). ancestor(Young, VeryOld) :- father(Young, Old), ancestor(Old, VeryOld).
And there it is, Prolog can unleash the magic and answer queries such as:
?- ancestor(luke, X).
X = anakin ;
X = a_slave_on_tattouin ;
X = someone ;
X = adam ;
false.
Some help to read those things above: Head :- Body
means Head
if Body
. So above,
ancestor(Young, VeryOld) :-
father(Young, Old),
ancestor(Old, VeryOld).
means VeryOld
is Young
's ancestor if Old
is Young
's father and VeryOld
is Old
's ancestor.
And ;
means or. So here Prolog tells us that luke has 4 ancestors, anakin
, a_slave_on_tattouin
, etc.
As you can see, we didn't specify anything about the algorithm and still Prolog can do awesome things such as providing us all the details of luke
's genealogy. It's the power of declarative programming: you specify the data, that's what you care about, and the rest is the job of the clever guys handling the compiler.
回答2:
It might take some time to get your head around Haskell and Prolog and whatever else, but that's worth doing. Here's an analogy to get you started on declarative vs imperative.
Let's imagine you want to find out what 5sin(7x+20)+cos(x) is when x is 5.279 and all you've got is your calculator. If it's an old calculator, you have to tell it how to do it and in what order. If we use MC
for clear memory to 0 and MR
for recall memory and M+
to adding the current number to the one in memory, what you actually type is
MC -- clear the memory
5.279 -- take the number 5.279
*7= -- multiply it by seven
+20= -- add twenty
sin= -- find sin of that
*5= -- times by five
M+ -- store that in the memory
5.279 -- take the number 5.279
cos -- find cos of it
M+ -- add that to what you had in the memory
MR -- get the total back out of memory onto the screen
You're telling the calculator step-by-step what calculations to do because it hasn't been programmed to understand maths, just arithmetic. The structure of what you're calculating has been obscured by turning it into steps. Some people like this step-by-step way of doing it, find it very straightforward and can't understand why other people can be bothered doing it any other way. It works. You don't have to trust the calculator.
Now any programming language and any modern calculator will let you type
5*sin(7*5.279+20)+cos(5.279)
which is much clearer and some calculators bought in the last decade will even let you do
5.279 -> x
5sin(7x+20)+cos(x)
Modern calculators let you express the problem in terms of your own understanding instead of breaking it down into steps for the calculator. Some people like this just-say-what-you-mean way of doing it, find it very straightforward and can't understand why other people can be bothered doing it any other way. It's clear. You don't have to explain to the calculator.
Although this 5.279 -> x; 5sin(7x+20)+cos(x)
is almost exactly how you would solve this particular problem in an imperative programming language, we're talking about styles of calculator just now, and the first way of doing it with the old calculator is much more imperative (step-by-step) in style, and this with the new calculator is in a much more declarative (just-say-what-you-mean) style.
Let's change context and instead of using a calculator to work out a number, we're using some felt tips to draw a simple nonsense picture. Let's assume we always start at the top left corner when we measure. The imperative (step-by-step) style would be
take a piece of paper 7 by 10
pick up a red pen
put it at the point (3,4)
draw a 2x4 rectangle here
colour it in
pick up a black pen
put it at the point (4,4)
draw a circle here radius 3
colour it in
whereas the declarative just-say-what-you-mean style would be
on a piece of paper 7 by 10
red 2x4 rectangle at (3,4)
underneath
black circle radius 3 at (4,4)
These aren't examples of imperative or declarative code, but an attempt to show you the differences in style of thinking and programming. In declarative programming you say what you mean, then use a necessarily very clever compiler to transform your declarations into the optimum sequence of instructions. Because you've said what you mean it can radically change the code that achieves this into the most efficient form whilst preserving your meaning. You trust the compiler.
In imperative programming you say step-by-step what you want to do, making sure yourself that you've used the most efficient method you can think of, then the compiler uses a couple of tricks to eliminate unnecessary slowness and translates into a lower-level sequence of instructions. Because you've specified what order to do things, there's a limit to how much optimization it can do, but you trust the programmer to have made the best choice already.
(I've talked about trust because someone who read a draft of this said trust was important - she explains she uses the calculator the imperative way because she doesn't really trust it if she does it in a declarative way. I think that's the same for programmers. Many programmers don't trust compilers enough to be declarative - they want the control, and like to optimize their code themselves.)
It sounds from this that it would be much easier to use declarative styles all the time, but there's usually a steep learning curve for declarative languages, and most people prefer imperative. Traditionally there was a huge difference in speed (imperative has fewer overheads because it's more directly like the computer works), but some more modern compilers of declarative code seem to be in the top few of the speed tables a lot of the time - compiled verses interpreted makes a much bigger difference than imperative vs declarative, which is why python etc are often slower even though they're imperative.
回答3:
To answer your question should I just read up I suggest the answer is no, you should start programming. I really don't believe that you can develop a good understanding of the utility and applicability of programming languages, or their various categorisations, without writing programs in them. Make reading about them complementary to programming, not a substitute for it.
回答4:
The language that usually gets pointed out as an example of declarative programming is Prolog (which is actually still on my "to learn" list; SWI-Prolog is available in the Debian repos as of Squeeze).
Trying it is best, of course, but if you're just looking to get a taste of the paradigm, it may be enough to take a hard look at some code examples.
回答5:
Declarative programming is mostly about inventing domain-specific languages (DSLs) and then programming in these sublanguages. Haskell is a very good choice for that particular style. In Haskell virtually all domain-specific languages are embedded in the host language. They are then called embedded DSLs (EDSLs). In other words, you don't need to write parsers, but benefit from the elegant light-weight syntax of Haskell. Let me name a few examples.
Let's say you want to encode image/video processing. You notice that almost all filters have something in common: They update points based on their surroundings. There is an abstract concept called comonads for that particular purpose. You don't express state changes, but instead encode the transition function associated with the filter. In other words you encode data correlations and dependencies. Notice the incredible conciseness of the blurring filter and the fact that it works for both pictures and videos (anything that is a Filterable
):
blur :: (Filterable img) => Radius -> img -> img
blur radius = extend (average . surroundingPoints radius)
Let's say you want to encode a graphical user interface (GUI). You notice that almost all GUIs have something in common: The individual elements of the GUI are basically time-varying fields with correlations between them. There is an abstract concept called functional reactive programming (FRP) for that particular purpose. You don't handle events, but instead just describe the data relationships between the elements:
text1 = textField "text1"
text2 = textField "text2"
sum = fmap show (text1 + text2) <|> "Please enter numbers"
dialog =
label "First number:" ~|~ text1
~---~
label "Second number:" ~|~ text2
~---~
label "Sum:" ~|~ label sum
Everything needed to build the dialog box is included in this description, including what happens when the user did not enter numbers. This is neither a special language nor pseudo-code. This is actual Haskell code (using the Netwire library)!
来源:https://stackoverflow.com/questions/12189151/declarative-language