Skip to content

Commit

Permalink
Merge pull request #68 from tPl0ch/feature/listt
Browse files Browse the repository at this point in the history
Implement `Listt::head` and `Listt::tail`
  • Loading branch information
widmogrod authored Dec 17, 2017
2 parents f0249ce + 67299e2 commit 08a34ad
Show file tree
Hide file tree
Showing 5 changed files with 173 additions and 10 deletions.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"require-dev": {
"phpunit/phpunit": "^4.7",
"friendsofphp/php-cs-fixer": "^2",
"codeclimate/php-test-reporter": "^0.4.4"
"codeclimate/php-test-reporter": "^0.4.4",
"giorgiosironi/eris": "^0.9"
},
"license": "MIT",
"authors": [
Expand Down
57 changes: 56 additions & 1 deletion composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/Functional/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -580,7 +580,7 @@ function flip(callable $func)
*/
function isNativeTraversable($value)
{
return is_array($value) || $value instanceof \Traversable;
return \is_iterable($value);
}

/**
Expand Down
62 changes: 55 additions & 7 deletions src/Primitive/Listt.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class Listt implements
{
use Common\PointedTrait;

const of = 'Widmogrod\Primitive\Listt::of';
public const of = 'Widmogrod\Primitive\Listt::of';

/**
* @param array $value
Expand All @@ -35,7 +35,7 @@ public function map(callable $transformation)
{
$result = [];
foreach ($this->value as $value) {
$result[] = call_user_func($transformation, $value);
$result[] = $transformation($value);
}

return self::of($result);
Expand Down Expand Up @@ -68,11 +68,13 @@ public function bind(callable $transformation)
*/
public function extract()
{
return array_map(function ($value) {
return $value instanceof Common\ValueOfInterface
return $this->reduce(function ($accumulator, $value) {
$accumulator[] = $value instanceof Common\ValueOfInterface
? $value->extract()
: $value;
}, $this->value);

return $accumulator;
}, []);
}

/**
Expand All @@ -81,7 +83,7 @@ public function extract()
public function reduce(callable $function, $accumulator)
{
foreach ($this->value as $item) {
$accumulator = call_user_func($function, $accumulator, $item);
$accumulator = $function($accumulator, $item);
}

return $accumulator;
Expand All @@ -103,7 +105,7 @@ public function traverse(callable $transformation)

return $functor
->map(f\append)
->ap($ys ? $ys : $functor::of([])); // https://github.com/widmogrod/php-functional/issues/30
->ap($ys ?: $functor::of([])); // https://github.com/widmogrod/php-functional/issues/30
}, false, $this);
}

Expand All @@ -125,6 +127,8 @@ public function getEmpty()

/**
* @inheritdoc
*
* @throws TypeMismatchError
*/
public function concat(FantasyLand\Semigroup $value)
{
Expand All @@ -148,4 +152,48 @@ public function equals($other)
? $this->extract() === $other->extract()
: false;
}

/**
* head :: [a] -> a
*
* @return mixed First element of Listt
*
* @throws \BadMethodCallException
*/
public function head()
{
return $this->guardEmptyGenerator('head of empty Listt')->current();
}

/**
* tail :: [a] -> [a]
*
* @return \Widmogrod\Primitive\Listt
*
* @throws \BadMethodCallException
*/
public function tail(): self
{
($generator = $this->guardEmptyGenerator('tail of empty Listt'))->next();

return $generator->valid()
? self::of((function ($values) {
yield from $values;
})($generator))
: self::mempty();
}

private function guardEmptyGenerator(string $message): \Generator
{
/** @var \Generator $generator */
$generator = (function ($values) {
yield from $values;
})($this->value);

if (!$generator->valid()) {
throw new \BadMethodCallException($message);
}

return $generator;
}
}
59 changes: 59 additions & 0 deletions test/Primitive/ListtTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace test\Monad;

use Eris\TestTrait;
use Widmogrod\FantasyLand\Applicative;
use Widmogrod\FantasyLand\Functor;
use Widmogrod\FantasyLand\Monoid;
Expand All @@ -11,9 +12,13 @@
use Widmogrod\Helpful\MonadLaws;
use Widmogrod\Helpful\MonoidLaws;
use Widmogrod\Primitive\Listt;
use function Eris\Generator\choose;
use function Eris\Generator\vector;

class ListtTest extends \PHPUnit_Framework_TestCase
{
use TestTrait;

/**
* @dataProvider provideData
*/
Expand Down Expand Up @@ -149,4 +154,58 @@ public function provideRandomizedData()
];
}, array_fill(0, 50, null));
}

public function test_head_on_empty_list_is_undefined()
{
$this->setExpectedException(\BadMethodCallException::class, 'head of empty Listt');

Listt::mempty()->head();
}

public function test_head_extracts_first_element()
{
$this->forAll(
vector(10, choose(1, 1000))
)(
function ($sequence) {
$list = Listt::of($sequence);
$current = current($sequence);

$this->assertSame($current, $list->head());
$this->assertSame($current, $list->head());
}
);
}

public function test_tail_on_empty_list()
{
$this->setExpectedException(\BadMethodCallException::class, 'tail of empty Listt');

Listt::mempty()->tail();
}

public function test_tail_with_single_element_Listt()
{
$this->forAll(
vector(1, choose(1, 1000))
)(
function ($sequence) {
$this->assertTrue(Listt::of($sequence)->tail()->equals(Listt::mempty()));
}
);
}

public function test_tail_with_multiple_element_Listt()
{
$this->forAll(
vector(10, choose(1, 1000))
)(
function ($sequence) {
$list = Listt::of($sequence);
array_shift($sequence);

$this->assertEquals($sequence, $list->tail()->extract());
}
);
}
}

0 comments on commit 08a34ad

Please sign in to comment.