I am learning Haskell. One of the exercises I was asked to do was to compute all sums of a power set of a set of integers, e.g.:
allSums [1, 2, 5] -- should be [8,3,6,1,7,2,5,0]
I came up with this after reading about applicatives and functors, to which lists belong. It worked.
allSums :: [Int] -> [Int]
allSums [] = [0]
allSums (x:xs) =
if x == 0
then allSums xs
else ( ) <$> [x, 0] <*> allSums xs
I was shook. It looks so terse, but it appears correct.
I do not know whom to talk to. My friends and parents think I am crazy.
How do you pronounce <$>
and <*>
? How do you even describe to people what they do?
CodePudding user response:
You pronounce this combination pattern of <$>
and <*>
as liftA2
:
liftA2 :: Applicative f
=> (a -> b -> c) -> f a -> f b -> f c
So then in your case f ~ []
, ( ) :: Num a => a -> a -> a
, and
liftA2 :: Num a
=> (a->a->a) -> [a] -> [a] -> [a]
-- ( ) ...
( ) <$> [x,0] <*> allSums xs
=
[(x ), (0 )] <*> allSums xs
=
liftA2 ( ) [x,0] allSums xs
=
[x r | x <- [x,0], r <- allSums xs]
and what they "do" in the case of lists is to form the Cartesian product-like combinations of the constituents and apply the function to each of the combinations.
In your case of summing the elements, using 0
for an element is like skipping over it completely. Thus indeed implementing the sums of the power set of numbers:
allSums [x1, x2, ..., xn]
=
[x1 r | x1 <- [x1,0], r <- allSums [x2, ..., xn]]
=
[x1 r | x1 <- [x1,0], r <- [x2 r | x2 <- [x2,0], r <- allSums [x3, ..., xn]]]
=
[x1 x2 r | x1 <- [x1,0], x2 <- [x2,0], r <- allSums [x3, ..., xn]]
=
...
=
[x1 x2 ... xn r | x1 <- [x1,0], x2 <- [x2,0], ..., xn <- [xn,0], r <- [0]]
=
[x1 x2 ... xn | x1 <- [x1,0], x2 <- [x2,0], ..., xn <- [xn,0]]
As to the pronunciation, I read ( ) <$> [x, 0] <*> allSums xs
as "plus over x
-and-0 over all sums of x
es".
As Daniel Wagner comments under the question, [x, 0]
could be tweaked into [x | x /= 0] [0]
, for efficiency.
CodePudding user response:
When reading these to myself, I do not pronounce them; they are just a visual glyph in my mind. If I must speak them aloud, I would say "eff-map" for <$>
(possibly "eff-mapped onto" if there's some ambiguity) and "app" for <*>
(possibly "applied to" if there's ambiguity), which come from the pre-Applicative
names fmap
and ap
.
Also, I hate saying fmap
out loud. For this reason and others, I would likely mentally transform this to
pure ( ) <*> [x,0] <*> allSums xs
before I said it so that I only need to pronounce pure
and (<*>)
. As an aside, I actually find it a little bit surprising that this style is not more common; especially when spreading applicative arguments across multiple lines, this is more uniform, as
pure f
<*> a
<*> b
<*> c
doesn't need to treat the a
line specially.