Completed
Push — master ( f80c85...97f147 )
by Amine
01:50
created

functions.php ➔ _number_of_args()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 1
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
1
<?php namespace Tarsana\Functional;
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 17 and the first side effect is on line 218.

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.

Loading history...
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
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

Syntax error, unexpected EOF, expecting T_STRING or '('
Loading history...