

Fusion makes functional programming fun - dons
http://donsbot.wordpress.com/2010/02/26/fusion-makes-functional-programming-fun/

======
asolove
Summary: side-effect free programming allows compilers to transform high-level
code into very efficient machine code.

I've always wondered about the utility of this in exploratory programming. We
always hear how great Lisp is because of dynamic typing, macros, etc., but
once you kind-of know the solution you go back and code it efficiently. If
you've implemented your code in terms of type classes in the first place, how
much easier is it in Haskell to just change a few types of the inputs (List ->
Vector, Integer -> Int#) and get a performance increase?

~~~
jgrant27
You simply do not know Lisp. Lisp is a term to describe different dialects in
a family of languages.

For example : Common Lisp is a multi-paradigm Lisp that supports dynamic
typing AND static typing. Haskell has NO equivalent to Common Lisp's macros.
Template Haskell is not equivalent Lisp's macros. Often a Common Lisp
implementation will simply require type hints to be added to gain large
performance improvements. Clojure(another Lisp dialect) is the same this way.

~~~
dons
> Template Haskell is not equivalent Lisp's macros

Well, Template Haskell _is_ a compile time meta programming system based on
quasi quotation and AST manipulation...

~~~
jgrant27
They are not equivalent. In Lisp there is no distinction between the syntax
and the AST. In Haskell the AST is modeled using explicit data types.

<http://www.haskell.org/bz/thdoc.htm> : "In Template Haskell, ordinary
algebraic data types represent Haskell program fragments. These types modeled
after Haskell language syntax and represents AST (abstract syntax tree) of
corresponding Haskell code. There is an Exp type to represent Haskell
expressions, Pat – for patterns, Lit – for literals, Dec – for declarations,
Type – for data types and so on. You can see definitions of all these types in
the module Language.Haskell.TH.Syntax."

~~~
jrockway
This is a good thing, though. With Common Lisp, you have to emulate this via a
code-walking library -- and because the code-walking library just uses
heuristics, you can never be sure that your macro is right in all cases. With
well-defined data types for each AST node, this problem goes away.

The advantage that CL-style macros have is that they are much easier to use
than TH. For me, anyway, writing a Lisp macro is a trivial thing that involves
almost no thinking. Writing a TH macro takes some testing and debugging.

~~~
jgrant27
There's no need to 'emulate' anything 'via code-walking' and 'heuristics' when
writing Lisp macros. This is a personal use case of your own.

If you know how to write Lisp macros(i.e. experience) then they will be
correct. Not all Lisp macros are trivial to implement even for the experienced
Lisp hacker. Writing a Lisp macro requires testing and debugging. The
difference is that Lisp inserts no 'airbags' between the language and the
programmer. They are very powerful but you may 'hurt' yourself.

~~~
gwern
> If you know how to write Lisp macros(i.e. experience) then they will be
> correct.

Is this the same argument as 'C is an insecure language only if you are a bad
C programmer'?

~~~
jgrant27
No. The statement simply means that experience is important. The same holds
for any language that you use.

------
colomon
Their code is

    
    
        import qualified Data.Vector as U
    
        main = print . U.sum $ U.enumFromTo 1 (100000000 :: Int)
    

Possibly it's just because I don't grok Haskell, but that strikes me as
incredibly ugly for such a simple operation. As a contrast, here's the Perl 6
version:

    
    
        say [+] 1..100000000
    

(For sure there is no Perl 6 implementation that will run the code as fast as
they show GHC running it -- odds are, in fact, that the current Perl 6
implementations will be drastically slower.)

I suspect there is a more elegant version in Haskell, too -- it probably just
doesn't run nearly as fast. This has been one of my main gripes with my
experiments with functional languages. It seems they are always showing you
this beautiful elegant code, and then explaining how if you want it to be
efficient, you need to use a different, much uglier, approach.

Of course, the other thing with this is the example is completely unrealistic.
If you actually wanted to compute that number, the fast way to do it is to use
the good old formula

    
    
        (N * (N-1))/2
    

If for some reason you wanted to compute that sort of thing a lot, to would
make a lot more sense to make a simple function to calculate that...

~~~
dons
I'm sorry I used a trivial example -- the point was to show how the assembly
corresponded to the fused loop, which necessitated a simple example. And this
is the internet, so everyone tries to find a closed form for everything. Which
misses the point!

Of course, the Haskell:

    
    
       print (sum (enumFromTo 1 n))
    

isn't exactly bad. And you can use any of these other 134 functions to get
fused loops:
[http://hackage.haskell.org/packages/archive/vector/0.5/doc/h...](http://hackage.haskell.org/packages/archive/vector/0.5/doc/html/Data-
Vector.html#1)

And there's lots of interesting problems you can solve with that rich a set of
primitives.

This isn't an example _benchmark_ , it's an example translation, so you can
see the optimization at work. Bigger examples yield assembly that no casual
reader could follow -- so don't get distracted by the fact there's a closed
form!!

~~~
colomon
Glad to see there is a prettier way of doing it in Haskell!

Do I understand then that this basically building up a sort of parallel
language for expressions in Haskell which optimize really, really well?

~~~
dons
In a way. It's a subset of Haskell focused on array operations that optimize
really really well. You can use these instead of list operations, basically,
and getter performance in all sorts of ways.

~~~
eru
You could get very good performance when you do the fusion manually, too. The
really exciting thing about fusion is that it allows really fast code that
_composes_ well.

Composability is also what makes Haskellers excited about Monads and software
transactional memory (STM). STM give composable concurrency --- in contrast
with locks which are notoriously uncomposable.

------
cousin_it
Cool! But what if I write a more complex program that I want to perform well?
Is it easy to understand where the compiler will or won't do this crazy fusion
thing?

~~~
dons
It's pretty easy to work out where fusion is going to happen, since it happens
compositionally. You compose fusible functions, and the (.) between them is
syntactically where fusion occurs. GHC will tell you how many fusion sites
were found.

~~~
eru
You can also add your own fusion rules, as far as I know.

