0

Can someone suggest a Python implementation of the Haskell span function?

span :: (a -> Bool) -> [a] -> ([a],[a]) 
span p [] = ([],[]) 
span p (x:xs) = if p x then (x:ys,zs)
                else ([],x:xs) 
                where (ys,zs) = span p xs

I'd like it to be lazy in each argument individually, so a list of two generators. I'd prefer if it were implemented recursively, to get a sense of Python definitions where you recurse on the tail of the list. Perhaps with yield from, if appropriate.

Eric Auld
  • 1,156
  • 2
  • 14
  • 23
  • In addition to the excellent points made in amalloy's answer, it's also worth noting that lists in Python and in Haskell are very different. Haskell lists are linked lists, which are excellent for recursion and fit very well in Haskell. Python lists are really just sequential arrays (similar to `std::vector` in C++ or `ArrayList` in Java), so you can't really take the "tail" of a list without copying nearly the entire list every time. Python programmers seldom work with linked lists, because it makes little sense with the paradigm. – Silvio Mayolo Oct 20 '21 at 03:21
  • Array based lists and lazyness doesn't play well together, because such a list isn't a recursive data structure and inherently strict. You need an imperative construct (generator) to be able to construct it lazily. –  Oct 20 '21 at 12:04

1 Answers1

2

I'd like it to be lazy in each argument individually, so a list of two generators.

You can't really do this. There's a big difference between a generator and a lazy list: the latter is cached so you can reuse its values. In this case, consider the Haskell implementation, where (xs, ys) is the result of span f zs. When you first look at xs, you need to advance through that list, which triggers more evaluation of span f zs...and at the end, more stuff is discovered at the end of both xs and ys. You can't do this with a generator, because reading from one of them would have to affect the other.

I discuss this more in my answer to Lazy partition-by.

amalloy
  • 89,153
  • 8
  • 140
  • 205
  • 2
    I guess the OP could create a `Thunk` type , which when evaluated shares its result (by an destructive update, for example). `Thunk` could implement applicative/monad to make the explicit thunk calls implicit again. –  Oct 20 '21 at 08:17