From 44f9ed1ad5c65beaa2bfa8169ae57449fdebf2b6 Mon Sep 17 00:00:00 2001 From: widmogrod Date: Sat, 30 Dec 2017 02:05:00 +0100 Subject: [PATCH 1/2] Refactor filter function to work only on Listt --- example/ListComprehensionWithMonadTest.php | 17 +++++------ src/Functional/functions.php | 34 +++++++++++++++------- test/Functional/FilterTest.php | 15 ++++++---- 3 files changed, 41 insertions(+), 25 deletions(-) diff --git a/example/ListComprehensionWithMonadTest.php b/example/ListComprehensionWithMonadTest.php index 552d8cd..30b0842 100644 --- a/example/ListComprehensionWithMonadTest.php +++ b/example/ListComprehensionWithMonadTest.php @@ -20,14 +20,13 @@ public function test_it_should_combine_two_lists() }); }); - $this->assertEquals( - fromIterable([ - [1, 'a'], - [1, 'b'], - [2, 'a'], - [2, 'b'] - ]), - $result - ); + $expected = fromIterable([ + [1, 'a'], + [1, 'b'], + [2, 'a'], + [2, 'b'] + ]); + + $this->assertTrue($expected->equals($result)); } } diff --git a/src/Functional/functions.php b/src/Functional/functions.php index bed18bb..894e84e 100644 --- a/src/Functional/functions.php +++ b/src/Functional/functions.php @@ -11,6 +11,7 @@ use Widmogrod\FantasyLand\Monad; use Widmogrod\FantasyLand\Traversable; use Widmogrod\Monad\Identity; +use Widmogrod\Primitive\EmptyListError; use Widmogrod\Primitive\Listt; use Widmogrod\Primitive\ListtCons; @@ -278,20 +279,31 @@ function foldr(callable $callable, $accumulator = null, Foldable $foldable = nul /** * filter :: (a -> Bool) -> [a] -> [a] * - * @param callable $predicate - * @param Foldable $list + * Because I want to make list filtering lazy, + * implementation of this method diverge from Haskell one * - * @return Foldable + * ```haskell + * filter pred (x:xs) + * | pred x = x : filter pred xs + * | otherwise = filter pred xs + * ``` + * + * @param callable $predicate + * @param Listt $xs + * @return Listt */ -function filter(callable $predicate, Foldable $list = null) +function filter(callable $predicate, Listt $xs = null) { - return curryN(2, function (callable $predicate, Foldable $list) { - return reduce(function (Listt $list, $x) use ($predicate) { - return $predicate($x) - ? new ListtCons(function () use ($list, $x) { - return [$x, $list]; - }) : $list; - }, fromNil(), $list); + return curryN(2, function (callable $predicate, Listt $xs): Listt { + try { + return $predicate(head($xs)) + ? new ListtCons(function () use ($predicate, $xs) : array { + return [head($xs), filter($predicate, tail($xs))]; + }) + : filter($predicate, tail($xs)); + } catch (EmptyListError $e) { + return fromNil(); + } })(...func_get_args()); } diff --git a/test/Functional/FilterTest.php b/test/Functional/FilterTest.php index 1f21dcf..6bc9fd8 100644 --- a/test/Functional/FilterTest.php +++ b/test/Functional/FilterTest.php @@ -7,6 +7,7 @@ use function Widmogrod\Functional\filter; use function Widmogrod\Functional\fromIterable; use function Widmogrod\Functional\fromNil; +use Widmogrod\Primitive\Listt; class FilterTest extends \PHPUnit\Framework\TestCase { @@ -14,16 +15,20 @@ class FilterTest extends \PHPUnit\Framework\TestCase * @dataProvider provideData */ public function test_it_should_filter_with_maybe( - $list, - $expected + Listt $list, + Listt $expected ) { $filter = function (int $i): bool { return $i % 2 === 1; }; - $this->assertEquals( - $expected, - filter($filter, $list) + $result = filter($filter, $list); + $r = print_r($result->extract(), true); + $e = print_r($expected->extract(), true); + + $this->assertTrue( + $result->equals($expected), + "$e != $r" ); } From 9fa2b5f77b3037d82f5f69327dc6356c2262fbf1 Mon Sep 17 00:00:00 2001 From: widmogrod Date: Sat, 30 Dec 2017 22:00:34 +0100 Subject: [PATCH 2/2] Make lists event lazier --- src/Functional/functions.php | 20 ++++++----- src/Functional/miscellaneous.php | 15 +++++++++ src/Primitive/ListtCons.php | 57 +++++++++++++++++++++++--------- test/Functional/FilterTest.php | 8 ++++- 4 files changed, 74 insertions(+), 26 deletions(-) diff --git a/src/Functional/functions.php b/src/Functional/functions.php index 894e84e..6978612 100644 --- a/src/Functional/functions.php +++ b/src/Functional/functions.php @@ -295,15 +295,17 @@ function foldr(callable $callable, $accumulator = null, Foldable $foldable = nul function filter(callable $predicate, Listt $xs = null) { return curryN(2, function (callable $predicate, Listt $xs): Listt { - try { - return $predicate(head($xs)) - ? new ListtCons(function () use ($predicate, $xs) : array { - return [head($xs), filter($predicate, tail($xs))]; - }) - : filter($predicate, tail($xs)); - } catch (EmptyListError $e) { - return fromNil(); - } + return new ListtCons(function () use ($predicate, $xs) { + try { + return $predicate(head($xs)) + ? new ListtCons(function () use ($predicate, $xs) { + return [head($xs), filter($predicate, tail($xs))]; + }) + : filter($predicate, tail($xs)); + } catch (EmptyListError $e) { + return fromNil(); + } + }); })(...func_get_args()); } diff --git a/src/Functional/miscellaneous.php b/src/Functional/miscellaneous.php index 2da884f..cc5f881 100644 --- a/src/Functional/miscellaneous.php +++ b/src/Functional/miscellaneous.php @@ -4,6 +4,21 @@ namespace Widmogrod\Functional; +/** + * @var callable + */ +const noop = 'Widmogrod\Functional\noop'; + +/** + * noop :: _ -> _ + * + * @return null + */ +function noop() +{ + return null; +} + /** * @var callable */ diff --git a/src/Primitive/ListtCons.php b/src/Primitive/ListtCons.php index ed0df28..f6c2b70 100644 --- a/src/Primitive/ListtCons.php +++ b/src/Primitive/ListtCons.php @@ -7,6 +7,8 @@ use Widmogrod\Common; use Widmogrod\FantasyLand; use Widmogrod\Functional as f; +use function Widmogrod\Functional\constt; +use const Widmogrod\Functional\noop; class ListtCons implements Listt, \IteratorAggregate { @@ -39,7 +41,16 @@ public function getIterator() { $tail = $this; do { - [$head, $tail] = $tail->headTail(); + $result = $tail->lazyHeadTail(function ($x, Listt $xs): array { + return [$x, $xs]; + }, noop); + + if (!is_array($result)) { + return; + } + + [$head, $tail] = $result; + yield $head; } while ($tail instanceof self); } @@ -50,9 +61,9 @@ public function getIterator() public function map(callable $transformation): FantasyLand\Functor { return new self(function () use ($transformation) { - [$head, $tail] = $this->headTail(); - - return [$transformation($head), $tail->map($transformation)]; + return $this->lazyHeadTail(function ($x, Listt $xs) use ($transformation) { + return [$transformation($x), $xs->map($transformation)]; + }, constt(self::mempty())); }); } @@ -148,9 +159,9 @@ public function concat(FantasyLand\Semigroup $value): FantasyLand\Semigroup if ($value instanceof self) { return new self(function () use ($value) { - [$x, $xs] = $this->headTail(); - - return [$x, $xs->concat($value)]; + return $this->lazyHeadTail(function ($x, Listt $xs) use ($value) { + return [$x, $xs->concat($value)]; + }, constt(self::mempty())); }); } @@ -162,7 +173,7 @@ public function concat(FantasyLand\Semigroup $value): FantasyLand\Semigroup */ public function equals($other): bool { - return $other instanceof self + return $other instanceof Listt ? $this->extract() === $other->extract() : false; } @@ -172,9 +183,11 @@ public function equals($other): bool */ public function head() { - [$head] = $this->headTail(); - - return $head; + return $this->lazyHeadTail(function ($head) { + return $head; + }, function () { + throw new EmptyListError(__FUNCTION__); + }); } /** @@ -182,13 +195,25 @@ public function head() */ public function tail(): Listt { - [$head, $tail] = $this->headTail(); - - return $tail; + return $this->lazyHeadTail(function ($head, $tail) { + return $tail; + }, function () { + throw new EmptyListError(__FUNCTION__); + }); } - public function headTail(): array + private function lazyHeadTail(callable $headTail, callable $nil) { - return call_user_func($this->next); + $result = $this; + + do { + $result = call_user_func($result->next); + } while ($result instanceof self); + + if ($result instanceof ListtNil) { + return $nil(); + } + + return $headTail(...$result); } } diff --git a/test/Functional/FilterTest.php b/test/Functional/FilterTest.php index 6bc9fd8..ac24f8e 100644 --- a/test/Functional/FilterTest.php +++ b/test/Functional/FilterTest.php @@ -4,10 +4,12 @@ namespace test\Functional; +use Widmogrod\Primitive\Listt; use function Widmogrod\Functional\filter; use function Widmogrod\Functional\fromIterable; use function Widmogrod\Functional\fromNil; -use Widmogrod\Primitive\Listt; +use function Widmogrod\Functional\repeat; +use function Widmogrod\Functional\take; class FilterTest extends \PHPUnit\Framework\TestCase { @@ -47,6 +49,10 @@ public function provideData() '$list' => fromIterable(new \ArrayIterator([1, 2, 3, 4, 5])), '$expected' => fromIterable([1, 3, 5]), ], + 'filter everything' => [ + '$list' => take(300, repeat(2)), + '$expected' => fromNil(), + ], ]; } }