1
|
|
|
<?php namespace Tarsana\Functional; |
|
|
|
|
2
|
|
|
/** |
3
|
|
|
* This file contains functions dealing with functions. |
4
|
|
|
*/ |
5
|
|
|
|
6
|
|
|
use Tarsana\Functional\Exceptions\InvalidArgument; |
7
|
|
|
|
8
|
|
|
/** |
9
|
|
|
* Adds the `Tarsana\Functional\` namespace to a function name. |
10
|
|
|
* This is useful to pass non-curried functions as parameter. |
11
|
|
|
* |
12
|
|
|
* @internal |
13
|
|
|
* @signature String -> Sring |
14
|
|
|
* @param string $name |
15
|
|
|
* @return string |
16
|
|
|
*/ |
17
|
|
|
function _f($name) { |
18
|
|
|
return "Tarsana\\Functional\\{$name}"; |
19
|
|
|
} |
20
|
|
|
|
21
|
|
|
/** |
22
|
|
|
* Gets the number of arguments of a function. |
23
|
|
|
* |
24
|
|
|
* @internal |
25
|
|
|
* @signature (* -> *) -> Number |
26
|
|
|
* @param callable $fn |
27
|
|
|
* @return int |
28
|
|
|
*/ |
29
|
|
|
function _number_of_args($fn) { |
30
|
|
|
$reflector = is_array($fn) ? |
31
|
|
|
new \ReflectionMethod($fn[0], $fn[1]) : |
32
|
|
|
new \ReflectionFunction($fn); |
33
|
|
|
return $reflector->getNumberOfRequiredParameters(); |
34
|
|
|
} |
35
|
|
|
|
36
|
|
|
/** |
37
|
|
|
* Checks if `$a` is am argument placeholder. |
38
|
|
|
* |
39
|
|
|
* @internal |
40
|
|
|
* @signature * -> Boolean |
41
|
|
|
* @param mixed $a |
42
|
|
|
* @return boolean |
43
|
|
|
*/ |
44
|
|
|
function _is_placeholder($a) { |
45
|
|
|
return $a instanceof Placeholder; |
46
|
|
|
} |
47
|
|
|
|
48
|
|
|
/** |
49
|
|
|
* Adds new given arguments to the list of bound arguments while filling placeholders. |
50
|
|
|
* |
51
|
|
|
* @internal |
52
|
|
|
* @signature Number -> [a] -> [a] -> [a] |
53
|
|
|
* @param int $fnArgsCount |
54
|
|
|
* @param array $boundArgs |
55
|
|
|
* @param array $givenArgs |
56
|
|
|
* @return array |
57
|
|
|
*/ |
58
|
|
|
function _merge_args($fnArgsCount, $boundArgs, $givenArgs) { |
59
|
|
|
$addArgument = function($currentBoundArgs, $arg) use($fnArgsCount) { |
60
|
|
|
$currentBoundArgsCount = count($currentBoundArgs); |
61
|
|
|
$placeholderPosition = 0; |
62
|
|
|
while($placeholderPosition < $currentBoundArgsCount && !_is_placeholder($currentBoundArgs[$placeholderPosition])) |
63
|
|
|
$placeholderPosition ++; |
64
|
|
|
if ($currentBoundArgsCount < $fnArgsCount || $placeholderPosition == $currentBoundArgsCount) { |
65
|
|
|
$currentBoundArgs[] = $arg; |
66
|
|
|
} else { // There is a placeholder and number of bound args >= $fnArgsCount |
67
|
|
|
$currentBoundArgs[$placeholderPosition] = $arg; |
68
|
|
|
} |
69
|
|
|
return $currentBoundArgs; |
70
|
|
|
}; |
71
|
|
|
|
72
|
|
|
return array_reduce($givenArgs, $addArgument, $boundArgs); |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
/** |
76
|
|
|
* Returns the curried version of a function with some arguments bound to it. |
77
|
|
|
* |
78
|
|
|
* @internal |
79
|
|
|
* @signature (* -> *) -> Number -> [*] -> (* -> *) |
80
|
|
|
* @param callable $fn |
81
|
|
|
* @param int $argsCount |
82
|
|
|
* @param array $boundArgs |
83
|
|
|
* @return callable |
84
|
|
|
*/ |
85
|
|
|
function _curried_function($fn, $argsCount, $boundArgs = []) { |
86
|
|
|
return function() use($fn, $argsCount, $boundArgs) { |
87
|
|
|
$boundArgs = _merge_args($argsCount, $boundArgs, func_get_args()); |
88
|
|
|
$numberOfPlaceholders = count(array_filter($boundArgs, _f('_is_placeholder'))); |
89
|
|
|
$numberOfGivenArgs = count($boundArgs) - $numberOfPlaceholders; |
90
|
|
|
if ($numberOfGivenArgs >= $argsCount) |
91
|
|
|
return call_user_func_array($fn, $boundArgs); |
92
|
|
|
return _curried_function($fn, $argsCount, $boundArgs); |
93
|
|
|
}; |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
/** |
97
|
|
|
* Returns a curried equivalent of the provided function. |
98
|
|
|
* ```php |
99
|
|
|
* $add = curry(function($x, $y){ |
100
|
|
|
* return $x + $y; |
101
|
|
|
* }); |
102
|
|
|
* $addFive = $add(5); // a function |
103
|
|
|
* $addFive(5); // 10 |
104
|
|
|
* $add(5, 5) // 10 |
105
|
|
|
* ``` |
106
|
|
|
* |
107
|
|
|
* @signature (* -> a) -> (* -> a) |
108
|
|
|
* @param callable $fn |
109
|
|
|
* @return callable |
110
|
|
|
*/ |
111
|
|
|
function curry($fn) { |
112
|
|
|
return _curried_function($fn, _number_of_args($fn)); |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
/** |
116
|
|
|
* Argument placeholder to use with curried functions. |
117
|
|
|
* ```php |
118
|
|
|
* $minus = curry(function ($x, $y) { return $x - $y; }); |
119
|
|
|
* $decrement = $minus(__(), 1); |
120
|
|
|
* $decrement(10) // 9 |
121
|
|
|
* |
122
|
|
|
* $reduce = curry('array_reduce'); |
123
|
|
|
* $sum = $reduce(__(), 'Tarsana\Functional\plus'); |
124
|
|
|
* $sum([1, 2, 3, 4], 0) // 10 |
125
|
|
|
* ``` |
126
|
|
|
* |
127
|
|
|
* @signature * -> Placeholder |
128
|
|
|
* @return \Cypress\Curry\Placeholder |
129
|
|
|
*/ |
130
|
|
|
function __() { |
131
|
|
|
return Placeholder::get(); |
132
|
|
|
} |
133
|
|
|
|
134
|
|
|
/** |
135
|
|
|
* Non curried version of apply for internal use. |
136
|
|
|
* |
137
|
|
|
* @internal |
138
|
|
|
* @param callable $fn |
139
|
|
|
* @param array $args |
140
|
|
|
* @return mixed |
141
|
|
|
*/ |
142
|
|
|
function _apply($fn, $args) { |
143
|
|
|
return call_user_func_array($fn, $args); |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
/** |
147
|
|
|
* Apply the provided function to the list of arguments. |
148
|
|
|
* ```php |
149
|
|
|
* apply('strlen', ['Hello']) // 5 |
150
|
|
|
* $replace = apply('str_replace'); |
151
|
|
|
* $replace(['l', 'o', 'Hello']) // 'Heooo' |
152
|
|
|
* ``` |
153
|
|
|
* |
154
|
|
|
* @signature (*... -> a) -> [*] -> a |
155
|
|
|
* @param callable $fn |
156
|
|
|
* @param array $args |
157
|
|
|
* @return mixed |
158
|
|
|
*/ |
159
|
|
|
function apply() { |
160
|
|
|
return _apply(curry('Tarsana\Functional\_apply'), func_get_args()); |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
/** |
164
|
|
|
* Performs left-to-right function composition. |
165
|
|
|
* The leftmost function may have any arity; |
166
|
|
|
* the remaining functions must be unary. |
167
|
|
|
* The result of pipe is curried. |
168
|
|
|
* **Calling pipe() without any argument throws Tarsana\Functional\Exceptions\InvalidArgument** |
169
|
|
|
* ```php |
170
|
|
|
* function add($x, $y) { return $x + $y; } |
171
|
|
|
* $double = function($x) { return 2 * $x; }; |
172
|
|
|
* $addThenDouble = pipe('add', $double); |
173
|
|
|
* $addThenDouble(2, 3) // 10 |
174
|
|
|
* ``` |
175
|
|
|
* |
176
|
|
|
* @signature (((a, b, ...) -> o), (o -> p), ..., (y -> z)) -> ((a, b, ...) -> z) |
177
|
|
|
* @param callable ...$fns |
178
|
|
|
* @return callable |
179
|
|
|
*/ |
180
|
|
|
function pipe() { |
181
|
|
|
$fns = func_get_args(); |
182
|
|
|
if(count($fns) < 1) |
183
|
|
|
throw new InvalidArgument("pipe() requires at least one argument"); |
184
|
|
|
return curry(function () use ($fns) { |
185
|
|
|
$result = _apply(array_shift($fns), func_get_args()); |
186
|
|
|
foreach ($fns as $fn) { |
187
|
|
|
$result = $fn($result); |
188
|
|
|
} |
189
|
|
|
return $result; |
190
|
|
|
}); |
191
|
|
|
} |
192
|
|
|
|
193
|
|
|
/** |
194
|
|
|
* A function that takes one argument and |
195
|
|
|
* returns exactly the given argument. |
196
|
|
|
* ```php |
197
|
|
|
* identity('Hello') // 'Hello' |
198
|
|
|
* identity([1, 2, 3]) // [1, 2, 3] |
199
|
|
|
* identity(null) // null |
200
|
|
|
* ``` |
201
|
|
|
* |
202
|
|
|
* @signature * -> * |
203
|
|
|
* @return mixed |
204
|
|
|
*/ |
205
|
|
|
function identity() { |
206
|
|
|
return apply(curry(function($value) { |
207
|
|
|
return $value; |
208
|
|
|
}), func_get_args()); |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
/** |
212
|
|
|
* Takes many predicates and returns a new predicate that |
213
|
|
|
* returns `true` only if all predicates are satisfied. |
214
|
|
|
* ```php |
215
|
|
|
* |
216
|
|
|
* ``` |
217
|
|
|
*/ |
218
|
|
|
function |
219
|
|
|
|
|
|
|
|
The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.
The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.
To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.