├── .gitignore ├── P ├── Applicative.php ├── Arithmetic.php ├── Bitwise.php ├── Boolean.php ├── Collection.php ├── Core.php ├── Either │ ├── Either.php │ ├── Left.php │ └── Right.php ├── Examples │ ├── Fun.php │ └── Validation.php ├── Functor.php ├── Maybe │ ├── Just.php │ ├── Maybe.php │ └── Nothing.php ├── Monad.php ├── Relational.php ├── Test │ ├── CollectionTest.php │ ├── CoreTest.php │ ├── EitherTest.php │ ├── Fixture │ │ ├── Foo.php │ │ └── Point.php │ ├── MaybeTest.php │ └── ValidationTest.php └── Validation │ ├── Failure.php │ ├── Success.php │ └── Validation.php ├── README.md └── composer.json /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | composer.lock 3 | .idea 4 | *.iml 5 | .DS_Store 6 | composer.phar 7 | -------------------------------------------------------------------------------- /P/Applicative.php: -------------------------------------------------------------------------------- 1 | > $b; 89 | }; 90 | Bitwise::$implementations['rightShift'] = Core::curry(Bitwise::$implementations['rightShift']); 91 | -------------------------------------------------------------------------------- /P/Boolean.php: -------------------------------------------------------------------------------- 1 | = 0; $i--) { 116 | $result = \call_user_func($f, $xs[$i], $result); 117 | \array_unshift($accumulator, $result); 118 | } 119 | 120 | return $accumulator; 121 | }; 122 | Collection::$implementations['scanr'] = Core::curry(Collection::$implementations['scanr']); 123 | 124 | /** 125 | * Returns a new array with the element in reverse order 126 | * 127 | * @param array $xs 128 | * @return array 129 | */ 130 | Collection::$implementations['reverse'] = function(array $xs) { 131 | return \array_reverse($xs); 132 | }; 133 | 134 | /** 135 | * Separates an array into two arrays, one with the elements where the predicate $f returns true, 136 | * the other with the elements where the predicate $f returns false 137 | * 138 | * @param callable $f 139 | * @param array $xs 140 | * @return array 141 | */ 142 | Collection::$implementations['partition'] = function(callable $f, array $xs) { 143 | $trues = []; 144 | $falses = []; 145 | 146 | foreach($xs as $x) { 147 | if (\call_user_func($f, $x)) { 148 | $trues[] = $x; 149 | } else { 150 | $falses[] = $x; 151 | } 152 | } 153 | 154 | return [$trues, $falses]; 155 | }; 156 | Collection::$implementations['partition'] = Core::curry(Collection::$implementations['partition']); 157 | 158 | /** 159 | * Returns an array with $n times $a in it 160 | * 161 | * @param int $n 162 | * @param mixed $a 163 | * @return array 164 | */ 165 | Collection::$implementations['replicate'] = function($n, $a) { 166 | $result = []; 167 | 168 | for ($i = 0; $i < $n; $i++) { 169 | $result[] = $a; 170 | } 171 | 172 | return $result; 173 | }; 174 | Collection::$implementations['replicate'] = Core::curry(Collection::$implementations['replicate']); 175 | 176 | /** 177 | * Returns a new array where all the elements are separated by a $a element 178 | * 179 | * @param mixed $a 180 | * @param array $xs 181 | * @return array 182 | */ 183 | Collection::$implementations['intersperse'] = function($a, array $xs) { 184 | $tail = Collection::$implementations['tail']; 185 | 186 | if (count($xs) < 2) { 187 | return $xs; 188 | } 189 | 190 | $result = [$xs[0]]; 191 | foreach($tail($xs) as $x) { 192 | $result[] = $a; 193 | $result[] = $x; 194 | } 195 | 196 | return $result; 197 | }; 198 | Collection::$implementations['intersperse'] = Core::curry(Collection::$implementations['intersperse']); 199 | 200 | /** 201 | * Concatenates two arrays 202 | * 203 | * @param array $xs 204 | * @param array $ys 205 | * @return array 206 | */ 207 | Collection::$implementations['concat'] = function(array $xs, array $ys) { 208 | return \array_merge($xs, $ys); 209 | }; 210 | Collection::$implementations['concat'] = Core::curry(Collection::$implementations['concat']); 211 | 212 | /** 213 | * Return the first element of an array wrapped in a Maybe 214 | * 215 | * @param array $xs 216 | * @return Maybe\Just|Nothing 217 | */ 218 | Collection::$implementations['head'] = function(array $xs) { 219 | return \count($xs) > 0 ? Maybe::of($xs[0]) 220 | : Nothing::instance(); 221 | }; 222 | 223 | /** 224 | * Returns a new array with all elements from $xs but the first 225 | * 226 | * @param array $xs 227 | * @return array 228 | */ 229 | Collection::$implementations['tail'] = function(array $xs) { 230 | return \count($xs) > 0 ? \array_slice($xs, 1) 231 | : []; 232 | }; 233 | 234 | /** 235 | * Returns the last element of $xs wrapped in a Maybe 236 | * 237 | * @param array $xs 238 | * @return Maybe\Just|Nothing 239 | */ 240 | Collection::$implementations['last'] = function(array $xs) { 241 | $length = \count($xs); 242 | return $length > 0 ? Maybe::of($xs[$length - 1]) 243 | : Nothing::instance(); 244 | }; 245 | 246 | /** 247 | * Returns all the elements of $xs but the last one 248 | * 249 | * @param array $xs 250 | * @return array 251 | */ 252 | Collection::$implementations['init'] = function(array $xs) { 253 | $length = \count($xs); 254 | 255 | return $length > 0 ? \array_slice($xs, 0, $length - 1) 256 | : []; 257 | }; 258 | 259 | /** 260 | * Returns the array $xs sorted using the comparator $f 261 | * 262 | * @param callable $f 263 | * @param array $xs 264 | * @return array 265 | */ 266 | Collection::$implementations['sortBy'] = function(callable $f, array $xs) { 267 | \usort($xs, $f); 268 | return $xs; 269 | }; 270 | Collection::$implementations['sortBy'] = Core::curry(Collection::$implementations['sortBy']); 271 | 272 | /** 273 | * Returns true if all the elements in $xs return true when passed to the predicate $f 274 | * 275 | * @param callable $f 276 | * @param array $xs 277 | * @return bool 278 | */ 279 | Collection::$implementations['all'] = function(callable $f, array $xs) { 280 | foreach($xs as $x) { 281 | if (!\call_user_func($f, $x)) { 282 | return false; 283 | } 284 | } 285 | 286 | return true; 287 | }; 288 | Collection::$implementations['all'] = Core::curry(Collection::$implementations['all']); 289 | 290 | /** 291 | * Returns true if any elements in $xs returns true when passed to the predicate $f 292 | * 293 | * @param callable $f 294 | * @param array $xs 295 | * @return bool 296 | */ 297 | Collection::$implementations['any'] = function(callable $f, array $xs) { 298 | foreach($xs as $x) { 299 | if (\call_user_func($f, $x)) { 300 | return true; 301 | } 302 | } 303 | 304 | return false; 305 | }; 306 | Collection::$implementations['any'] = Core::curry(Collection::$implementations['any']); 307 | 308 | /** 309 | * Returns the first element from $xs that returns true when passed to the predicate $f, 310 | * wrapped in a Maybe 311 | * 312 | * @param callable $f 313 | * @param array $xs 314 | * @return Maybe\Just|Nothing 315 | */ 316 | Collection::$implementations['find'] = function(callable $f, array $xs) { 317 | foreach($xs as $x) { 318 | if (call_user_func($f, $x)) { 319 | return Maybe::of($x); 320 | } 321 | } 322 | 323 | return Nothing::instance(); 324 | }; 325 | Collection::$implementations['find'] = Core::curry(Collection::$implementations['find']); 326 | 327 | /** 328 | * Returns the index of the first element from $xs that returns true when passed to the predicate $f, 329 | * wrapped in a Maybe 330 | * 331 | * @param callable $f 332 | * @param array $xs 333 | * @return Maybe\Just|Nothing 334 | */ 335 | Collection::$implementations['findIndex'] = function(callable $f, array $xs) { 336 | foreach($xs as $index => $x) { 337 | if (\call_user_func($f, $x)) { 338 | return Maybe::of($index); 339 | } 340 | } 341 | 342 | return Nothing::instance(); 343 | }; 344 | Collection::$implementations['findIndex'] = Core::curry(Collection::$implementations['findIndex']); 345 | 346 | /** 347 | * Returns the first $n elements of $xs 348 | * 349 | * @param int $n 350 | * @param array $xs 351 | * @return array 352 | */ 353 | Collection::$implementations['take'] = function($n, array $xs) { 354 | return \array_slice($xs, 0, $n); 355 | }; 356 | Collection::$implementations['take'] = Core::curry(Collection::$implementations['take']); 357 | 358 | /** 359 | * Returns the longest prefix of $xs that satisfies the predicate $f 360 | * 361 | * @param callable $f 362 | * @param array $xs 363 | * @return array 364 | */ 365 | Collection::$implementations['takeWhile'] = function(callable $f, array $xs) { 366 | $keep = true; 367 | $result = []; 368 | 369 | foreach($xs as $x) { 370 | if ($keep && !\call_user_func($f, $x)) { 371 | $keep = false; 372 | } 373 | if ($keep) { 374 | $result[] = $x; 375 | } 376 | } 377 | 378 | return $result; 379 | }; 380 | Collection::$implementations['takeWhile'] = Core::curry(Collection::$implementations['takeWhile']); 381 | 382 | /** 383 | * Returns a new array with the first $n elements removed 384 | * 385 | * @param int $n 386 | * @param array $xs 387 | * @return array 388 | */ 389 | Collection::$implementations['drop'] = function ($n, array $xs) { 390 | return \array_slice($xs, $n); 391 | }; 392 | Collection::$implementations['drop'] = Core::curry(Collection::$implementations['drop']); 393 | 394 | 395 | /** 396 | * Returns a new array where the first X elements satisfying the predicate $f are removed 397 | * 398 | * @param callable $f 399 | * @param array $xs 400 | * @return array 401 | */ 402 | Collection::$implementations['dropWhile'] = function (callable $f, array $xs) { 403 | $keep = false; 404 | $result = []; 405 | 406 | foreach($xs as $x) { 407 | if (!$keep && !\call_user_func($f, $x)) { 408 | $keep = true; 409 | } 410 | if ($keep) { 411 | $result[] = $x; 412 | } 413 | } 414 | 415 | return $result; 416 | }; 417 | Collection::$implementations['dropWhile'] = Core::curry(Collection::$implementations['dropWhile']); 418 | 419 | /** 420 | * Returns a new array of the result of $f applied to every element of both $xs and $ys 421 | * 422 | * @param callable $f 423 | * @param array $xs 424 | * @param array $ys 425 | * @return array 426 | */ 427 | Collection::$implementations['zipWith'] = function (callable $f, array $xs, array $ys) { 428 | return \array_map($f, $xs, $ys); 429 | }; 430 | Collection::$implementations['zipWith'] = Core::curry(Collection::$implementations['zipWith']); 431 | -------------------------------------------------------------------------------- /P/Core.php: -------------------------------------------------------------------------------- 1 | []]; 11 | 12 | private function __construct() {} 13 | 14 | private function __clone() {} 15 | 16 | public static function __callStatic($name, array $arguments) 17 | { 18 | if (!isset(self::$implementations[$name])) { 19 | throw new \Exception("Function $name not found"); 20 | } 21 | 22 | return \call_user_func_array(self::$implementations[$name], $arguments); 23 | } 24 | } 25 | 26 | /** 27 | * Converts an uncurried callable to a curried one 28 | * 29 | * @param callable $f Callable to curry 30 | * @param mixed[] $initialArguments Initial arguments 31 | * @param int $forcedArity Force a specific arity (Useful for variadic callables) 32 | * @return callable 33 | */ 34 | Core::$implementations['curry'] = function(callable $f, array $initialArguments = [], $forcedArity = null) { 35 | $findNumberOfParameters = Core::$implementations['private']['findNumberOfParameters']; 36 | $doCurry = Core::$implementations['private']['doCurry']; 37 | $arity = isset($forcedArity) ? $forcedArity 38 | : $findNumberOfParameters($f); 39 | 40 | if ($arity === 0) { 41 | return function() use ($f) { 42 | return \call_user_func($f); 43 | }; 44 | } 45 | 46 | return $doCurry($f, $initialArguments, $arity); 47 | }; 48 | 49 | Core::$implementations['private']['doCurry'] = function(callable $f, array $arguments, $arity) { 50 | $doCurry = Core::$implementations['private']['doCurry']; 51 | 52 | return function() use ($doCurry, $f, $arguments, $arity) { 53 | $allArguments = \array_merge($arguments, \func_get_args()); 54 | $numberOfAllArguments = \count($allArguments); 55 | $actualArguments = \array_filter($allArguments, function($argument) { return $argument !== Core::_; }); 56 | $numberOfActualArguments = \count($actualArguments); 57 | 58 | if ($numberOfActualArguments > $arity) { 59 | throw \Exception("Too many arguments"); 60 | } 61 | 62 | if ($numberOfActualArguments === $arity) { 63 | $hasPlaceholders = \count($allArguments) > $numberOfActualArguments; 64 | 65 | if ($hasPlaceholders) { 66 | $numberOfPlaceHolders = $numberOfAllArguments - $numberOfActualArguments; 67 | $argumentsToFill = \array_slice($actualArguments, -$numberOfPlaceHolders); 68 | 69 | $placeholderIndex = 0; 70 | $argumentIndex = 0; 71 | while ($placeholderIndex < $numberOfPlaceHolders) { 72 | if ($allArguments[$argumentIndex] == Core::_) { 73 | $allArguments[$argumentIndex] = $argumentsToFill[$placeholderIndex]; 74 | $placeholderIndex++; 75 | } 76 | $argumentIndex++; 77 | } 78 | return \call_user_func_array($f, $allArguments); 79 | } else { 80 | return \call_user_func_array($f, $allArguments); 81 | } 82 | } 83 | 84 | return $doCurry($f, $allArguments, $arity); 85 | }; 86 | }; 87 | 88 | Core::$implementations['private']['findNumberOfParameters'] = function(callable $f) { 89 | if (\is_string($f) && \strpos($f, '::') !== false) { 90 | $f = \explode('::', $f); 91 | } 92 | 93 | if (\is_array($f)) { 94 | $reflectionMethod = new \ReflectionMethod($f[0], $f[1]); 95 | return $reflectionMethod->getNumberOfParameters(); 96 | } 97 | 98 | $reflectionFunction = new \ReflectionFunction($f); 99 | return $reflectionFunction->getNumberOfParameters(); 100 | }; 101 | 102 | /** 103 | * Composes 2 callables together 104 | * 105 | * @param callable $f First callable to compose 106 | * @param callable $g Second callable to compose 107 | * @param mixed $a 108 | * @return callable 109 | */ 110 | Core::$implementations['compose'] = function (callable $f, callable $g, $a) { 111 | return \call_user_func($f, call_user_func($g, $a)); 112 | }; 113 | Core::$implementations['compose'] = \call_user_func(Core::$implementations['curry'], Core::$implementations['compose']); 114 | 115 | /** 116 | * Flips the first two arguments of a callable 117 | * 118 | * @param callable $f Callable on which to flip the first two arguments 119 | * @param mixed $x 120 | * @param mixed $y 121 | * @return callable 122 | */ 123 | Core::$implementations['flip'] = function(callable $f, $x, $y) { 124 | return \call_user_func(\call_user_func($f, $y), $x); 125 | }; 126 | Core::$implementations['flip'] = Core::curry(Core::$implementations['flip']); 127 | 128 | /** 129 | * Returns the argument it is called with 130 | * 131 | * @param $a mixed Value to return 132 | * @return mixed 133 | */ 134 | Core::$implementations['identity'] = function($a) { 135 | return $a; 136 | }; 137 | 138 | /** 139 | * Returns the first argument 140 | * 141 | * @param $a mixed First argument 142 | * @param $b mixed Second argument 143 | * @return mixed 144 | */ 145 | Core::$implementations['constant'] = function($a, $b) { 146 | return $a; 147 | }; 148 | Core::$implementations['constant'] = Core::curry(Core::$implementations['constant']); 149 | 150 | /** 151 | * Lifts a unary callable 152 | * 153 | * @param callable $f Unary callable to lift 154 | * @param Applicative $a Applicative on which to apply the callable 155 | * @return Applicative 156 | */ 157 | Core::$implementations['liftA'] = function(callable $f, Applicative $a) { 158 | return $a->map($f); 159 | }; 160 | Core::$implementations['liftA'] = Core::curry(Core::$implementations['liftA']); 161 | 162 | /** 163 | * Lifts a binary callable 164 | * 165 | * @param callable $f Binary callable to lift 166 | * @param Applicative $a1 First Applicative on which to apply the callable 167 | * @param Applicative $a2 Second Applicative on which to apply the callable 168 | * @return Applicative 169 | */ 170 | Core::$implementations['liftA2'] = function(callable $f, Applicative $a1, Applicative $a2) { 171 | return $a1->map($f)->ap($a2); 172 | }; 173 | Core::$implementations['liftA2'] = Core::curry(Core::$implementations['liftA2']); 174 | 175 | /** 176 | * Lifts a ternary callable 177 | * 178 | * @param callable $f Ternary callable to lift 179 | * @param Applicative $a1 First Applicative on which to apply the callable 180 | * @param Applicative $a2 Second Applicative on which to apply the callable 181 | * @param Applicative $a3 Third Applicative on which to apply the callable 182 | * @return Applicative 183 | */ 184 | Core::$implementations['liftA3'] = function(callable $f, Applicative $a1, Applicative $a2, Applicative $a3) { 185 | return $a1->map($f)->ap($a2)->ap($a3); 186 | }; 187 | Core::$implementations['liftA3'] = Core::curry(Core::$implementations['liftA3']); 188 | 189 | /** 190 | * Applies a callable to a Functor 191 | * 192 | * @param callable $f Callable to apply to the Functor 193 | * @param Functor $a Functor on which to apply the callable 194 | * @return Functor 195 | */ 196 | Core::$implementations['map'] = function(callable$f, $a) { 197 | if (\is_array($a)) { 198 | return Collection::map($f, $a); 199 | } 200 | 201 | return $a->map($f); 202 | }; 203 | Core::$implementations['map'] = Core::curry(Core::$implementations['map']); 204 | 205 | /** 206 | * Applies a lifted callable to an Applicative 207 | * 208 | * @param Applicative $f 209 | * @param Applicative $b 210 | * @return mixed 211 | */ 212 | Core::$implementations['ap'] = function(Applicative $f, Applicative $b) { 213 | return $f->ap($b); 214 | }; 215 | Core::$implementations['ap'] = Core::curry(Core::$implementations['ap']); 216 | 217 | /** 218 | * Evaluates each action in the sequence from left to right, and collects the results 219 | * 220 | * @param string $monadClass Qualified name of the monad 221 | * @param Monad[] $xs Array of Monads to sequence 222 | * @return Monad 223 | */ 224 | Core::$implementations['sequence'] = function($monadClass, array $xs) { 225 | $initial = \call_user_func([$monadClass, 'of'], []); 226 | 227 | return \array_reduce($xs, function($acc, $m) use ($monadClass) { 228 | return $acc->bind(function($val1) use ($monadClass, $m) { 229 | return $m->bind(function($val2) use ($monadClass, $val1) { 230 | return \call_user_func([$monadClass, 'of'], \array_merge($val1, [$val2])); 231 | }); 232 | }); 233 | }, $initial); 234 | }; 235 | Core::$implementations['sequence'] = Core::curry(Core::$implementations['sequence']); 236 | 237 | /** 238 | * Equivalent to sequence + map 239 | * 240 | * @param string $monadClass Qualified name of the Monad 241 | * @param Monad[] $xs Array of elements 242 | * @param callable $f Callable that returns a lifted value 243 | * @return Monad 244 | */ 245 | Core::$implementations['mapM'] = function($monadClass, callable $f, array $xs) { 246 | $sequence = Core::$implementations['sequence']; 247 | return $sequence($monadClass, \array_map($f, $xs)); 248 | }; 249 | Core::$implementations['mapM'] = Core::curry(Core::$implementations['mapM']); 250 | 251 | /** 252 | * Applies $f on the elements and filter them 253 | * 254 | * @param string $monadClass Qualified name of the Monad 255 | * @param callable $f Callable that returns a lifted predicate 256 | * @param array $xs Array of elements 257 | * @return Monad 258 | */ 259 | Core::$implementations['filterM'] = function($monadClass, callable $f, array $xs) { 260 | $filterM = Core::$implementations['filterM']; 261 | 262 | if (\count($xs) === 0) { 263 | return \call_user_func([$monadClass, 'of'], []); 264 | } 265 | 266 | $head = $xs[0]; 267 | $tail = \array_slice($xs, 1); 268 | 269 | $m = \call_user_func($f, $head); 270 | 271 | return $m->bind(function($isTrue) use ($filterM, $monadClass, $f, $head, $tail) { 272 | $remaining = $filterM($monadClass, $f, $tail); 273 | 274 | return $remaining->bind(function($val) use ($monadClass, $head, $isTrue) { 275 | return \call_user_func([$monadClass, 'of'], $isTrue ? \array_merge([$head], $val) : $val); 276 | }); 277 | }); 278 | }; 279 | Core::$implementations['filterM'] = Core::curry(Core::$implementations['filterM']); 280 | 281 | /** 282 | * Left-to-right Kleisli composition of Monads. 283 | * 284 | * @param callable $f First callable to compose 285 | * @param callable $g Second callable to compose 286 | * @param Monad $a 287 | * @return callable 288 | */ 289 | Core::$implementations['composeM'] = function(callable $f, callable $g, $a) { 290 | $return = \call_user_func($f, $a); 291 | 292 | return $return->bind($g); 293 | }; 294 | Core::$implementations['composeM'] = Core::curry(Core::$implementations['composeM']); 295 | 296 | /** 297 | * Removes one level of Monad 298 | * 299 | * @param Monad $m The Monad on which to remove a level (Unwraps) 300 | * @return Monad 301 | */ 302 | Core::$implementations['join'] = function(Monad $m) { 303 | return $m->bind('P\Core::identity'); 304 | }; 305 | 306 | /** 307 | * Returns a curried function that constructs an object using its constructor 308 | * 309 | * @param string $className 310 | * @return callable 311 | */ 312 | Core::$implementations['construct'] = function($className) { 313 | $reflectionClass = new \ReflectionClass($className); 314 | $contructor = $reflectionClass->getConstructor(); 315 | $numberOfArguments = $contructor->getNumberOfParameters(); 316 | 317 | return Core::curry(function() use ($reflectionClass) { 318 | return $reflectionClass->newInstanceargs(\func_get_args()); 319 | }, [], $numberOfArguments); 320 | }; 321 | 322 | /** 323 | * Return the fixed point of a callable (Y Combinator) 324 | * 325 | * @param callable $f 326 | * @return callable 327 | */ 328 | Core::$implementations['fix'] = function(callable $f) { 329 | $tmp = function($g) use ($f) { return function($n) use ($f, $g) { return \call_user_func(\call_user_func($f, \call_user_func($g, $g)), $n); }; }; 330 | return $tmp(function($g) use ($f) { return function($n) use ($f, $g) { return \call_user_func(\call_user_func($f, \call_user_func($g, $g)), $n); }; }); 331 | }; 332 | 333 | /** 334 | * Returns the result of the callable associated with the first predicate that returns true 335 | * 336 | * @param array $xs 337 | * @return callable 338 | */ 339 | Core::$implementations['conditions'] = function(array $xs) { 340 | return function($a) use ($xs) { 341 | $numberOfConditions = \count($xs); 342 | 343 | for ($index = 0; $index < $numberOfConditions; $index++) { 344 | list($predicate, $condition) = $xs[$index]; 345 | 346 | if (\call_user_func($predicate, $a)) { 347 | return \call_user_func($condition, $a); 348 | } 349 | } 350 | 351 | throw new \Exception("No condition was true"); 352 | }; 353 | }; 354 | 355 | /** 356 | * Returns the $name property of an object or array 357 | * 358 | * @param string $name 359 | * @param array|object $a 360 | * @return callable 361 | */ 362 | Core::$implementations['property'] = function($name, $a) { 363 | if (\is_object($a)) { 364 | return \property_exists($a, $name) ? Just::of($a->{$name}) 365 | : Nothing::instance(); 366 | } else if (\is_array($a)) { 367 | return isset($a[$name]) ? Just::of($a[$name]) 368 | : Nothing::instance(); 369 | } 370 | }; 371 | Core::$implementations['property'] = Core::curry(Core::$implementations['property']); 372 | 373 | /** 374 | * Return the callable $f applied to $x 375 | * 376 | * @param callable $f 377 | * @param mixed $x 378 | * @return mixed 379 | */ 380 | Core::$implementations['apply'] = function(callable $f, $x) { 381 | return \call_user_func($f, $x); 382 | }; 383 | Core::$implementations['apply'] = Core::curry(Core::$implementations['apply']); 384 | -------------------------------------------------------------------------------- /P/Either/Either.php: -------------------------------------------------------------------------------- 1 | a = $a; 11 | } 12 | 13 | /** 14 | * Returns true if current instance of Either is Left 15 | * 16 | * @return bool 17 | */ 18 | public function isLeft() 19 | { 20 | return true; 21 | } 22 | 23 | /** 24 | * Returns true if current instance of Either is Right 25 | * 26 | * @return bool 27 | */ 28 | public function isRight() 29 | { 30 | return false; 31 | } 32 | 33 | /** 34 | * Returns true on equality 35 | * 36 | * @param Either $either 37 | * @return bool 38 | */ 39 | public function equals(Either $either) 40 | { 41 | return $either->isLeft() && $this->a === $either->a; 42 | } 43 | 44 | /** 45 | * Standard Functor map callable 46 | * 47 | * @param callable $f 48 | * @return Functor 49 | */ 50 | public function map(callable $f) 51 | { 52 | return self::of($this->a); 53 | } 54 | 55 | /** 56 | * Sequential application 57 | * 58 | * @param Applicative $a 59 | * @return Applicative 60 | */ 61 | public function ap(Applicative $a) { 62 | return $this; 63 | } 64 | 65 | /** 66 | * Sequentially compose two actions, passing any value produced by the first as an argument to the second. 67 | * 68 | * @param callable $f 69 | * @return Monad 70 | */ 71 | public function bind(callable $f) 72 | { 73 | return self::of($this->a); 74 | } 75 | 76 | public function getOrElse(callable $f) 77 | { 78 | return \call_user_func($f, $this->a); 79 | } 80 | 81 | public static function of($a) 82 | { 83 | return new Left($a); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /P/Either/Right.php: -------------------------------------------------------------------------------- 1 | a = $a; 11 | } 12 | 13 | /** 14 | * Returns true if current instance of Either is Left 15 | * 16 | * @return bool 17 | */ 18 | public function isLeft() 19 | { 20 | return false; 21 | } 22 | 23 | /** 24 | * Returns true if current instance of Either is Right 25 | * 26 | * @return bool 27 | */ 28 | public function isRight() 29 | { 30 | return true; 31 | } 32 | 33 | /** 34 | * Returns true on equality 35 | * 36 | * @param Either $either 37 | * @return bool 38 | */ 39 | public function equals(Either $either) 40 | { 41 | return $either->isRight() && $this->a === $either->a; 42 | } 43 | 44 | /** 45 | * Standard Functor map callable 46 | * 47 | * @param callable $f 48 | * @return Functor 49 | */ 50 | public function map(callable $f) 51 | { 52 | return self::of(\call_user_func($f, $this->a)); 53 | } 54 | 55 | /** 56 | * Sequential application 57 | * 58 | * @param Applicative $a 59 | * @return Applicative 60 | */ 61 | public function ap(Applicative $a) { 62 | return $a->map($this->a); 63 | } 64 | 65 | /** 66 | * Sequentially compose two actions, passing any value produced by the first as an argument to the second. 67 | * 68 | * @param callable $f 69 | * @return Monad 70 | */ 71 | public function bind(callable $f) 72 | { 73 | return \call_user_func($f, $this->a); 74 | } 75 | 76 | public function getOrElse(callable $f) 77 | { 78 | return $this->a; 79 | } 80 | 81 | public static function of($a) 82 | { 83 | return new Right($a); 84 | } 85 | } -------------------------------------------------------------------------------- /P/Examples/Fun.php: -------------------------------------------------------------------------------- 1 | = $n ? Success::of($str) 16 | : Failure::of(["$prop is shorter than $n characters"]); 17 | } 18 | 19 | function maxLength($prop, $str, $n) { 20 | return strlen($str) <= $n ? Success::of($str) 21 | : Failure::of(["$prop is longer than $n characters"]); 22 | } 23 | 24 | function containsAtLeastOneNumber($prop, $str) { 25 | return preg_match('/[0-9]+/', $str) ? Success::of($str) 26 | : Failure::of(["$prop should contain at least one number"]); 27 | } 28 | 29 | function containsAtLeastOneCapitalLetter($prop, $str) { 30 | return preg_match('/[A-Z]+/', $str) ? Success::of($str) 31 | : Failure::of(["$prop should contain at least one capital letter"]); 32 | } 33 | 34 | function isValidUsername($username) { 35 | return Validation::of($username) 36 | ->seql(notEmpty("Username", $username)) 37 | ->seql(minLength("Username", $username, 5)) 38 | ->seql(maxLength("Username", $username, 50)); 39 | } 40 | 41 | function isValidPassword($pass) { 42 | return Validation::of($pass) 43 | ->seql(notEmpty("Password", $pass)) 44 | ->seql(minLength("Password", $pass, 5)) 45 | ->seql(maxLength("Password", $pass, 50)) 46 | ->seql(containsAtLeastOneCapitalLetter("Password", $pass)) 47 | ->seql(containsAtLeastOneNumber("Password", $pass)); 48 | } 49 | 50 | class User { 51 | private $username; 52 | private $password; 53 | 54 | public function __construct($username, $password) 55 | { 56 | $this->username = $username; 57 | $this->password = $password; 58 | } 59 | 60 | public function getUsername() 61 | { 62 | return $this->username; 63 | } 64 | 65 | public function getPassword() 66 | { 67 | return $this->password; 68 | } 69 | } 70 | 71 | // Using Applicative style: `ap` (<*>) 72 | $person = Validation::of(P\Core::construct('User')) 73 | ->ap(isValidUsername("Testing")) 74 | ->ap(isValidPassword("")->map('sha1')) 75 | ->getOrElse(function($failures) { return $failures; }); 76 | 77 | var_dump($person); 78 | 79 | // or using liftA2 80 | $person = Core::liftA2( 81 | P\Core::construct('User'), 82 | isValidUsername("Testing"), 83 | isValidPassword("Abc123")->map('sha1') 84 | )->getOrElse(function($failures) { return $failures; }); 85 | 86 | var_dump($person); 87 | -------------------------------------------------------------------------------- /P/Functor.php: -------------------------------------------------------------------------------- 1 | a = $a; 11 | } 12 | 13 | /** 14 | * Returns true if current instance of Maybe is Just 15 | * 16 | * @return bool 17 | */ 18 | public function isJust() 19 | { 20 | return true; 21 | } 22 | 23 | /** 24 | * Returns true if current instance of Maybe is Nothing 25 | * 26 | * @return bool 27 | */ 28 | public function isNothing() 29 | { 30 | return false; 31 | } 32 | 33 | /** 34 | * Returns true on equality 35 | * 36 | * @param Maybe $maybe 37 | * @return mixed 38 | */ 39 | public function equals(Maybe $maybe) 40 | { 41 | return $maybe->isJust() && $this->a === $maybe->a; 42 | } 43 | 44 | /** 45 | * Standard Functor map callable 46 | * 47 | * @param callable $f 48 | * @return Functor 49 | */ 50 | public function map(callable $f) 51 | { 52 | return self::of(\call_user_func($f, $this->a)); 53 | } 54 | 55 | /** 56 | * Sequential application 57 | * 58 | * @param Applicative $a 59 | * @return Applicative 60 | */ 61 | public function ap(Applicative $a) { 62 | return $a->map($this->a); 63 | } 64 | 65 | /** 66 | * Sequentially compose two actions, passing any value produced by the first as an argument to the second. 67 | * 68 | * @param callable $f 69 | * @return Monad 70 | */ 71 | public function bind(callable $f) 72 | { 73 | return \call_user_func($f, $this->a); 74 | } 75 | 76 | public function getOrDefault($default = null) 77 | { 78 | return $this->a; 79 | } 80 | 81 | public static function of($a) 82 | { 83 | return new Just($a); 84 | } 85 | } -------------------------------------------------------------------------------- /P/Maybe/Maybe.php: -------------------------------------------------------------------------------- 1 | isNothing(); 43 | } 44 | 45 | /** 46 | * Standard Functor map callable 47 | * 48 | * @param callable $f 49 | * @return Functor 50 | */ 51 | public function map(callable $f) 52 | { 53 | return self::$instance; 54 | } 55 | 56 | /** 57 | * Sequential application 58 | * 59 | * @param Applicative $a 60 | * @return Applicative 61 | */ 62 | public function ap(Applicative $a) { 63 | return self::$instance; 64 | } 65 | 66 | /** 67 | * Sequentially compose two actions, passing any value produced by the first as an argument to the second. 68 | * 69 | * @param callable $f 70 | * @return Monad 71 | */ 72 | public function bind(callable $f) 73 | { 74 | return self::$instance; 75 | } 76 | 77 | public function getOrDefault($default = null) 78 | { 79 | return $default; 80 | } 81 | 82 | public static function instance() { 83 | if (!isset(self::$instance)) { 84 | self::$instance = new Nothing(); 85 | } 86 | 87 | return self::$instance; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /P/Monad.php: -------------------------------------------------------------------------------- 1 | $b; 55 | }; 56 | Relational::$implementations['greaterThan'] = Core::curry(Relational::$implementations['greaterThan']); 57 | 58 | /** 59 | * Returns true if the $a is greater or equal to $b 60 | * 61 | * @param mixed $a 62 | * @param mixed $b 63 | * @return bool 64 | */ 65 | Relational::$implementations['greaterOrEqualTo'] = function($a, $b) { 66 | return $a >= $b; 67 | }; 68 | Relational::$implementations['greaterOrEqualTo'] = Core::curry(Relational::$implementations['greaterOrEqualTo']); 69 | 70 | /** 71 | * Returns true if the $a is less than $b 72 | * 73 | * @param mixed $a 74 | * @param mixed $b 75 | * @return bool 76 | */ 77 | Relational::$implementations['lessThan'] = function($a, $b) { 78 | return $a < $b; 79 | }; 80 | Relational::$implementations['lessThan'] = Core::curry(Relational::$implementations['lessThan']); 81 | 82 | /** 83 | * Returns true if the $a is less or equal to $b 84 | * 85 | * @param mixed $a 86 | * @param mixed $b 87 | * @return bool 88 | */ 89 | Relational::$implementations['lessOrEqualTo'] = function($a, $b) { 90 | return $a <= $b; 91 | }; 92 | Relational::$implementations['lessOrEqualTo'] = Core::curry(Relational::$implementations['lessOrEqualTo']); 93 | -------------------------------------------------------------------------------- /P/Test/CollectionTest.php: -------------------------------------------------------------------------------- 1 | assertThat(Collection::map(Arithmetic::multiply(2), [1, 2, 3, 4]), $this->equalTo([2, 4, 6, 8])); 18 | } 19 | 20 | public function test_filter_should_return_a_new_array_filtered_with_callable() 21 | { 22 | $this->assertThat(Collection::filter(Relational::greaterThan(Core::_, 2), [1, 2, 3, 4]), $this->equalTo([3, 4])); 23 | } 24 | 25 | public function test_foldl_should_reduce_an_array_to_a_single_element_beginning_from_the_left() 26 | { 27 | $this->assertThat(Collection::foldl('P\Arithmetic::add', 0, [1, 2, 3, 4]), $this->equalTo(10)); 28 | } 29 | 30 | public function test_foldr_should_reduce_an_array_to_a_single_element_beginning_from_the_right() 31 | { 32 | $this->assertThat(Collection::foldr('P\Arithmetic::add', 0, [1, 2, 3, 4]), $this->equalTo(10)); 33 | } 34 | 35 | public function test_scanl_should_reduce_an_array_accumulating_the_intermediate_results_from_the_left() 36 | { 37 | $this->assertThat(Collection::scanl('P\Arithmetic::add', 0, [1, 2, 3, 4]), $this->equalTo([0, 1, 3, 6, 10])); 38 | } 39 | 40 | public function test_scanr_should_reduce_an_array_accumulating_the_intermediate_results_from_the_right() 41 | { 42 | $this->assertThat(Collection::scanr('P\Arithmetic::add', 0, [1, 2, 3, 4]), $this->equalTo([10, 9, 7, 4, 0])); 43 | } 44 | 45 | public function test_reverse_should_return_an_array_with_its_elements_reversed() 46 | { 47 | $this->assertThat(Collection::reverse([1, 2, 3, 4]), $this->equalTo([4, 3, 2, 1])); 48 | } 49 | 50 | public function test_partition_should_return_an_array_with_the_elements_that_satisfies_the_callable_and_another_with_the_rest() 51 | { 52 | $this->assertThat(Collection::partition(Relational::greaterThan(Core::_, 2), [1, 2, 3, 4]), $this->equalTo([[3, 4], [1, 2]])); 53 | } 54 | 55 | public function test_replicate_should_return_an_array_of_N_times_some_value() 56 | { 57 | $this->assertThat(Collection::replicate(4, 1), $this->equalTo([1, 1, 1, 1])); 58 | } 59 | 60 | public function test_intersperse_should_return_an_array_where_the_elements_are_separated_by_some_value() 61 | { 62 | $this->assertThat(Collection::intersperse(1, [1, 2, 3, 4]), $this->equalTo([1, 1, 2, 1, 3, 1, 4])); 63 | } 64 | 65 | public function test_concat_should_concatenate_two_arrays() 66 | { 67 | $this->assertThat(Collection::concat([1, 2], [3, 4]), $this->equalTo([1, 2, 3, 4])); 68 | } 69 | 70 | public function test_head_should_return_the_first_element_of_an_array_wrapped_in_a_maybe() 71 | { 72 | $this->assertTrue(Collection::head([1, 2])->equals(Maybe::of(1))); 73 | $this->assertTrue(Collection::head([])->equals(Nothing::instance())); 74 | } 75 | 76 | public function test_tail_should_return_all_the_elements_of_an_array_except_the_first() 77 | { 78 | $this->assertThat(Collection::tail([1, 2, 3, 4]), $this->equalTo([2, 3, 4])); 79 | } 80 | 81 | public function test_last_should_return_the_last_element_of_an_array_wrapped_in_a_maybe() 82 | { 83 | $this->assertTrue(Collection::last([1, 2])->equals(Maybe::of(2))); 84 | $this->assertTrue(Collection::last([])->equals(Nothing::instance())); 85 | } 86 | 87 | public function test_init_should_return_all_the_elements_of_an_array_except_the_last() 88 | { 89 | $this->assertThat(Collection::init([1, 2, 3, 4]), $this->equalTo([1, 2, 3])); 90 | } 91 | 92 | public function test_sortBy_should_return_an_array_sorted_using_a_comparator_callable() 93 | { 94 | $comparator = 'P\Arithmetic::substract'; 95 | $this->assertThat(Collection::sortBy($comparator, [4, 2, 5, 1, 3]), $this->equalTo([1, 2, 3, 4, 5])); 96 | } 97 | 98 | public function test_all_should_return_true_if_all_the_elements_satisfy_the_predicate() 99 | { 100 | $predicate = 'P\Core::identity'; 101 | $this->assertThat(Collection::all($predicate, [true, true, true]), $this->equalTo(true)); 102 | $this->assertThat(Collection::all($predicate, [true, false, true]), $this->equalTo(false)); 103 | } 104 | 105 | public function test_any_should_return_true_if_any_of_the_elements_satisfies_the_predicate() 106 | { 107 | $predicate = 'P\Core::identity'; 108 | $this->assertThat(Collection::any($predicate, [false, false, true]), $this->equalTo(true)); 109 | $this->assertThat(Collection::any($predicate, [false, false, false]), $this->equalTo(false)); 110 | } 111 | 112 | public function test_find_should_return_a_found_element_wrapped_in_a_maybe() 113 | { 114 | $this->assertTrue(Collection::find(Relational::equal(42), [23, 545, 42, 45])->equals(Maybe::of(42))); 115 | $this->assertTrue(Collection::find(Relational::equal(42), [23, 545, 45])->equals(Nothing::instance())); 116 | } 117 | 118 | public function test_findIndex_should_return_the_index_of_a_found_element_wrapped_in_a_maybe() 119 | { 120 | $this->assertTrue(Collection::findIndex(Relational::equal(42), [23, 545, 42, 45])->equals(Maybe::of(2))); 121 | $this->assertTrue(Collection::findIndex(Relational::equal(42), [23, 545, 45])->equals(Nothing::instance())); 122 | } 123 | 124 | public function test_take_should_return_the_first_X_elements_of_an_array() 125 | { 126 | $this->assertThat(Collection::take(2, [1, 2, 3, 4]), $this->equalTo([1, 2])); 127 | } 128 | 129 | public function test_the_longest_prefix_of_an_array_that_satisfies_a_predicate() 130 | { 131 | $this->assertThat(Collection::takeWhile(Relational::lessThan(Core::_, 3), [1, 2, 3, 4]), $this->equalTo([1, 2])); 132 | } 133 | 134 | public function test_drop_should_return_a_new_array_with_the_first_X_elements_removed() 135 | { 136 | $this->assertThat(Collection::drop(3, [1, 2, 3, 4]), $this->equalTo([4])); 137 | } 138 | 139 | public function test_dropWhile_should_return_a_new_array_where_the_first_X_elements_satisfying_the_predicate_are_removed() 140 | { 141 | $this->assertThat(Collection::dropWhile(Relational::lessThan(Core::_, 3), [1, 2, 3, 4]), $this->equalTo([3, 4])); 142 | } 143 | 144 | public function test_zipWith_returns_a_new_array_of_the_result_of_a_callable_applied_to_every_element_of_two_arrays() 145 | { 146 | $this->assertThat(Collection::zipWith('P\Arithmetic::add', [1, 2, 3, 4], [4, 3, 2, 1]), $this->equalTo([5, 5, 5, 5])); 147 | } 148 | 149 | } 150 | -------------------------------------------------------------------------------- /P/Test/CoreTest.php: -------------------------------------------------------------------------------- 1 | assertThat($curriedFunction(), $this->equalTo(42)); 28 | } 29 | 30 | public function test_curry_should_accept_static_method_strings_as_callable() 31 | { 32 | $curriedStaticMethod = Core::curry('P\Test\Fixture\Foo::staticMethod'); 33 | 34 | $this->assertThat($curriedStaticMethod(), $this->equalTo(42)); 35 | } 36 | 37 | public function test_curry_should_accept_static_method_arrays_methods_as_callable() 38 | { 39 | $curriedStaticMethod = Core::curry(['P\Test\Fixture\Foo', 'staticMethod']); 40 | 41 | $this->assertThat($curriedStaticMethod(), $this->equalTo(42)); 42 | } 43 | 44 | public function test_curry_should_accept_instance_method_arrays_methods_as_callable() 45 | { 46 | $instance = new Foo(); 47 | $curriedInstanceMethod = Core::curry([$instance, 'method']); 48 | 49 | $this->assertThat($curriedInstanceMethod(), $this->equalTo(42)); 50 | } 51 | 52 | public function test_curry_should_accept_closures_as_callable() 53 | { 54 | $curriedClosure = Core::curry(function($x, $y) { return $x + $y; }, [2]); 55 | 56 | $this->assertThat($curriedClosure(40), $this->equalTo(42)); 57 | } 58 | 59 | public function test_curry_should_accept_initial_arguments() 60 | { 61 | $curriedFunction = Core::curry('P\Test\addition', [2]); 62 | 63 | $this->assertThat($curriedFunction(40), $this->equalTo(42)); 64 | } 65 | 66 | public function test_curry_should_accept_all_arguments() 67 | { 68 | $curriedFunction = Core::curry('P\Test\addition', [2, 40]); 69 | 70 | $this->assertThat($curriedFunction(), $this->equalTo(42)); 71 | } 72 | 73 | public function test_curry_should_accept_placeholders() 74 | { 75 | $add2 = Arithmetic::add(Core::_, 2); 76 | $twoPlaceholderAdd = Arithmetic::add(Core::_, Core::_); 77 | 78 | $this->assertThat($add2(40), $this->equalTo(42)); 79 | $this->assertThat($twoPlaceholderAdd(40, 2), $this->equalTo(42)); 80 | } 81 | 82 | public function test_curry_should_force_a_callable_to_a_specific_arity() 83 | { 84 | $curriedFunction = Core::curry("max", [10, 42], 3); 85 | 86 | $this->assertThat($curriedFunction(12), $this->equalTo(42)); 87 | } 88 | 89 | public function test_compose_should_compose_two_functions_together() 90 | { 91 | $composedFunction = Core::compose( 92 | function($x) { return $x + 2; }, 93 | function($x) { return $x * 2; } 94 | ); 95 | 96 | $this->assertThat($composedFunction(10), $this->equalTo(22)); 97 | } 98 | 99 | public function test_flip_should_reverse_the_arguments_of_a_binary_callable() 100 | { 101 | $function = Core::curry(function($x, $y) { return $x / $y; }); 102 | $flippedFunction = Core::flip($function); 103 | 104 | $this->assertThat($flippedFunction(2, 10), $this->equalTo(5)); 105 | } 106 | 107 | public function test_identity_should_return_the_argument_passed() 108 | { 109 | $this->assertThat(Core::identity(42), $this->equalTo(42)); 110 | } 111 | 112 | public function test_constant_should_return_the_first_argument_passed() 113 | { 114 | $this->assertThat(Core::constant(42, 10), $this->equalTo(42)); 115 | } 116 | 117 | public function test_liftA_should_apply_a_unary_callable_to_an_applicative() 118 | { 119 | $applicative = Maybe::of(84); 120 | $callable = function($n) { return $n / 2; }; 121 | 122 | $this->assertTrue(Core::liftA($callable, $applicative)->equals(Maybe::of(42))); 123 | } 124 | 125 | public function test_liftA2_should_apply_a_binary_callable_to_two_applicatives() 126 | { 127 | $applicative1 = Maybe::of(20); 128 | $applicative2 = Maybe::of(22); 129 | $callable = Core::curry(function($x, $y) { return $x + $y; }); 130 | 131 | $this->assertTrue(Core::liftA2($callable, $applicative1, $applicative2)->equals(Maybe::of(42))); 132 | } 133 | 134 | public function test_liftA3_should_apply_a_ternary_callable_to_three_applicatives() 135 | { 136 | $applicative1 = Maybe::of(20); 137 | $applicative2 = Maybe::of(10); 138 | $applicative3 = Maybe::of(12); 139 | $callable = Core::curry(function($x, $y, $z) { return $x + $y + $z; }); 140 | 141 | $this->assertTrue(Core::liftA3($callable, $applicative1, $applicative2, $applicative3)->equals(Maybe::of(42))); 142 | } 143 | 144 | public function test_map_should_apply_a_callable_to_a_functor() 145 | { 146 | $applicative = Maybe::of(21); 147 | 148 | $this->assertTrue(Core::map(function($x) { return $x * 2; }, $applicative)->equals(Maybe::of(42))); 149 | } 150 | 151 | public function test_ap_should_apply_a_lifted_callable_to_an_applicative() 152 | { 153 | $liftedCallable = Maybe::of(function($x) { return $x * 2; }); 154 | $applicative = Maybe::of(21); 155 | 156 | $this->assertTrue(Core::ap($liftedCallable, $applicative)->equals(Maybe::of(42))); 157 | } 158 | 159 | public function test_sequence_should_return_a_monad_containing_the_results() 160 | { 161 | $allSuccess = Core::sequence('P\Maybe\Maybe', [ 162 | Maybe::of(1), Maybe::of(1), Maybe::of(1), Maybe::of(1) 163 | ]); 164 | 165 | $withFailure = Core::sequence('P\Maybe\Maybe', [ 166 | Maybe::of(1), Maybe::of(1), Nothing::instance(), Maybe::of(1) 167 | ]); 168 | 169 | $this->assertTrue($allSuccess->equals(Maybe::of([1, 1, 1, 1]))); 170 | $this->assertTrue($withFailure->equals(Nothing::instance())); 171 | } 172 | 173 | public function test_mapM_should_return_a_monad_containing_the_results_with_a_callable_applied() 174 | { 175 | $testCallable = function($x) { return $x === 1 ? Maybe::of($x + 4) : Nothing::instance(); }; 176 | 177 | $allSuccess = Core::mapM('P\Maybe\Maybe', $testCallable, [ 178 | 1, 1, 1, 1 179 | ]); 180 | 181 | $withFailure = Core::mapM('P\Maybe\Maybe', $testCallable, [ 182 | 1, 1, 2, 1 183 | ]); 184 | 185 | $this->assertTrue($allSuccess->equals(Maybe::of([5, 5, 5, 5]))); 186 | $this->assertTrue($withFailure->equals(Nothing::instance())); 187 | } 188 | 189 | public function test_filterM_should_return_a_monad_containing_the_filtered_results_of_the_applied_callable() 190 | { 191 | $testCallable = function($x) { return is_bool($x) ? Maybe::of($x) : Nothing::instance(); }; 192 | 193 | $allSuccess = Core::filterM('P\Maybe\Maybe', $testCallable, [ 194 | true, true, false, false 195 | ]); 196 | 197 | $withFailure = Core::filterM('P\Maybe\Maybe', $testCallable, [ 198 | true, true, "OMG", false 199 | ]); 200 | 201 | $this->assertTrue($allSuccess->equals(Maybe::of([true, true]))); 202 | $this->assertTrue($withFailure->equals(Nothing::instance())); 203 | } 204 | 205 | public function test_composeM_should_do_the_left_to_right_kleisli_composition_of_monads() 206 | { 207 | $aToMb = function($x) { return Maybe::of($x * 2); }; 208 | $bToMc = function($x) { return Maybe::of($x + 2); }; 209 | $aToMc = Core::composeM($aToMb, $bToMc); 210 | 211 | $this->assertTrue(Maybe::of(20)->bind($aToMc)->equals(Maybe::of(42))); 212 | } 213 | 214 | public function test_join_should_remove_a_level_of_monadic_structure() 215 | { 216 | $twoLevelsMonad = Maybe::of(Maybe::of(42)); 217 | 218 | $this->assertTrue(Core::join($twoLevelsMonad)->equals(Maybe::of(42))); 219 | } 220 | 221 | public function test_construct_should_create_an_object_using_new() 222 | { 223 | $pointConstructor = Core::construct('P\Test\Fixture\Point'); 224 | $point = $pointConstructor(10, 20); 225 | 226 | $this->assertThat($point, $this->isInstanceOf('P\Test\Fixture\Point')); 227 | $this->assertThat($point->x(), $this->equalTo(10)); 228 | $this->assertThat($point->y(), $this->equalTo(20)); 229 | } 230 | 231 | public function test_fix_should_return_the_fixedpoint_of_a_callable() 232 | { 233 | $factorial = Core::fix(function($partial) { 234 | return function($n) use ($partial) { 235 | return $n === 0 ? 1 : $n * $partial($n - 1); 236 | }; 237 | }); 238 | 239 | $fibonacci = Core::fix(function($partial) { 240 | return function($n) use ($partial) { 241 | return $n <= 1 ? $n : $partial($n - 1) + $partial($n - 2); 242 | }; 243 | }); 244 | 245 | $this->assertThat($factorial(5), $this->equalTo(120)); 246 | $this->assertThat($fibonacci(10), $this->equalTo(55)); 247 | } 248 | 249 | public function test_conditions_should_return_the_result_of_the_callable_associated_with_the_first_predicate_that_returns_true() 250 | { 251 | $myCondition = Core::conditions([ 252 | [Relational::equal(10), 'P\Core::identity'], 253 | [Relational::equal(20), Core::constant(42)], 254 | [Core::constant(true), Core::constant(100)] 255 | ]); 256 | 257 | $this->assertThat($myCondition(10), $this->equalTo(10)); 258 | $this->assertThat($myCondition(20), $this->equalTo(42)); 259 | $this->assertThat($myCondition(42), $this->equalTo(100)); 260 | } 261 | 262 | public function test_property_should_return_the_property_of_an_array_or_object_wrapped_in_a_maybe() 263 | { 264 | $testArray = ['foo' => 42]; 265 | $testObject = new \stdClass(); 266 | $testObject->foo = 42; 267 | 268 | $nestedTest = new \stdClass(); 269 | $nestedTest->foo = ['bar' => 42]; 270 | 271 | $fooProperty = Core::property('foo'); 272 | $barProperty = Core::property('bar'); 273 | 274 | $nestedProperty = Core::composeM($fooProperty, $barProperty); 275 | 276 | $this->assertTrue($fooProperty($testArray)->equals(Maybe::of(42))); 277 | $this->assertTrue($barProperty($testArray)->equals(Nothing::instance())); 278 | $this->assertTrue($fooProperty($testObject)->equals(Maybe::of(42))); 279 | $this->assertTrue($barProperty($testObject)->equals(Nothing::instance())); 280 | $this->assertTrue($nestedProperty($nestedTest)->equals(Maybe::of(42))); 281 | } 282 | 283 | public function test_apply_should_apply_a_callable_to_an_argument() { 284 | $this->assertThat(Core::apply(Arithmetic::multiply(2), 21), $this->equalTo(42)); 285 | } 286 | 287 | } 288 | -------------------------------------------------------------------------------- /P/Test/EitherTest.php: -------------------------------------------------------------------------------- 1 | map('P\Core::identity'); 13 | $right = Either::of(42); 14 | 15 | $this->assertTrue($left->equals($right)); 16 | } 17 | 18 | public function test_functor_laws_composition() 19 | { 20 | $left = Either::of(40)->map(Core::compose('P\Arithmetic::succ', 'P\Arithmetic::succ')); 21 | $right = Either::of(40)->map('P\Arithmetic::succ') 22 | ->map('P\Arithmetic::succ'); 23 | 24 | $this->assertTrue($left->equals($right)); 25 | } 26 | 27 | public function test_Applicative_laws_identity() 28 | { 29 | $left = Either::of('P\Core::identity')->ap(Either::of(42)); 30 | $right = Either::of(42); 31 | 32 | $this->assertTrue($left->equals($right)); 33 | } 34 | 35 | public function test_Applicative_laws_composition() 36 | { 37 | $left = Either::of('P\Core::compose')->ap(Either::of(Arithmetic::add(2))) 38 | ->ap(Either::of(Arithmetic::multiply(2))) 39 | ->ap(Either::of(20)); 40 | $right = Either::of(Arithmetic::add(2))->ap(Either::of(Arithmetic::multiply(2))->ap(Either::of(20))); 41 | 42 | $this->assertTrue($left->equals($right)); 43 | } 44 | 45 | public function test_Applicative_laws_homomorphism() 46 | { 47 | $left = Either::of(Arithmetic::multiply(2))->ap(Either::of(21)); 48 | $right = Either::of(Arithmetic::multiply(2, 21)); 49 | 50 | $this->assertTrue($left->equals($right)); 51 | } 52 | 53 | public function test_Applicative_laws_interchange() 54 | { 55 | $left = Either::of(Arithmetic::multiply(2))->ap(Either::of(21)); 56 | $right = Either::of(Core::apply(Core::_, 21))->ap(Either::of(Arithmetic::multiply(2))); 57 | 58 | $this->assertTrue($left->equals($right)); 59 | } 60 | 61 | public function test_Monad_laws_left_identity() 62 | { 63 | $f = Core::compose('P\Either\Either::of', 'P\Core::identity'); 64 | $left = Either::of(42)->bind($f); 65 | $right = $f(42); 66 | 67 | $this->assertTrue($left->equals($right)); 68 | } 69 | 70 | public function test_Monad_laws_right_identity() 71 | { 72 | $left = Either::of(42)->bind('P\Either\Either::of'); 73 | $right = Either::of(42); 74 | 75 | $this->assertTrue($left->equals($right)); 76 | } 77 | 78 | public function test_Monad_laws_associativity() 79 | { 80 | $f = Core::compose('P\Either\Either::of', 'P\Core::identity'); 81 | $g = Core::compose('P\Either\Either::of', 'P\Arithmetic::succ'); 82 | $left = Either::of(41)->bind($f) 83 | ->bind($g); 84 | $right = Either::of(41)->bind(function($x) use ($f, $g) { return $f($x)->bind($g); }); 85 | 86 | $this->assertTrue($left->equals($right)); 87 | } 88 | 89 | public function test_Functor_map_left() 90 | { 91 | $left = Left::of("Failure")->map('P\Core::identity'); 92 | $right = Left::of("Failure"); 93 | $this->assertTrue($left->equals($right)); 94 | } 95 | 96 | public function test_Functor_map_right() 97 | { 98 | $left = Either::of(42)->map('P\Core::identity'); 99 | $right = Either::of(42); 100 | $this->assertTrue($left->equals($right)); 101 | } 102 | 103 | public function test_Applicative_ap_left() 104 | { 105 | $left = Either::of('P\Core::identity')->ap(Left::of("Failure")); 106 | $right = Left::of("Failure"); 107 | 108 | $this->assertTrue($left->equals($right)); 109 | } 110 | 111 | public function test_Applicative_ap_right() 112 | { 113 | $left = Either::of('P\Core::identity')->ap(Either::of(42)); 114 | $right = Either::of(42); 115 | 116 | $this->assertTrue($left->equals($right)); 117 | } 118 | 119 | public function test_Applicative_ap_left_callable() 120 | { 121 | $left = Left::of("Failure")->ap(Either::of(42)); 122 | $right = Left::of("Failure"); 123 | 124 | $this->assertTrue($left->equals($right)); 125 | } 126 | 127 | public function test_Monad_bind_left() 128 | { 129 | $left = Left::of("Failure")->bind(Core::compose('P\Either\Either::of', 'P\Core::identity')); 130 | $right = Left::of("Failure"); 131 | 132 | $this->assertTrue($left->equals($right)); 133 | } 134 | 135 | public function test_Monad_bind_right() 136 | { 137 | $left = Either::of(42)->bind(Core::compose('P\Either\Either::of', 'P\Core::identity')); 138 | $right = Either::of(42); 139 | 140 | $this->assertTrue($left->equals($right)); 141 | } 142 | 143 | } -------------------------------------------------------------------------------- /P/Test/Fixture/Foo.php: -------------------------------------------------------------------------------- 1 | x = $x; 12 | $this->y = $y; 13 | } 14 | 15 | public function x() 16 | { 17 | return $this->x; 18 | } 19 | 20 | public function y() 21 | { 22 | return $this->y; 23 | } 24 | } -------------------------------------------------------------------------------- /P/Test/MaybeTest.php: -------------------------------------------------------------------------------- 1 | map('P\Core::identity'); 13 | $right = Maybe::of(42); 14 | 15 | $this->assertTrue($left->equals($right)); 16 | } 17 | 18 | public function test_functor_laws_composition() 19 | { 20 | $left = Maybe::of(40)->map(Core::compose('P\Arithmetic::succ', 'P\Arithmetic::succ')); 21 | $right = Maybe::of(40)->map('P\Arithmetic::succ') 22 | ->map('P\Arithmetic::succ'); 23 | 24 | $this->assertTrue($left->equals($right)); 25 | } 26 | 27 | public function test_Applicative_laws_identity() 28 | { 29 | $left = Maybe::of('P\Core::identity')->ap(Maybe::of(42)); 30 | $right = Maybe::of(42); 31 | 32 | $this->assertTrue($left->equals($right)); 33 | } 34 | 35 | public function test_Applicative_laws_composition() 36 | { 37 | $left = Maybe::of('P\Core::compose')->ap(Maybe::of(Arithmetic::add(2))) 38 | ->ap(Maybe::of(Arithmetic::multiply(2))) 39 | ->ap(Maybe::of(20)); 40 | $right = Maybe::of(Arithmetic::add(2))->ap(Maybe::of(Arithmetic::multiply(2))->ap(Maybe::of(20))); 41 | 42 | $this->assertTrue($left->equals($right)); 43 | } 44 | 45 | public function test_Applicative_laws_homomorphism() 46 | { 47 | $left = Maybe::of(Arithmetic::multiply(2))->ap(Maybe::of(21)); 48 | $right = Maybe::of(Arithmetic::multiply(2, 21)); 49 | 50 | $this->assertTrue($left->equals($right)); 51 | } 52 | 53 | public function test_Applicative_laws_interchange() 54 | { 55 | $left = Maybe::of(Arithmetic::multiply(2))->ap(Maybe::of(21)); 56 | $right = Maybe::of(Core::apply(Core::_, 21))->ap(Maybe::of(Arithmetic::multiply(2))); 57 | 58 | $this->assertTrue($left->equals($right)); 59 | } 60 | 61 | public function test_Monad_laws_left_identity() 62 | { 63 | $f = Core::compose('P\Maybe\Maybe::of', 'P\Core::identity'); 64 | $left = Maybe::of(42)->bind($f); 65 | $right = $f(42); 66 | 67 | $this->assertTrue($left->equals($right)); 68 | } 69 | 70 | public function test_Monad_laws_right_identity() 71 | { 72 | $left = Maybe::of(42)->bind('P\Maybe\Maybe::of'); 73 | $right = Maybe::of(42); 74 | 75 | $this->assertTrue($left->equals($right)); 76 | } 77 | 78 | public function test_Monad_laws_associativity() 79 | { 80 | $f = Core::compose('P\Maybe\Maybe::of', 'P\Core::identity'); 81 | $g = Core::compose('P\Maybe\Maybe::of', 'P\Arithmetic::succ'); 82 | $left = Maybe::of(41)->bind($f) 83 | ->bind($g); 84 | $right = Maybe::of(41)->bind(function($x) use ($f, $g) { return $f($x)->bind($g); }); 85 | 86 | $this->assertTrue($left->equals($right)); 87 | } 88 | 89 | public function test_Functor_map_nothing() 90 | { 91 | $left = Nothing::instance()->map('P\Core::identity'); 92 | $right = Nothing::instance(); 93 | $this->assertTrue($left->equals($right)); 94 | } 95 | 96 | public function test_Functor_map_just() 97 | { 98 | $left = Maybe::of(42)->map('P\Core::identity'); 99 | $right = Maybe::of(42); 100 | $this->assertTrue($left->equals($right)); 101 | } 102 | 103 | public function test_Applicative_ap_nothing() 104 | { 105 | $left = Maybe::of('P\Core::identity')->ap(Nothing::instance()); 106 | $right = Nothing::instance(); 107 | 108 | $this->assertTrue($left->equals($right)); 109 | } 110 | 111 | public function test_Applicative_ap_just() 112 | { 113 | $left = Maybe::of('P\Core::identity')->ap(Maybe::of(42)); 114 | $right = Maybe::of(42); 115 | 116 | $this->assertTrue($left->equals($right)); 117 | } 118 | 119 | public function test_Applicative_ap_nothing_callable() 120 | { 121 | $left = Nothing::instance()->ap(Maybe::of(42)); 122 | $right = Nothing::instance(); 123 | 124 | $this->assertTrue($left->equals($right)); 125 | } 126 | 127 | public function test_Monad_bind_nothing() 128 | { 129 | $left = Nothing::instance()->bind(Core::compose('P\Maybe\Maybe::of', 'P\Core::identity')); 130 | $right = Nothing::instance(); 131 | 132 | $this->assertTrue($left->equals($right)); 133 | } 134 | 135 | public function test_Monad_bind_just() 136 | { 137 | $left = Maybe::of(42)->bind(Core::compose('P\Maybe\Maybe::of', 'P\Core::identity')); 138 | $right = Maybe::of(42); 139 | 140 | $this->assertTrue($left->equals($right)); 141 | } 142 | 143 | } -------------------------------------------------------------------------------- /P/Test/ValidationTest.php: -------------------------------------------------------------------------------- 1 | map('P\Core::identity'); 13 | $right = Validation::of(42); 14 | 15 | $this->assertTrue($left->equals($right)); 16 | } 17 | 18 | public function test_functor_laws_composition() 19 | { 20 | $left = Validation::of(40)->map(Core::compose('P\Arithmetic::succ', 'P\Arithmetic::succ')); 21 | $right = Validation::of(40)->map('P\Arithmetic::succ') 22 | ->map('P\Arithmetic::succ'); 23 | 24 | $this->assertTrue($left->equals($right)); 25 | } 26 | 27 | public function test_Applicative_laws_identity() 28 | { 29 | $left = Validation::of('P\Core::identity')->ap(Validation::of(42)); 30 | $right = Validation::of(42); 31 | 32 | $this->assertTrue($left->equals($right)); 33 | } 34 | 35 | public function test_Applicative_laws_composition() 36 | { 37 | $left = Validation::of('P\Core::compose')->ap(Validation::of(Arithmetic::add(2))) 38 | ->ap(Validation::of(Arithmetic::multiply(2))) 39 | ->ap(Validation::of(20)); 40 | $right = Validation::of(Arithmetic::add(2))->ap(Validation::of(Arithmetic::multiply(2))->ap(Validation::of(20))); 41 | 42 | $this->assertTrue($left->equals($right)); 43 | } 44 | 45 | public function test_Applicative_laws_homomorphism() 46 | { 47 | $left = Validation::of(Arithmetic::multiply(2))->ap(Validation::of(21)); 48 | $right = Validation::of(Arithmetic::multiply(2, 21)); 49 | 50 | $this->assertTrue($left->equals($right)); 51 | } 52 | 53 | public function test_Applicative_laws_interchange() 54 | { 55 | $left = Validation::of(Arithmetic::multiply(2))->ap(Validation::of(21)); 56 | $right = Validation::of(Core::apply(Core::_, 21))->ap(Validation::of(Arithmetic::multiply(2))); 57 | 58 | $this->assertTrue($left->equals($right)); 59 | } 60 | 61 | public function test_Functor_map_left() 62 | { 63 | $left = Failure::of(["Failure"])->map('P\Core::identity'); 64 | $right = Failure::of(["Failure"]); 65 | $this->assertTrue($left->equals($right)); 66 | } 67 | 68 | public function test_Functor_map_right() 69 | { 70 | $left = Validation::of(42)->map('P\Core::identity'); 71 | $right = Validation::of(42); 72 | $this->assertTrue($left->equals($right)); 73 | } 74 | 75 | public function test_Applicative_ap_left() 76 | { 77 | $left = Validation::of('P\Core::identity')->ap(Failure::of(["Failure"])); 78 | $right = Failure::of(["Failure"]); 79 | 80 | $this->assertTrue($left->equals($right)); 81 | } 82 | 83 | public function test_Applicative_ap_right() 84 | { 85 | $left = Validation::of('P\Core::identity')->ap(Validation::of(42)); 86 | $right = Validation::of(42); 87 | 88 | $this->assertTrue($left->equals($right)); 89 | } 90 | 91 | public function test_Applicative_ap_left_callable() 92 | { 93 | $left = Failure::of(["Failure"])->ap(Validation::of(42)); 94 | $right = Failure::of(["Failure"]); 95 | 96 | $this->assertTrue($left->equals($right)); 97 | } 98 | 99 | public function test_Validation_should_accumulate_failures() 100 | { 101 | $left = Validation::of("Hello")->seql(Failure::of(["Failure 1"])) 102 | ->seql(Validation::of("Success")) 103 | ->seql(Failure::of(["Failure 2"])); 104 | $right = Failure::of(["Failure 1", "Failure 2"]); 105 | 106 | $this->assertTrue($left->equals($right)); 107 | } 108 | 109 | } -------------------------------------------------------------------------------- /P/Validation/Failure.php: -------------------------------------------------------------------------------- 1 | a = $a; 11 | } 12 | 13 | /** 14 | * Returns true if current instance of Validation is Success 15 | * 16 | * @return bool 17 | */ 18 | public function isSuccess() 19 | { 20 | return false; 21 | } 22 | 23 | /** 24 | * Returns true if current instance of Validation is Failure 25 | * 26 | * @return bool 27 | */ 28 | public function isFailure() 29 | { 30 | return true; 31 | } 32 | 33 | /** 34 | * Returns true on equality 35 | * 36 | * @param Validation $validation 37 | * @return bool 38 | */ 39 | public function equals(Validation $validation) 40 | { 41 | return $validation->isFailure() && $this->a === $validation->a; 42 | } 43 | 44 | /** 45 | * Standard Functor map callable 46 | * 47 | * @param callable $f 48 | * @return Functor 49 | */ 50 | public function map(callable $f) 51 | { 52 | return self::of($this->a); 53 | } 54 | 55 | /** 56 | * Sequential application 57 | * 58 | * @param Applicative $a 59 | * @return Applicative 60 | */ 61 | public function ap(Applicative $a) { 62 | return $a->isFailure() ? self::of(array_merge($this->a, $a->a)) 63 | : self::of($this->a); 64 | } 65 | 66 | public function getOrElse(callable $f) 67 | { 68 | return \call_user_func($f, $this->a); 69 | } 70 | 71 | public static function of($a) 72 | { 73 | return new Failure($a); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /P/Validation/Success.php: -------------------------------------------------------------------------------- 1 | a = $a; 11 | } 12 | 13 | /** 14 | * Returns true if current instance of Validation is Success 15 | * 16 | * @return bool 17 | */ 18 | public function isSuccess() 19 | { 20 | return true; 21 | } 22 | 23 | /** 24 | * Returns true if current instance of Validation is Failure 25 | * 26 | * @return bool 27 | */ 28 | public function isFailure() 29 | { 30 | return false; 31 | } 32 | 33 | /** 34 | * Returns true on equality 35 | * 36 | * @param Validation $validation 37 | * @return bool 38 | */ 39 | public function equals(Validation $validation) 40 | { 41 | return $validation->isSuccess() && $this->a === $validation->a; 42 | } 43 | 44 | /** 45 | * Standard Functor map callable 46 | * 47 | * @param callable $f 48 | * @return Functor 49 | */ 50 | public function map(callable $f) 51 | { 52 | return self::of(\call_user_func($f, $this->a)); 53 | } 54 | 55 | /** 56 | * Sequential application 57 | * 58 | * @param Applicative $a 59 | * @return Applicative 60 | */ 61 | public function ap(Applicative $a) { 62 | return $this->isFailure() ? $a 63 | : $a->map($this->a); 64 | } 65 | 66 | public function getOrElse(callable $f) 67 | { 68 | return $this->a; 69 | } 70 | 71 | public static function of($a) 72 | { 73 | return new Success($a); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /P/Validation/Validation.php: -------------------------------------------------------------------------------- 1 |