Completed
Push — master ( cfe428...5e2d42 )
by Amine
02:00
created

functions.php ➔ _merge_args()   B

Complexity

Conditions 5
Paths 1

Size

Total Lines 16
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 12
nc 1
nop 3
dl 0
loc 16
rs 8.8571
c 0
b 0
f 0
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());
0 ignored issues
show
Bug introduced by
Consider using a different name than the imported variable $boundArgs, or did you forget to import by reference?

It seems like you are assigning to a variable which was imported through a use statement which was not imported by reference.

For clarity, we suggest to use a different name or import by reference depending on whether you would like to have the change visibile in outer-scope.

Change not visible in outer-scope

$x = 1;
$callable = function() use ($x) {
    $x = 2; // Not visible in outer scope. If you would like this, how
            // about using a different variable name than $x?
};

$callable();
var_dump($x); // integer(1)

Change visible in outer-scope

$x = 1;
$callable = function() use (&$x) {
    $x = 2;
};

$callable();
var_dump($x); // integer(2)
Loading history...
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
0 ignored issues
show
Bug introduced by
There is no parameter named $fn. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
156
 * @param  array    $args
0 ignored issues
show
Bug introduced by
There is no parameter named $args. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
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