
Do I really want to be using a language where memoize is a PhD-level topic?  - signa11
http://groups.google.com/group/clojure/msg/c2a9e8d27a1fff3f
======
ekidd
Not as my _only_ programming language, no. But in general, why not? I value
programming languages for what they can teach me. Haskell does make state
slightly difficult. (If you need it, just use the IO monad, already. It
doesn't bite.) But Haskell makes certain other things easy, things which are
extraordinarily difficult in almost any other programming language.

For most of my professional life, I've been paid to program in Common Lisp,
Scheme, Dylan, etc. Macros, to me, are an ordinary and natural part of
programming. But after a while, macros become boring: 99% of them are just a
thin syntactic wrapper over something I already know.

In Haskell, you don't build higher-order abstractions using macros. Instead,
you build your higher-order abstractions using math. And math is almost
entirely stateless, lazy and functional. You are forced to think in terms of
combinators, abstract algebras, algebraic optimizations, and, yes, category
theory. Category theory is the closest link between the lambda calculus and
mathematical logic, for example, allowing you to transform some very exotic
programming paradigms into actual code.

So, yes, Haskell is hard, and it's an ongoing research project. You will spend
a lot of time reading papers. (And frankly, you don't want to maintain
somebody else's code if you haven't read the same papers.) But some of those
papers and theses have blown my mind more in a single weekend than some entire
years of hacking in Lisp.

Haskell is not the most practical language I know. (State is not the biggest
problem for me, but rather the lack of subtyping.) But Haskell is the language
I'd be saddest to forget, and the language which has stretched my mind the
most.

~~~
jakevoytko
There is a vast gulf between learning and using.

The original poster obviously enjoys learning new things: he posts on the
Clojure mailing list, and hints that he has learned bits and pieces of
Haskell. That places him early in the adoption curve. But when you want a
language to get things done (one you can "use"), work from a comfort zone -
start with a steady base, and branch out to unfamiliar topics. Some people are
comfortable finding and reading academic papers as part of their workflow, and
others aren't. That's their preference.

Other parts of your time should be spent the opposite way: start at the
unfamiliar, and make it familiar. But it's not pragmatic to always work like
this.

------
klodolph
A couple years back I read a blog post with someone complaining about how hard
it was to implement the traditional Lisp "map" / "mapcar" function.
Specifically, one that took N lists and a single function with N arguments.
The author was totally right, too. I could implement this generalized "map"
function in Haskell, but it required defining an extra type class which is a
little extreme.

Of course, the real problem is that people who are new to Haskell expect to
sit down and solve problems in the same way they've been solving problems all
along. This author was used to the "map" function from Lisp, and wasn't
thinking about whether it was necessary, or whether Haskell had a different
way of doing things.

Here's what the "mapN" function does:

    
    
        > mapN (+1) [1,2]
        [2,3]
        > mapN (+) [1,2] [5,8]
        [6,10]
    

The reason that we don't want to use "mapN" in Haskell is because of the
following problem:

    
    
        > mapN (+) [1,2]
        ???
    

The result is either "[Int] -> [Int]" or "[Int -> Int]", with no clue as to
which is correct. This is why Haskell has "zipWith", "zipWith3", ... Not as
pedantry, but because the alternative is ambiguous. (You can use
Control.Applicative + ZipList, but that's a tad verbose).

My conclusion is that Haskell's differences are what throw people off --
programmers have to relearn how to do things they thought they had figured out
for good years ago. (In this case, "everything is curried" means "varargs
requires type classes"). It's FINE if you don't want to learn to do things
differently and I won't think less of you, I respect your choices for what you
do with your precious free time, and I understand that you might want stuff
just to work now without learning anything, just please stop stereotyping the
Haskellers as a bunch of ivory tower PhDs. I'm not even done with my
Bachelor's.

~~~
llimllib
If you want to suggest that the main problem is people "trying to solve
problems in the same way they've been solving problems all along", then you
should have an answer for how you implement memoization "the haskell way".

If not, your "conclusion that Haskell's differences are what throw people off"
is incorrect.

I don't think the author of the email is "stereotyping the Haskellers as a
bunch of ivory tower PhDs", he's relating a story that's actually true of
finding out that a basic technique he wanted to implement was most easily
implemented by following the technique in a PhD thesis.

~~~
astine
Now, I could be very wrong, but it is my understanding that Lazy Evaluation
will provide the main benefits of memoization, without any action on the part
of the programmer.

~~~
jongraehl
That's not generally true, although you can make it work out that way in some
cases (I think threads mean that some work may be duplicated).

~~~
klodolph
Be careful lest thou misunderstand the GHC runtime, which is fairly subtle.

In Haskell, the semantics are unchanged if the runtime evaluates a particular
thunk multiple times. Haskell is fairly unique in this regard. For exactly
this reason, Haskell threads do not need to synchronize when evaluating thunks
in multicore environments. Synchronization has a cost, and that cost can
exceed the cost of duplicate evaluation.

It's a trade off.

------
dons
Long and detailed discussion, including original participants, on Reddit,
[http://www.reddit.com/r/programming/comments/crgxs/do_i_real...](http://www.reddit.com/r/programming/comments/crgxs/do_i_really_want_to_be_using_a_language_where/)

My take away:

* The Haskell culture is to take questions seriously * In doing so, the literature will be cited where appropriate * Some people get turned off by research papers

In this case, the guy asked a fairly profound question, and received a long,
friendly answer, which included references to the literature.

Also, it plays on a stereotype, hence all the upvotes.

~~~
swannodette
Rich Hickey about this thread from the ML -

    
    
      Everyone, please refrain from language wars. Haskell is a tremendously
      ambitious and inspiring language, for which I have the highest
      respect. There is zero point in bashing it (or its users) in this
      forum.

~~~
jrockway
But Haskell users picked the wrong religion! God won't like us anymore if we
don't bash all other languages!

(And is it really "bash" when you accuse the users of being too smart?)

------
skm
Actually, arrays in Haskell are automatically memoized and lazily evaluated.
So in most practical situations you get memoization for free.

For example, imagine you need to calculate a function f(n) for various values
of n (let's say for n ranging from 0 to N). You simply define an array memo_f
such that memo_f!n = f(n). ('!' is the array selection operator in Haskell).

Because Haskell evaluates lazily (i.e. not until it absolutely has to), it
simply stores a link to the definition of the function for each array element.
But once a particular array element is used, that link is replaced by the
calculated answer.

I'll post some actual code to demonstrate this in a minute, just in case
anyone's interested.

~~~
skm
Haskell code: (explanation below)

    
    
        import Data.Array
        
        f :: Integer -> Integer
        f n = sum [0..n]
        
        largeNumber = round 2e6 :: Integer
        
        memo_f :: Array Integer Integer
        memo_f = listArray (0,largeNumber) [ (f n) | n <- [0..largeNumber] ] 
        
        
        main = do
            putStrLn (show (memo_f!largeNumber))     -- slow
            putStrLn (show (memo_f!(largeNumber-1))) -- slow
            putStrLn (show (memo_f!(largeNumber)))   -- fast
            putStrLn (show (memo_f!(largeNumber-1))) -- fast
            putStrLn (show (memo_f!(largeNumber-2))) -- slow
            putStrLn (show (memo_f!(largeNumber-3))) -- slow
            putStrLn (show (memo_f!(largeNumber)))   -- fast
            putStrLn (show (memo_f!(largeNumber-2))) -- fast
            putStrLn (show (memo_f!(largeNumber-4))) -- slow
            putStrLn (show (memo_f!(largeNumber-5))) -- slow
    
    
    

When I run this, I see nothing for about 3 seconds (that's the time spent
setting up the array). Then the numbers commented '-- slow' take about 1/3
second to print, while the numbers commented '--fast' print instantaneously,
because they were calculated previously. (I'm on a recent macbook air, in case
anyone's curious about the timings). Obviously to calculate the entire array
of values would take far too long (like a week or so).

~~~
yaroslavvb
Well, that looks easy...so why are people saying that writing memoization in
Haskell is PhD-thesis level task?

~~~
jrockway
Because memoization is best implemented in the compiler and runtime, and the
person on the Clojure list wanted to implement it in his application.

~~~
silentbicycle
I've seen people get exasperated trying to implement "if" in Prolog, too. _The
whole language_ is already an 'if' / pattern matching engine!

("How do you implement lazy evaluation in Haskell? No, like, in an
application...")

~~~
jrockway
Yeah, lazy evaluation can be surprising (in a good way). Once you get in the
mindset of thinking "this is going to be hard", you start programming Haskell
like you would C, and while it works, you feel stupid when you realize you
just rewrote the Haskell compiler. Poorly.

(Personally, I have done this a number of times. I remember writing some
dependency evaluation system, with a central data structure that looked
something like:

    
    
       data Dependency a e = Thunk (e -> a) | Resolved a
    

with a bunch of code to turn a set of Thunks into Resolved when necessary.

But of course, Haskell always does this anyway!)

I was also surprised when I was on Windows and needed the "head" utility to
look at the first line of a file, but didn't have it installed. Since I had a
ghci session going, I just wrote:

    
    
       readFile "foo.csv" >>= putStrLn . unlines . take 10 . lines
    

Half-expecting it to error out because the file didn't fit into memory. Nope!
Problem solved!

(And yes, I know unsafeInterleaveIO is evil.)

------
fogus
There is (has always been?) an interesting formula for writing academic C.S.
papers: define the most constrained environment imaginable and then do mundane
things in it. Haskell is a very beautiful language in a very pure sense and it
is the perfect garden for C.S. papers. Likewise, it's an incredibly fertile
garden for language features and programming techniques. Is it hard? In some
ways perhaps, but to me Javascript's notion of logical truthiness is extremely
difficult to hold in my head. It's all relative I suppose.

~~~
WilliamLP
> Javascript's notion of logical truthiness is extremely difficult to hold in
> my head.

So it isn't intuitive to you that an empty object and array should both
evaluate to true, but neither are equal to true, the empty array is equal to
false, and the empty object is neither equal to true _or_ false?

~~~
sesqu

      > Boolean([])
      true
      > []==false
      true
      > [] == ![]
      true

How the hell does that work? Aren't all non-booleans implicitly cast?

 _edit: rephrased_

~~~
chc
It's because the == operator converts its arguments to be of the same type,
and it does so in a rather unintuitive way. This makes it both inefficient and
frequently baffling. You pretty much always want === instead, which does not
allow comparisons between different types.

------
loup-vaillant
"PhD" may be a scary, Haskell's theses are not. Most academic paper I've read
about it are quite readable (I don't have a PhD). The only serious
prerequisite for a competent programmer is an introductory course on ML or
Haskell.

------
jacquesm
It may that Haskell will never be a mainstream language, but concepts from
Haskell will find their way in to other languages, and that's good enough.

Not every language has to be a mainstream web-centric enterprise level
application oriented (buzzwords enough like that ?) success in order to be
successful by some other measure. Haskell has always been research oriented
(avoid success at all cost), and if that's the goal then that's fine.

------
greenlblue
This argument comes up against haskell over and over again and I think it is
because it has some merit. Type level hackery and monad wizardry are great but
it just feels like a whole bunch of smart people showing off about how clever
they can be with avoiding state and pushing it all into some high level
categorical constructs.

~~~
yason
I've criticized Haskell many times, and being criticized many times of that.

You see, I would _love to love_ Haskell but in practice the language just
always turns out to be an overly intellectual exercise. Real world programs
need to manage state, preferably in a very controlled way, but the bottom line
is that they need to do that in some obvious and easy way.

It doesn't help if you explain me that you really don't need to explicitly
store state anywhere, that you can just use monads to carry it over from
computation to computation. Monads are an interesting abstraction per se but I
don't want to use such heavy-weight artillery _to make a mere assignment, to
write a pointer to memory_. I want my language to put some effort in
protecting myself from the hazards of unintended concurrency but when I want
to do it, the language must obey.

That said, Clojure is just perfect. For now, Clojure is my Blub.

~~~
loup-vaillant
You _never_ want "to make a mere assignment", nor "to write a pointer to
memory". These are not problems you want solved, they are parts of a possible
solution.

Haskell don't need assignments nor memory writes. It solves problems
(including state management) differently. You may not like that, but don't say
that not using some specific low-level feature is bad by itself.

~~~
yason
It doesn't help to explain state in a very sophisticated way because
eventually a memory write _is_ what you're after.

I'm programming on a real computer that has memory and a cpu and in order for
theory and practice to meet, there must be in the language a concept of
variables that somehow map to the concept of reading and writing memory.
Clojure doesn't have memory access either but it has mutable containers with
very controlled, concurrent accessors.

Assignments and memory writes are very much problems I want to be solved and
I'm just unhappy because Haskell doesn't want to help me with that except by
strictly with its own doctrine.

In Clojure, I can partition the program state into mutable vars and refs at
appropriate levels, depending on the problem and my architecture. Most of my
code is pure and in general, I don't use many of those mutable containers, but
I do.

If Clojure didn't have a solution for that it would remain an academic toy
language.

~~~
applicative
> It doesn't help to explain state in a very sophisticated way because
> eventually a memory write is what you're after.

I'm not sure about memory writes, but people who adopt this 'realistic' view
that 'look it's a machine, it's not math', are often in a state of confusion
about the workings of their own compilers.

I saw this in action recently when I was fiddling semi-competently with the
'threadring' benchmark
([http://shootout.alioth.debian.org/u64q/performance.php?test=...](http://shootout.alioth.debian.org/u64q/performance.php?test=threadring)).
What the requisite program is actually doing is to calculate a certain pure
function of input (something like `x mod 503 - 1`). This would be lightning
fast as ... well:

    
    
        main = head <$> getArgs >>= print . f 
          where f n = n `mod` 503 - 1
    

But the terms of the benchmark are that you do this in an amusing way where
the steps involve passing news from one thread to another in a ring. In
Haskell this is done with `forkIO` and that sort of thing.

When compiled with any of GHC's normal flags, my attempts would chug along on
big inputs evidently actually _doing_ what the terms of the benchmark
required: 503 threads were passing a hot potato around in my cpu.

When I compiled with the 'via C' flag, by contrast, it was lightning fast, a
little slower than the pure function mentioned above.

The reason was plain: the C compiler recognized that I was writing a pure
function and optimized away the ridiculous division of threads that was
imposed by the terms of the benchmark. I'm pretty sure the 'winning' C program
is employing this advantage though the program text is legit, but I don't have
the C chops to prove it. (In any case the benchmark seems to have been
deprecated, rightly -- wish I'd realized that before spending several hours
working on it!)

MORAL: if you _really_ want forking processes, memory reads etc. -- if you
_really_ want to "get your hands on the real machine with real CPU" -- then
.... use GHC, and tell it that's what you want.

Otherwise, you're only imagining it -- and maybe your compiler is doing it,
maybe it isn't; it's none of your business.

~~~
igouy
> the benchmark seems to have been deprecated, rightly

Well, not actually deprecated as such - but neither thread-ring nor chameneos-
redux are included in the which-programming-languages-are-fastest summary.

Mostly because there turned out to be naive parallel approaches for other
benchmarks, for example spectral-norm
[http://shootout.alioth.debian.org/u32q/performance.php?test=...](http://shootout.alioth.debian.org/u32q/performance.php?test=spectralnorm)

------
bpyne
I'd like to look at a different facet of the issue. My employer mostly
implements COTS; our vendors drive our technology choices. The vendors of our
two most critical systems decided on JVM based languages - Java and Groovy -
so we're moving in their direction.

Java is new to most of my department. Currently people use C, SQL, and PL/SQL.
I started with Java in 1998: it's not very exciting for me anymore. I'd like
to use languages with greater abstraction capabilities. Despite being a really
attractive language, Clojure is a tough sell because of its Lisp-like syntax:
being a JVM-based language I can probably still get it in house for small
apps.

Haskell, however, is non-JVM and has unique features by comparison with Java.
This combination make it impossible to make an argument for despite all the
goodness that it has. Unfortunately for me, it's relegated to the category of
a language I'll play with periodically and someday hopefully use in industry.

~~~
cageface
Have you looked at Scala? It's sort of like a Haskell for the JVM. It's very
easy to integrate Scala into a Java project and to gradually learn Scala if
you have a Java background.

~~~
bpyne
Thanks for the reply.

Yes I have. I didn't want to mention it in my original response to avoid
taking the topic away from Haskell and Clojure.

------
WorkerBee
topic is a dud link - it takes me to a login page.

~~~
plinkplonk
here it is so you don't have to log in.

"

On Mon, Jul 19, 2010 at 4:34 PM, Jared <tri...@gmail.com> wrote: > I am
curious; for the people with Haskell experience why did you decide to use
Clojure? I am asking this because Haskell and Clojure seem to solve similar
types of problems. When would you want to use Haskell instead of Clojure and
visa-versa?

[Mark Engelberg replies]

I think stateful things are too hard to do in Haskell, and they are an
important part of most real-world programs. Clojure's blend of persistent data
structures with a variety of reference-type objects that can contain them
feels much more pragmatic to me. Also, I'm just happier working in a
dynamically-typed language.

As a side note, years ago, I wanted to write something in Haskell that worked
like Clojure's memoize (which is implemented in a half-dozen or so lines of
code in Clojure's core), and asked about it on the Haskell mailing list. I was
pointed to a PhD dissertation on the topic of how to write memoize in Haskell.
All I could think was,

"Do I really want to be using a language where memoize is a PhD-level topic?"
"

