Hacker News new | ask | show | jobs
by throwaway7645 3462 days ago
Best example I heard was in an F# talk. The guy used a bar tending analogy:

FP => I'll have a Sazerac

Imperative => Excuse me sir, could you take some ice, add rye whiskey, add bitters, add absinthe, shake, strain into a glass, and add a lemon garnish before bringing it to me

6 comments

Well, isn't this nice - outsourcing the knowledge what makes Sazerac, and how to make it to somebody else, and just declaring that you want it?

Would you mind actually making Sazerac in your FP "analogy" as well?

I like Clojure, but stopped using it due to not having a good way to define DTOs at a service level (I prefer noisy statically typed languages apparently). Best guess.

  (-> {}
    ice
    (rye :2-fingers)
    bitters
    absinthe
    shake
    strain
    garnish)
Now I think that strain flipped the returned type from drink to a glass with the drink.

All this shows is that OO and FP are duals [1]. I don't claim to get FP perfectly, but my moment of zen was realizing this.

1 - http://wiki.c2.com/?ClosuresAndObjectsAreEquivalent

Thank you, I had not seen that c2 page. Many in the JavaScript community have fervent arguments for/against closures/objects (which I do not share). The educated debate in that link is a quality resource on the subject.
forgot the sugar!
Haha...didn't think so many people would respond. There is a Lisp example below. F# is similar, but with pipes and arrows.
Imperative (especially Java-style OO-imperative) programming will just about always win over non-OO declarative programming in an analogy like that (i.e. a Simulation of a real world process) by nature of them being focused on step-by-step "world manipulation" (and object interaction in case of OO).

The value for FP comes from proper abstraction over these processes in functional terms, at which point they can be trivially implemented (few bugs, few iteration cycles to get right). This can probably be done for every problem space, question is at which the abstraction costs outweight the gain. Considering that FP becomes more and more mainstream it's probably more viable than thought in the past, still I imagine system-driven games or complex real-world simulations, with lots of side effects, would lose more from FP than they'd gain.

> Imperative/OO [...] programming will just about always win over non-OO declarative programming in an analogy like that (i.e. a Simulation of a real world process)

Some time ago I thought that, too, but I'm no longer convinced. Directly mutating values (OO style) feels more natural at first, but then you have trouble with side effects and order of execution matters more than it should, you start keeping snapshots of the whole state just to get a consistent world state during computation, otherwise this whole mess produces a whole class of bugs on its own. These problem drive you more and more into FP direction, and the FP style definitely does have its merits in this regard.

I think the following article articulates this very well:

"A Worst Case for Functional Programming?"

http://prog21.dadgum.com/189.html

FP is more like: drink(sazerac(garnish(strain(shake(absinthe(bitters(whiskey(ice(glass)))))))))

Ultimately it's the same result? The difference is when you can reuse and compose functions.

Exactly.

FP you start with the methods and just keep composing. With OO you start with classes and objects.

In OOP you compose nicely as well. It is also easier to understand because it is just conversation.

Me(Drinker)->drink( Sazerac(Cocktail) ->garnish() ->strain() ->shake() ->absinthe() ->bitters() ->whiskey() ->ice() ->glass() )

I would probably use threading to achieve comparable readability.

  (def sazerac 
    (-> (mix :ice :whiskey :bitters :absinthe)
        shake
        strain
        garnish))

  (drink :me sazerac)
I never had a Sazerac. It sounds like a nice drink.
I would create a Cocktail data structure and an instance of Monad for it.

    sazerac = do
        add ice
        add ryeWhisky
        add bitters
        add absinthe
        shake
        strainInto glass
        add lemonGarnish

    main = serve $ makeCocktail sazerac
Haskell really is a pretty nice imperative language sometimes.
That isn't composition to me but sequencing. Function composition yields a function, not the result of applying the functions.

Either you have an object with all of garnish(), strain() and whatnot on it, or each method returns the object to handle the next step in the chain. Either methods don't scale at all without modifying existing code.

The real difference is that function composition gives you a reusable function you can further compose while objects keep piling up methods until you're left with god objects or indirection hell.

And people have the cheek to complain about parens in Lisp...
Its ironic because foo() and (foo) have the same number of characters; but the later is actually data you can manipulate directly.

Reminds me of the blub paradox in beating the averages[1].

[1]: http://paulgraham.com/avg.html

I once had a comment where I translated a clojure function into it's equivalent syntax in python. It was still pretty hard to read. I think its about how lisp uses function composition for everything makes code hard to parse until you get good at it. Even with the standard practice is to hide it with macros and many small functions.

https://news.ycombinator.com/item?id=11174946#11177360

I like more forward-building, pipe style FP. Reading outwards on the statement level still makes things hard for me.
fluent interfaces from OOP are different than composition because they mutate-and-return the object reference; if the instance is aliased anywhere else in the program there is spooky-action-at-a-distance. Composition is about programming with values without mutation. As far as syntax goes it is a trivial macro to translate `x.f().g()` into `g(f(x))` (clojure actually provides it : http://clojure.org/guides/threading_macros)
> they mutate-and-return....

Not necessarily; you can easily write instance methods which merely copy the existing object.

If you want to create a bar food (Finger Food object, not Cocktail), which also could use a garnish() method, which inherits from which? Or should both objects have a father, Garnishable object to inherit from? It's clear to me that object composition is less flexible than functional composition.
You can have trait/interface. But since garnish will be doing different thing it is OK to have two implementation. FP looks nice in theoretical examples in real world not so much.

In FP you will end with garnishFingerFood and garnishCocktail because you need to encode somewhere a specifics of garnish action. In OOP you will have garnish methods on Coctail and FingerFood and specifics and related knowledge how you need to perform garnish will be on object itself.

OOP is really powerful concept but failing in languages that have shit implementation. Java, C++ forsake OOP principles for "performance" or are made by people that do not understand concepts (Python, PHP).

From the specific example you're presenting, I don't see how you couldn't just have a Garnishable typeclass, that implements garnish differently for the FingerFood or Cocktail types.

The comment that FP isn't nice in the real world is pure baloney. For lots of "real world" IO-bound types of problems there is nothing better suited than a functional programming language with powerful abstractions. Things like monads let you write code in an imperative style without losing any of the benefits of writing in the functional paradigm.

OOP doesn't actually have the market cornered on polymorphism. As my sibling replies show, FP languages have long known how to achieve it.
> In FP you will end with garnishFingerFood and garnishCocktail because you need to encode somewhere a specifics of garnish action. In OOP you will have garnish methods on Coctail and FingerFood and specifics and related knowledge how you need to perform garnish will be on object itself.

You're complecting. In the real world of Clojure, you could simply define a protocol and provide different implementations of "garnish".

You just have the method as an aspect that you import into the class ;)
Generic functions: functional yet dynamically dispatched.
Generics aren't dynamically dispatched. Java has type erasure and C# emits variants for every instance.
The example is just about abstraction.

The imperative equivalent would be:

"Give me a Sazerac!"

(imperative, hehe)

"It is imperative that you give me a Sazerac!"
Some time ago at University, we had to develop a puzzle solver in FP. In the report's introduction, I wrote something like "In this project, we must code imperatively in the functional programming language OCaml". The joke was well received.
First of all, that's not how you make a Sazerac. You rinse the glass with absinthe and toss the absinthe, you don't add it to the mix. There's also an ordering dependence: the rye and bitters can be mixed in either order, but adding ice, shaking, and straining should happen in that order with nothing in between, because the longer the warm ingredients are in contact with the ice, the more you're watering down the drink. I'm not going to go so far as to say the lemon garnish is wrong, but an orange peel rubbed on the rim and then garnished is better IMHO.

Second, that's not functional programming, that's calling a library function.

I think a better analogy would be:

FP => I'll have a Sazerac

Imperative => Serve me a Sazerac

Imperative programming isn't devoid of abstractions; it just has different ones.