News for hackers. Stuff that matters.
Re:Why Functional Programming Matters (1984) [pdf] by jlouis on Thursday December 08, 2016 @08:03AM (#13130014)
Functional programming largely has two schools: Treat "commands" as a separate entity from expressions, and bake "commands" into expressions. The former is largely Haskell, Clean, ... and the latter is exemplified by e.g., Standard ML or OCaml.

There are trade-offs between the two, but I definitely belong to the second school: we simply add an imperative subset to our functional language. This means we can exploit any efficiency trick an imperative program can do, including low-level stuff. The seasoned FP programmer will then proceed to encapsulate the efficiency trick in a abstract module such that the rest of the program doesn't have to worry about it. Of course, the price to pay for this is that you are losing purity. I think this is a fair trade-off but others disagree.

As for performance and memory usage: it is always a property of the architecture or system, not of the programming language. Dropping to a low-level language, such as C, usually doesn't buy you too much these days. What is more important is that most C compilers in use have vastly more time invested into optimizing routines than the typical FP compiler. Apart from that, you can easily mange the same kind of data in e.g., OCaml than you can in C.

The reason FP can beat the curve of performance in practice is because you operate at a higher level of abstraction. You have more attempts at writing the correct architecture, and it is easier to change over time. Since most real-world problems are heavily time-constrained, this makes them beat low-level solutions: when the C programmer has written the first working version, the FP programmer has tried 5 different solutions.

There is one area FP tends to fare poorly: CPU bound tasks where an inner-loop has to squeeze out performance (video encoding comes to mind). But most low-level programming fares poorly as well: either you use assembly, write GPU-level programs, use an FPGA or create your own ASIC/SoC solution for this. Also note that moving to faster solutions here costs an order of magnitude in time and in dollars: FPGAs are, relatively speaking, expensive beasts.

Re:Why Functional Programming Matters (1984) [pdf] by michaelfeathers on Thursday December 08, 2016 @10:50AM (#13131021)
The key insight is that immutability is not an end in itself, it's a tool to give us referential transparency.

If a language can allow mutability in an area of code without allowing side effects to escape from it we can have all of the reasoning advantage that FP gives us at a level above the mutations. We can also have the performance we want.

Re:Why Functional Programming Matters (1984) [pdf] by stcredzero on Thursday December 08, 2016 @01:48PM (#13132937)
The key insight is that immutability is not an end in itself, it's a tool to give us referential transparency.

How about a programming environment tailored for small simulations or games? I could imagine such an environment maintaining referential transparency without strict immutability. Rather, such a system could provide a kind of "poor-man's" immutability by only allowing pure functions that take state from tick N and output state for tick N+1.

Perhaps such a system could even achieve high performance by exploiting its constraints? Maybe the language could essentially be built on top of a custom VM and around the mechanism of bump allocation, read/write barriers, and Beltway garbage collection?

Re:Why Functional Programming Matters (1984) [pdf] by jpt4 on Thursday December 08, 2016 @08:47PM (#13136026)
Are you familiar with Urbit [0]?

[0] urbit.org

Re:Why Functional Programming Matters (1984) [pdf] by dTal on Friday December 09, 2016 @08:25AM (#13138580)
Is anyone?
Re:Why Functional Programming Matters (1984) [pdf] by samuell on Monday December 12, 2016 @01:31AM (#13156082)
> If a language can allow mutability in an area of code without allowing side effects to escape from it we can have all of the reasoning advantage that FP gives us at a level above the mutations. We can also have the performance we want.

As far as I see and can tell from experience, this exactly the power of the black-box processes of Flow-based Programming (FBP), communicating only via message passing (on buffered channels) [1].

FBP processes can be compared to "functions", but are free to change state etc. The "only communicate via message passing" means funny side-effects are effectively contained to the process itself.

Furthermore, I would argue that FBP, via its specification of keeping the network connectivity separate from the process implementation, makes FBP programs so extremely much more composable than most typical FP programs, which often allow references to other functions be hard-coded inside function code.

It is like making the call graph of an FP program declaratively configured by a list of processes and connections:

Processes: A (ports out1, out2) B (ports in1, out1) C (ports in1, out1)

Connections: A.out1 -> B.in1 A.out2 -> C.in1

etc.

I wrote a little text processing app in this style some time ago (The network connectivity scheme can be seen here: https://github.com/rdfio/rdf2smw/blob/master/main.go#L100-L1... ) and was amazed by the clarity and composability that emerged, even though I just used my own little experimental subset of full FBP ( http://flowbase.org ). As far as I can tell, this helped me going from first line of code to finished app go extremely fast ... app written in 2 days without ever really getting stuck in any strange bug due to bad code organization, which use to happen all the time otherwise.

[1] https://en.wikipedia.org/wiki/Flow-based_programming

Re:Why Functional Programming Matters (1984) [pdf] by samuell on Monday December 12, 2016 @11:23AM (#13159212)
Ouch, didn't see that the formatting got lost before editing was frozen ... should be:

Processes:

A (ports out1, out2)

B (ports in1, out1)

C (ports in1, out1)

Connections:

A.out1 -> B.in1

A.out2 -> C.in1

Re:Why Functional Programming Matters (1984) [pdf] by efnx on Saturday December 10, 2016 @03:24AM (#13144708)
This is what StateT in Haskell is for.
Re:Why Functional Programming Matters (1984) [pdf] by adwn on Thursday December 08, 2016 @10:15AM (#13130762)
> when the C programmer has written the first working version, the FP programmer has tried 5 different solutions.

... none of which are necessarily faster than the other 4 solutions. It's not just the architecture of your code that influences performance, it's also – sometimes even more so – the underlying details of your implementation, like arrays vs. pointer-based data structures; Athas' comment describes this pretty well.

As a consequence, this statement:

> it is always a property of the architecture or system, not of the programming language

is wrong, simply because your programming language will heavily influence a lot of your productivity vs. performance tradeoffs.

Re:Why Functional Programming Matters (1984) [pdf] by Athas on Thursday December 08, 2016 @09:42AM (#13130532)
> As for performance and memory usage: it is always a property of the architecture or system, not of the programming language. Dropping to a low-level language, such as C, usually doesn't buy you too much these days. What is more important is that most C compilers in use have vastly more time invested into optimizing routines than the typical FP compiler. Apart from that, you can easily mange the same kind of data in e.g., OCaml than you can in C.

This is true in in principle, but not in practice. It is not just a question of whether a language is particularly amenable to optimisation and clever compilation (C is not, by the way), but also whether the baseline of naive compilation is efficient in itself. A naive C compiler will generate vastly better code than a naive compiler for most functional languages, if only because naive C compilation will primarly allocate statically and on the stack, while a functional language will perform an enormous amount of heap allocation. Futhermore, natural C programming style tends towards cache-friendly arrays and bulk allocations, while natural functional programming style tends towards lots of pointers pointing everywhere. Certainly, there are functional languages with efficient array libraries and the like, but they are less natural, and their use is often considered an "optimisation". And of course, most functional languages give you some way of accessing raw memory and essentially just writing C-in-Haskell or whatever, but then you're not really doing functional programming anymore.

Of course, if the problem is at its essence about pointer chasing, or composing IO pipelines, then functional programming is a fine choice, because the performance of the language is less important, and the ability to reason about complicated control flow is important. What I find interesting about Haskell is that due to the clear reification of IO, the compiler can actually perform optimisations on IO pipelines, such as fusion. Think about it - IO is usually the prime example of a fusion inhibitor, but GHC can actually do it for libraries such as conduit! It is a little ironic that Haskell is probably the best language I know of for describing complex IO operations.

An interesting twist is of course when you construct a functional language with an eye towards efficient compilation from the start. Then the primary compound data type is no longer the linked list, but the array, and you tend to end up with an array language. Examples include SISAL, Single Assignment C, NESL, Accelerate, Futhark, Lift, etc, which are naturally fairly efficient due to their programming model, and which provide strong functional invariants that the compiler can then exploit. These .anguages are all still pretty experimental and unwieldy in practice, though.

Re:Why Functional Programming Matters (1984) [pdf] by bad_user on Thursday December 08, 2016 @10:05AM (#13130684)
And it's not just about linked lists vs arrays, but also about the actual call stack.

FP code tends to be heavy on recursive algorithms, which means you need mutually tail recursive functions. And laziness. Somebody mentioned the State monad and the IO type. Well, these are lazy abstractions with a memory-safe bind/flatMap operation that makes them tick.

Well, the problem with these abstractions is that they'll require building a data structure that cannot be an array. And in the case of languages like Scala or Clojure that don't have real tail recursion because of the JVM, you need to manage your own trampoline as well. We now have the Free monad, which is awesome, except that it is very heap unfriendly.

Actually for FP you need persistent data structures everywhere, not just lists, like maps, hashes, vectors and all known implementations are some sort of trees. And this is an active area of research, but building cache friendly trees is a hard problem.

Re:Why Functional Programming Matters (1984) [pdf] by omaranto on Thursday December 08, 2016 @11:58AM (#13131765)
Saying that Clojure and Scala don't do tail call optimization because of the JVM could be a little misleading. The JVM is a pretty general compilation target, I'm sure you could implement tail call optimization in a compiler targeting the JVM in any of the usual ways. If Clojure and Scala don't do it it must be for some other reason. If I had to guess I'd say it's probably (1) that the developers don't think TCO is that important (lots of language implementations don't have it and are perfectly usable), (2) for ease of implementation, (3) for better interop with Java libraries, or a mix of these reasons.
Re:Why Functional Programming Matters (1984) [pdf] by johncolanduoni on Thursday December 08, 2016 @12:38PM (#13132188)
You can implement tail recursion optimization on top of the JVM (which is far, far away from a general compilation target BTW), where the distinction is that the function may only call itself. These cases can always be trivially rewritten into loops, and I know at least Scala does this just fine.

More generally tail call optimization allows you to avoid pushing a stack frame for any call in tail position. The JVM (or even LLVM) does not give you the necessary tools to implement this yourself, since you can't play games with your stack frame and return address directly.

Re:Why Functional Programming Matters (1984) [pdf] by omaranto on Thursday December 08, 2016 @05:37PM (#13134954)
An example of a language implementation that does full TCO and targets the JVM: http://sisc-scheme.org/

I couldn't find this right away, but vaguely recall reading SISC transforms to CPS to anable this (and call/cc).

I think Kawa scheme supports two calling conventions: the standard JVM one and a second one for which it can do full TCO. The second one is a bit slower so the default in Kawa is not to do full TCO.

Re:Why Functional Programming Matters (1984) [pdf] by omaranto on Thursday December 08, 2016 @05:23PM (#13134854)
I think you're implicitly thinking that a sensible compiler targeting the JVM would compile whatever functions the source language has to JVM methods and call them via the usual JVM method call instruction. That's certainly a good thing to do if you want have simple Java interop and to reuse the JVM's call logic instead of simulating your own calling convention with other instructions.

I was implicitly thinking there's nothing forcing you to use the JVM calling convention. For example your compiler could transform the source to CPS. That probably would generate slow code and interop would be awkward (because normal Java libraries aren't already in CPS).

Re:Why Functional Programming Matters (1984) [pdf] by gengstrand on Thursday December 08, 2016 @10:51AM (#13131031)
It appears that your claim "Scala or Clojure that don't have real tail recursion because of the JVM" is in error.

http://stackoverflow.com/questions/1677419/does-scala-suppor...

http://clojure.org/reference/special_forms#recur

Re:Why Functional Programming Matters (1984) [pdf] by bad_user on Thursday December 08, 2016 @11:30AM (#13131447)
I specifically mentioned mutually tail recursive functions. Self-recursive functions, like what you're describing, are much less useful or interesting.

I say that as a Scala developer that loves Scala and the JVM - the lack of tail calls optimization at the JVM level is a pain in the ass, because in order to work around it you have to effectively build your own call stack.

Re:Why Functional Programming Matters (1984) [pdf] by tel on Thursday December 08, 2016 @11:17AM (#13131312)
It's not exactly an error. Neither language offers enough tail recursion to handle complex cases though they both offer simple support.
Re:Why Functional Programming Matters (1984) [pdf] by lmm on Thursday December 08, 2016 @12:25PM (#13132071)
As your own link makes clear, Scala does not have real tail recursion because of the JVM. It has a very limited form of tail recursion.
Re:Why Functional Programming Matters (1984) [pdf] by pka on Thursday December 08, 2016 @08:51AM (#13130228)
> This means we can exploit any efficiency trick an imperative program can do, including low-level stuff. The seasoned FP programmer will then proceed to encapsulate the efficiency trick in a abstract module such that the rest of the program doesn't have to worry about it.

You can do that in a pure language as well (i.e. ST monad.) Purity shouldn't be given up lightly.

Re:Why Functional Programming Matters (1984) [pdf] by marcosdumay on Thursday December 08, 2016 @09:29AM (#13130441)
I think he was talking about something like unsafePerformIO. There are plenty of tricks you can not write on pure monads, and there are many Haskell libs that use the unsafePerformIO trick too.
Re:Why Functional Programming Matters (1984) [pdf] by pka on Thursday December 08, 2016 @09:57AM (#13130626)
> There are plenty of tricks you can not write on pure monads

This made me curious, could you list some?

Re:Why Functional Programming Matters (1984) [pdf] by marcosdumay on Thursday December 08, 2016 @11:56AM (#13131733)
Anything that needs rewriting a memory address without losing time by allocation and garbage collection.

Handling IO exceptions and masking them as different errors.

Thread, process, or machine coordination in parallel execution.

Re:Why Functional Programming Matters (1984) [pdf] by pka on Thursday December 08, 2016 @03:21PM (#13133909)
You don't need unsafePerformIO for any of these.

> Anything that needs rewriting a memory address without losing time by allocation and garbage collection.

ST gives you mutable references. Or just use IO.

Re:Why Functional Programming Matters (1984) [pdf] by marcosdumay on Thursday December 08, 2016 @07:03PM (#13135544)
You need it for doing what the OP is talking about.
Re:Why Functional Programming Matters (1984) [pdf] by kazinator on Saturday December 10, 2016 @09:09PM (#13149197)
ST monad is not state; it's a convoluted way of threading a value through a chain of functions. State means we put the value into the same memory location (or CPU register, or piece of tape or whatever).
Re:Why Functional Programming Matters (1984) [pdf] by pka on Wednesday December 14, 2016 @05:46PM (#13180412)
ST is exactly that. You probably mean the State monad.
Re:Why Functional Programming Matters (1984) [pdf] by johncolanduoni on Thursday December 08, 2016 @11:10AM (#13131242)
> There are trade-offs between the two, but I definitely belong to the second school: we simply add an imperative subset to our functional language. This means we can exploit any efficiency trick an imperative program can do, including low-level stuff. The seasoned FP programmer will then proceed to encapsulate the efficiency trick in a abstract module such that the rest of the program doesn't have to worry about it. Of course, the price to pay for this is that you are losing purity. I think this is a fair trade-off but others disagree.

I think the trade-off is unnecessary. If your language can embed a convenient method for analyzing the imperative state in the way it is typically used (F* is a good example), you can get a similar result by lifting your internally stateful but externally pure code into truly pure functions, even if it wants to do something more complicated than simpler state monads can do conveniently. The compiler can then understand the language's state construction and write the imperative code directly when generating machine code, with little more infrastructure than OCaml needs to allow you to do so anywhere.

This way you get to keep purity without requiring you to lose performance or jump through hoops to keep your pure code downstream of your impure code.