Goodbye, Object Oriented Programming

Trending - https://medium.com/@cscalfani/goo...ogramming-a59cda4c0e53#.x4smtqmkn

Edited by Abner Coimbre on Reason: Formatting
Maybe we should just move the "Function Programming sucks too" argument over here where it will be more fun?
If you (and I mean you, dear reader, not specifically anyone on this thread) haven't already read it, stop what you're doing right now and read Fred Brooks' classic essay, No Silver Bullet.

Note that this was written at a time in history when OOAD was one of many technical fads (anyone remember Warnier-Orr diagrams, or Jackson structured programming?) which were not yet mainstream. This was the tail end of the "software crisis" and five years before the "three amigos" started the modern OO takeover of almost all new programming languages developed since 1994.
EDIT to remove double post. Could a moderator please remove this? Thanks.

Edited by Andrew Bromage on Reason: double post
timothy.wright
Maybe we should just move the "Function Programming sucks too" argument over here where it will be more fun?


Both can be nice in moderation. Object hierarchies that are 2 levels deep, functional approaches when it doesn't kill performance or require a mess of code to do what needs doing.

There are some nice 'functional first' languages out there that make for a nice middle ground (F#, OCaml, Rust, many others I'm sure) that, if you put the GCC/Clang compiler brains on them for a couple years would be reasonably performant. Rust already is decent but still has some things missing (solid SIMD for instance) All of these languages allow you to drop out of functional land and just do loops and mutable variables whenever you want with no fuss.

I've been doing some experiments with F# vs C lately and had some interesting results, where I can get very close to C level performance with functional approaches to number crunching on arrays - but, I have to roll my own fold/map operations. The core libs aren't marked as inline, the compiler isn't smart enough to inline them automatically if it is small. The compiler won't vectorize a loop, ever, so you have to do that by hand. The core libs don't use lazy evaluation so stringing list operations together iterates over them multiple times, etc. You can fix all of this if you roll your own map/fold/reduce etc operations for Arrays but...if you have to do that, why bother using a functional language? You can roll these things in C too.

whereas the very most obvious C code, a for loop with the operations I want in it compiles straight to automatically vectorized code that is super fast. There is no reason the same couldn't be true for F#, if the .NET JIT team made performance a priority, and the F# compiler team made performance a priority. This is an example of where someone using the language might say "Man F# is slow, probably because garbage collection or because virtual machine", but no, really it is because the ecosystem don't put performance at a really high priority. So the obvious F# code is ~5 times slower than the obvious C code. The clever F# code is just 1% slower. That gap between 5x and 1% could be erased, and the universe would be better. The gap should be even smaller with OCaml (natively compiled) and smaller again with Rust (no GC) if they can mature.

Apologies for the random stream of consciousness.



timothy.wright
Maybe we should just move the "Function Programming sucks too" argument over here where it will be more fun?


For those wondering about the "argument", it starts here: https://twitter.com/AbnerCoimbre/status/757259848404856832
MandleBro
timothy.wright
Maybe we should just move the "Function Programming sucks too" argument over here where it will be more fun?


Both can be nice in moderation. Object hierarchies that are 2 levels deep, functional approaches when it doesn't kill performance or require a mess of code to do what needs doing.

There are some nice 'functional first' languages out there that make for a nice middle ground (F#, OCaml, Rust, many others I'm sure) that, if you put the GCC/Clang compiler brains on them for a couple years would be reasonably performant. Rust already is decent but still has some things missing (solid SIMD for instance) All of these languages allow you to drop out of functional land and just do loops and mutable variables whenever you want with no fuss.

I've been doing some experiments with F# vs C lately and had some interesting results, where I can get very close to C level performance with functional approaches to number crunching on arrays - but, I have to roll my own fold/map operations. The core libs aren't marked as inline, the compiler isn't smart enough to inline them automatically if it is small. The compiler won't vectorize a loop, ever, so you have to do that by hand. The core libs don't use lazy evaluation so stringing list operations together iterates over them multiple times, etc. You can fix all of this if you roll your own map/fold/reduce etc operations for Arrays but...if you have to do that, why bother using a functional language? You can roll these things in C too.

whereas the very most obvious C code, a for loop with the operations I want in it compiles straight to automatically vectorized code that is super fast. There is no reason the same couldn't be true for F#, if the .NET JIT team made performance a priority, and the F# compiler team made performance a priority. This is an example of where someone using the language might say "Man F# is slow, probably because garbage collection or because virtual machine", but no, really it is because the ecosystem don't put performance at a really high priority. So the obvious F# code is ~5 times slower than the obvious C code. The clever F# code is just 1% slower. That gap between 5x and 1% could be erased, and the universe would be better. The gap should be even smaller with OCaml (natively compiled) and smaller again with Rust (no GC) if they can mature.

Apologies for the random stream of consciousness.





Without real code to substantiate this its not very useful for discussion.
Task: take an array, return the sum of the square of all the items

Obvious C code that ends up AVX2 vectorized by Visual C++
1
2
3
4
5
double sum = 0.0;    
for (int i = 0; i < COUNT; i++) {
    double v = values[i] * values[i];  //square em
    sum += v;
}


Obvious F# code which is very slow
1
2
3
4
  let sum =
        values
        |> Array.map square
        |> Array.sum


3rd Party Streams Library which takes advantage of the pure functions by using lazy evaluation so you only iterate over the collection once. Why isn't this part of the Core library, since this is a functional first language? This is faster but still slow

1
2
3
4
 let sum =
        values
        |> Stream.map square
        |> Stream.sum


This particular task doesn't really need to be two separate operations, we can just use reduce, or fold. If I roll my own reduce identical to the core library but tag it 'inline', it goes twice as fast. Why doesn't the F# compiler or the JIT inline these small function anyway, regardless of the hint?
1
2
3
 let sum =
        values
        |> Array.fastReduce addAndSquare



But still no vectorization because the .NET JIT team willfully made that not a goal. So we do it by hand.
"Clever" F# code which is just a wee bit slower than the C code
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
  static member inline SIMDFold f h (start:'T) (values : 'T[]) =        
        let mutable i = 0;
        let mutable v = Vector<'T>(start)
        while i < values.Length - Vector<'T>.Count do            
            v <- f v (Vector<'T>(values,i))
            i <- i + Vector<'T>.Count
        i <- 0
        let mutable result = start        
        while i < Vector<'T>.Count do
            result <- h result v.[i]
            i <- i+1
        result

 let sum =
        values
        |> Array.SIMDFold addAndSquare (+) 0.0



And yes I realize this is a toy example, and it ignores the cost of allocating and freeing the array and so on. It made me think though of how JBlow often recommends just 'typing the obvious code', which in C (and hopefully JAI) does tend to result in pretty good performance, often enough. But in higher level language, often not at all. I end up having to type *more* which is counterproductive.

Although! That SIMD code will appropriately work at runtime with AVX2 or SEE4 or SSE2 or whatever automatically which is nice. Though I guess C++ libraries exist which will do that too.


Edited by Jack Mott on
The problem that I've always had with functional programming is its stateless nature. Functions are pure, contain no state, and never modify any variable, they just take inputs and compute outputs.

I made a recursive descent parser in College, using the M language, which was rather quite fascinating. But it was a small program, and while interesting from a computer science aspect, it was useless in the real world. I have yet to find a single example of a function program out in the wild that was just as good as the same program in C, let alone an order of magnitude better. Every one I have used had to "cheat" and use state somewhere, and ultimately ended up a complicated, un-debuggable pile of code.

"Functional programs are and order of magnitude better than procedural programs because the are and order of magnitude shorter."
Why Functional Programming Matters, by John Hughes.

I really have trouble with this idea that functional programming will be the new "Silver Bullet." It touts the same modularity , testablility, and code reuse that OOP did. Sometimes I think it would be better to just stop working on function languages until we find a problem that they actually solve.
It would be very remiss of me to not quote John Carmack here:

Carmack
My pragmatic summary: A large fraction of the flaws in software development are due to programmers not fully understanding all the possible states their code may execute in. In a multithreaded environment, the lack of understanding and the resulting problems are greatly amplified, almost to the point of panic if you are paying attention. Programming in a functional style makes the state presented to your code explicit, which makes it much easier to reason about, and, in a completely pure system, makes thread race conditions impossible.
.
.
.
C++ doesn't encourage functional programming, but it doesn't prevent you from doing it, and you retain the power to drop down and apply SIMD intrinsics to hand laid out data backed by memory mapped files, or whatever other nitty-gritty goodness you find the need for.


Quote Source

To the extent performance requirements are met, actively going functional is a worthy goal to me. Just, you know, don't do it when you're in the space of things like writing bytes of output on a serial port.

-Abner

Edited by Abner Coimbre on Reason: Added signature
Right, which is why I was talking up languages that don't do anything to enforce purity of functions, they just make it easier to do so if you wish. I suppose there is a mild pain point in that you have to mark things that you want to be mutable, rather than marking things you want to be immutable. Depending on which is more common for your use case.

Of course you can code in a functional style in ANSI C or C++ too, but some things are made more difficult. However I have noticed the C die hards have no problem with typing more to get things done!

timothy.wright
I have yet to find a single example of a function program out in the wild that was just as good as the same program in C, let alone an order of magnitude better.

It depends what you mean by "as good".

There have been attempts to test this in controlled (i.e. realistic but not real) conditions. Take it with the appropriate amount of NaCl.

timothy.wright
Every one I have used had to "cheat" and use state somewhere, and ultimately ended up a complicated, un-debuggable pile of code.

I've done a lot of programming in Haskell and I do concede that my early code did look like that, because I was insufficiently indoctrinated. The orthodox answer is that the programmer was doing something wrong. Well-written functional programs move the impure/semi-pure statefulness to the edges of the program, not in the middle.

There are principled ways to simulate state if you really need. Monads may be appropriate. For interaction, you may want to use the actor model.

I kid about being indoctrinated, of course, but there's a grain of truth here. Forcing myself to program in this ways, and truly understanding how to do it "right", has improved my C++ code no end. It's usually not hard to make my code multi-threaded now, because it's written the right way. Even operations which change shared state are expressed as two-phase commits where possible, to limit the amount of synchronisation to an absolute minimum.
I am really starting to like the Handmade Network and the people it attracts. This is the first usefully discussion I've had on FP vs others, EVER.

Some of that information is very helpful. I'll be back after digesting it.
Re: languages which prioritize functional but allow other stuff, many many years ago I tried Ocaml for that reason, but was burned by it (at the time at least) not having sufficient support for imperative stuff; for example, you couldn't return a value from the middle of a function.

James Hague's blog is periodically intermixed with posts exploring function programming and looking at the real interactions w/ using such a language in games and similar, finding ways to make it fit with the grain of the function language. I'm not at all sold, but I think it should be required reading for people who want more functional programming in their lives without jumping off the deep end. There's no master link to all of them, but his blog is worth poking around, and here's the first one: http://prog21.dadgum.com/23.html
Yeah return values from the middle of functions is not possible in the way you are used to because most functional-first languages treat everything as expressions. So the downside is that something like:

1
2
3
4
5
6
7
void someFunc (int x)
{
  //operations on x here
  if (x < 1) return x
  //more operations on x here
  return x
}


becomes something like this in OCaml/F# etc
1
2
3
4
5
6
7
let someFunc x =
  //operations on x
  if x < 1 then
    return x
  else
    //more operations on x here
  x


This can sometimes get cumbersome, but on the bright side you can do things like this:
1
2
3
4
5
6
7
let foo = 
  if x < 1 then
    "small"
  else if x > 10 then 
    "large"
  else 
    "just right"


or with pattern matching syntax, where incomplete coverage becomes a warning
1
2
3
4
5
let foo =
  match x with
  | x when x < 1 -> "small"
  | x when x > 10 -> "large"
  | _ -> "just right"



It goes along with paradigm shifts that the things you are used to, if you can't do them, you want flip the table over in anger. But sometimes there are are equivalent ways you just aren't used to, or there are benefits that make it worth it overall.

I don't pretend to know if the 'everything is an expression' choice is worth the pain points or not, and overall if you are used to being able to extract near infinite performance with C, there isn't yet a functional-first style language that lets you do that. Closest would be Rust and OCaml but both have some work to do.

Rust lets you return early, btw, while still allowing use of if statements as expressions.


Edited by Jack Mott on