#401 takeWhile, dropWhile

helium Fri 21 Nov 2008

As I often program in a more declarative than imperative style I miss some methods. For example:

foo := [1, 2, 3, 4, 5]


bar := foo.takeWhile |Int x->Bool| { return x < 3 }

// bar == [1, 2]


baz := foo.dropWhile |Int x->Bool| { return x < 3 }

// baz == [3, 4, 5]

takeWhile takes elements from a list while the predicates is true. dropWhile drops elements while the predicate is true and returns the rest. If you'd concatenate the result of both methods with the same predicate you'd get a copy of the original list.

Haskell has it as takeWhile and dropWhile in it's prelude, C# has it as extension methods TakeWhile and SkipWhile on IEnumerable.

Why can't you write them yourself?

As said I don't realy understand Fan's generics system. So how do I write a function that takes a list of any type and returns a list of the same type?

And how do I write a function that takes a list of any type and a mapping function from the type of the list to another type and return a list of that other type, e.g. if I want to write ... don't know, a reverse map function?

How do I write a zipWith function taking two lists and a function taking two arguments, one of the type of the first list, one of the type of the second list. The function should return a list of the type that the passed function returns.

listA := [1, 2, 3]
listB := ["a", "b", "c"]

result = zipWith(listA, listB) |Int a, String b->String| { return a.toString() + " => " + b }

// result == ["1 => a", "2 => b", "3 => c"]

alexlamsl Fri 21 Nov 2008

(I've replied to your message in the other thread before noticing this one.)

Unless I have mistaken, it seems that we have such functionality already:

bar := foo.findAll |Int x->Bool| { return x < 3 }

baz := foo.exclude |Int x->Bool| { return x < 3 }

helium Fri 21 Nov 2008

Fansh:

fansh> x := [1, 2, 3, 1, 2, 3]
[1, 2, 3, 1, 2, 3]
fansh> x.findAll |Int y->Bool| { return y < 3 }
[1, 2, 1, 2]
fansh> x.exclude |Int y->Bool| { return y < 3 }
[3, 3]

GHCi:

Prelude> let x = [1, 2, 3, 1, 2, 3]
Prelude> takeWhile (<3) x
[1,2]
Prelude> dropWhile (<3) x
[3,1,2,3]

Result:

[1, 2, 1, 2] is not the same as [1,2]
[3, 3] is not the same as [3,1,2,3]

katox Fri 21 Nov 2008

If you'd concatenate the result of both methods with the same predicate you'd get a copy of the original list.

Helium: That's not true regarding your last post. What is the precise semantics?

helium Fri 21 Nov 2008

BTW:

Haskell: filter

Ruby: filter

Python: filter

SML/OCaml: filter

Common Lisp: filter

Scala: filter

Fan: findAll

helium Fri 21 Nov 2008

> If you'd concatenate the result of both methods with the same predicate you'd get a copy of the original list. Helium: That's not true regarding your last post. What is the precise semantics?

Huh?

Prelude> takeWhile (<3) x ++ dropWhile (<3) x
[1,2,3,1,2,3]

Precise semantics ... code?

takeWhile           :: (a -> Bool) -> [a] -> [a]
takeWhile p []       = []
takeWhile p (x:xs)
         | p x       = x : takeWhile p xs
         | otherwise = []

dropWhile           :: (a -> Bool) -> [a] -> [a]
dropWhile p []       = []
dropWhile p xs@(x:xs')
         | p x       = dropWhile p xs'
         | otherwise = xs

takeWhile returns elements from a list as long as the predicate is true. It stops at the first element for which the predicate is false in contrast to findAll which would go on till the end of the list.

dropWhile bypasses elements in a list as long as the predicate is true and then returns the remaining elements.

brian Fri 21 Nov 2008

The different between takeWhile and dropWhile is that the loop terminates as soon as the function returns false. This is similar to how eachBreak terminates on a non-null return.

The way you would implement that functionality today:

index := x.findIndex |Int y->Bool| { return y < 3 }
takeWhile := x[0..index]
dropWhile := x[index..-1]

If there is agreement that those methods are useful, I don't mind adding them.

Although I'm not sure the name is consistent with other methods. I'd probably be inclined to use the following naming convention:

eachWhile     // rename eachBreak for consistenty
findWhile     // instead of takeWhile to be consistent with find and findAll
excludeWhile  // instead of dropWhile to be consistent with exclude

As said I don't realy understand Fan's generics system. So how do I write a function that takes a list of any type and returns a list of the same type?

In general you just use Obj[] and rely on the compiler to auto-cast if necessary - like you'd program in Python. If you really want to keep reflective types you can do this:

Obj[] foo(Obj[] x) { return List(x.of, 0) }

echo(foo([3, 4, 5]).type)  // prints Int[]

jodastephen Fri 21 Nov 2008

Are eachWhile/findWhile/excludeWhile that useful? The List class is so core that we need to be very careful that what we add is really useful to all. Particularly need to work out what Fan's coding style is (ie. not overly functional...)

helium Fri 21 Nov 2008

Good point. I might be the only person that writes on this forum that would use this functionality.

andy Fri 21 Nov 2008

I actually really like the xxxWhile convention - I vote eachBreak change to eachWhile. And I could see findWhile and excludeWhile being useful, but I've yet to come across a use case for them personally.

brian Fri 21 Nov 2008

Yeah, now that Helium has exposed us to takeWhile and dropWhile - I'm thinking we should use the "while" naming convention instead of "break" from our previous discussion. So I propose we rename eachBreak to eachWhile. Or maybe we should rename it to findWhile and takeWhile might be findAllWhile?

Then if we decide to add findWhile and excludeWhile we'd have a consistent naming scheme.

JohnDG Fri 21 Nov 2008

find suggests the wrong thing and should not be used in combination with while.

Universally, in languages and APIs that use while, it means to start from the beginning of the list and continue until some predicate evaluates to false. find universally means to search until matches are found -- skipping over non-matches. find and while are fundamentally incompatible.

find is an element collector. It collects all elements for which the predicate is true. As helium pointed out, it's usually known as filter, because it filters out stuff you don't want.

takeWhile starts at the beginning of the list and collects elements until reaching the first element for which the predicate is false. take is a specialization of takeWhile that evaluates to false after N invocations.

dropWhile starts at the beginning of the list and removes elements until reaching the first element for which the predicate is true. drop is a specialization of dropWhile that evaluates to false after N invocations.

take/drop/takeWhile/dropWhile are likely to be used only by programmers having some functional background. That said, I imagine they might be used by a wider audience in Fan, because Fan offers built-in support for immutability.

alexlamsl Fri 21 Nov 2008

Sincerely apology for not knowing what those Haskell calls were for. Although I'd find Brian's findIndex code to be simple and straight-forward enough.

IMHO, eachBreak --> eachWhile yes, but findAll and exclude are good as they are. If for anything, findAll --> includeAll and exclude --> excludeAll seems more precise in implying what they do.

jodastephen Sat 22 Nov 2008

I consider findAll() to be a more friendly name than filter().

In my head, I would expect filter() to modify the existing list, whereas findAll() to return a new list. Perhaps the definition of method names differs based on whether you have mutable state in a language, and whether that mutable state (OO) is more important than the non-mutable state (FP).

Finally, I still consider eachBreak() to be a hack. I want it to be something we remove if we can get non-local returns working that are hard for a developer to get wrong.

Overall, I wouldn't add these take/drop/while methods and I wouldn't rename eachBreak. Instead, we should focus on finally solving construction/with-blocks/non-local returns.

brian Sat 22 Nov 2008

Finally, I still consider eachBreak() to be a hack. I want it to be something we remove if we can get non-local returns working that are hard for a developer to get wrong.

I agree non-locals would be nice. But even if we had them I don't consider having a specialized iterator which terminates on a condition a "hack". In fact its has turned out to be a very useful (and used) method in the API.

I will rename eachBreak to eachWhile. Then we can evaluate over time if the others make sense. Typically I like to wait until I come across a bit of code where I need it myself - and now that I am aware of dropWhile and takeWhile I'll be keeping it in the back of my mind.

Login or Signup to reply.