loading...

re: Why I love learning functional programming VIEW POST

TOP OF THREAD FULL DISCUSSION
re: Why do you consider this to be declarative? const double = (arr) => arr.filter(v => v % 2 === 0).map(v => v*v); It looks very m...
 

Thanks for your comment!

Why do you consider this to be declarative?

I find it declarative because it expresses the intent of the computation instead of listing the explicit statements how to achieve the result of the computation. For example, it's not specified if the computation should proceed from left to right, if the array should be traversed in-order and if it should be traversed once or twice, or even if the computation is done in parallel or not. That's all meaningless for the declared intent.

Within an algorithm you can have whatever effects you find convenient, but since it implements a function, the scope of those effects is limited to the body of the procedure, meaning that the cost of proving global correctness does not increase.

This is a good point. With "implicit side effects", I meant functions shouldn't generally have observable side effects that are hidden from the caller. The function is allowed to do whatever it wants internally, as long as the side effects aren't observable.

Once you understand this, it's should be clear that the rest is just syntax, and you should pick the syntax that's most convenient for the use-case, rather than taking an ideological stance. :)

My number one goal for this post was to make it clear I don't have any ideological stance in any direction, so I sure hope it didn't come out looking like that! I explicitly said that one style of programming isn't better than the other and that functional programming even isn't the opposite of imperative programming. But I do think that functional programming does make the declarative style more attractive and, personally, I find declarative style easier to deal with.

 

If that's your definition of declarative, any layer of abstraction will suffice.

Are the following declarative?
They look like imperative procedure calls to me.

foo(a);
a.foo();
a.foo(b).bar(c);

Good question! I wouldn't call those declarative, because they modify state, or at least they don't return anything. I would also expect the function names to convey some intent that's understandable in the context of the program.

As long as we don't have a clear definition of declarative programming that we both agree on, I don't think we'll reach agreement of what code should be called declarative and what imperative. Maybe that's not that even so important in practice, to me it's most important that code is easy to read and understand and it has zero surprises.

Imperative code feels, though, easier to recognize when I see it 😊

Well, it's more that you don't seem to have a coherent definition for declarative programming. :)

It seems to be:

  1. Doesn't modify state.
  2. Returns things.
  3. Has meaningful names.
  4. Rejects a.foo(b).bar(c), but accepts a.map(b).reduce(c).

Whereas I recognize declarative programming as programming by making declarations about a universe and then reasoning about these declarations.

For example.

mother_child(trude, sally).

father_child(tom, sally).
father_child(tom, erica).
father_child(mike, tom).

sibling(X, Y)      :- parent_child(Z, X), parent_child(Z, Y).

parent_child(X, Y) :- father_child(X, Y).
parent_child(X, Y) :- mother_child(X, Y).

Allows us to test the claim

 ?- sibling(sally, erica).

This is what I understand declarative programming to be about -- making and testing declarations.

I think that perhaps you need to work on a more coherent definition for 'declarative programming' for yourself, since it doesn't seem coherent, so far.

Thanks for the comment! It's indeed true I don't have a coherent definition of declarative programming. To me, this SO post suggests that it's really hard to define declarative programming in a coherent manner that everyone can agree on. I like this answer though, it's maybe not rigorous but seems useful to me. As for imperative programming, Functional Programming in Scala describes it as "programming with statements that modify some program state". That's also something that's quite easy to grasp.

But to be honest, I don't think it really matters what the exact definition is or which blocks of code are imperative and which declarative. Like I said, functional programs can be written in imperative style and that's fine.

Having a coherent understanding of the terms you use is important.

Otherwise how do you know what you are saying, if anything? :)

Personally, what I understand is the following.

  1. Imperative expressed as instructions to an agent.
  2. Declarative expressed as declarations about the universe.
  3. Procedural is in the form of operations that occur over time.
  4. Functional is in the form of relationships that are invariant over time.

And this gives me a coherent basis for discussion. :)

While you may not agree with these, I advise trying to make your definitions coherent.

Hi @pentacular , your definition sounds more like Prolog to me!

Thanks for your comment, I'll definitely try and make my definitions coherent! Have a nice day!

@johnkazer , well, prolog is the classic declarative programming language -- so that's unsurprising.

Rejects a.foo(b).bar(c), but accepts a.map(b).reduce(c).

Kimmo explained perfectly why a.foo(b).bar(c) may not be declarative.

Good question! I wouldn't call those declarative, because they modify state, or at least they don't return anything. I would also expect the function names to convey some intent that's understandable in the context of the program.

You presented it in a context where either the return value is ignored or there was no return value. If there was no return value then there has to be a side effect, which makes it not pure declarative code. If all functions in your example are pure and you ignore the return value then it's all declarative code, but it's also useless since the return value is the only thing you get out of it.

The Wikipedia page for Declarative Programming states that any code that has side effects is procedural.

I believe the reason Kimmo brought up the function names is that they may convey if the function has side effects or not, like map and reduce,

Sure, my point is that if a.foo(b).bar(c) isn't declarative, then it's difficult for a.map(b).reduce(c) to be declarative -- they're the same kind of expression.

a.map(b) and a.reduce(c) may have side-effects.

Does this mean that you're saying that a.map(b).reduce(c) is sometimes declarative and sometimes not declarative?

Any code that has effects is procedural, side-effects is a sub-set of effects. :)

But I'm not sure why that's relevant to being declarative.

Are you making a claim along the lines that imperative function application is declarative, and so functional programming is the same as declarative programming?

That would seem incoherent, so I'd think carefully about it. :)

I was going by the definition from Wikipedia that declarative code is referentially transparent, ie. no side effects.

I'm not familiar with just the term "effect", but this is side effects according to me:

Example side effects include modifying a non-local variable, modifying a static local variable, modifying a mutable argument passed by reference, performing I/O or calling other side-effect functions. - Side effect - Wikipedia

Does this mean that you're saying that a.map(b).reduce(c) is sometimes declarative and sometimes not declarative?

According to the above definitions, then yes. As ridiculous as that may sound.

Are you making a claim along the lines that imperative function application is declarative, and so functional programming is the same as declarative programming?

Functional programming is a subcategory of declarative programming. Logic programming, like your Prolog, is also a subcategory of declarative programming.

I'm not even sure about what you mean by "imperative function application". What makes a function application imperative?

A side-effect is an effect that escapes the local function.

An effect is a change that occurs over time.

I'm glad that you realize that it is ridiculous. :)

The reason it's ridiculous is that you're conflating functional and declarative.

Let's consider two function expressions -- one declarative and one imperative.

  1. "In this world it is raining."
  2. "Find me a world where it is raining."

Declarative programming is where the program is expressed in terms of declarations (and need not be functional -- consider that the classic declarative programming language, prolog, is not functional due to things like the cut operator).

Imperative programming is where the program is expressed in terms of instructions (which include applications, such as function applications), and these can be purely functional applications.

What it comes down to is that imperative / declarative is a matter of how you speak, rather than what you say.

What it comes down to is that imperative / declarative is a matter of how you speak, rather than what you say.

I'm very familiar with the difference between the two. But I was questioning your "imperative function application", which I still have no idea what you mean.

you're conflating functional and declarative.

This may be true, all of my experience with declarative programming is functional programming. I have never tried logic programming. But as I said, I was going by the definitions of declarative programming that I found online. The category of a.map(b).reduce(c) would depend on what the functions b and c are. If they are referentially transparent or not. The reason I found it ridiculous is that I always thought of map and reduce as being declarative (ie. referentially transparent). But I realize now that I have used them for IO sometimes.

The part that you don't seem to agree on is the referential transparency requirement for code to be declarative. If we ignore that then we are on the same page. But as someone said earlier, it can be hard to define what declarative programming is and there are conflicting opinions. So I think we can leave that for now.

I'm still curious to hear what you mean by "imperative function application" if you are willing to explain.

Let's start with the basic wikipedia definitions, that you referred to above.

Declarative programming is a non-imperative style of programming in which programs describe their desired results without explicitly listing commands or steps that must be performed.

a.map(b).reduce(c) is a list of commands that must be performed.

So it is not declarative -- regardless of if those commands have side effects of not.

The only argument that you could make here is that the flow control is perhaps more granular than with a for loop.

If you want to go in the other direction and argue that a sufficiently intelligent compiler could deconstruct the map/reduce form into whatever it likes, then I'll point out that precisely the same holds true for a for loop, which would render the for loop declarative by that definition -- and I hope that would be an excessively ridiculous conclusion. :)

Although pure functional languages are non-imperative, they often provide a facility for describing the effect of a function as a series of steps.

Pointing out that purely functional languages can have non-declarative expressions.

Referential transparency doesn't even come into it -- it is completely independent of the style of expression used.

a.map(b).reduce(c) is a list of commands that must be performed.

So it is not declarative -- regardless of if those commands have side effects of not.

So you are saying that map andreduce are not declarative? That feels odd considering they are pretty much the face of functional programming. Anyone who hears of FP will first hear of those two functions.

I think "map function b over list a" is a very declarative way of describing the code and it forms what is essentially a single expression, not a list of individual statements to execute.

Are you able to show some actually declarative JavaScript code?

What's declarative about telling the machine to transform a sequence into another sequence by applying a procedure to each element?

What's declarative about telling the machine to compute a value from a sequence by iteratively applying a procedure to each element and the value computed so far?

map and reduce aren't function -- they're just higher order operations -- that is operations parameterized by operations.

In the case of javascript, these operations are procedures, making them higher order procedures, rather than higher order functions.

Higher order operations are often used in functional programming styles, but that's because they're convenient, not because they're fundamentally functional programming mechanisms.

Javascript is a procedural language with a generally imperative style of expression.

Declarations in javascript are pretty well limited to variable, function, import and export declarations.

map and reduce aren't function -- they're just higher order operations -- that is operations parameterized by operations.

In the case of javascript, these operations are procedures, making them higher order procedures, rather than higher order functions.

So what do you consider to be the difference between a function and an operation? And a procedure is usually defined as a series of statements that do not return anything. Map and reduce obviously return something.

Higher order operations are often used in functional programming styles, but that's because they're convenient, not because they're fundamentally functional programming mechanisms.

Oh but they are fundamentally functional. Lambda calculus, the very base of functional programming, works by treating everything as a function. Defining anything useful in lambda calculus requires higher order functions. And in untyped lambda calculus everything is a higher-order fuction. :)

JavaScript is considered a multi-paradigm language. Although it's imperative-first it is very much possible writing in the style of functional programming. Although I'm not very much into JavaScript myself, the amount of functional programming that JS developers use seem to have increased very much lately. There are many libraries for it as well, eg. Immutablejs, Rambda.

Operations encompass both functions and procedures.

A function is time-invariant.

Procedures are a series of operations over time, and may include flow-control mechanisms such as return.

I wrote 'higher order operations' rather than 'higher order functions' for a reason.

It's true that lambda calculus is generally functional, but it's not fundamentally required to be so -- consider web.mit.edu/6.827/www/old/lectures... for example.

(But that's irrelevant, since your reference to lambda calculus came from confusing higher order operation with higher order function)

Certainly you can write procedures that implement functions in javascript -- I am not sure why this is relevant.

Operations encompass both functions and procedures.
A function is time-invariant.
I wrote 'higher order operations' rather than 'higher order functions' for a reason.

Regarding this, there seems to have been a miss on my part. When I was speaking of the functions map and reduce I didn't mean they had to be time-invariant (by time-invariant I assume you mean it follows the mathematical definition of a function, also known as pure or referentially transparent). The word "function" is used so widely in the programming world to just refer to what you call operation. When speaking of time-invariant functions the most common terminology is "pure function" (from what I have seen).

It's true that lambda calculus is generally functional, but it's not fundamentally required to be so -- consider web.mit.edu/6.827/www/old/lectures... for example.

From what I understand it is a requirement. The lecture you linked was very interesting. I didn't understand all of it. But they use what they call I-structures and M-structures to isolate the side effects and keeping them pure. So in a similar manner to Haskell's IO Monad, but it's not a monad. Either way, it's not the kind of "do IO anywhere" that imperative languages have. Apparently this method should let them model languages with implicit parallelization and they used it to create the language pH (Parallel Haskell), not sure about the state of that language today since this is from the late '90s. But they also lose some benefits of regular lambda calculus when expanding it like that.

I feel like this discussion has reached a point where neither of us is making any progress in convincing the other, so I'm leaving this as my last comment and will not reply any further. Below I linked some more resources on lambda calculus and side effects that I read to get a better understanding of the lecture slides. It was very fun and interesting to read. So thanks for that.

I saw that the lecture you linked was the first result on Google for "lambda calculus side effects". It's not very comprehensive and was hard to understand. Here's a paper that covers some more basic parts first.
cs.tufts.edu/~nr/cs257/archive/nea...

Here's the research paper which I believe the lecture is based off. I only read about half of the paper, but it helped me understand the lecture slides.
reader.elsevier.com/reader/sd/pii/...

Both papers are by the same researchers and it was pretty much the only resources I found on side effects in lambda calculus.

That's fine.

I suggest that you go back over the thread and look at the places where your positions have been self-inconsistent, as it may be educational.

Best of luck.

code of conduct - report abuse