PHP User Group Munich - 2017-09-27
Michael Haeuslmann for TNG Technology Consulting GmbH / @michaelhaeu
I am not an expert at functional programming. These opinions are my own and do not reflect the opinions of proper functional programmers ;)
This talk is from a pragmatic developer for other pragmatic developers.
We want to get stuff done!
SQL is declarative,
we only say what we want,
and not how.
SELECT *
FROM users
WHERE age="42"
Processor's aren't getting any faster,
we can only expect to get more cores.
First functional language
(defun factorial (n &optional (acc 1))
(if (= n 0) acc
(factorial (- n 1) (* acc n))))
First object-oriented language
Simulation Begin
Class FittingRoom; Begin
Ref (Head) door;
Boolean inUse;
Procedure request; Begin
If inUse Then Begin
Wait (door);
door.First.Out;
End;
inUse:= True;
End;
Procedure leave; Begin
inUse:= False;
Activate door.First;
End;
door:- New Head;
End;
Integer u;
Ref (FittingRoom) fittingRoom1;
fittingRoom1:- New FittingRoom;
Hold (100);
End;
C-with-classes
#include <iostream>
using namespace std;
class Box {
public:
double length; // Length of a box
double breadth; // Breadth of a box
double height; // Height of a box
};
Purely functional and lazy
factorial n
| n < 2 = 1
| otherwise = n * factorial (n - 1)
Not functional, but functions always have been first-class citizens
class PseudoOO {
method() {
return
`I won't return what you
think I should.`;
}
}
const tax = R.curry((tax, price) => {
if (!_.isNumber(price)) return Left(new Error());
return Right(price + (tax * price));
});
"Modern" OO, support for a more functional style since Java 8
List<String> myList =
Arrays.asList("a1", "a2", "b1", "c2", "c1");
myList
.stream()
.filter(s -> s.startsWith("c"))
.map(String::toUpperCase)
.sorted()
.forEach(System.out::println);
// C1
// C2
Modern Java with many functional features
class Point(
val x: Double, val y: Double,
addToGrid: Boolean = false
) {
// ...
}
val a = List(1, 2, 3, 4, 5)
val b = a.map(x => x * x)
val c = b.reduce((x, y) => x + y)
Microsoft's answer to Scala
type Person(name : string, age : int) =
member x.Name = name
member x.Age = age
(def bit-bucket-writer
(proxy [java.io.Writer] []
(write [buf] nil)
(close [] nil)
(flush [] nil)))
array_map
, array_filter
, array_reduce
Closure::fromCallable
array_map( fn($x) => $x * 2, [1, 2, 3] );
by Levi Morrison, Bob WeinandBefore:
$ret =
array_merge(
$ret,
getFileArg(
array_map(
function ($x) use ($arg) { return $arg . '/' . $x; },
array_filter(
scandir($arg),
function ($x) { return $x !== '.' && $x !== '..'); }
)
)
)
);
by Sara GolemonAfter:
$ret = scandir($arg)
|> array_filter($$, function($x) { return $x !== '.' && $x != '..'; })
|> array_map(function ($x) use ($arg) { return $arg . '/' . $x; }, $$)
|> getFileArg($$)
|> array_merge($ret, $$);
by Sara GolemonBefore:
$action = CustomerController::class . '::delete';
After:
$action = {CustomerController::delete};
// which is a simplification for
$action = Closure::fromCallable('CustomerController::delete');
by MichaĆ Brzuchalski
$times = function ($x) {
return function ($y) use ($x) {
return $x * $y;
};
};
$times(7)(6); // 42
The output of a function, depends only on the input arguments.
function getUsers($db) {
return $db->query('SELECT * FROM users');
}
public function getUsers() {
return $this->db->query('SELECT * FROM users');
}
You wanted a banana but what you got was a gorilla holding the banana and the entire jungle.
- Joe Armstrong, Creator of Erlang
function bumpCounter() {
static $counter = 0;
++$counter;
echo $counter;
}
bumpCounter(); // 1
bumpCounter(); // 2
class Counter{
private $counter = 0;
public function increment() {
$clone = clone $this;
++$clone->counter;
return $clone;
}
public function __toString() { /** ... */}
}
$c1 = (new Counter)->increment();
$c2 = $c1->increment();
echo "$c1 < $c2"; // 1 < 2
$xs = [4, 5, 1, 2, 3];
usort($xs, function ($x, $y) {
return $x <=> $y;
});
Describing addition in "Hindley-Milner Form" (Haskell):
add :: a -> b -> c
a
b
...c
.add(int $a, int $b) : int
?We could do it the normal way:
function increment($x) {
return $x + 1;
}
But we already have everything we need:
$increment = add(1);
map :: (a -> b) -> [a] -> [b]
Every function takes only one argument.
a
to a b
...a
...b
.
foreach ($xs as $x) {
/** @var Object $x */
$x->sideEffect();
}
array_walk($xs, function (Object $x) {
$x->sideEffect();
});
each($xs, fn($x) => $x->sideEffect());
$app = new App();
$app->get(
'customers',
function (RequestInterface $request, ResponseInterface $response) : ResponseInterface {
return $response;
}
);
$deferred = new React\Promise\Deferred();
$deferred->promise()
->then(function ($x) {
return $x + 1;
})
->then(function ($x) {
throw new \Exception($x + 1);
})
->otherwise(function (\Exception $x) {
return $x->getMessage() + 1;
})
->then(function ($x) {
echo 'Mixed ' . $x;
});
$deferred->resolve(1);
function filter($collection, callable $callback) : array {}
function map($collection, callable $callback) : array {}
function reduce_left($collection, callable $callback, $initial = null) : array {}
function reduce_right($collection, callable $callback, $initial = null) : array {}
function each($collection, callable $callback) : null {}
// ...
Imperative:
foreach ($xs as $x) {
if ($x % 2 === 0) {
return true;
}
}
return false;
Declarative:
Functional\some($xs, function ($x) {
return $x % 2 === 0;
});
A curried mod function
function mod($mod) {
return function ($x) use ($mod) {
return $x % $mod;
};
}
some($xs, mod(2));
Or: if our operators were functions
$even = curry('%')(2); // operators as functions RFC pending
some($xs, $even);