A newbie's proud first code

Hi! I'm glad to have run accross Haskell, as a project I've been toying with in my mind seems very well-suited for this. It involves the numeric solution of differential equations. I'm thrilled to see how easy this is, as shown in my first little code snippet: {snip} -- Euler's method for solution of first-order differential equations -- In function arguments, the initial value problem under consideration is: -- df/dt = f'(f, t) -- f(0) = f0 -- The euler function returns an infinite list of the values at each interval -- of length dt of the solution. euler :: (Num a) => (a -> a -> a) -> a -> a -> [a] euler f' dt f0 = euler' f' dt f0 0 -- t "defaults" to 0 -- This function does the actual work, sparing the user from having to supply -- a value for t (which should always be 0 in the initial call, anyway) euler' :: (Num a) => (a -> a -> a) -> a -> a -> a -> [a] euler' f' dt f t = f : euler' f' dt fnext (t + dt) where fnext = f + dt * (f' f t) {snip} (Only three lines of actual code (not counting declarations)! Consider me a convert :-) Yes, you could do it like this in Lisp, I'm sure, but this is so much more succinct and expressive ... and Lisp can't do this cool infinite list stuff, can it?) My only issue (and it is nitpicky) is that I don't really like having to declare an extra function (euler') in the same scope as euler. I tried playing with where clauses or let expressions, but I couldn't find a way to make GHC happy without introducing a bunch of extra variables (since I'd need new versions of the arguments to euler; in euler', I just used the same names. Also, nested "where" doesn't work as I'd like it to.) Is there, in fact, a more concise way to express what I'm doing? Would anyone like to comment on my style (or lack thereof), idiom, etc., so I can squelch any bad habits before they start :-) ? Thanks! Jyrinx jyrinx_list at mindspring dot com

On Monday 05 November 2001 7:27 am, Luke Maurer wrote:
-- Euler's method for solution of first-order differential equations -- In function arguments, the initial value problem under consideration is:
-- df/dt = f'(f, t) -- f(0) = f0
-- The euler function returns an infinite list of the values at each interval -- of length dt of the solution. euler :: (Num a) => (a -> a -> a) -> a -> a -> [a] euler f' dt f0 = euler' f' dt f0 0 -- t "defaults" to 0
-- This function does the actual work, sparing the user from having to supply -- a value for t (which should always be 0 in the initial call, anyway) euler' :: (Num a) => (a -> a -> a) -> a -> a -> a -> [a] euler' f' dt f t = f : euler' f' dt fnext (t + dt) where fnext = f + dt * (f' f t)
My only issue (and it is nitpicky) is that I don't really like having to declare an extra function (euler') in the same scope as euler. I tried playing with where clauses or let expressions, but I couldn't find a way to make GHC happy without introducing a bunch of extra variables (since I'd need new versions of the arguments to euler; in euler', I just used the same names. Also, nested "where" doesn't work as I'd like it to.) Is there, in fact, a more concise way to express what I'm doing?
Well, I'm no expert in Haskell style, but some might say it's better style to lift out the constants f' and dt as free variables in a local definition of euler' .. euler :: (Num a) => (a -> a -> a) -> a -> (a -> [a]) euler f' dt = euler' 0 where euler' t f = let fnext = f + dt * (f' f t) tnext = t + dt in f : euler' tnext fnext I think that works. I'm not sure if or why that's better but Haskeller's seem to do this kind of thing a lot. It's good for looking smart and confusing newbies if nothing else :-) Regards -- Adrian Hey

Luke Maurer wrote:
My only issue (and it is nitpicky) is that I don't really like having to declare an extra function (euler') in the same scope as euler. I tried playing with where clauses or let expressions, but I couldn't find a way to make GHC happy without introducing a bunch of extra variables (since I'd need new versions of the arguments to euler; in euler', I just used the same names. Also, nested "where" doesn't work as I'd like it to.) Is there, in fact, a more concise way to express what I'm doing?
How about? euler :: (Num a) => (a -> a -> a) -> a -> a -> [a] euler f' dt f0 = euler' f0 0 where euler' f t = f : euler' fnext (t + dt) where fnext = f + dt * (f' f t) -- Lennart

Lennart Augustsson suggests another version of the code of Luke Maurer who wrote his first 3-liner in Haskell:
My only issue (and it is nitpicky) is that I don't really like having to declare an extra function (euler') in the same scope as euler. I tried playing with where clauses or let expressions, but I couldn't find a way to make GHC happy without introducing a bunch of extra variables (since I'd need new versions of the arguments to euler; in euler', I just used the same names. Also, nested "where" doesn't work as I'd like it to.) Is there, in fact, a more concise way to express what I'm doing?
How about?
euler :: (Num a) => (a -> a -> a) -> a -> a -> [a] euler f' dt f0 = euler' f0 0 where euler' f t = f : euler' fnext (t + dt) where fnext = f + dt * (f' f t)
-- Lennart
Before that Adrian Hey produced a 4-liner. === GENTLEMEN! What happened to you?! Why all of you are so incredibly wasteful? Full three (or even four...) lines for just that? Here you have a one-liner: euler f' dt f0 = map snd (iterate (\(t,f)->(t+dt,f+dt*f' t f)) (0,f0) ) (The map snd () prefix is just to get rid of useless time sequence, but you might find some use of it, especially if you want to plot the result. Then the code is even shorter.) You might think that I am a shameless obfuscator, but I really often code like that, and with an adequate support of fold-like primitives which facilitate the processing of lists representing arithmetic sequences, it is possible to write even a one-liner for a Runge-Kutta solver, not only Euler. Perhaps Luke Maurer might look up this never published paper: http://users.info.unicaen.fr/~karczma/arpap/laseq.pdf or a slightly improved one in French, somewhere near. Now, is it a good thing to empoison beginners with such stuff? Not everybody. I wouldn't dare to give it to my students. But for people who are fascinated by the genericity and the compactness of functional coding, and by the self-bootstrapping lazy sequences -- why not? Jerzy Karczmarczuk Caen, France

Jerzy suggested: eulerJ f' dt f0 = map snd (iterate (\(t,f)->(t+dt,f+dt*f' t f)) (0,f0) ) This is nice. Some people prefer list comprehensions over the use of `map' and `iterate': eulerK f' dt f0 = fs where fs = f0 : [ f+dt*f' t f | (t,f) <- [0,dt..] `zip` fs ] (Jerzy, you can write this on one line if you want :-) /Koen.

Well, *I* for one liked Lennart's version:
How about?
euler :: (Num a) => (a -> a -> a) -> a -> a -> [a] euler f' dt f0 = euler' f0 0 where euler' f t = f : euler' fnext (t + dt) where fnext = f + dt * (f' f t)
-- Lennart
My attempt to use a where clause used more variables than I needed, methinks. But I'm skeptical about Jerzy's code:
GENTLEMEN! What happened to you?! Why all of you are so incredibly wasteful? Full three (or even four...) lines for just that?
Here you have a one-liner:
euler f' dt f0 = map snd (iterate (\(t,f)->(t+dt,f+dt*f' t f)) (0,f0) )
{snip}
Jerzy Karczmarczuk Caen, France
Is this likely to result in a faster program, especially what with an optimising compiler like GHC? I mean, a one-line version is nice, but the longer ones seem more intuitive to me. (I also play with Python, whose philosophy is that expressiveness is more important than the absolute minimum of code.) Jyrinx jyrinx@mindspring.com

Jyrinx likes the expanded Lennart's version of the diff.eq. solver:
euler :: (Num a) => (a -> a -> a) -> a -> a -> [a] euler f' dt f0 = euler' f0 0 where euler' f t = f : euler' fnext (t + dt) where fnext = f + dt * (f' f t)
-- Lennart ...
But I'm skeptical about Jerzy's code:
euler f' dt f0 = map snd (iterate (\(t,f)->(t+dt,f+dt*f' t f)) (0,f0) )
Is this likely to result in a faster program, especially what with an optimising compiler like GHC? I mean, a one-line version is nice, but the longer ones seem more intuitive to me. (I also play with Python, whose philosophy is that expressiveness is more important than the absolute minimum of code.)
Oh, bother. First, you ask about whether it is fast, and then you admit that the expresiveness may be more important than the code obfuscatominimalism. Of course that longer versions are more readable. And they may be more efficient, because the code is direct, without polymorphic functionals. I enjoy being crazy, but everything has its limits, and beginners should be protected from such code. It was for fun. But serious. Still, the author asked for that. He presented his code, confessed his admiration for its compactness, and asked for comments about the style. Well, the usage of functionals such as folds, iterates, maps, zips, etc. IS an archetypical feature of functional languages, and using them as a replacement of typical recursive patterns may be encouraged. Koen adds to it the comprehensions, rrright. But comprehensions (and maps) in an incremental context (requiring rather folds than maps), have the property that they need *recursive data* (auto-referential) to be constructed, and this may not be easy to digest by newbies, even if they know what the recursion is. This is, on the other hand, a specific archetype for lazy languages. Jerzy Karczmarczuk Caen, France

Lennart writes:
euler :: (Num a) => (a -> a -> a) -> a -> a -> [a] euler f' dt f0 = euler' f0 0 where euler' f t = f : euler' fnext (t + dt) where fnext = f + dt * (f' f t)
Jerzy writes:
euler f' dt f0 = map snd (iterate (\(t,f)->(t+dt,f+dt*f' t f)) (0,f0) )
Then Jyrinx writes:
I'm sceptical of Jerzy's code ... Is this likely to result in a faster program, especially what with an optimising compiler like GHC?
Unless I'm missing some important factor, Jerzy's version will run as fast with GHC as Lennart's --faster in some contexts, eg, (map (*2) (euler f' dt f0)), because Jerzy's version is eligible for "fold/build" rewriting. For more info, there's a nice paper on this called "A shortcut to deforestation". Of course, for Haskell newbies, Lennart's "explicit recursion" version is easier to understand, unless you are very comfortable with abstraction like many math types like Jerzy are.
participants (7)
-
Adrian Hey
-
Jerzy Karczmarczuk
-
Jyrinx
-
Koen Claessen
-
Lennart Augustsson
-
Luke Maurer
-
Richard Uhtenwoldt