powerset
Derek Elkins
ddarius@hotpop.com
Wed, 4 Jun 2003 19:50:01 -0400
On Wed, 4 Jun 2003 15:08:44 +0100
Liyang HU <liyang@nerv.cx> wrote:
> Hi Graham,
>
> On Wed, Jun 04, 2003 at 12:08:38PM +0100, Graham Klyne wrote:
> > I thought this may be a useful exercise to see how well I'm picking
> > up on functional programming idioms, so I offer the following for
> > comment:
>
> > foldl (++) [] [ combinations n as | n <- intRange 1 (length as)
> > ]
*cries out in pain and horror* fold_l_ (++) over combinatorially large
lists! (++) has gotten a reputation for being slow.
(++) isn't slow in and of itself, even using it a lot isn't slow, what
-is- slow is using it left associatively. What happens then is that
(a++b)++c builds a copy of a then tacks on b, then it builds a copy of
a++b and tacks on c. In this case we've copied a twice when we should
have only copied it once. Obviously for ((a++b)++c)++d it'll be copied
three times and b twice and so forth. To add insult to injury, there is
already a standard function that does what you want, concat, which is
defined as foldr (++) [] in the report. In fact, you could rewrite the
whole thing as concatMap (flip combinations as) [1..length as]. A list
comprehension with only one source and no filters is the same as a map.
> By your use of the `intRange' function, I get the feeling you're still
> thinking somewhat imperatively. (It's awfully reminiscent of a
> for-loop...) Were you trying to write the function from some
> definition? (The set of all subsets of X with size <= |X| et c. You're
> looping over the size of the subset, per se...?)
>
> (Side note: I can think of few instances where you _need_ to deal with
> the length of a list directly -- more often than not you can (and
> probably should) let recursion take care of that. You can also write
> [1..length as] rather than use the intRange function, which looks
> prettier. :-)
Indeed, I think I've used length all of 3 times. You (Graham) also have
some parentheses issues; e.g. in foo ++ (combinations 5 l) the
parentheses are superfluous.
(++) is slow though in that seemingly innocent uses can become n^2. A
simple example is displaying a binary tree. A tree like
/\
/\
will cause left associative uses of (++). Hence the prelude type ShowS
= String -> String and shows :: Show a => a -> ShowS. The problem is we
don't want left associativity, so what we do is make a context that
says do everything else to the right, then this, e.g.
"Foo"++everythingelse. This is simple enough to do with ("Foo"++). (As
a side note, using the Writer/Output monad with the list/string monoid
is probably not the best of ideas, instead you can use the function
monoid and tell ("foo"++).) You can see that this technique is more or
less just adding an accumulating parameter as you'll have to provide the
'initial' value.