Created
November 18, 2013 10:32
-
-
Save m4rw3r/7525724 to your computer and use it in GitHub Desktop.
Attempting to implement something akin to Haskell's typeclasses
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace m4rw3r; | |
use ReflectionMethod; | |
use ReflectionFunction; | |
function compose($f, $g) | |
{ | |
switch(callableReflection($g)->getNumberOfParameters()) { | |
case 0: | |
return function() use($f, $g) { | |
return $f($g()); | |
}; | |
case 1: | |
return function($a) use($f, $g) { | |
return $f($g($a)); | |
}; | |
case 2: | |
return function($a, $b) use($f, $g) { | |
return $f($g($a, $b)); | |
}; | |
case 3: | |
return function($a, $b, $c) use($f, $g) { | |
return $f($g($a, $b, $c)); | |
}; | |
case 4: | |
return function($a, $b, $c, $d) use($f, $g) { | |
return $f($g($a, $b, $c, $d)); | |
}; | |
case 5: | |
return function($a, $b, $c, $d, $e) use($f, $g) { | |
return $f($g($a, $b, $c, $d, $e)); | |
}; | |
case 6: | |
return function($a, $b, $c, $d, $e, $h) use($f, $g) { | |
return $f($g($a, $b, $c, $d, $e, $h)); | |
}; | |
case 7: | |
return function($a, $b, $c, $d, $e, $h, $i) use($f, $g) { | |
return $f($g($a, $b, $c, $d, $e, $h, $i)); | |
}; | |
default: | |
throw new RuntimeException('Trying to compose a function $g with more than 7 parameters.'); | |
} | |
} | |
function autoCurry(callable $callable, $num_params = false, array $params = array()) | |
{ | |
if($num_params === false) { | |
$num_params = callableReflection($callable)->getNumberOfParameters(); | |
} | |
return function() use($callable, $num_params, $params) { | |
$p = array_merge($params, func_get_args()); | |
if(count($p) >= $num_params) { | |
return call_user_func_array($callable, $p); | |
} | |
return autoCurry($callable, $num_params, $p); | |
}; | |
} | |
function callableReflection(callable $callable) | |
{ | |
if(is_array($callable) && count($callable) === 2) { | |
return new ReflectionMethod(array_shift($callable), array_shift($callable)); | |
} | |
elseif(is_string($callable) && strpos($callable, ':') !== false) { | |
return new ReflectionMethod($callable); | |
} | |
elseif(is_object($callable)) { | |
return new ReflectionMethod($callable, '__invoke'); | |
} | |
else { | |
return new ReflectionFunction($callable); | |
} | |
} | |
function just($a) | |
{ | |
return new Just($a); | |
} | |
function nothing() | |
{ | |
return new Nothing(); | |
} | |
function seq() | |
{ | |
return new Seq(func_get_args()); | |
} | |
function identity($a) | |
{ | |
return Identity::pure($a); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace m4rw3r; | |
include "src/m4rw3r/typeclasses.php"; | |
include "src/m4rw3r/functions.php"; | |
$mul = autoCurry(function($a, $b) { | |
return $a * $b; | |
}); | |
$add = autoCurry(function($a, $b) { | |
return $a + $b; | |
}); | |
$v = seq(function($a){ return $a + 1; }, function($a){ return $a - 1; }); | |
print_r($v->apply(identity(3))); | |
print_r(seq($mul, $add)->apply(identity(3))->apply(identity(5))); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace m4rw3r; | |
use Traversable; | |
use InvalidArgumentException; | |
interface Functor | |
{ | |
/* fmap */ | |
public function map(callable $f); | |
public function concat(array $a); | |
public function __invoke($param); | |
} | |
interface Applicative | |
{ | |
/** pure :: $val -> Applicative $val */ | |
public static function pure($val); | |
public function apply(Functor $b); | |
} | |
interface Monad | |
{ | |
public function bind(callable $f); | |
/* public static function ret($value); */ | |
} | |
class Identity implements Applicative, Functor, Monad | |
{ | |
protected $value; | |
public function __construct($value) | |
{ | |
$this->value = $value; | |
} | |
public function map(callable $f) | |
{ | |
return new Identity($f($this->value)); | |
} | |
public function concat(array $a) | |
{ | |
return array_merge($a, array($this->value)); | |
} | |
public function __invoke($param) | |
{ | |
$call = $this->value; | |
return $call($param); | |
} | |
public static function pure($value) | |
{ | |
return new Identity($value); | |
} | |
public function bind(callable $f) | |
{ | |
return $f($this->value); | |
} | |
public function apply(Functor $b) | |
{ | |
return $b($this->value); | |
} | |
} | |
class Seq implements Monad, Functor, Applicative | |
{ | |
protected $list = array(); | |
public function __construct($value = array()) | |
{ | |
if( ! is_array($value) && ! $value instanceof Traversable) { | |
throw new InvalidArgumentException('Parameter to Seq::__construct must be a Traversable or array'); | |
} | |
$this->list = $value; | |
} | |
public static function pure($value) | |
{ | |
return new Seq([$value]); | |
} | |
public function map(callable $f) | |
{ | |
return new Seq(array_map(function($e) use($f) { | |
return $f($e); | |
}, $this->list)); | |
} | |
public function concat(array $a) | |
{ | |
return array_merge($a, $this->list); | |
} | |
public function __invoke($param) | |
{ | |
return array_map(function($e) use($param) { | |
return $e($param); | |
}, $this->list); | |
} | |
public function apply(Functor $b) | |
{ | |
$r = array(); | |
foreach($this->list as $f) { | |
$r[] = $b->map($f); | |
} | |
return new Seq($r); | |
} | |
public function bind(callable $f) | |
{ | |
$r = array(); | |
foreach($this->list as $f) { | |
$r = $b($f)->concat($r); | |
} | |
return new Seq($r); | |
} | |
} | |
abstract class Maybe implements Monad, Applicative, Functor | |
{ | |
abstract public function map(callable $f); | |
abstract public function bind(callable $f); | |
public static function pure($value) | |
{ | |
return new Just($value); | |
} | |
public static function just($value) | |
{ | |
return new Just($value); | |
} | |
public static function nothing() | |
{ | |
return new Nothing(); | |
} | |
} | |
class Nothing extends Maybe | |
{ | |
public function __construct() {} | |
public function map(callable $f) | |
{ | |
return $this; | |
} | |
public function concat(array $a) | |
{ | |
return $a; | |
} | |
public function __invoke($param) | |
{ | |
return $this; | |
} | |
public function bind(callable $f) | |
{ | |
return $this; | |
} | |
public function apply(Functor $b) | |
{ | |
return $this; | |
} | |
} | |
class Just extends Maybe | |
{ | |
protected $value; | |
public function __construct($value) | |
{ | |
if(is_callable($value)) { | |
$value = autoCurry($value); | |
} | |
$this->value = $value; | |
} | |
public function map(callable $f) | |
{ | |
return new Just($f($this->value)); | |
} | |
public function concat(array $a) | |
{ | |
return array_merge($a, [$this->value]); | |
} | |
public function __invoke($param) | |
{ | |
$call = $this->value; | |
return $call($param); | |
} | |
public function bind(callable $f) | |
{ | |
return $f($this->value); | |
} | |
public function apply(Functor $b) | |
{ | |
return $b($this->value); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment