views:

1512

answers:

9

I was just reading this excellent post, and got some better understanding of what exactly object oriented programming is, how Java implements it in one extreme manner, and how functional programming languages are a contrast.

What I was thinking is this: if functional programming languages cannot save any state, how do they do some simple stuff like reading input from a user (I mean how do they "store" it), or storing any data for that matter?

For example - how would this simple C thing translate to any functional programming language, for example haskell?

#include<stdio.h>
int main() {
    int no;
    scanf("%d",&no);
    return 0;
}
+9  A: 

Functional programing drives from lambda Calculus. If you truly want to understand Functional programing check out http://worrydream.com/AlligatorEggs/

It is a "fun" way to learn lambda Calculus and bring you into the exciting world of Functional programming!

How knowing Lambda Calculus is helpful in functional programming.

So Lambda Calculus is the foundation for many real-world programming languages such as Lisp, Scheme, ML, Haskell,....

Suppose we want to describe a function that adds three to any input to do so we would write:

plus3 x = succ(succ(succ x)) 

Read “plus3 is a function which, when applied to any number x, yields the successor of the successor of the successor of x”

Note that the function which adds 3 to any number need not be named plus3; the name “plus3” is just a convenient shorthand for naming this function

(plus3 x) (succ 0) ≡ ((λ x. (succ (succ (succ x)))) (succ 0))

Notice we use the lambda symbol for a function (I think it looks kind of like an Alligator I'm guessing thats where the idea for Alligator eggs came from)

The lambda symbol is the Alligator (a function) and the x is its color. You can also think of x as an argument (Lambda Calculus functions are really only suppose to have one argument) the rest you can think of it as the body of the function.

Now consider the abstraction:

g ≡ λ f. (f (f (succ 0)))

The argument f is used in a function position (in a call). We call g a higher-order function because it takes another function as an input. You can think of the other function calls f as "eggs". Now taking the two functions or "Alligators" we have created we can do something like this:

(g plus3) = (λ f. (f (f (succ 0)))(λ x . (succ (succ (succ x)))) 
= ((λ x. (succ (succ (succ x)))((λ x. (succ (succ (succ x)))) (succ 0)))
 = ((λ x. (succ (succ (succ x)))) (succ (succ (succ (succ 0)))))
 = (succ (succ (succ (succ (succ (succ (succ 0)))))))

If you notice you can see that our λ f Alligator eats our λ x Alligator and then the λ x Alligator and dies. Then our λ x Alligator is reborn in the λ f's Alligator eggs. Then the process repeats and the λ x Alligator on the left now eats the other λ x Alligator on the right.

Then you can use this simple set of rules of "Alligators" eating "Alligators" to design a grammar and thus Functional programming languages were born!

So you can see if you know Lambda Calculus you will understand how Functional Languages work.

tuckster
@tuckster: I have studied lambda calculus a number of times before... and yes AlligatorEggs article makes sense to me. But I am not able to relate that to programming. For me, right now, labda calculus is like a separate theory, that is just there. How are the concepts of lambda calculus used in programming languages?
Lazer
@eSKay: Haskell *is* lambda calculus, with a thin layer of syntactic sugar to make it look more like a normal programming language. Lisp-family languages are also very similar to the untyped lambda calculus, which is what Alligator Eggs represents. Lambda calculus itself is essentially a minimalist programming language, kind of like a "functional programming assembly language".
camccann
@eSKay: I have added a bit about how it relates with some examples. I hope this helps!
tuckster
If you are going to subtract from my answer could you please leave a comment about why so I can try and improve my answer. Thank you.
tuckster
A: 

haskell:

main = do no <- readLn
          print (no + 1)

You can of course assign things to variables in functional languages. You just can't change them (so basically all variables are constants in functional languages).

sepp2k
@sepp2k: why, whats the harm in changing them?
Lazer
@eSKay if you can't change the variables then you know that they are allways the same. This makes it easier to debug, forces you to make simpler functions that do one thing only and very well. It also helps a great deal when working with concurrency.
henrikh
@eSKay: Functional programmers believe that mutable state introduces many possibility for bugs and makes it harder to reason about the behavior of programs. For example if you have a function call `f(x)` and you want to see what the value of x is, you just have to go to the place where x is defined. If x were mutable you'd also have to consider whether there's any point where x could be changed between its definition and its use (which is non-trivial if x is not a local variable).
sepp2k
@sepp2k: It isn't just functional programmers that distrust mutable state and side effects. Immutable objects and command/query separation are well-regarded by quite a few OO programmers, and almost *everyone* thinks mutable global variables are a bad idea. Languages like Haskell just take the idea further that most...
camccann
@eSKay: It's not so much that mutation is harmful is that it turns out if you agree to avoid mutation it becomes much easier to write code that is modular and reusable. Without shared mutable state, coupling between different parts of the code becomes explicit and it is a lot easier to understand and maintain your design. John Hughes explains this better than I can; grab his paper *Why Functional Programming Matters*.
Norman Ramsey
+1  A: 

Functional language can save state! They usually just either encourage or force you to be explicit about doing so.

For example, check out Haskell's State Monad.

Shaun
And keep in mind that there's nothing about `State` or `Monad` that enables state, since they are both defined in terms of simple, general, functional tools. They just capture relevant patterns, so you don't have to reinvent the wheel so much.
Conal
+7  A: 

(Some functional languages permit impure functions.)

For purely functional languages, the real world interaction is usually included as one of the function arguments, like this:

RealWorld pureScanf(RealWorld world, const char* format, ...);

Different languages have different strategies to abstract the world away from the programmer. Haskell, for instance, uses monads to hide the world argument.


But the pure part of functional language itself is already Turing complete, meaning anything doable in C is also doable in Haskell. The main difference to imperative language is instead of modifying states in place:

int compute_sum_of_squares (int min, int max) {
  int result = 0;
  for (int i = min; i < max; ++ i)
     result += i * i;  // modify "result" in place
  return result;
}

You incorporate the modification part into a function call, usually turning loops into recursions:

int compute_sum_of_squares (int min, int max) {
  if (min >= max)
    return 0;
  else
    return min * min + compute_sum_of_squares(min + 1, max);
}
KennyTM
Or just `computeSumOfSquares min max = sum [x*x | x <- [min..max]]` ;-)
FredOverflow
@Fred: List comprehension is just a syntactic sugar (and then you need to explain the List monad in detail). And how do you implement `sum`? Recursion is still needed.
KennyTM
`sum = foldl (+) 0` ;-)
FredOverflow
+47  A: 
Antal S-Z
+1, amazing answer.
fig
The thing that always gets me about Haskell (which I have made, and am making valiant efforts to learn) is the ugliness of the syntax. It's like they took the worst bits of every other language, dropped them in a bucket and stirred furiously. And these people will then complain about C++'s admittedly (in places) weird syntax!
anon
Neil: Really? I actually find Haskell's syntax very clean. I'm curious; what in particular are you referring to? (For what it's worth, C++ doesn't really bother me either, except for the need to do `> >` in templates.)
Antal S-Z
@absz I suppose I'm comparing it with C as a procedural language (pointer declarations the most obvious failure), Smalltalk as an OO language (precedence the big problem) and Scheme as a functional language (usual moans about lisp syntax), But in all these I can when looking at a piece of code get a good idea of what it is doing (and I am far, far from being either an ST or Scheme expert). I simply cannot do that with Haskell. It seems like the tokens of the language were chosen more or less at random. Well, maybe it will all become clear - I hope so.l
anon
To my eye, while Haskell's syntax isn't as clean as, say, Scheme, it doesn't begin to compare to the hideous syntax of, well, even the nicest of the curly-brace languages, of which C++ is among the worst. No accounting for taste, I suppose. I don't think a language exists that everyone finds syntactically pleasing.
camccann
@NeilButterworth: I suspect your problem isn't so much the syntax as the function names. If functions like `>>=` or `$` had more where instead called `bind` and `apply`, haskell code would look a lot less like perl. I mean the main difference between haskell and scheme syntax is that haskell has infix operators and optional parens. If people would refrain from overusing infix operators, haskell would look a lot like scheme with less parens.
sepp2k
@sepp2k: I think I know what you're getting at, but "infix operators and optional parens"? Without parens and prefix notation, Scheme barely has any syntax left! It's like saying "well, the main difference between a lamppost and an apple tree is that the lamppost isn't a tree and doesn't grow apples".
camccann
sepp2k
Conal
@Conal: I saw that answer, and you are absolutely correct. I'd never thought about that aspect of `IO` before.
Antal S-Z
+10  A: 

Plenty of good answers here, but they are long. I'm going to try to give a helpful short answer:

  • Functional languages put state in the same places that C does: in named variables and in objects allocated on the heap. The differences are that:

    • In a functional language, a "variable" gets its initial value when it comes into scope (through a function call or let-binding), and that value doesn't change afterward. Similarly, an object allocated on the heap is immediately initialized with the values of all its fields, which don't change thereafter.

    • "Changes of state" handled not by mutating existing variables or objects but by binding new variables or allocating new objects.

  • IO works by a trick. A side-effecting computation that produces a string is described by a function that takes a World as argument, and returns a pair containing the string and a new World. The World includes the contents of all disk drives, the history of every network packet ever sent or received, the color of each pixel on the screen, and stuff like that. The key to the trick is that access to the World is carefully restricted so that

    • No program can make a copy of the World (where would you put it?)

    • No program can throw away the World

    Using this trick makes it possible for there to be one, unique World whose state evolves over time. The language run-time system, which is not written in a functional language, implement a side-effecting computation by updating the unique World in place instead of returning a new one.

    This trick is beautifully explained by Simon Peyton Jones and Phil Wadler in their landmark paper "Imperative Functional Programming".

Norman Ramsey
As far as I can tell, this `IO` story (`World -> (a,World)`) is a myth when applied to Haskell, as that model explains only purely sequential computation, while Haskell's `IO` type includes concurrency. By "purely sequential", I mean that not even the world (universe) is allowed to change between the start and end of an imperative computation, other than due to that computation. For instance, while your computer is chugging away, your brain etc cannot. Concurrency can be handled by something more like `World -> PowerSet [(a,World)]`, which allows for nondeterminism and interleaving.
Conal
@Conal: I think the IO story generalizes pretty nicely to nondeterminism and interleaving; if I remember right, there's a pretty good explanation in the "Awkward Squad" paper. But I don't know of a good paper that explains true parallelism clearly.
Norman Ramsey
Conal
+1 This has helped me understand IO Monads a lot more as well as answering the question.
CaptainCasey
A: 

might be useful , Function Programming for the rest of us

Ahmed Kotb
+10  A: 

The technique for handling state in Haskell is very straightforward. And you don't need to understand monads to get a handle on it.

In a programming language with state, you typically have some value stored somewhere, some code executes, and then you have a new value stored. In imperative languages this state is just somewhere "in the background". In a (pure) functional language you make this explicit, so you explicitly write the function that transforms the state.

So instead of having some state of type X, you write functions that map X to X. That's it! You switch from thinking about state to thinking about what operations you want to perform on the state. You can then chain these functions together and combine them together in various ways to make entire programs. Of course you're not limited to just mapping X to X. You can write functions to take various combinations of data as input and return various combinations at the end.

Monads are one tool, among many, to help organise this. But monads aren't actually the solution to the problem. The solution is to think about state transformations instead of state.

This also works with I/O. In effect what happens is this: instead of getting input from the user with some direct equivalent of scanf, and storing it somewhere, you instead write a function to say what you'd do with the result of scanf if you had it, and then pass that function to the I/O API. That's exactly what >>= does when you use the IO monad in Haskell. So you never need to store the result of any I/O anywhere - you just need to write code that says how you'd like to transform it.

+1 Nice explenation
Ikke
+5  A: 

I'm breaking off a comment reply to a new answer, to give more space:

I wrote:

As far as I can tell, this IO story (World -> (a,World)) is a myth when applied to Haskell, as that model explains only purely sequential computation, while Haskell's IO type includes concurrency. By "purely sequential", I mean that not even the world (universe) is allowed to change between the start and end of an imperative computation, other than due to that computation. For instance, while your computer is chugging away, your brain etc cannot. Concurrency can be handled by something more like World -> PowerSet [(a,World)], which allows for nondeterminism and interleaving.

Norman wrote:

@Conal: I think the IO story generalizes pretty nicely to nondeterminism and interleaving; if I remember right, there's a pretty good explanation in the "Awkward Squad" paper. But I don't know of a good paper that explains true parallelism clearly.

@Norman: Generalizes in what sense? I'm suggesting that the denotational model/explanation usually given, World -> (a,World), doesn't match Haskell IO because it doesn't account for nondeterminism and concurrency. There might be a more complex model that does fit, such as World -> PowerSet [(a,World)], but I don't know whether such a model has been worked out and shown adequate & consistent. I personally doubt such a beast can be found, given that IO is populated by thousands of FFI-imported imperative API calls. And as such, IO is fulfilling its purpose:

Open problem: the IO monad has become Haskell’s sin- bin. (Whenever we don’t understand something, we toss it in the IO monad.)

(From Simon PJ's POPL talk Wearing the hair shirt Wearing the hair shirt: a retrospective on Haskell.)

In Section 3.1 of Tackling the Awkward Squad, Simon points what doesn't work about type IO a = World -> (a, World), including "The approach does not scale well when we add concurrency". He then suggests a possible alternative model, and then abandons the attempt at denotational explanations, saying

However we will instead adopt an operational semantics, based on standard approaches to the semantics of process calculi.

This failure to find a precise & useful denotational model is at the root of why I see Haskell IO as a departure from the spirit and the deep benefits of what we call "functional programming", or what Peter Landin more specifically named "denotative programming". See comments here.

Conal
Thanks for the longer answer. I think perhaps I have been brainwashed by our new operational overlords. Left movers and right movers and so on have made it possible to prove some useful theorems. Have you seen *any* denotational model you like that accounts for nondeterminism and concurrency? I have not.
Norman Ramsey
I like how `World -> PowerSet [World]` crisply captures nondeterminism and interleaving-style concurrency. This domain definition tells me that mainstream concurrent imperative programming (including Haskell's) is intractable--literally exponentially more complex than sequential. The great harm I see in the Haskell `IO` myth is obscuring this inherent complexity, demotivating its overthrow.
Conal
While I see why `World -> (a, World)` is broken, I'm not clear on why the replacement `World -> PowerSet [(a,World)]` properly models concurrency, etc. To me, that seems to imply that programs in `IO` should run in something like the list monad, applying themselves to every item of the set returned by the `IO` action. What am I missing?
Antal S-Z
@Absz: First, my suggested model, `World -> PowerSet [(a,World)]` isn't right. Let's try `World -> PowerSet ([World],a)` instead. `PowerSet` gives the set of possible results (nondeterminism). `[World]` is sequences of intermediate states (not the list/nondeterminism monad), allowing for interleaving (thread scheduling).And `([World],a)` isn't quite right either, as it allows access to `a` before going through all the intermediate states. Instead, define use `World -> PowerSet (Computation a)` where `data Computation a = Result a | Step World (Computation a)`
Conal