Skip to content

Instantly share code, notes, and snippets.

@m4rw3r
Created November 18, 2013 10:32
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save m4rw3r/7525724 to your computer and use it in GitHub Desktop.
Save m4rw3r/7525724 to your computer and use it in GitHub Desktop.
Attempting to implement something akin to Haskell's typeclasses
<?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);
}
<?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)));
<?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