Completed
Push — master ( d55531...cfe428 )
by Amine
02:42
created

functions.php ➔ _merge_args()   C

Complexity

Conditions 8
Paths 4

Size

Total Lines 37
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 30
nc 4
nop 3
dl 0
loc 37
rs 5.3846
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
 * Gets the number of arguments of a function.
10
 *
11
 * @internal
12
 * @signature (* -> *) -> Number
13
 * @param  callable $fn
14
 * @return int
15
 */
16
function _number_of_args($fn) {
17
    $reflector = is_array($fn) ?
18
        new \ReflectionMethod($fn[0], $fn[1]) :
19
        new \ReflectionFunction($fn);
20
    return $reflector->getNumberOfParameters();
21
}
22
23
/**
24
 * Checks if `$a` is am argument placeholder.
25
 *
26
 * @internal
27
 * @signature * -> Boolean
28
 * @param  mixed  $a
29
 * @return boolean
30
 */
31
function _is_placeholder($a) {
32
    return $a instanceof Placeholder;
33
}
34
35
/**
36
 * Adds new given arguments to the list of bound arguments while filling placeholders.
37
 *
38
 * @internal
39
 * @signature Number -> [a] -> [a] -> [a]
40
 * @param  int   $fnArgsCount
41
 * @param  array $boundArgs
42
 * @param  array $givenArgs
43
 * @return array
44
 */
45
function _merge_args($fnArgsCount, $boundArgs, $givenArgs) {
46
    $allArgs = array_merge($boundArgs, $givenArgs);
47
    $args = array_slice($allArgs, 0, $fnArgsCount);
48
    $boundArgsCount = count($boundArgs);
49
    $givenArgsCount = count($givenArgs);
50
    $allArgsCount = $boundArgsCount + $givenArgsCount;
51
    $placeholdersCount = count(array_filter($args, 'Tarsana\Functional\_is_placeholder'));
52
    if ($placeholdersCount > 0) {
53
        if ($allArgsCount > $fnArgsCount) {
54
            $rest = array_slice($args, $fnArgsCount - $allArgsCount);
55
            $i = 0; // index in $args
56
            $k = 0; // index in rest
57
            $restCount = $allArgsCount - $fnArgsCount;
58
            while($placeholdersCount > 0 && $k < $restCount) {
59
                $arg = $rest[$k];
60
                while(! _is_placeholder($args[$i]))
61
                    $i++;
62
                if (! _is_placeholder($arg)) {
63
                    $args[$i] = $arg;
64
                    $placeholdersCount --;
65
                }
66
                $i ++;
67
                $k ++;
68
            }
69
            if ($k < $restCount) {
70
                $rest = array_slice($rest, $k);
71
                $cleanedRest = array_filter($rest, function($a) { return !_is_placeholder($a); });
72
                $args = array_merge($args, $cleanedRest);
73
            }
74
        }
75
    }
76
    $count = count($args) - count(array_filter($args, 'Tarsana\Functional\_is_placeholder'));
77
    return [
78
        'count' => $count,
79
        'args' => $args
80
    ];
81
}
82
83
/**
84
 * Returns the curried version of a function with some arguments bound to it.
85
 *
86
 * @internal
87
 * @signature (* -> *) -> Number -> [*] -> (* -> *)
88
 * @param  callable $fn
89
 * @param  int $argsCount
90
 * @param  array  $boundArgs
91
 * @return callable
92
 */
93
function _curried_function($fn, $argsCount, $boundArgs = []) {
94
    return function() use($fn, $argsCount, $boundArgs) {
95
        $merged = _merge_args($argsCount, $boundArgs, func_get_args());
96
        if ($merged['count'] >= $argsCount)
97
            return call_user_func_array($fn, $merged['args']);
98
        return _curried_function($fn, $argsCount, $merged['args']);
99
    };
100
}
101
102
/**
103
 * Returns a curried equivalent of the provided function.
104
 * ```php
105
 * $add = curry(function($x, $y){
106
 *     return $x + $y;
107
 * });
108
 * $addFive = $add(5); // a function
109
 * $addFive(5); // 10
110
 * $add(5, 5) // 10
111
 * ```
112
 *
113
 * @signature (* -> a) -> (* -> a)
114
 * @param  callable $fn
115
 * @return callable
116
 */
117
function curry($fn) {
118
    return _curried_function($fn, _number_of_args($fn));
119
}
120
121
/**
122
 * Argument placeholder to use with curried functions.
123
 * ```php
124
 * $minus = curry(function ($x, $y) { return $x - $y; });
125
 * $decrement = $minus(__(), 1);
126
 * $decrement(10) // 9
127
 *
128
 * $reduce = curry('array_reduce');
129
 * $sum = $reduce(__(), 'Tarsana\Functional\plus');
130
 * $sum([1, 2, 3, 4], 0) // 10
131
 * ```
132
 *
133
 * @signature * -> Placeholder
134
 * @return \Cypress\Curry\Placeholder
135
 */
136
function __() {
137
    return Placeholder::get();
138
}
139
140
/**
141
 * Non curried version of apply for internal use.
142
 *
143
 * @internal
144
 * @param  callable $fn
145
 * @param  array    $args
146
 * @return mixed
147
 */
148
function _apply($fn, $args) {
149
    return call_user_func_array($fn, $args);
150
}
151
152
/**
153
 * Apply the provided function to the list of arguments.
154
 * ```php
155
 * apply('strlen', ['Hello']) // 5
156
 * $replace = apply('str_replace');
157
 * $replace(['l', 'o', 'Hello']) // 'Heooo'
158
 * ```
159
 *
160
 * @signature (*... -> a) -> [*] -> a
161
 * @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...
162
 * @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...
163
 * @return mixed
164
 */
165
function apply() {
166
    return _apply(curry('Tarsana\Functional\_apply'), func_get_args());
167
}
168
169
/**
170
 * Performs left-to-right function composition.
171
 * The leftmost function may have any arity;
172
 * the remaining functions must be unary.
173
 * The result of pipe is curried.
174
 * **Calling pipe() without any argument throws Tarsana\Functional\Exceptions\InvalidArgument**
175
 * ```php
176
 * function add($x, $y) { return $x + $y; }
177
 * $double = function($x) { return 2 * $x; };
178
 * $addThenDouble = pipe('add', $double);
179
 * $addThenDouble(2, 3) // 10
180
 * ```
181
 *
182
 * @signature (((a, b, ...) -> o), (o -> p), ..., (y -> z)) -> ((a, b, ...) -> z)
183
 * @param  callable ...$fns
184
 * @return callable
185
 */
186
function pipe() {
187
    $fns = func_get_args();
188
    if(count($fns) < 1)
189
        throw new InvalidArgument("pipe() requires at least one argument");
190
    return curry(function () use ($fns) {
191
        $result = _apply(array_shift($fns), func_get_args());
192
        foreach ($fns as $fn) {
193
            $result = $fn($result);
194
        }
195
        return $result;
196
    });
197
}
198
199
/**
200
 * A function that takes one argument and
201
 * returns exactly the given argument.
202
 * ```php
203
 * identity('Hello') // 'Hello'
204
 * identity([1, 2, 3]) // [1, 2, 3]
205
 * identity(null) // null
206
 * ```
207
 *
208
 * @signature * -> *
209
 * @return mixed
210
 */
211
function identity() {
212
    return apply(curry(function($value) {
213
        return $value;
214
    }), func_get_args());
215
}
216