Elegant way to get non empty list or `[[]]` (default value) in Haskell

Viewed 462

Is it possible to simplify the definition of this function, which returns [[]] if l is empty and l otherwise?

f :: Eq t => [[t]] -> [[t]]
f l = if l == [] then [[]] else l

for ex. in python I can do this:

f = lambda l: l or [[]]

I thought about applicative:

f l = l <|> [[]]

but it appends [] in case of the non-empty l

Is there any way to do this more elegantly in Haskell?

5 Answers

If you need Eq only to check whether a list is empty, you are doing it wrong.

f :: [[a]] -> [[a]]
f [] = [[]]
f x  = x

Or, if you prefer if-then-else:

f :: [[a]] -> [[a]]
f x = if null x then [[]] else x

Or perhaps more generally:

class Fallible a where
   failed ∷ a -> Bool

instance Fallible [a] where
   failed = null

orelse ∷ Fallible a => a -> a -> a  
orelse x y = if failed x then y else x

f :: [[a]] -> [[a]]
f x = x `orelse` [[]]

There are certainly some improvements one could make, although I wouldn't call any of them simplifications. First, we can get rid of the Eq constraint, by using pattern matching instead:

f :: [[a]] -> [[a]]
f [] = [[]]
f x = x

Or we could get rid of it in another way, by using foldr:

f :: [[a]] -> [[a]]
f x = foldr (const (const x)) [[]] x

Now we don't actually need the input to be a list anymore. It could be anything Foldable, except that it has to be the same as the output type. But the only reason we need the output to be a list is so we can build a singleton out of it. So we don't really need to specialize to list after all - anything Applicative will do:

f :: (Foldable f, Applicative f) => f [a] -> f [a]
f x = foldr (const (const x)) (pure []) x

Finally we could try to generalize over the last [], to make this function applicable in the most places possible. Some obvious choices for [] are Monoid or Alternative - either of these have a concept of emptiness. I'd probably choose Monoid for being the simplest, yielding:

f :: (Foldable f, Applicative f, Monoid a) => f a -> f a
f x = foldr (const (const x)) (pure mempty) x

What does all this abstraction buy us? We can now do a similar operation with different data types. Suppose, for example, we arbitrarily choose f ~ Maybe and a ~ Ordering. Then our function acts like this (inlining definitions from the relevant typeclass instances):

f :: Maybe Ordering -> Maybe Ordering
f (Just result) = Just result
f Nothing = Just EQ

Kinda a weird function (it would be more natural to unwrap the Maybe than to put it in Just), but I'd argue no weirder than your original function (why is [[]] any better than [] anyway?).

You also can use ZipList for this:

> ZipList [[1, 2, 3]] <|> ZipList [[]]
ZipList {getZipList = [[1, 2, 3]]}

> ZipList [[1, 2], [3]] <|> ZipList [[]]
ZipList {getZipList = [[1, 2], [3]]}

> ZipList [[]] <|> ZipList [[]]
ZipList {getZipList = [[]]}

> ZipList [] <|> ZipList [[]]
ZipList {getZipList = [[]]}
foo :: [[t]] -> [[t]]
foo xs  =  head  $  (xs <$ xs)  <|>  [ [[]] ]
      -- take one of
      --         either `xs`    OR    the default

Notice the type though. It will only work for [[t]] lists, because that's the type of [[]].

It might feel a bit obfuscated, unless you've become comfortable with the xs <$ xs trick (which is the same as [xs | _ <- xs]).

I'm not sure what you mean by elegant, but if you want to write it in a style that more closely resembles the Python, you can write:

import Data.Function ((&))
import Data.Bool (bool)

f :: [[t]] -> [[t]]
f l = null l & l `bool` [[]]

The bool function takes as arguments first the else/False case and then the then/True case and finally the boolean that it's checking. Here, we use infix notation to provide it with its first two arguments. In other words, l `bool` [[]] has the type Bool -> [[t]]. Finally, the operator & is a flipped version of $—that is, it takes the argument first and then the function. Here, we provide null l as the argument, and the function is l `bool` [[]].


Unfortunately the : symbol is reserved in Haskell. On the other hand, the ? symbol isn't. So, if you're okay with &, we can at least redefine bool and achieve:

(?) = bool

f :: [[t]] -> [[t]]
f l = null l & l ? [[]]

It's certainly concise, and it's similar to short-hand if-statements from other languages.

Related