Completed
Push — master ( 3e4d42...ec481a )
by Amine
10s
created

_stream.php ➔ _stream_throw_error()   D

Complexity

Conditions 9
Paths 9

Size

Total Lines 32
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 30
nc 9
nop 1
dl 0
loc 32
rs 4.909
c 0
b 0
f 0
1
<?php namespace Tarsana\Functional;
2
/**
3
 * This file contains Stream internal functions.
4
 * @file
5
 */
6
7
/**
8
 * Operation :: {
9
 *     name: String,
10
 *     signatures: [[String]],
11
 *     fn: Function
12
 * }
13
 *
14
 * @type
15
 */
16
17
/**
18
 * Transformation :: {
19
 *     operation: Operation,
20
 *     args: [Any]
21
 * }
22
 *
23
 * @type
24
 */
25
26
/**
27
 * Stream :: {
28
 *     operations: {name1: Operation1, ...},
29
 *     data: Any,
30
 *     transformations: [Transformation],
31
 *     type: String,
32
 *     result: Any,
33
 *     resolved: Boolean (The result is computed)
34
 * }
35
 *
36
 * @type
37
 */
38
39
/**
40
 * Returns supported types in signatures.
41
 *
42
 * @signature List
43
 * @return array
44
 */
45
function _stream_types() {
46
    return [
47
        'Null',
48
        'Boolean',
49
        'Number',
50
        'String',
51
        'Resource',
52
        'Function',
53
        'List', // [1, 2, 'Hi']
0 ignored issues
show
Unused Code Comprehensibility introduced by
70% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
54
        'Array', // ['foo' => 'bar', 'baz']
0 ignored issues
show
Unused Code Comprehensibility introduced by
64% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
55
        'Object',
56
        'Any'
57
    ];
58
}
59
60
/**
61
 * Throws an error.
62
 *
63
 * @signature String -> *
64
 *     'unknown-callable', callable
65
 *     'invalid-signature', string
66
 *     'unknown-operation', string
67
 *     'invalid-args', string, [string], [[string]]
68
 *     'duplicated-operation', string
69
 * @param  string $type
70
 * @return void
71
 */
72
function _stream_throw_error($type) {
73
    $params = tail(func_get_args());
74
    $msg = 'Stream: unknown error happened';
75
    switch ($type) {
76
        case 'unknown-callable':
77
            $fn = is_string($params[0]) ? $params[0] : toString($params[0]);
78
            $msg = "Stream: unknown callable '{$fn}'";
79
        break;
80
        case 'invalid-signature':
81
            $msg = "Stream: invalid signature '{$params[0]}' it should follow the syntax 'TypeArg1 -> TypeArg2 -> ... -> ReturnType' and types to use are Boolean, Number, String, Resource, Function, List, Array, Object, Any";
82
        break;
83
        case 'unknown-operation':
84
            $msg = "Stream: call to unknown operation '{$params[0]}'";
85
        break;
86
        case 'duplicated-operation':
87
            $msg = "Stream: operation '{$params[0]}' already exists";
88
        break;
89
        case 'ambiguous-signatures':
90
            $msg = "Stream: signatures of the operation '{$params[0]}' are duplicated or ambiguous";
91
        break;
92
        case 'wrong-operation-args':
93
            $args = join(', ', $params[0]);
94
            $msg = "Stream: wrong arguments ({$args}) given to operation '{$params[1]}'";
95
        break;
96
        case 'wrong-transformation-args':
97
            $args = join(', ', $params[1]);
98
            $types = join(', ', $params[2]);
99
            $msg = "Stream: operation '{$params[0]}' could not be called with arguments types ({$args}); expected types are ({$types})";
100
        break;
101
    }
102
    throw Error::of($msg);
103
}
104
105
/**
106
 * Creates an operation.
107
 *
108
 * ```php
109
 * // Using function name
110
 * $length = F\_stream_operation('length', 'List|Array -> Number', 'count');
111
 * $length; //=> ['name' => 'length', 'signatures' => [['List', 'Number'], ['Array', 'Number']], 'fn' => 'count']
112
 * // Using closure
113
 * $increment = function($x) {
114
 *     return 1 + $x;
115
 * };
116
 * $operation = F\_stream_operation('increment', 'Number -> Number', $increment);
117
 * $operation; //=> ['name' => 'increment', 'signatures' => [['Number', 'Number']], 'fn' => $increment]
118
 * // Without callable
119
 * F\_stream_operation('count', 'List -> Number'); //=> ['name' => 'count', 'signatures' => [['List', 'Number']], 'fn' => 'count']
120
 * // Invalid signature
121
 * F\_stream_operation('count', 'Number'); // throws "Stream: invalid signature 'Number' it should follow the syntax 'TypeArg1 -> TypeArg2 -> ... -> ReturnType' and types to use are Boolean, Number, String, Resource, Function, List, Array, Object, Any"
122
 * // Invalid callable
123
 * F\_stream_operation('foo', 'List -> Number'); // throws "Stream: unknown callable 'foo'"
124
 * ```
125
 *
126
 * @signature String -> String -> Maybe(Function) -> Operation
127
 * @param  string $name
128
 * @param  string $signature
129
 * @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...
130
 * @return array
131
 */
132
function _stream_operation($name, $signature, $callable = null) {
133
    $callable = $callable ?: $name;
134
    if (!is_callable($callable))
135
        _stream_throw_error('unknown-callable', $callable);
136
137
    return [
138
        'name' => $name,
139
        'signatures' => _stream_make_signatures($signature),
140
        'fn' => $callable
141
    ];
142
}
143
144
/**
145
 * Transforms a signature text to an array of signatures.
146
 *
147
 * ```php
148
 * F\_stream_make_signatures('Number|List -> Number -> String|Array -> Number'); //=> [
149
 *     ['Number', 'Number', 'String', 'Number'],
150
 *     ['List', 'Number', 'String', 'Number'],
151
 *     ['Number', 'Number', 'Array', 'Number'],
152
 *     ['List', 'Number', 'Array', 'Number']
153
 * ]
154
 * F\_stream_make_signatures('List'); // throws "Stream: invalid signature 'List' it should follow the syntax 'TypeArg1 -> TypeArg2 -> ... -> ReturnType' and types to use are Boolean, Number, String, Resource, Function, List, Array, Object, Any"
155
 * F\_stream_make_signatures('List -> Foo'); // throws "Stream: invalid signature 'List -> Foo' it should follow the syntax 'TypeArg1 -> TypeArg2 -> ... -> ReturnType' and types to use are Boolean, Number, String, Resource, Function, List, Array, Object, Any"
156
 * ```
157
 *
158
 * @signature String -> [[String]]
159
 * @param  string $text
160
 * @return array
161
 */
162
function _stream_make_signatures($text) {
163
    // Assuming $text  = 'Number|List -> Number -> String|Array -> Number'
164
    $parts = map(pipe(split('|'), map(pipe('trim', _stream_ensure_type($text)))), split('->', $text));
165
    // $parts = [['Number', 'List'], ['Number'], ['String', 'Array'], ['Number']]
0 ignored issues
show
Unused Code Comprehensibility introduced by
71% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
166
    if (length($parts) < 2)
167
        _stream_throw_error('invalid-signature', $text);
168
169
    return reduce(function($result, $part){
170
        return chain(function($item) use($result){
171
            return map(append($item), $result);
172
        }, $part);
173
    }, [[]], $parts);
174
    // 0: $result = [[]]
0 ignored issues
show
Unused Code Comprehensibility introduced by
59% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
175
    // 1: $part = ['Number', 'List']  => $result = [['Number'], ['List']]
0 ignored issues
show
Unused Code Comprehensibility introduced by
62% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
176
    // 2: $part = ['Number']          => $result = [['Number', 'Number'], ['List', 'Number']]
0 ignored issues
show
Unused Code Comprehensibility introduced by
62% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
177
    // 2: $part = ['String', 'Array'] => $result = [['Number', 'Number', 'String'], ['List', 'Number', 'String'],
0 ignored issues
show
Unused Code Comprehensibility introduced by
63% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
178
    //                                              ['Number', 'Number', 'Array'], ['List', 'Number', 'Array']]
0 ignored issues
show
Unused Code Comprehensibility introduced by
73% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
179
    // 3: $part = ['Number']          => $result = [['Number', 'Number', 'String', 'Number'],
0 ignored issues
show
Unused Code Comprehensibility introduced by
60% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
180
    //                                              ['List', 'Number', 'String', 'Number'],
0 ignored issues
show
Unused Code Comprehensibility introduced by
72% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
181
    //                                              ['Number', 'Number', 'Array', 'Number'],
0 ignored issues
show
Unused Code Comprehensibility introduced by
72% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
182
    //                                              ['List', 'Number', 'Array', 'Number']]
0 ignored issues
show
Unused Code Comprehensibility introduced by
72% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
183
}
184
185
/**
186
 * Ensures an element of a signature is a correct type or throws an error if not.
187
 *
188
 * ```php
189
 * F\_stream_ensure_type('List -> Bar', 'List'); //=> 'List'
190
 * F\_stream_ensure_type('List -> Bar', 'Bar'); // throws "Stream: invalid signature 'List -> Bar' it should follow the syntax 'TypeArg1 -> TypeArg2 -> ... -> ReturnType' and types to use are Boolean, Number, String, Resource, Function, List, Array, Object, Any"
191
 * ```
192
 * @signature String -> String -> String
193
 * @param  string $signature
0 ignored issues
show
Bug introduced by
There is no parameter named $signature. 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...
194
 * @param  string $type
0 ignored issues
show
Bug introduced by
There is no parameter named $type. 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...
195
 * @return string
196
 */
197
function _stream_ensure_type() {
198
    $ensureType = function($signature, $type) {
199
        if (! contains($type, _stream_types()))
200
            _stream_throw_error('invalid-signature', $signature);
201
        return $type;
202
    };
203
    return apply(curry($ensureType), func_get_args());
204
}
205
206
/**
207
 * Makes a Stream with the given args.
208
 *
209
 * @signature [Operation] -> Any -> [Transformation] -> String -> Any -> Boolean -> Stream
210
 * @param  array $operations
211
 * @param  mixed $data
212
 * @param  array $transformations
213
 * @param  string $type
214
 * @param  mixed $result
215
 * @param  bool $resolved
216
 * @return array
217
 */
218
function _stream_make($operations, $data, $transformations, $type, $result, $resolved) {
219
    return [
220
        'data' => $data,
221
        'type' => $type,
222
        'result' => $result,
223
        'resolved' => $resolved,
224
        'operations' => $operations,
225
        'transformations' => $transformations
226
    ];
227
}
228
229
/**
230
 * Creates a Stream with operations and initial data.
231
 *
232
 * ```php
233
 * $map = F\map();
234
 * $operations = [
235
 *     F\_stream_operation('length', 'List|Array -> Number', 'count'),
236
 *     F\_stream_operation('length', 'String -> Number', 'strlen'),
237
 *     F\_stream_operation('map', 'Function -> List -> List', $map)
238
 * ];
239
 *
240
 * F\_stream($operations, 11); //=> [
241
 *     'data' => 11,
242
 *     'type' => 'Number',
243
 *     'result' => 11,
244
 *     'resolved' => true,
245
 *     'operations' => [
246
 *         'length' => [
247
 *             [
248
 *                 'name' => 'length',
249
 *                 'signatures' => [['List', 'Number'], ['Array', 'Number']],
250
 *                 'fn' => 'count'
251
 *             ],
252
 *             [
253
 *                 'name' => 'length',
254
 *                 'signatures' => [['String', 'Number']],
255
 *                 'fn' => 'strlen'
256
 *             ]
257
 *         ],
258
 *         'map' => [
259
 *             [
260
 *                 'name' => 'map',
261
 *                 'signatures' => [['Function', 'List', 'List']],
262
 *                 'fn' => $map
263
 *             ]
264
 *         ]
265
 *     ],
266
 *     'transformations' => []
267
 * ]
268
 * ```
269
 *
270
 * @param  array $operations
271
 * @param  mixed $data
272
 * @return array
273
 */
274
function _stream($operations, $data) {
275
    return _stream_make(
276
        map(_f('_stream_validate_operations'), groupBy(get('name'), $operations)),
277
        $data,
278
        [],
279
        type($data),
280
        $data,
281
        true
282
    );
283
}
284
285
/**
286
 * Validates a list of operations having the same name.
287
 * It throws an error if a signature is duplicated.
288
 * ```php
289
 * F\_stream_validate_operations([
290
 *     [
291
 *         'name' => 'length',
292
 *         'signatures' => [['List', 'Number'], ['Array', 'Number']],
293
 *         'fn' => 'count'
294
 *     ],
295
 *     [
296
 *         'name' => 'length',
297
 *         'signatures' => [['String', 'Number']],
298
 *         'fn' => 'strlen'
299
 *     ]
300
 * ]); //=> [
301
 *     [
302
 *         'name' => 'length',
303
 *         'signatures' => [['List', 'Number'], ['Array', 'Number']],
304
 *         'fn' => 'count'
305
 *     ],
306
 *     [
307
 *         'name' => 'length',
308
 *         'signatures' => [['String', 'Number']],
309
 *         'fn' => 'strlen'
310
 *     ]
311
 * ]
312
 *
313
 * F\_stream_validate_operations([
314
 *     [
315
 *         'name' => 'length',
316
 *         'signatures' => [['List', 'Number'], ['Array', 'Number']],
317
 *         'fn' => 'count'
318
 *     ],
319
 *     [
320
 *         'name' => 'length',
321
 *         'signatures' => [['String', 'Number'], ['List', 'Number']],
322
 *         'fn' => 'strlen'
323
 *     ]
324
 * ]); // throws "Stream: signatures of the operation 'length' are duplicated or ambiguous"
325
 * ```
326
 *
327
 * @param  array $operations
328
 * @return array
329
 */
330
function _stream_validate_operations($operations) {
331
    $signatures = chain(get('signatures'), $operations);
332
    if (length($signatures) != length(uniqueBy(equalBy(map(init())), $signatures))) {
333
        _stream_throw_error('ambiguous-signatures', get('name', head($operations)));
334
    }
335
    return $operations;
336
}
337
338
/**
339
 * Applies an operation (adds a transformation) to a stream.
340
 * ```php
341
 * $operations = [
342
 *     F\_stream_operation('length', 'List|Array -> Number', 'count'),
343
 *     F\_stream_operation('length', 'String -> Number', 'strlen'),
344
 *     F\_stream_operation('map', 'Function -> List -> List', F\map())
345
 * ];
346
 *
347
 * $stream = F\_stream($operations, [1, 2, 3]);
348
 *
349
 * F\_stream_apply_operation('length', [], $stream); //=> [
350
 *     'data' => [1, 2, 3],
351
 *     'type' => 'Number',
352
 *     'result' => null,
353
 *     'resolved' => false,
354
 *     'operations' => [
355
 *         'length' => [
356
 *             [
357
 *                 'name' => 'length',
358
 *                 'signatures' => [['List', 'Number'], ['Array', 'Number']],
359
 *                 'fn' => 'count'
360
 *             ],
361
 *             [
362
 *                 'name' => 'length',
363
 *                 'signatures' => [['String', 'Number']],
364
 *                 'fn' => 'strlen'
365
 *             ]
366
 *         ],
367
 *         'map' => [
368
 *             [
369
 *                 'name' => 'map',
370
 *                 'signatures' => [['Function', 'List', 'List']],
371
 *                 'fn' => F\map()
372
 *             ]
373
 *         ]
374
 *     ],
375
 *     'transformations' => [
376
 *         [
377
 *             'operation' => [
378
 *                 'name' => 'length',
379
 *                 'signatures' => [['List', 'Number']],
380
 *                 'fn' => 'count'
381
 *             ],
382
 *             'args' => []
383
 *         ]
384
 *     ]
385
 * ]
386
 *
387
 * F\_stream_apply_operation('foo', [], $stream); // throws "Stream: call to unknown operation 'foo'"
388
 * F\_stream_apply_operation('length', [5], $stream); // throws "Stream: wrong arguments (Number, List) given to operation 'length'"
389
 * F\_stream_apply_operation('map', [], $stream); // throws "Stream: wrong arguments (List) given to operation 'map'"
390
 * F\_stream_apply_operation('map', [[1, 2]], $stream); // throws "Stream: wrong arguments (List, List) given to operation 'map'"
391
 *
392
 * ```
393
 *
394
 * @signature String -> [Any] -> Stream -> Stream
395
 * @param  string $name
396
 * @param  array $args
397
 * @param  array $stream
398
 * @return array
399
 */
400
function _stream_apply_operation($name, $args, $stream) {
401
    $operations = getPath(['operations', $name], $stream);
402
    if (null == $operations) {
403
        _stream_throw_error('unknown-operation', $name);
404
    }
405
406
    $argsTypes = append(get('type', $stream), map(type(), $args));
407
    $operation = head(filter(_stream_operation_is_applicable($argsTypes),
408
        chain(_f('_stream_split_operation_signatures'), $operations)));
409
    if (null == $operation) {
410
        _stream_throw_error('wrong-operation-args', $argsTypes, $name);
411
    }
412
413
    $returnType = last(head(get('signatures', $operation)));
414
    return _stream_make(
415
        get('operations', $stream), // operations
416
        get('data', $stream), // data
417
        append([
418
            'operation' => $operation,
419
            'args' => $args
420
        ], get('transformations', $stream)), // transformations
421
        $returnType, // type
422
        null, // result
423
        false // resolved
424
    );
425
}
426
427
/**
428
 * Splits an operation with multiple signatures into a list of operation; each having one signature.
429
 * ```php
430
 * F\_stream_split_operation_signatures([
431
 *     'name' => 'length',
432
 *     'signatures' => [['List', 'Number'], ['Array', 'Number']],
433
 *     'fn' => 'count'
434
 * ]); //=> [
435
 *     [
436
 *         'name' => 'length',
437
 *         'signatures' => [['List', 'Number']],
438
 *         'fn' => 'count'
439
 *     ],
440
 *     [
441
 *         'name' => 'length',
442
 *         'signatures' => [['Array', 'Number']],
443
 *         'fn' => 'count'
444
 *     ]
445
 * ]
446
 * ```
447
 *
448
 * @signature Operation -> [Operation]
449
 * @param  array $operation
450
 * @return array
451
 */
452
function _stream_split_operation_signatures($operation) {
453
    $name = get('name', $operation);
454
    $fn = get('fn', $operation);
455
    return map(function($signature) use($name, $fn) {
456
        return [
457
            'name' => $name,
458
            'fn' => $fn,
459
            'signatures' => [$signature]
460
        ];
461
    }, get('signatures', $operation));
462
}
463
464
/**
465
 * Checks if the operation can be applied with the given arguments types.
466
 *
467
 * ```php
468
 * $isApplicable = F\_stream_operation_is_applicable(['Number', 'Number']);
469
 * $isApplicable(F\_stream_operation('add', 'Number -> Number -> Number', F\plus())); //=> true
470
 * $isApplicable(F\_stream_operation('length', 'List|Array|String -> Number', F\length())); //=> false
471
 * F\_stream_operation_is_applicable(
472
 *     ['List'],
473
 *     F\_stream_operation('length', 'List|Array|String -> Number', F\length())
474
 * ); //=> true
475
 * F\_stream_operation_is_applicable(
476
 *     ['String'],
477
 *     F\_stream_operation('length', 'List|Array|String -> Number', F\length())
478
 * ); //=> true
479
 * F\_stream_operation_is_applicable(
480
 *     ['Number'],
481
 *     F\_stream_operation('length', 'List|Array|String -> Number', F\length())
482
 * ); //=> false
483
 * F\_stream_operation_is_applicable(
484
 *     ['Number', 'String'],
485
 *     F\_stream_operation('fill', 'Number -> Any -> List', function(){})
486
 * ); //=> true
487
 * F\_stream_operation_is_applicable(
488
 *     ['Any', 'String'],
489
 *     F\_stream_operation('fill', 'Number -> Any -> List', function(){})
490
 * ); //=> true
491
 * ```
492
 *
493
 * @signature [String] -> Operation -> Boolean
494
 * @param  array $argsTypes
0 ignored issues
show
Bug introduced by
There is no parameter named $argsTypes. 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...
495
 * @param  array $operation
0 ignored issues
show
Bug introduced by
There is no parameter named $operation. 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...
496
 * @return bool
497
 */
498
function _stream_operation_is_applicable() {
499
    static $operationIsApplicable = false;
500
    $operationIsApplicable = $operationIsApplicable ?: curry(function($argsTypes, $operation) {
501
        return null !== findIndex(function($signature) use($argsTypes) {
502
            $types = init($signature);
503
            if (length($types) == length($argsTypes)) {
504
                return allSatisfies(function($pair) {
505
                    return 'Any' == $pair[0] || 'Any' == $pair[1] || $pair[0] == $pair[1];
506
                }, pairsFrom($types, $argsTypes));
507
            }
508
            return false;
509
        }, get('signatures', $operation));
510
    });
511
    return _apply($operationIsApplicable, func_get_args());
512
}
513
514
/**
515
 * Computes the result of a stream.
516
 *
517
 * ```php
518
 * $operations = [
519
 *     F\_stream_operation('length', 'List|Array -> Number', 'count'),
520
 *     F\_stream_operation('length', 'String -> Number', 'strlen'),
521
 *     F\_stream_operation('map', 'Function -> List -> List', F\map()),
522
 *     F\_stream_operation('reduce', 'Function -> Any -> List -> Any', F\reduce()),
523
 *     F\_stream_operation('increment', 'Number -> Number', F\plus(1)),
524
 *     F\_stream_operation('upperCase', 'String -> String', F\upperCase()),
525
 *     F\_stream_operation('toString', 'Any -> String', F\toString()),
526
 *     F\_stream_operation('head', 'List -> Any', F\head())
527
 * ];
528
 *
529
 * $stream = F\_stream($operations, [1, 2, 3]);
530
 * $stream = F\_stream_apply_operation('length', [], $stream);
531
 * $stream = F\_stream_resolve($stream);
532
 * F\get('resolved', $stream); //=> true
533
 * F\get('result', $stream); //=> 3
534
 *
535
 * $stream = F\_stream($operations, [1, 2, 3]);
536
 * $stream = F\_stream_apply_operation('map', [F\plus(2)], $stream); // [3, 4, 5]
537
 * $stream = F\_stream_apply_operation('reduce', [F\plus(), 0], $stream); // 12
538
 * $stream = F\_stream_apply_operation('increment', [], $stream); // 13
539
 * $stream = F\_stream_resolve($stream);
540
 * F\get('resolved', $stream); //=> true
541
 * F\get('result', $stream); //=> 13
542
 *
543
 * $stream = F\_stream($operations, []);
544
 * $stream = F\_stream_apply_operation('head', [], $stream); // null
545
 * $stream = F\_stream_apply_operation('increment', [], $stream); // Error
546
 * $stream = F\_stream_apply_operation('toString', [], $stream); // Error
547
 * $stream = F\_stream_resolve( $stream); // throws "Stream: operation 'increment' could not be called with arguments types (Null); expected types are (Number)"
548
 *
549
 * ```
550
 *
551
 * @signature Stream -> Stream
552
 * @param  array $stream
553
 * @return array
554
 */
555
function _stream_resolve($stream) {
556
    if (get('resolved', $stream))
557
        return $stream;
558
    $transformations = get('transformations', $stream);
559
    $transformation = head($transformations);
560
    if (null === $transformation) {
561
        return _stream_make(
562
            get('operations', $stream), // operations
563
            get('data', $stream), // data
564
            get('transformations', $stream), // transformations
565
            get('type', $stream), // type
566
            get('data', $stream), // result
567
            true // resolved
568
        );
569
    }
570
571
    $args = append(get('data', $stream), get('args', $transformation));
572
    $argsTypes = map(type(), $args);
573
    $operation = get('operation', $transformation);
574
    if (! _stream_operation_is_applicable($argsTypes, $operation)) {
575
        _stream_throw_error('wrong-transformation-args', $operation['name'], $argsTypes, init(head(get('signatures', $operation))));
576
    }
577
578
    return _stream_resolve(_stream_make(
579
        get('operations', $stream), // operations
580
        _apply(get('fn', $operation), $args), // data
581
        tail($transformations), // transformations
582
        get('type', $stream), // type
583
        null, // result
584
        false // resolved
585
    ));
586
}
587
588
/**
589
 * Applies a function to a single argument.
590
 * To be used as the `then()` method of Stream.
591
 *
592
 * @signature (a -> b) -> a -> b
593
 * @param  callable $fn
594
 * @param  mixed $arg
595
 * @return mixed
596
 */
597
function _stream_then($fn, $arg) {
598
    return _apply($fn, [$arg]);
599
}
600
601