[Haskell-cafe] Basic list exercise
Todd Wilson
twilson at csufresno.edu
Sun Mar 26 17:59:13 UTC 2023
I want to thank all of those who contributed to this thread. If I can
summarize the contributions (feel free to clarify any misrepresentations):
- The code I provided for this problem is probably optimal, except
perhaps for the small point about using an @-pattern in `run` to avoid
reconstructing `y:ys` in the last line -- but isn't this also something the
compiler could easily discover and take advantage of without explicit
direction?
- The compiler will avoid breaking down and reconstructing pairs on each
recursive call, even though that is how it is coded.
- Although nobody commented on my Prolog version (perhaps because this
is a Haskell list!), the point I made there about compiling this as a tail
call that doesn't grow the stack should also apply to Haskell (does it?).
One thing that's clear from the Prolog code is that it goes down the input
list and distributes the elements to one of two places -- the end of the
current run or the beginning of a new run -- interspersed with some
cons-cell creation. Is that what's happening in Haskell also?
- Reference was made to Haskell's `sort` implementation, which
essentially finds runs (increasing and decreasing) in the input before
repeatedly merging them in pairs until one run remains. Decreasing runs can
be turned into increasing ones by using a tail call with a list
accumulator, the way iterative reverse works. But increasing runs are
handled in the same way that my Prolog code does, essentially by using what
appears to be the Haskell equivalent of Prolog's difference-lists:
functions whose arguments are the extendible ends of lists.
I'm intrigued by the idea mentioned in this last bullet point. Is this
functional equivalent of Prolog's difference lists -- essentially partial
data structures that are grown top-down rather than bottom-up, actually
efficient? Is there an overhead penalty to be paid for using functions
here, or is it compiled away into Prolog-like tail calls that fill in the
missing parts of the data structure?
Todd Wilson
On Thu, Mar 16, 2023 at 6:33 PM Todd Wilson <twilson at csufresno.edu> wrote:
> Dear Cafe,
>
> Here's a basic exercise in list processing: define a function
>
> runs :: Ord a => [a] -> [[a]]
>
> that breaks up its input list into a list of increasing "runs"; e.g.,
>
> runs [3,4,5,6,2,3,4,1,2,1] ---> [[3,4,5,6],[2,3,4],[1,2],[1]]
>
> A natural solution is the following:
>
> runs [] = []
> runs (x:xs) = let (ys, zs) = run x xs
> in (x:ys) : runs zs
> where
> run x [] = ([], [])
> run x (y:ys) = if x <= y
> then let (us, vs) = run y ys
> in (y:us, vs)
> else ([], y:ys)
>
> My question: can we do better than this? It seems that this solution is
> constantly building and breaking apart pairs. (Or is it, when optimized?)
>
> Todd Wilson
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.haskell.org/pipermail/haskell-cafe/attachments/20230326/1a5b331d/attachment.html>
More information about the Haskell-Cafe
mailing list