Skip to content

Instantly share code, notes, and snippets.

@marcosh
Last active April 10, 2020 14:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save marcosh/81a7c224addfeaabc60ee850248139d0 to your computer and use it in GitHub Desktop.
Save marcosh/81a7c224addfeaabc60ee850248139d0 to your computer and use it in GitHub Desktop.
<?php
declare(strict_types=1);
/**
* @template F
* @template A
* @extends Functor<F,A>
*/
interface Apply extends Functor
{
/**
* @template B
* @param HK $f
* @psalm-param HK<F,callable(A): B> $f
* @return HK
* @psalm-return HK<F, B>
*/
public function apply($f): HK;
}
<?php
declare(strict_types=1);
/**
* @template F
* @template A
* @extends HK<F,A>
*/
interface Functor extends HK
{
/**
* @template B
* @param callable $f
* @psalm-param callable(A): B $f
* @return HK
* @psalm-return HK<F, B>
*
*/
public function map(
callable $f
): HK;
}
<?php
declare(strict_types=1);
/**
* Define Higher Kinded Types following the paper Lightweight Higher Kinded Polymorphism
* By Jeremy Yallop and Leo White
* https://www.cl.cam.ac.uk/~jdy22/papers/lightweight-higher-kinded-polymorphism.pdf
*
* @template F
* @template A
*/
interface HK
{
}
<?php
declare(strict_types=1);
/**
* @template A
* @implements Functor<MaybeBrand, A>
*/
final class Maybe implements Functor
{
/** @var bool */
private $isJust;
/**
* @var mixed
* @psalm-var A|null
*/
private $value = null;
/**
* @param bool $isJust
* @param mixed $value
* @psalm-param A|null $value
* @psalm-pure
*/
private function __construct(bool $isJust, $value = null)
{
$this->isJust = $isJust;
$this->value = $value;
}
/**
* @template B
* @param mixed $value
* @psalm-param B $value
* @return self
* @psalm-return self<B>
* @psalm-pure
*/
public static function just($value): self
{
return new self(true, $value);
}
/**
* @template B
* @return self
* @psalm-return self<B>
* @psalm-pure
*/
public static function nothing(): self
{
return new self(false);
}
/**
* @template B
* @param mixed $ifNothing
* @psalm-param B $ifNothing
* @param callable $ifJust
* @psalm-param callable(A): B $ifJust
* @return mixed
* @psalm-return B
* @psalm-pure
*/
public function eval(
$ifNothing,
callable $ifJust
) {
if ($this->isJust) {
/** @psalm-suppress PossiblyNullArgument */
return $ifJust($this->value);
}
return $ifNothing;
}
/**
* @template B
* @param callable $f
* @psalm-param callable(A): B $f
* @return HK
* @psalm-return HK<MaybeBrand, B>
*/
public function map(callable $f): HK
{
return $this->eval(
Maybe::nothing(),
/**
* @psalm-param A $value
* @psalm-return self<B>
*/
fn($value) => self::just($f($value))
);
}
/**
* @template B
* @param HK $f
* @psalm-param HK<MaybeBrand, callable(A): B> $f
* @return HK
* @return HK<MaybeBrand, B>
*/
public function apply(HK $f)
{
/**
* @psalm-var Maybe $f the only instance of MaybeBrand is Maybe
*/
return $f->eval(
self::nothing(),
fn($g) => $this->map($g)
);
}
}
<?php
declare(strict_types=1);
final class MaybeBrand
{
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment