Skip to content

Commit 08a34ad

Browse files
authored
Merge pull request #68 from tPl0ch/feature/listt
Implement `Listt::head` and `Listt::tail`
2 parents f0249ce + 67299e2 commit 08a34ad

File tree

5 files changed

+173
-10
lines changed

5 files changed

+173
-10
lines changed

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
"require-dev": {
88
"phpunit/phpunit": "^4.7",
99
"friendsofphp/php-cs-fixer": "^2",
10-
"codeclimate/php-test-reporter": "^0.4.4"
10+
"codeclimate/php-test-reporter": "^0.4.4",
11+
"giorgiosironi/eris": "^0.9"
1112
},
1213
"license": "MIT",
1314
"authors": [

composer.lock

Lines changed: 56 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Functional/functions.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -580,7 +580,7 @@ function flip(callable $func)
580580
*/
581581
function isNativeTraversable($value)
582582
{
583-
return is_array($value) || $value instanceof \Traversable;
583+
return \is_iterable($value);
584584
}
585585

586586
/**

src/Primitive/Listt.php

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class Listt implements
1616
{
1717
use Common\PointedTrait;
1818

19-
const of = 'Widmogrod\Primitive\Listt::of';
19+
public const of = 'Widmogrod\Primitive\Listt::of';
2020

2121
/**
2222
* @param array $value
@@ -35,7 +35,7 @@ public function map(callable $transformation)
3535
{
3636
$result = [];
3737
foreach ($this->value as $value) {
38-
$result[] = call_user_func($transformation, $value);
38+
$result[] = $transformation($value);
3939
}
4040

4141
return self::of($result);
@@ -68,11 +68,13 @@ public function bind(callable $transformation)
6868
*/
6969
public function extract()
7070
{
71-
return array_map(function ($value) {
72-
return $value instanceof Common\ValueOfInterface
71+
return $this->reduce(function ($accumulator, $value) {
72+
$accumulator[] = $value instanceof Common\ValueOfInterface
7373
? $value->extract()
7474
: $value;
75-
}, $this->value);
75+
76+
return $accumulator;
77+
}, []);
7678
}
7779

7880
/**
@@ -81,7 +83,7 @@ public function extract()
8183
public function reduce(callable $function, $accumulator)
8284
{
8385
foreach ($this->value as $item) {
84-
$accumulator = call_user_func($function, $accumulator, $item);
86+
$accumulator = $function($accumulator, $item);
8587
}
8688

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

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

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

126128
/**
127129
* @inheritdoc
130+
*
131+
* @throws TypeMismatchError
128132
*/
129133
public function concat(FantasyLand\Semigroup $value)
130134
{
@@ -148,4 +152,48 @@ public function equals($other)
148152
? $this->extract() === $other->extract()
149153
: false;
150154
}
155+
156+
/**
157+
* head :: [a] -> a
158+
*
159+
* @return mixed First element of Listt
160+
*
161+
* @throws \BadMethodCallException
162+
*/
163+
public function head()
164+
{
165+
return $this->guardEmptyGenerator('head of empty Listt')->current();
166+
}
167+
168+
/**
169+
* tail :: [a] -> [a]
170+
*
171+
* @return \Widmogrod\Primitive\Listt
172+
*
173+
* @throws \BadMethodCallException
174+
*/
175+
public function tail(): self
176+
{
177+
($generator = $this->guardEmptyGenerator('tail of empty Listt'))->next();
178+
179+
return $generator->valid()
180+
? self::of((function ($values) {
181+
yield from $values;
182+
})($generator))
183+
: self::mempty();
184+
}
185+
186+
private function guardEmptyGenerator(string $message): \Generator
187+
{
188+
/** @var \Generator $generator */
189+
$generator = (function ($values) {
190+
yield from $values;
191+
})($this->value);
192+
193+
if (!$generator->valid()) {
194+
throw new \BadMethodCallException($message);
195+
}
196+
197+
return $generator;
198+
}
151199
}

test/Primitive/ListtTest.php

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace test\Monad;
44

5+
use Eris\TestTrait;
56
use Widmogrod\FantasyLand\Applicative;
67
use Widmogrod\FantasyLand\Functor;
78
use Widmogrod\FantasyLand\Monoid;
@@ -11,9 +12,13 @@
1112
use Widmogrod\Helpful\MonadLaws;
1213
use Widmogrod\Helpful\MonoidLaws;
1314
use Widmogrod\Primitive\Listt;
15+
use function Eris\Generator\choose;
16+
use function Eris\Generator\vector;
1417

1518
class ListtTest extends \PHPUnit_Framework_TestCase
1619
{
20+
use TestTrait;
21+
1722
/**
1823
* @dataProvider provideData
1924
*/
@@ -149,4 +154,58 @@ public function provideRandomizedData()
149154
];
150155
}, array_fill(0, 50, null));
151156
}
157+
158+
public function test_head_on_empty_list_is_undefined()
159+
{
160+
$this->setExpectedException(\BadMethodCallException::class, 'head of empty Listt');
161+
162+
Listt::mempty()->head();
163+
}
164+
165+
public function test_head_extracts_first_element()
166+
{
167+
$this->forAll(
168+
vector(10, choose(1, 1000))
169+
)(
170+
function ($sequence) {
171+
$list = Listt::of($sequence);
172+
$current = current($sequence);
173+
174+
$this->assertSame($current, $list->head());
175+
$this->assertSame($current, $list->head());
176+
}
177+
);
178+
}
179+
180+
public function test_tail_on_empty_list()
181+
{
182+
$this->setExpectedException(\BadMethodCallException::class, 'tail of empty Listt');
183+
184+
Listt::mempty()->tail();
185+
}
186+
187+
public function test_tail_with_single_element_Listt()
188+
{
189+
$this->forAll(
190+
vector(1, choose(1, 1000))
191+
)(
192+
function ($sequence) {
193+
$this->assertTrue(Listt::of($sequence)->tail()->equals(Listt::mempty()));
194+
}
195+
);
196+
}
197+
198+
public function test_tail_with_multiple_element_Listt()
199+
{
200+
$this->forAll(
201+
vector(10, choose(1, 1000))
202+
)(
203+
function ($sequence) {
204+
$list = Listt::of($sequence);
205+
array_shift($sequence);
206+
207+
$this->assertEquals($sequence, $list->tail()->extract());
208+
}
209+
);
210+
}
152211
}

0 commit comments

Comments
 (0)