8

Hello Haskellers and Haskellettes,

when reading http://learnyouahaskell.com/ a friend of mine came up with a problem:

Is it possible in Haskell to write a recursive function that gives True if all sub-sub-_-sublists are empty. My first guess was - should be - but i have a big problem just writing the type annotation.

he tried something like

nullRec l = if null l
               then True
               else if [] `elem` l
                    then nullRec (head [l]) && nullRec (tail l)
                    else False

which is - not working - :-)

i came up with something like

  • folding with concat - to get a a single long list
    (giving me problems implementing)
  • or making an infinite treelike datatype - and making this from the list
    (haven't implemented yet)

but the latter sound a bit like overkill for this problem. what is your ideas - on a sunny sunday like this ;-)

Thanks in advance


as a reaction to all the comments - this being bad style i'd like to add this is just an experiment!
DO not try this at home! ;-)

epsilonhalbe
  • 15,637
  • 5
  • 46
  • 74
  • 3
    Think about what the type of such a function should be (if you implement it on normal lists)! – yatima2975 Jul 10 '11 at 14:46
  • i aleady thought about it - it should be kind of infinite [[…[a]…]] but that isn't possible to write down in haskell - that's why i came up with the second approach. But is there an easier way to do this. Additionally my brain is a bit slow as i'm sick today. – epsilonhalbe Jul 10 '11 at 14:51
  • 2
    Sick or not, you're on the right track! It's easy enough to write a family of functions `nullRec2 :: [[a]] -> Bool`, `nullRec3 :: [[[a]]] -> Bool` and so on (try a couple!), but you can't get them to fit a single type signature easily. You either need a tree type like `data Tree a = Branch [Tree a] | Node a` or maybe there's something possible with typeclasses (haven't thought much about this approach yet). – yatima2975 Jul 10 '11 at 14:58
  • 1
    http://stackoverflow.com/questions/5994051 this might help –  Jul 10 '11 at 15:31
  • 1
    @FUZxxl's answer will work. I consider the need to solve this problem a red flag -- if it were more than just an experiment, I would suggest rethinking the architecture. – luqui Jul 10 '11 at 16:21
  • i don't think i will ever occur this problem - it was just a thought experiment when stumbling over recursive functions - so no flags are necessary – epsilonhalbe Jul 10 '11 at 16:30
  • 1
    To reinforce luqui's point, think about what the values of `nullRec [""]` and `nullRec [empty :: ByteString]` should be. You shall see that there is no good answer. – Rotsor Jul 10 '11 at 16:30

2 Answers2

5

How about a typeclass?

{-# LANGUAGE FlexibleInstances, OverlappingInstances #-}

class NullRec a where
  nullRec :: a -> Bool

instance NullRec a => NullRec [a] where
  nullRec [] = True
  nullRec ls = all nullRec ls

instance NullRec a where
  nullRec _  = False

main = print . nullRec $ ([[[[()]]]] :: [[[[()]]]])
fuz
  • 88,405
  • 25
  • 200
  • 352
  • 5
    More precisely, you are using not only a type class but also the GHC extension [OverlappingInstances](http://www.haskell.org/ghc/docs/7.0.4/html/users_guide/type-class-extensions.html#instance-overlap). Without this extension, even using a type class cannot achieve what is required in the question. This strongly suggests that if anyone _has to_ implement this (other than as an experiment), the program is probably not designed in the right way. – Tsuyoshi Ito Jul 10 '11 at 16:59
4

This is not possible using parametric polymorphism only, because of the following.

Consider these values:

x = [8] :: [Int]
y = [3.0] :: [Double]
z = [[]] :: [[Int]]

Obviously, you want your function to work with both x and y, thus its type must be null1 :: [a] -> Bool. (Can someone help me make this argument look formal? How can I show that this is the unique most specific context-less type unifiable with [Int] -> Bool and [Double] -> Bool? Is there a name for that relation between types?)

Now, if you have this type, then null1 z will be equal to null1 x because they differ only in values of the list elements, which are abstracted away from. (Not even close to formal proof again :()

What you want for z is null2 :: [[a]] -> Bool, which will differ in behaviour, and thus giving null1 and null2 the same name will require overloading. (see the FUZxxl's answer)

Rotsor
  • 13,655
  • 6
  • 43
  • 57
  • I find that argument fairly convincing. – luqui Jul 10 '11 at 16:20
  • I feel like `null1 . (:[]) :: a -> Bool` brings us closer to formal proof, but I still don't know how to make the last step (showing that `a -> Bool` is the same as `Bool`). – Rotsor Jul 10 '11 at 16:41
  • It's sort of a free theorem in reverse, but the largest lower bound of `Int` and `Double` (on the lattice of Haskell context-free types, with substition as the relationship) is `a`, as you can't substitute type variables in both of them (there aren't any!) and end up with the same type. Or am I getting my up & down directions mixed up? – yatima2975 Jul 10 '11 at 22:44