The very first 1-Haskell-a-Day exercise confuses me to a point of no return.
The objective is to filter out elements of a list unless it's equal to the one following. E.g.
> filterByPair [1, 2, 2, 2, 3, 3, 4]
[2,2,3]
(I was trying to make two offset lists, zip them into tuples and remove the tuples that didn't have the same number both times, e.g. [(2,2),(2,2),(3,3)], etc.)
But the mind-blowingly simple solution uses the =<< binding operator:
filterByPair :: Eq a => [a] -> [a]
filterByPair = (init =<<) . group
I'm having trouble figuring that operator out. I don't get the same results in ghci when I try to use it:
> init =<< [2,3,4]
No instance for (Num [b0]) arising from the literal `2'
I find that in ghci I can use, say, replicate with =<<. It seems to be feeding each element of the list into the function:
> replicate 2 =<< [2,3,4]
[2,2,3,3,4,4]
So the first go-around is equivalent to:
> replicate 2 2
[2,2]
It somehow knows to put the list in the second argument of replicate, not the first, and to output them all in one big list rather than separate ones, like fmap does:
> fmap (replicate 2) [2,3,4]
[[2,2],[3,3],[4,4]]
I've read that the binding operator is defined by the monad being used. It's a monad function isn't it? Where's the monad with filterByPair? Does it have something to do with the Eq typeclass in its signature?
Likewise, if I use =<< on its own in ghci, what monad is being used there?
First, filterPairs rewritten into pointful form for clarity:
filterPairs xs = group xs >>= init
We'll be able to get a good idea what's happening from the types alone.
xs :: Eq a => [a]
group :: Eq a => [a] -> [[a]]
init :: [a] -> [a]
(>>=) :: Monad m => m a -> (a -> m b) -> m b
The first argument to (>>=) is group xs :: [[s]], so if we substitute m ~ [] and a ~ [s] in (>>=) we'll get
(>>=) :: [[a]] -> ([a] -> [s]) -> [s]
So we're using the Monad instance of [] here, which is equivalent to concatMap (check the types!). Now we can rewrite filterPairs again
filterPairs xs = concatMap init (group xs)
Now if you figure out how concatMap, init and group work, you should be able to figure how the original filterPairs works.
Related
Say I want to apply a simple function (\x -> x+1) to all elements in the list [1,2,3].
I do map (\x -> x+1) [1,2,3]
and get as expected [2,3,4]. The return type is Num a => [a].
Now what happens if my function is returning a Monad type and is defined as \x -> do return x+1?
I want to apply this function somehow to all elements in the list and get back a type (Monad m, Num a) => m [a].
And the value will be the same [2,3,4], just wrapped with a Monad.
I've been struggling with this for a while without much progress. Any idea how can I map this function to my list?
You're looking for mapM. You can find it using Hoogle and just typing in
Monad m => (a -> m b) -> [a] -> m [b]
You'll find it in the first few results. The only difference in the type signature is that it's generalized to Traversable:
mapM :: (Traversable t, Monad m) => (a -> m b) -> t a -> m (t b)
With it, you can do things like
> mapM (Just . (+) 1) [1,2,3]
> Just [2,3,4]
Or, a bit less trivial:
> mapM (\x -> if x < 5 then Just x else Nothing) [1..10]
> Nothing
There is a monadic version of map - called mapM:
mapM (\x -> return (x+1)) [1,2,3]
I'm sure I am missing something very obvious here. Here's what I'm trying to achieve at a conceptual level:
action1 :: (MonadIO m) => m [a]
action1 = pure []
action2 :: (MonadIO m) => m [a]
action2 = pure [1, 2, 3]
action3 :: (MonadIO m) => m [a]
action3 = error "should not get evaluated"
someCombinator [action1, action2, action3] == m [1, 2, 3]
Does this hypothetical someCombinator exist? I have tried playing round with <|> and msum but couldn't get what I want.
I guess, this could be generalised in two ways:
-- Will return the first monadic value that is NOT an mempty
-- (should NOT blindly execute all monadic actions)
-- This is something like the msum function
someCombinator :: (Monoid a, Monad m, Traversable t, Eq a) => t m a -> m a
-- OR
-- this is something like the <|> operator
someCombinator :: (Monad m, Alternative f) => m f a -> m f a -> m f a
I'm not aware of a library that provides this, but it's not hard to implement:
someCombinator :: (Monoid a, Monad m, Foldable t, Eq a) => t (m a) -> m a
someCombinator = foldr f (pure mempty)
where
f item next = do
a <- item
if a == mempty then next else pure a
Note that you don't even need Traversable: Foldable is enough.
On an abstract level, the first non-empty value is a Monoid called First. It turns out, however, that if you just naively lift your IO values into First, you'll have a problem with action3, since the default monoidal append operation is strict under IO.
You can get lazy monoidal computation using the FirstIO type from this answer. It's not going to be better than Fyodor Soikin's answer, but it highlights (I hope) how you can compose behaviour from universal abstractions.
Apart from the above-mentioned FirstIO wrapper, you may find this function useful:
guarded :: Alternative f => (a -> Bool) -> a -> f a
guarded p x = if p x then pure x else empty
I basically just copied it from Protolude since I couldn't find one in base that has the desired functionality. You can use it to wrap your lists in Maybe so that they'll fit with FirstIO:
> guarded (not . null) [] :: Maybe [Int]
Nothing
> guarded (not . null) [1, 2, 3] :: Maybe [Int]
Just [1,2,3]
Do this for each action in your list of actions and wrap them in FirstIO.
> :t (firstIO . fmap (guarded (not . null))) <$> [action1, action2, action3]
(firstIO . fmap (guarded (not . null))) <$> [action1, action2, action3]
:: Num a => [FirstIO [a]]
In the above GHCi snippet, I'm only showing the type with :t. I can't show the value, since FirstIO has no Show instance. The point, however, is that you now have a list of FirstIO values from which mconcat will pick the first non-empty value:
> getFirstIO $ mconcat $ (firstIO . fmap (guarded (not . null))) <$> [action1, action2, action3]
Just [1,2,3]
If you want to unpack the Maybe, you can use fromMaybe from Data.Maybe:
answer :: IO [Integer]
answer =
fromMaybe [] <$>
(getFirstIO $ mconcat $ (firstIO . fmap (guarded (not . null))) <$> [action1, action2, action3])
This is clearly more complex than Fyodor Soikin's answer, but I'm fascinated by how Haskell enables you to assembling desired functionality by sort of 'clicking together' existing things, almost like Lego bricks.
So, to the question of does this combinator already exist? the answer is that it sort of does, but some assembly is required.
Sorry, I am a newbie to Haskell. This questions may be very easy...
From Hoogle, return signature is return :: Monad m => a -> m a
and head signature is head :: [a] -> a
Before I did this head $ return [1,2,3], I thought that ghci would throw errors because m [a] is not the same [a]. But to my surprise, it returned [1,2,3]. And tail $ return [1,2,3] returned []. Why?
One more question:
I wrote a function to generate random number:
drawFloat :: Float -> Float -> IO Float
drawFloat x y = getStdRandom (randomR (x,y))
randList = mapM (const $ drawFloat 2 10) [1..10] -- generate a list of random numbers
When I want to get the head of the list, I first tried head randList (failed) but head <$> randList worked. What is <$>? Can someone explain? Thanks!
I thought that ghci would throw errors because m [a] is not the same as [a].
Maybe not, but m [a] and [b] can unify! For example, we can set m ~ [] and b ~ [a] so that m [a] ~ [] [a] ~ [[a]] and [b] ~ [[a]]. Then we just need to check that [] is a Monad, which it is. And this is just what's happening:
> return [1,2,3] :: [[Int]]
[[1,2,3]]
Then it should be clear why head returns [1,2,3] and tail returns [].
randList = mapM (const $ drawFloat 2 10) [1..n]
As a comment, not answering your question yet: this is better spelled replicateM n (drawFloat 2 10).
head randList (failed) but head <$> randList worked. What is <$>?
The problem here is that head is still expecting a list. Before, when you were using return, the monad hadn't been chosen yet, and so it could be chosen to be []; but here, it's clear that the monad you're using is IO. So head can't make progress. The solution is to teach head how to handle IO. There are many ways, and <$> is one of them; it has this type:
> :t (<$>)
(<$>) :: Functor f => (a -> b) -> f a -> f b
The way to read this is: given a pure function, teach it how to handle IO (or any other kind of effect that qualifies as a Functor). For example, it also has this type:
(<$>) :: ([a] -> a) -> IO [a] -> IO a
There are several other kinds of teachers. Two commonly used ones are <*> and =<<:
Prelude Control.Applicative> :t (<*>)
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
Prelude Control.Applicative> :t (=<<)
(=<<) :: Monad m => (a -> m b) -> m a -> m b
Read the first as: given an effectful computation that produces a pure function, produce a function that can handle effects. The second should be read as: given a function which has some effects before it produces its output, produce a function which can handle effects.
At this point, if these explanations aren't helpful, you should turn to one of the many excellent monad tutorials available elsewhere on the web. My personal favorites are You Could Have Invented Monads! and All About Monads.
Regarding your first question:
return takes a "raw" value and gives you a monad with the value "wrapped" in it. Since you are using head and tail on return [1,2,3] , the monad in question is the list monad []. So in this context,
return [1,2,3] == [[1,2,3]]
apply head and tail on it, you will get [1,2,3] and [], respectively.
On the second question, note that the type of randList is IO [Float]. So it's not a list any more, but a list inside a monad. To apply any function to the content of the monad, you can use fmap (<$> is its infix shorthand), which has the type:
fmap :: Functor f => (a -> b) -> f a -> f b
A monad is always a Functor. So what fmap does, is that it takes an ordinary function, (head in your example), and a monadic value, applies the function to the "content" of the monad, and returns a return-value of the function wrapped in the same monad f.
I'm currently in the process of trying to learn Haskell, and ran into an odd issue regarding the Maybe monad which I can't seem to figure out.
As an experiment, I'm currently trying to take a string, convert each letter to an arbitrary number, and multiply/combine them together. Here's what I have so far:
lookupTable :: [(Char, Int)]
lookupTable = [('A', 1), ('B', 4), ('C', -6)]
strToInts :: String -> [Maybe Int]
strToInts = map lookupChar
where
lookupChar :: Char -> Maybe Int
lookupChar c = lookup c lookupTable
-- Currently fails
test :: (Num n, Ord n) => [Maybe n] -> [Maybe n]
test seq = [ x * y | (x, y) <- zip seq $ tail seq, x < y ]
main :: IO ()
main = do
putStrLn $ show $ test $ strToInts "ABC"
When I try running this, it returns the following error:
test.hs:13:16:
Could not deduce (Num (Maybe n)) arising from a use of `*'
from the context (Num n, Ord n)
bound by the type signature for
test :: (Num n, Ord n) => [Maybe n] -> [Maybe n]
at test.hs:12:9-48
Possible fix: add an instance declaration for (Num (Maybe n))
In the expression: x * y
In the expression: [x * y | (x, y) <- zip seq $ tail seq]
In an equation for `test':
test seq = [x * y | (x, y) <- zip seq $ tail seq]
I'm not 100% sure why this error is occurring, or what it exactly means, though I suspect it might be because I'm trying to multiply two Maybe monads together -- if I change the definition of test to the following, the program compiles and runs fine:
test :: (Num n, Ord n) => [Maybe n] -> [Maybe n]
test seq = [ x | (x, y) <- zip seq $ tail seq, x < y ]
I also tried changing the type declaration to the below, but that didn't work either.
test :: (Num n, Ord n) => [Maybe n] -> [Num (Maybe n)]
I'm not really sure how to go about fixing this error. I'm fairly new to Haskell, so it might just be something really simple that I'm missing, or that I've structured everything completely wrong, but this is stumping me. What am I doing wrong?
Maybe does not have a num instance so you cannot multiply them together directly. You need to somehow apply the pure function to the values inside the context. This is exactly what applicative functors are for!
Applicative functors live in Control.Applicative:
import Control.Applicative
So you have this function and you want to apply it to 2 arguments in a context:
(*) :: Num a => a -> a -> a
You probably learned about fmap, it takes a function and applies it to a value in a context. <$> is an alias for fmap. When we fmap the pure function over the maybe value we get the following result:
(*) <$> Just 5 :: Num a => Maybe (a -> a)
So now we have maybe a function and we need to apply it to maybe a value, this is exactly what the applicative functor does. Its main operator is <*> which has the signature:
(<*>) :: f (a -> b) -> f a -> f b
When we specialize it we get the function we need:
(<*>) :: Maybe (a -> b) -> Maybe a -> Maybe b
We apply it and the output is the number you expect.
(*) <$> Just 5 <*> Just 5 :: Num a => Maybe a
So to make your code compile you need to change your test function to use <$> and <*>, see if you can figure out how.
Reite's answer is correct, and it's generally how I'd normally recommend handling it - however, it seems to me that you don't quite understand how to work with Maybe values; if so there is little sense looking at applicative functors right now.
The definition of Maybe is basically just
data Maybe a = Nothing | Just a
Which basically means exactly what it sounds like when you read it in plain english "A value of type Maybe a is either the value Nothing for that type, or a value of the form Just a".
Now you can use pattern matching to work with that, with the example of lists:
maybeReverse :: Maybe [a] -> Maybe [a]
maybeReverse Nothing = Nothing
maybeReverse (Just xs) = Just $ reverse xs
Which basically means "If the value is Nothing, then there's nothing to reverse, so the result is Nothing again. If the value is Just xs then we can reverse xs and wrap it with Just again to turn it into a Maybe [a] value).
Of course writing functions like this for every single function we ever want to use with a Maybe value would be tedious; So higher order functions to the rescue! The observation here is that in maybeReverse we didn't do all that much with reverse, we just applied it to the contained value and wrapped the result of that in Just.
So we can write a function called liftToMaybe that does this for us:
liftToMaybe :: (a->b) -> Maybe a -> Maybe b
liftToMaybe f Nothing = Nothing
liftToMaybe f (Just a) = Just $ f a
A further observation we can make is that because functions are values, we can also have Maybe values of functions. To do anything useful with those we could again unwrap them... or notice we're in the same situation as in the last paragraph, and immediately notice that we don't really care what function exactly is in that Maybe value and just write the abstraction directly:
maybeApply :: Maybe (a->b) -> Maybe a -> Maybe b
maybeApply Nothing _ = Nothing
maybeApply _ Nothing = Nothing
maybeApply (Just f) (Just a) = Just $ f a
Which, using our liftToMaybe function above, we can simplify a bit:
maybeApply :: Maybe (a->b) -> Maybe a -> Maybe b
maybeApply Nothing _ = Nothing
maybeApply (Just f) x = liftToMaybe f x
The <$> and <*> operators in Reite's answer are basically just infix names for liftToMaybe (which is also known as fmap) and maybeApply respectively; They have the types
(<$>) :: Functor f => (a->b) -> f a -> f b
(<*>) :: Applicative f => f (a->b) -> f a -> f b
You don't really need to know what the Functor and Applicative things are right now (although you should look into them at some point; They're basically generalizations of the above Maybe functions for other kinds of "context") - basically, just replace f with Maybe and you'll see that these are basically the same functions we've talked about earlier.
Now, I leave applying this to your original problem of multiplication to you (although the other answers kinda spoil it).
You are correct, the problem is that you are trying to multiply two Maybe values together, but (*) only works in instances of Num.
As it turn out, Maybe is an instance of the Applicative typeclass. This means that you can "lift" funcions that work with a type a to functions that work with a type Maybe a.
import Control.Applicative
Two functions provided by Applicative are:
pure :: a -> f a Puts a pure value in a "neutral context". For Maybe, this is Just.
(<*>) :: f (a -> b) -> f a -> f b Lets you apply a "function in a context" to two "values in a context".
So, suppose we have this pure computation:
(*) 2 3
Here are some analogous computations in a Maybe context:
Just (*) <*> Just 2 <*> Just 3
-- result is Just 6
pure (*) <*> pure 2 <*> pure 3
-- result is Just 6 -- equivalent to the above
pure (*) <*> pure 2 <*> Nothing
-- Nothing
Nothing <*> pure 2 <*> Just 3
-- Nothing
Nothing <*> Nothing <*> Nothing
-- Nothing
If the function or one of the argument is "missing", we return Nothing.
(<*>) is the explicit application operator when working with applicatives (instead of using a blank space, as when we work with pure values).
It's instructive to explore what (<*>) does with other instances of Applicative, like []:
ghci> [succ,pred] <*> pure 3
[4,2]
ghci> [succ,pred] <*> [3]
[4,2]
ghci> pure succ <*> [2,5]
[3,6]
ghci> [succ] <*> [2,5]
[3,6]
ghci> [(+),(*)] <*> pure 2 <*> pure 3
[5,6]
ghci> [(+),(*)] <*> [2,1] <*> pure 3
[5,4,6,3]
ghci> [(+),(*)] <*> [2,1] <*> [3,7]
[5,9,4,8,6,14,3,7]
I’m trying to learn Haskell and I was trying to create a function that takes a list of lists and groups the sublist by equivalent sums. This is not homework.
import Data.List
let x = [[1,2],[2,1],[5,0],[0,3],[1,9]]
let groups = groupBy (\i j -> sum i == sum j) x
I get this output in GHCi:
[[[1,2],[2,1]],[[5,0]],[[0,3]],[[1,9]]]
I get [[1,2],[2,1]] grouping together, but not with [0,3]. Why is this?
I suspect I need to use map, but I can’t seem to make it work.
The groupBy function preserves the input order and is thus invertible. If you’re willing to throw away that information, you could use code along the lines of
import Data.List (foldl')
import Data.Map (elems,empty,insertWith')
bucketBy :: Ord b => (a -> b) -> [a] -> [[a]]
bucketBy eq = elems . foldl' go empty
where go m l = insertWith' (++) (eq l) [l] m
In action:
*Main> bucketBy sum x
[[[0,3],[2,1],[1,2]],[[5,0]],[[1,9]]]
How it works
The application of elems from Data.Map gives a clue for what’s happening.
elems :: Map κ α -> [α]
O(n). Return all elements of the map in the ascending order of their keys.
elems (fromList [(5,"a"), (3,"b")]) == ["b","a"]
elems empty == []
Mapping
A Map associates values of some type κ with values of another possibly distinct type α. In the example from your question, you start with x whose type is
*Main> :type x
x :: [[Integer]]
That is, x is a list of integer lists. The type of the resulting partition of x you want is
*Main> :t [[[0,3],[2,1],[1,2]],[[5,0]],[[1,9]]]
[[[0,3],[2,1],[1,2]],[[5,0]],[[1,9]]] :: Num τ => [[[τ]]]
or a list of lists where each of the latter lists are themselves lists that all have the same sum. The Num τ => bit is a context that constrains the type τ to be an instance of the typeclass Num. Happy for us, Integer is such a type:
*Main> :info Integer
data Integer
…
instance Num Integer -- Defined in GHC.Num
…
We know then that the type of the partition is [[[Integer]]]. This typeclass nonsense may seem unnecessarily fussy, but we’ll need the concept again in just a moment. (To give you an idea of what’s going on, the typechecker doesn’t have enough information to decide whether the literal 0, for example, is of type Int or Integer.)
Each sublist contains lists with the same sum. In other words, there exists a mapping from a sum to a list of integer lists. Therefore, the type of the Map used in bucketBy must resemble
Map Integer [[Integer]]
For example, with the sum 3 we associate the list
[ [0,3]
, [2,1]
, [1,2]
]
The fold recursion pattern
Folding is a highly general pattern. Left fold, foldl and friends in Haskell lets you “insert” an operator between elements of a list beginning with the zero value at the left end of the list. For example, the sum of [5,3,9,1] expressed as a left fold is
((((0 + 5) + 3) + 9) + 1)
or
foldl (+) 0 [5,3,9,1]
That is, beginning with a base value of zero, we successively add elements of the list and accumulate the sum.
Recall the definition of bucketBy contains
elems . foldl' go empty
This means the result of the left fold must be of type Map Integer [[Integer]], the zero value for our fold is the empty Map of that type, and go is somehow adding each successive value of a list into the map.
Note that foldl' is the strict cousin of foldl, but strictness is beyond the scope of this answer. (See also “Stack overflow” on HaskellWiki.)
Dude, where’s my list?
Given the type of foldl'
*Main> :t foldl'
foldl' :: (a -> b -> a) -> a -> [b] -> a
we should have three arguments in the application, but only two are present in the code above. This is because the code is written in point-free style. Your list is there implicitly due to partial application of foldl'.
Think back to the sum-as-fold example above. The type of that application without the final argument is
*Main> :t foldl (+) 0
foldl (+) 0 :: Num b => [b] -> b
Partial application allows us to create new functions. Here we defined a function that computes a number from some list of numbers. Hmm, sounds familiar.
*Main> :t sum
sum :: Num a => [a] -> a
The . combinator expresses function composition. Its name is chosen to resemble the notation g∘f as commonly seen in mathematics textbooks to mean “do f first and then compute g from the result.” This is exactly what’s happening in the definition of bucketBy: fold the list of values into a Map and then get the values of out the Map.
If ya gotta go, go with a smile
In your comment, you asked about the purpose of m. With an explicit type annotation, we might define go as
...
where go :: Map Integer [[Integer]] -> [Integer] -> Map Integer [[Integer]]
go m l = insertWith' (++) (eq l) [l] m
Matching variables with types, m is the Map we’ve accumulated so far, and l is the next Integer list that we want to toss into the appropriate bucket. Recall that eq is an argument to the outer bucketBy.
We can control how a new item goes into the map using insertWith'. (By convention, functions whose names end with trailing quotes are strict variants.)
The (++) combinator appends lists. The application eq l determines the appropriate bucket for l.
Had we written l rather than [l], the result would want to be
*Main> bucketBy sum x
[[0,3,2,1,1,2],[5,0],[1,9]]
but then we lose the structure of the innermost lists.
We’ve already constrained the type of bucketBy's result to be [[[α]]] and thus the type of the Map's elements. Say the next item l to fold is [1,2]. We want to append, (++), it to some other list of type [[Integer]], but the types don’t match.
*Main> [[0,3],[2,1]] ++ [1,2]
<interactive>:1:21:
No instance for (Num [t0])
arising from the literal `2'
Possible fix: add an instance declaration for (Num [t0])
In the expression: 2
In the second argument of `(++)', namely `[1, 2]'
In the expression: [[0, 3], [2, 1]] ++ [1, 2]
Wrapping l gets us
*Main> [[0,3],[2,1]] ++ [[1,2]]
[[0,3],[2,1],[1,2]]
Generalizing further
You might stop with
bucketBy :: ([Integer] -> Integer) -> [[Integer]] -> [[[Integer]]]
bucketBy eq = elems . foldl' go empty
where go m l = insertWith' (++) (eq l) [l] m
or even
bucketBy :: ([Integer] -> Integer) -> [[Integer]] -> [[[Integer]]]
bucketBy eq = elems . foldl' go empty
where go :: Map Integer [[Integer]] -> [Integer] -> Map Integer [[Integer]]
go m l = insertWith' (++) (eq l) [l] m
and be perfectly happy because it handles the case from your question.
Suppose down the road you have a different list y defined as
y :: [[Int]]
y = [[1,2],[2,1],[5,0],[0,3],[1,9]]
Even though the definition is very nearly identical to x, bucketBy is of no use with y.
*Main> bucketBy sum y
<interactive>:1:15:
Couldn't match expected type `Integer' with actual type `Int'
Expected type: [[Integer]]
Actual type: [[Int]]
In the second argument of `bucketBy', namely `y'
In the expression: bucketBy sum y
Let’s assume you can’t change the type of y for some reason. You might copy-and-paste to create another function, say bucketByInt, where the only change is replacing Integer with Int in the type annotations.
This would be highly, highly unsatisfying.
Maybe later you have some list of strings that you want to bucket according to the length of the longest string in each. In this imaginary paradise you could
*Main> bucketBy (maximum . map length) [["a","bc"],["d"],["ef","g"],["hijk"]]
[[["d"]],[["ef","g"],["a","bc"]],[["hijk"]]]
What you want is entirely reasonable: bucket some list of things using the given criterion. But alas
*Main> bucketBy (maximum . map length) [["a","bc"],["d"],["ef","g"],["hijk"]]
<interactive>:1:26:
Couldn't match expected type `Integer' with actual type `[a0]'
Expected type: Integer -> Integer
Actual type: [a0] -> Int
In the first argument of `map', namely `length'
In the second argument of `(.)', namely `map length'
Again, you may be tempted to write bucketByString, but by this point, you’re ready to move away and become a shoe cobbler.
The typechecker is your friend. Go back to your definition of bucketBy that’s specific to Integer lists, simply comment out the type annotation and ask its type.
*Main> :t bucketBy
bucketBy :: Ord k => (b -> k) -> [b] -> [[b]]
Now you can apply bucketBy for the different cases above and get the expected results. You were already in paradise but didn’t know it!
Now, in keeping with good style, you provide annotations for the toplevel definition of bucketBy to help the poor reader, perhaps yourself. Note that you must provide the Ord constraint due to the use of insertWith', whose type is
insertWith' :: Ord k => (a -> a -> a) -> k -> a -> Map k a -> Map k a
You may want to be really explicit and give an annotation for the inner go, but this requires use of the scoped type variables extension.
{-# LANGUAGE ScopedTypeVariables #-}
import Data.List (foldl')
import Data.Map (Map,elems,empty,insertWith')
bucketBy :: forall a b. Ord b => (a -> b) -> [a] -> [[a]]
bucketBy eq = elems . foldl' go empty
where go :: Map b [a] -> a -> Map b [a]
go m l = insertWith' (++) (eq l) [l] m
Without the extension and with a type annotation of
bucketBy :: Ord b => (a -> b) -> [a] -> [[a]]
the typechecker will fail with errors of the form
Could not deduce (b ~ b1)
from the context (Ord b)
bound by the type signature for
bucketBy :: Ord b => (a -> b) -> [a] -> [[a]]
at prog.hs:(10,1)-(12,46)
`b' is a rigid type variable bound by
the type signature for
bucketBy :: Ord b => (a -> b) -> [a] -> [[a]]
at prog.hs:10:1
`b1' is a rigid type variable bound by
the type signature for go :: Map b1 [a1] -> a1 -> Map b1 [a1]
at prog.hs:12:9
In the return type of a call of `eq'
In the second argument of `insertWith'', namely `(eq l)'
In the expression: insertWith' (++) (eq l) [l] m
This is because the typechecker treats the b on the inner type annotation as a distinct and entirely unrelated type b1 even though a human reader plainly sees the intent that they be the same type.
Read the scoped type variables documentation for details.
One last small surprise
You may wonder where the outer layer of brackets went. Notice that the type annotation generalized from
bucketBy :: ([Integer] -> Integer) -> [[Integer]] -> [[[Integer]]]
to
bucketBy :: forall a b. Ord b => (a -> b) -> [a] -> [[a]]
Note that [Integer] is itself another type, represented here as a.
groupBy splits the list into chunks of adjacent elements satisfying the given predicate. Since in your case, the [0,3] is separated from the [1,2] and [2,1], the first group includes only these. To collect all elements of the list having the same sum into one group, you need some preprocessing, e.g. with sortBy.
import Data.List
import Data.Function
import Data.Ord
groupBySum :: Num a => [[a]] -> [[[a]]]
groupBySum xss = groups
where
ys = map (\xs -> (sum xs,xs)) xss
sortedSums = sortBy (comparing fst) ys
groupedSums = groupBy ((==) `on` fst) sortedSums
groups = map (map snd) groupedSums
From hackage:
The group function takes a list and returns a list of lists such that the concatenation of the result is equal to the argument.
groupBy is the same, except that you can specify your equality test. Thus, since in your input list [0,3] is not adjacent to [1,2] or [2,1], it is put on its own.