bartfeenstra / fu-php

🛠 Functional programming tools for PHP 🐘
https://twitter.com/BartFeenstra
MIT License
2 stars 1 forks source link

Add Iterator::assert() that raises an exception if the given predicate does not apply to an item #66

Open bartfeenstra opened 7 years ago

bartfeenstra commented 7 years ago

Add Iterator::assert() that raises an exception if the given predicate does not apply to an item.

Jasu commented 7 years ago

Maybe add assertSome and assertAll to check that all items match or that there exists a matching item? Although assertSome would probably not be as useful.

bartfeenstra commented 7 years ago

Thanks for the review! ::assertAll() would be like the current ::assert() behavior in this PR (bail as soon as one item is found that does not meet the conditions). Do we know use cases for::assertSome()/::assertAny()? I'm trying to come up with one where we'd want to bail out of processing an iterable if not at least one item meets a condition (meaning n-1 items can still NOT meet the condition). This is keeping in mind that assertions are more a development tool than a simple condition check: use them only when you really want things to fail.

bartfeenstra commented 7 years ago

Also note that my original use case for this PR was to ensure an iterable is a proper list or map (all keys and values share the same type), so I'll have to take some time to try and come up with ::assertSome() use cases as well :)

bartfeenstra commented 7 years ago

While biking from the office just now I realized the iterator behavior of this method is identical to that of ::each(): perform an action with possible side effects for each item, without producing a result (that's why they both provide a fluent interface for daisy chaining). From this perspective we've got the same dilemma as with https://github.com/bartfeenstra/fu-php/issues/61: do we want to provide such shortcut methods on our primary interface, if they can easily be composed by two other methods as well? Assertions with ::each() would look like this:

$iterator->each(function ($value) {
  assert($value instanceof Foo);
})->map(...);

versus

$iterator->assert(P\instance_of([Foo::class])->map(...);

Using ::each() we have maximum flexibility. ::assert() is shorter and easy to read, but limited to using predicates (which contribute to the fact it's shorter) and it can only throw exceptions of a single type, meaning it's more useful for debugging, and less for runtime checks where exceptions are used to catch recoverable problems, as API-specific exceptions cannot be thrown.

bartfeenstra commented 7 years ago

https://github.com/bartfeenstra/fu-php/pull/68#issuecomment-330353701 proposes ::assert() takes an optional second parameter which is an operation (like ::each() takes) and throws the exception. Although then the syntax becomes arguably more verbose than when using ::each() directly.