Recursive definition of the (!!)-function

  • A+
Category:Languages

So I tried to build the (!!) function as already defined in GHC.List recursively. I want to extract the n-th element of a list and return that. Here's what I got first:

taken0 :: [β] -> Int -> β -- but not recursive βs `taken0` 0 = head βs βs `taken0` n = last (take (n+1) βs) 

This worked, but it wasn't recursive...

then I tried the following:

taken :: [γ] -> Int -> γ -- doesn't compile taken γs 0 = head γs taken γs 1 = head (tail γs) taken γs n = head ( tail (takenth γs (n-1)) ) 

After some fixing I ended up with this:

taken :: [γ] -> Int -> [γ] -- works, but returns a list taken γs 0 = γs taken γs 1 = tail γs taken γs n = tail (taken γs (n-1)) 

Which does indeed compile but is ugly to handle, it returns a list whoose first element is that one "entered" by n.

*Main> head ([0,1,2,3,4,5,6,7,8,9] `taken` 0)      returns 0 *Main> head ([0,1,2,3,4,5,6,7,8,9] `taken` 1)      returns 1 *Main> head ([0,1,2,3,4,5,6,7,8,9] `taken` 2)      returns 2 etc. 

Always returns the right (n-th element) but I had to insert head before.

What I want is a function, which, although recursive, returns a single element instead of a list... Is there a way to accomplish this without writing another function or using head everytime ? like:

*Main> taken2 [5,8,6,0,2,5,7] 3                    returns 0 

thanks in advance !

 


Writing a recursive function on lists, you should almost always start by mirroring the recursive definition of the list type itself: a case for empty lists, and a case for a cons pair:

taken :: [γ] -> Int -> γ taken [] n = _ taken (γ:γs) n = _ 

Note, the above syntax with underscore placeholders is actual Haskell syntax (for recent enough GHC), which will cause the compiler to print out errors like this asking you to fill in the blanks, and telling you about the pieces you have available to fill them in:

foo.hs:2:14: error:     • Found hole: _ :: γ       Where: ‘γ’ is a rigid type variable bound by                the type signature for:                  taken :: forall γ. [γ] -> Int -> γ                at foo.hs:1:1-24     • In the expression: _       In an equation for ‘taken’: taken [] n = _     • Relevant bindings include         n :: Int (bound at foo.hs:2:10)         taken :: [γ] -> Int -> γ (bound at foo.hs:2:1)   | 2 | taken [] n = _   |              ^  foo.hs:3:18: error:     • Found hole: _ :: γ       Where: ‘γ’ is a rigid type variable bound by                the type signature for:                  taken :: forall γ. [γ] -> Int -> γ                at foo.hs:1:1-24     • In the expression: _       In an equation for ‘taken’: taken (γ : γs) n = _     • Relevant bindings include         n :: Int (bound at foo.hs:3:14)         γs :: [γ] (bound at foo.hs:3:10)         γ :: γ (bound at foo.hs:3:8)         taken :: [γ] -> Int -> γ (bound at foo.hs:2:1)   | 3 | taken (γ:γs) n = _   |   

So the first hole we need to fill in is of type γ. However the only things we have available are the Int n, and making a recursive call to taken. Since the list is empty, recursing isn't going to help us; it'll just end up back at the same case we're in. And thinking about what our function is supposed to do, we can't get the nth element of an empty list no matter what n is. So we'll need to just call error here.

taken :: [γ] -> Int -> γ taken [] n = error "Index out of range" taken (γ:γs) n = _ 

The second hole is also of type γ, and GHC tells us:

• Relevant bindings include     n :: Int (bound at foo.hs:3:14)     γs :: [γ] (bound at foo.hs:3:10)     γ :: γ (bound at foo.hs:3:8)     taken :: [γ] -> Int -> γ (bound at foo.hs:2:1) 

So we can obviously just use γ to appease the type checker, but logically which value we return should depend on n. If we're taking the 0th element of this list, well we've already got the head element decomposed as value γ due to our pattern match, so that'll be correct in that case. Lets try:

taken :: [γ] -> Int -> γ taken [] n = error "Index out of range" taken (γ:γs) n   | n == 0    = γ   | otherwise = _ 

Which gives us:

foo.hs:5:17: error:     • Found hole: _ :: γ       Where: ‘γ’ is a rigid type variable bound by                the type signature for:                  taken :: forall γ. [γ] -> Int -> γ                at foo.hs:1:1-24     • In the expression: _       In an equation for ‘taken’:           taken (γ : γs) n             | n == 0 = γ             | otherwise = _     • Relevant bindings include         n :: Int (bound at foo.hs:3:14)         γs :: [γ] (bound at foo.hs:3:10)         γ :: γ (bound at foo.hs:3:8)         taken :: [γ] -> Int -> γ (bound at foo.hs:2:1)   | 5 |   | otherwise = _   |                 

Same type of hole, same relevant bindings available. But we know that γ isn't the right answer, since we've already handled the case when it is. The answer we do want to return should be somewhere in γs, and we know we want to write this function recursively, so the obvious thing to do is insert a recursive call:

taken :: [γ] -> Int -> γ taken [] n = error "Index out of range" taken (γ:γs) n   | n == 0    = γ   | otherwise = taken γs _  foo.hs:5:26: error:     • Found hole: _ :: Int     • In the second argument of ‘taken’, namely ‘_’       In the expression: taken γs _       In an equation for ‘taken’:           taken (γ : γs) n             | n == 0 = γ             | otherwise = taken γs _     • Relevant bindings include         n :: Int (bound at foo.hs:3:14)         γs :: [γ] (bound at foo.hs:3:10)         γ :: γ (bound at foo.hs:3:8)         taken :: [γ] -> Int -> γ (bound at foo.hs:2:1)   | 5 |   | otherwise = taken γs _   |                  

Now we're getting somewhere. The remaining hole is of type Int, and we have n :: Int available. Plugging that straight in is tempting, but doesn't make sense if we stop to think about what we're doing. Taking the nth element of the list (γ:γs) (which is the result we're supposed to be returning) when n /= 0 should be the same as taking the (n - 1)th element of γs, so:

taken :: [γ] -> Int -> γ taken [] n = error "Index out of range" taken (γ:γs) n   | n == 0    = γ   | otherwise = taken γs (n - 1) 

No more holes! And this actually works. The only problem is that we don't handle negative values of n. It turns out that's actually sortof okay; for finite lists we eventually run off the end and hit the error "Index out of range" case, which is accurate. But it would be nicer to fail before iterating the whole list. So:

taken :: [γ] -> Int -> γ taken [] n = error "Index out of range" taken (γ:γs) n   | n == 0    = γ   | n < 0     = error "Negative index"   | otherwise = taken γs (n - 1) 

I highly recommend this "hole driven development" style (whether you use actual hole syntax and get GHC to typecheck them or just do it yourself as you write the code). Let the structure of the types you're using guide the "shape" of the function you're writing (e.g. when writing a function on lists, use a case for [] and a case for (x:xs)), and then fill in the holes one at a time. Sometimes you'l need a different structure than this guides you towards, but very often not, and even when you do having started this approach and found out what the problems are gives you much better information for guessing the right structure.

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: