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
|
|
|
* operations: [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'] |
|
|
|
|
54
|
|
|
'Array', // ['foo' => 'bar', 'baz'] |
|
|
|
|
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(' or ', map(pipe(join(', '), prepend('('), append(')')), $params[2])); |
99
|
|
|
|
100
|
|
|
$msg = "Stream: operation '{$params[0]}' could not be called with arguments types ({$args}); expected types are {$types}"; |
101
|
|
|
break; |
102
|
|
|
} |
103
|
|
|
throw Error::of($msg); |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
/** |
107
|
|
|
* Creates an operation. |
108
|
|
|
* |
109
|
|
|
* ```php |
110
|
|
|
* // Using function name |
111
|
|
|
* $length = F\_stream_operation('length', 'List|Array -> Number', 'count'); |
112
|
|
|
* $length; //=> ['name' => 'length', 'signatures' => [['List', 'Number'], ['Array', 'Number']], 'fn' => 'count'] |
113
|
|
|
* // Using closure |
114
|
|
|
* $increment = function($x) { |
115
|
|
|
* return 1 + $x; |
116
|
|
|
* }; |
117
|
|
|
* $operation = F\_stream_operation('increment', 'Number -> Number', $increment); |
118
|
|
|
* $operation; //=> ['name' => 'increment', 'signatures' => [['Number', 'Number']], 'fn' => $increment] |
119
|
|
|
* // Without callable |
120
|
|
|
* F\_stream_operation('count', 'List -> Number'); //=> ['name' => 'count', 'signatures' => [['List', 'Number']], 'fn' => 'count'] |
121
|
|
|
* // Invalid signature |
122
|
|
|
* 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" |
123
|
|
|
* // Invalid callable |
124
|
|
|
* F\_stream_operation('foo', 'List -> Number'); // throws "Stream: unknown callable 'foo'" |
125
|
|
|
* ``` |
126
|
|
|
* |
127
|
|
|
* @signature String -> String -> Maybe(Function) -> Operation |
128
|
|
|
* @param string $name |
129
|
|
|
* @param string $signature |
130
|
|
|
* @param callable $fn |
|
|
|
|
131
|
|
|
* @return array |
132
|
|
|
*/ |
133
|
|
|
function _stream_operation($name, $signature, $callable = null) { |
134
|
|
|
$callable = $callable ?: $name; |
135
|
|
|
if (!is_callable($callable)) |
136
|
|
|
_stream_throw_error('unknown-callable', $callable); |
137
|
|
|
|
138
|
|
|
return [ |
139
|
|
|
'name' => $name, |
140
|
|
|
'signatures' => _stream_make_signatures($signature), |
141
|
|
|
'fn' => $callable |
142
|
|
|
]; |
143
|
|
|
} |
144
|
|
|
|
145
|
|
|
/** |
146
|
|
|
* Transforms a signature text to an array of signatures. |
147
|
|
|
* |
148
|
|
|
* ```php |
149
|
|
|
* F\_stream_make_signatures('Number|List -> Number -> String|Array -> Number'); //=> [ |
150
|
|
|
* ['Number', 'Number', 'String', 'Number'], |
151
|
|
|
* ['List', 'Number', 'String', 'Number'], |
152
|
|
|
* ['Number', 'Number', 'Array', 'Number'], |
153
|
|
|
* ['List', 'Number', 'Array', 'Number'] |
154
|
|
|
* ] |
155
|
|
|
* 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" |
156
|
|
|
* 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" |
157
|
|
|
* ``` |
158
|
|
|
* |
159
|
|
|
* @signature String -> [[String]] |
160
|
|
|
* @param string $text |
161
|
|
|
* @return array |
162
|
|
|
*/ |
163
|
|
|
function _stream_make_signatures($text) { |
164
|
|
|
// Assuming $text = 'Number|List -> Number -> String|Array -> Number' |
165
|
|
|
$parts = map(pipe(split('|'), map(pipe('trim', _stream_ensure_type($text)))), split('->', $text)); |
166
|
|
|
// $parts = [['Number', 'List'], ['Number'], ['String', 'Array'], ['Number']] |
|
|
|
|
167
|
|
|
if (length($parts) < 2) |
168
|
|
|
_stream_throw_error('invalid-signature', $text); |
169
|
|
|
|
170
|
|
|
return reduce(function($result, $part){ |
171
|
|
|
return chain(function($item) use($result){ |
172
|
|
|
return map(append($item), $result); |
173
|
|
|
}, $part); |
174
|
|
|
}, [[]], $parts); |
175
|
|
|
// 0: $result = [[]] |
|
|
|
|
176
|
|
|
// 1: $part = ['Number', 'List'] => $result = [['Number'], ['List']] |
|
|
|
|
177
|
|
|
// 2: $part = ['Number'] => $result = [['Number', 'Number'], ['List', 'Number']] |
|
|
|
|
178
|
|
|
// 2: $part = ['String', 'Array'] => $result = [['Number', 'Number', 'String'], ['List', 'Number', 'String'], |
|
|
|
|
179
|
|
|
// ['Number', 'Number', 'Array'], ['List', 'Number', 'Array']] |
|
|
|
|
180
|
|
|
// 3: $part = ['Number'] => $result = [['Number', 'Number', 'String', 'Number'], |
|
|
|
|
181
|
|
|
// ['List', 'Number', 'String', 'Number'], |
|
|
|
|
182
|
|
|
// ['Number', 'Number', 'Array', 'Number'], |
|
|
|
|
183
|
|
|
// ['List', 'Number', 'Array', 'Number']] |
|
|
|
|
184
|
|
|
} |
185
|
|
|
|
186
|
|
|
/** |
187
|
|
|
* Ensures an element of a signature is a correct type or throws an error if not. |
188
|
|
|
* |
189
|
|
|
* ```php |
190
|
|
|
* F\_stream_ensure_type('List -> Bar', 'List'); //=> 'List' |
191
|
|
|
* 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" |
192
|
|
|
* ``` |
193
|
|
|
* @signature String -> String -> String |
194
|
|
|
* @param string $signature |
|
|
|
|
195
|
|
|
* @param string $type |
|
|
|
|
196
|
|
|
* @return string |
197
|
|
|
*/ |
198
|
|
|
function _stream_ensure_type() { |
199
|
|
|
$ensureType = function($signature, $type) { |
200
|
|
|
if (! contains($type, _stream_types())) |
201
|
|
|
_stream_throw_error('invalid-signature', $signature); |
202
|
|
|
return $type; |
203
|
|
|
}; |
204
|
|
|
return apply(curry($ensureType), func_get_args()); |
205
|
|
|
} |
206
|
|
|
|
207
|
|
|
/** |
208
|
|
|
* Makes a Stream with the given args. |
209
|
|
|
* |
210
|
|
|
* @signature [Operation] -> Any -> [Transformation] -> String -> Any -> Boolean -> Stream |
211
|
|
|
* @param array $operations |
212
|
|
|
* @param mixed $data |
213
|
|
|
* @param array $transformations |
214
|
|
|
* @param string $type |
215
|
|
|
* @param mixed $result |
216
|
|
|
* @param bool $resolved |
217
|
|
|
* @return array |
218
|
|
|
*/ |
219
|
|
|
function _stream_make($operations, $data, $transformations, $type, $result, $resolved) { |
220
|
|
|
return [ |
221
|
|
|
'data' => $data, |
222
|
|
|
'type' => $type, |
223
|
|
|
'result' => $result, |
224
|
|
|
'resolved' => $resolved, |
225
|
|
|
'operations' => $operations, |
226
|
|
|
'transformations' => $transformations |
227
|
|
|
]; |
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
/** |
231
|
|
|
* Creates a Stream with operations and initial data. |
232
|
|
|
* |
233
|
|
|
* ```php |
234
|
|
|
* $map = F\map(); |
235
|
|
|
* $operations = [ |
236
|
|
|
* F\_stream_operation('length', 'List|Array -> Number', 'count'), |
237
|
|
|
* F\_stream_operation('length', 'String -> Number', 'strlen'), |
238
|
|
|
* F\_stream_operation('map', 'Function -> List -> List', $map) |
239
|
|
|
* ]; |
240
|
|
|
* |
241
|
|
|
* F\_stream($operations, 11); //=> [ |
242
|
|
|
* 'data' => 11, |
243
|
|
|
* 'type' => 'Number', |
244
|
|
|
* 'result' => 11, |
245
|
|
|
* 'resolved' => true, |
246
|
|
|
* 'operations' => [ |
247
|
|
|
* 'length' => [ |
248
|
|
|
* [ |
249
|
|
|
* 'name' => 'length', |
250
|
|
|
* 'signatures' => [['List', 'Number'], ['Array', 'Number']], |
251
|
|
|
* 'fn' => 'count' |
252
|
|
|
* ], |
253
|
|
|
* [ |
254
|
|
|
* 'name' => 'length', |
255
|
|
|
* 'signatures' => [['String', 'Number']], |
256
|
|
|
* 'fn' => 'strlen' |
257
|
|
|
* ] |
258
|
|
|
* ], |
259
|
|
|
* 'map' => [ |
260
|
|
|
* [ |
261
|
|
|
* 'name' => 'map', |
262
|
|
|
* 'signatures' => [['Function', 'List', 'List']], |
263
|
|
|
* 'fn' => $map |
264
|
|
|
* ] |
265
|
|
|
* ] |
266
|
|
|
* ], |
267
|
|
|
* 'transformations' => [] |
268
|
|
|
* ] |
269
|
|
|
* ``` |
270
|
|
|
* |
271
|
|
|
* @param array $operations |
272
|
|
|
* @param mixed $data |
273
|
|
|
* @return array |
274
|
|
|
*/ |
275
|
|
|
function _stream($operations, $data) { |
276
|
|
|
return _stream_make( |
277
|
|
|
map(_f('_stream_validate_operations'), groupBy(get('name'), $operations)), |
278
|
|
|
$data, |
279
|
|
|
[], |
280
|
|
|
type($data), |
281
|
|
|
$data, |
282
|
|
|
true |
283
|
|
|
); |
284
|
|
|
} |
285
|
|
|
|
286
|
|
|
/** |
287
|
|
|
* Validates a list of operations having the same name. |
288
|
|
|
* It throws an error if a signature is duplicated. |
289
|
|
|
* ```php |
290
|
|
|
* F\_stream_validate_operations([ |
291
|
|
|
* [ |
292
|
|
|
* 'name' => 'length', |
293
|
|
|
* 'signatures' => [['List', 'Number'], ['Array', 'Number']], |
294
|
|
|
* 'fn' => 'count' |
295
|
|
|
* ], |
296
|
|
|
* [ |
297
|
|
|
* 'name' => 'length', |
298
|
|
|
* 'signatures' => [['String', 'Number']], |
299
|
|
|
* 'fn' => 'strlen' |
300
|
|
|
* ] |
301
|
|
|
* ]); //=> [ |
302
|
|
|
* [ |
303
|
|
|
* 'name' => 'length', |
304
|
|
|
* 'signatures' => [['List', 'Number'], ['Array', 'Number']], |
305
|
|
|
* 'fn' => 'count' |
306
|
|
|
* ], |
307
|
|
|
* [ |
308
|
|
|
* 'name' => 'length', |
309
|
|
|
* 'signatures' => [['String', 'Number']], |
310
|
|
|
* 'fn' => 'strlen' |
311
|
|
|
* ] |
312
|
|
|
* ] |
313
|
|
|
* |
314
|
|
|
* F\_stream_validate_operations([ |
315
|
|
|
* [ |
316
|
|
|
* 'name' => 'length', |
317
|
|
|
* 'signatures' => [['List', 'Number'], ['Array', 'Number']], |
318
|
|
|
* 'fn' => 'count' |
319
|
|
|
* ], |
320
|
|
|
* [ |
321
|
|
|
* 'name' => 'length', |
322
|
|
|
* 'signatures' => [['String', 'Number'], ['List', 'Number']], |
323
|
|
|
* 'fn' => 'strlen' |
324
|
|
|
* ] |
325
|
|
|
* ]); // throws "Stream: signatures of the operation 'length' are duplicated or ambiguous" |
326
|
|
|
* ``` |
327
|
|
|
* |
328
|
|
|
* @param array $operations |
329
|
|
|
* @return array |
330
|
|
|
*/ |
331
|
|
|
function _stream_validate_operations($operations) { |
332
|
|
|
$signatures = chain(get('signatures'), $operations); |
333
|
|
|
if (length($signatures) != length(uniqueBy(equalBy(map(init())), $signatures))) { |
334
|
|
|
_stream_throw_error('ambiguous-signatures', get('name', head($operations))); |
335
|
|
|
} |
336
|
|
|
return $operations; |
337
|
|
|
} |
338
|
|
|
|
339
|
|
|
/** |
340
|
|
|
* Applies an operation (adds a transformation) to a stream. |
341
|
|
|
* ```php |
342
|
|
|
* $operations = [ |
343
|
|
|
* F\_stream_operation('length', 'List|Array -> Number', 'count'), |
344
|
|
|
* F\_stream_operation('length', 'String -> Number', 'strlen'), |
345
|
|
|
* F\_stream_operation('map', 'Function -> List -> List', F\map()) |
346
|
|
|
* ]; |
347
|
|
|
* |
348
|
|
|
* $stream = F\_stream($operations, [1, 2, 3]); |
349
|
|
|
* |
350
|
|
|
* F\_stream_apply_operation('length', [], $stream); //=> [ |
351
|
|
|
* 'data' => [1, 2, 3], |
352
|
|
|
* 'type' => 'Number', |
353
|
|
|
* 'result' => null, |
354
|
|
|
* 'resolved' => false, |
355
|
|
|
* 'operations' => [ |
356
|
|
|
* 'length' => [ |
357
|
|
|
* [ |
358
|
|
|
* 'name' => 'length', |
359
|
|
|
* 'signatures' => [['List', 'Number'], ['Array', 'Number']], |
360
|
|
|
* 'fn' => 'count' |
361
|
|
|
* ], |
362
|
|
|
* [ |
363
|
|
|
* 'name' => 'length', |
364
|
|
|
* 'signatures' => [['String', 'Number']], |
365
|
|
|
* 'fn' => 'strlen' |
366
|
|
|
* ] |
367
|
|
|
* ], |
368
|
|
|
* 'map' => [ |
369
|
|
|
* [ |
370
|
|
|
* 'name' => 'map', |
371
|
|
|
* 'signatures' => [['Function', 'List', 'List']], |
372
|
|
|
* 'fn' => F\map() |
373
|
|
|
* ] |
374
|
|
|
* ] |
375
|
|
|
* ], |
376
|
|
|
* 'transformations' => [ |
377
|
|
|
* [ |
378
|
|
|
* 'operations' => [[ |
379
|
|
|
* 'name' => 'length', |
380
|
|
|
* 'signatures' => [['List', 'Number']], |
381
|
|
|
* 'fn' => 'count' |
382
|
|
|
* ]], |
383
|
|
|
* 'args' => [] |
384
|
|
|
* ] |
385
|
|
|
* ] |
386
|
|
|
* ] |
387
|
|
|
* |
388
|
|
|
* F\_stream_apply_operation('foo', [], $stream); // throws "Stream: call to unknown operation 'foo'" |
389
|
|
|
* F\_stream_apply_operation('length', [5], $stream); // throws "Stream: wrong arguments (Number, List) given to operation 'length'" |
390
|
|
|
* F\_stream_apply_operation('map', [], $stream); // throws "Stream: wrong arguments (List) given to operation 'map'" |
391
|
|
|
* F\_stream_apply_operation('map', [[1, 2]], $stream); // throws "Stream: wrong arguments (List, List) given to operation 'map'" |
392
|
|
|
* |
393
|
|
|
* ``` |
394
|
|
|
* |
395
|
|
|
* @signature String -> [Any] -> Stream -> Stream |
396
|
|
|
* @param string $name |
397
|
|
|
* @param array $args |
398
|
|
|
* @param array $stream |
399
|
|
|
* @return array |
400
|
|
|
*/ |
401
|
|
|
function _stream_apply_operation($name, $args, $stream) { |
402
|
|
|
$operations = getPath(['operations', $name], $stream); |
403
|
|
|
if (null == $operations) { |
404
|
|
|
_stream_throw_error('unknown-operation', $name); |
405
|
|
|
} |
406
|
|
|
|
407
|
|
|
$argsTypes = append(get('type', $stream), map(type(), $args)); |
408
|
|
|
$validOperations = filter(_stream_operation_is_applicable($argsTypes), |
409
|
|
|
chain(_f('_stream_split_operation_signatures'), $operations)); |
410
|
|
|
if (0 == length($validOperations)) { |
411
|
|
|
_stream_throw_error('wrong-operation-args', $argsTypes, $name); |
412
|
|
|
} |
413
|
|
|
|
414
|
|
|
$returnTypes = map(_f('_stream_return_type_of_operation'), $validOperations); |
415
|
|
|
$returnType = reduce(_f('_stream_merge_types'), head($returnTypes), $returnTypes); |
416
|
|
|
return _stream_make( |
417
|
|
|
get('operations', $stream), // operations |
418
|
|
|
get('data', $stream), // data |
419
|
|
|
append([ |
420
|
|
|
'operations' => $validOperations, |
421
|
|
|
'args' => $args |
422
|
|
|
], get('transformations', $stream)), // transformations |
423
|
|
|
$returnType, // type |
424
|
|
|
null, // result |
425
|
|
|
false // resolved |
426
|
|
|
); |
427
|
|
|
} |
428
|
|
|
|
429
|
|
|
/** |
430
|
|
|
* Splits an operation with multiple signatures into a list of operation; each having one signature. |
431
|
|
|
* ```php |
432
|
|
|
* F\_stream_split_operation_signatures([ |
433
|
|
|
* 'name' => 'length', |
434
|
|
|
* 'signatures' => [['List', 'Number'], ['Array', 'Number']], |
435
|
|
|
* 'fn' => 'count' |
436
|
|
|
* ]); //=> [ |
437
|
|
|
* [ |
438
|
|
|
* 'name' => 'length', |
439
|
|
|
* 'signatures' => [['List', 'Number']], |
440
|
|
|
* 'fn' => 'count' |
441
|
|
|
* ], |
442
|
|
|
* [ |
443
|
|
|
* 'name' => 'length', |
444
|
|
|
* 'signatures' => [['Array', 'Number']], |
445
|
|
|
* 'fn' => 'count' |
446
|
|
|
* ] |
447
|
|
|
* ] |
448
|
|
|
* ``` |
449
|
|
|
* |
450
|
|
|
* @signature Operation -> [Operation] |
451
|
|
|
* @param array $operation |
452
|
|
|
* @return array |
453
|
|
|
*/ |
454
|
|
|
function _stream_split_operation_signatures($operation) { |
455
|
|
|
$name = get('name', $operation); |
456
|
|
|
$fn = get('fn', $operation); |
457
|
|
|
return map(function($signature) use($name, $fn) { |
458
|
|
|
return [ |
459
|
|
|
'name' => $name, |
460
|
|
|
'fn' => $fn, |
461
|
|
|
'signatures' => [$signature] |
462
|
|
|
]; |
463
|
|
|
}, get('signatures', $operation)); |
464
|
|
|
} |
465
|
|
|
|
466
|
|
|
/** |
467
|
|
|
* Checks if the operation can be applied with the given arguments types. |
468
|
|
|
* |
469
|
|
|
* ```php |
470
|
|
|
* $isApplicable = F\_stream_operation_is_applicable(['Number', 'Number']); |
471
|
|
|
* $isApplicable(F\_stream_operation('add', 'Number -> Number -> Number', F\plus())); //=> true |
472
|
|
|
* $isApplicable(F\_stream_operation('length', 'List|Array|String -> Number', F\length())); //=> false |
473
|
|
|
* F\_stream_operation_is_applicable( |
474
|
|
|
* ['List'], |
475
|
|
|
* F\_stream_operation('length', 'List|Array|String -> Number', F\length()) |
476
|
|
|
* ); //=> true |
477
|
|
|
* F\_stream_operation_is_applicable( |
478
|
|
|
* ['String'], |
479
|
|
|
* F\_stream_operation('length', 'List|Array|String -> Number', F\length()) |
480
|
|
|
* ); //=> true |
481
|
|
|
* F\_stream_operation_is_applicable( |
482
|
|
|
* ['Number'], |
483
|
|
|
* F\_stream_operation('length', 'List|Array|String -> Number', F\length()) |
484
|
|
|
* ); //=> false |
485
|
|
|
* F\_stream_operation_is_applicable( |
486
|
|
|
* ['Number', 'String'], |
487
|
|
|
* F\_stream_operation('fill', 'Number -> Any -> List', function(){}) |
488
|
|
|
* ); //=> true |
489
|
|
|
* F\_stream_operation_is_applicable( |
490
|
|
|
* ['Any', 'String'], |
491
|
|
|
* F\_stream_operation('fill', 'Number -> Any -> List', function(){}) |
492
|
|
|
* ); //=> true |
493
|
|
|
* ``` |
494
|
|
|
* |
495
|
|
|
* @signature [String] -> Operation -> Boolean |
496
|
|
|
* @param array $argsTypes |
|
|
|
|
497
|
|
|
* @param array $operation |
|
|
|
|
498
|
|
|
* @return bool |
499
|
|
|
*/ |
500
|
|
|
function _stream_operation_is_applicable() { |
501
|
|
|
static $operationIsApplicable = false; |
502
|
|
|
$operationIsApplicable = $operationIsApplicable ?: curry(function($argsTypes, $operation) { |
503
|
|
|
return null !== findIndex(function($signature) use($argsTypes) { |
504
|
|
|
$types = init($signature); |
505
|
|
|
if (length($types) == length($argsTypes)) { |
506
|
|
|
return allSatisfies(function($pair) { |
507
|
|
|
return 'Any' == $pair[0] || 'Any' == $pair[1] || $pair[0] == $pair[1]; |
508
|
|
|
}, pairsFrom($types, $argsTypes)); |
509
|
|
|
} |
510
|
|
|
return false; |
511
|
|
|
}, get('signatures', $operation)); |
512
|
|
|
}); |
513
|
|
|
return _apply($operationIsApplicable, func_get_args()); |
514
|
|
|
} |
515
|
|
|
|
516
|
|
|
/** |
517
|
|
|
* Gets the return type of an operation having a single signature. |
518
|
|
|
* |
519
|
|
|
* ```php |
520
|
|
|
* F\_stream_return_type_of_operation(F\_stream_operation( |
521
|
|
|
* 'count', 'List -> Number' |
522
|
|
|
* )); //=> 'Number' |
523
|
|
|
* F\_stream_return_type_of_operation(F\_stream_operation( |
524
|
|
|
* 'count', 'List ->Function -> String' |
525
|
|
|
* )); //=> 'String' |
526
|
|
|
* F\_stream_return_type_of_operation(F\_stream_operation( |
527
|
|
|
* 'count', 'List ->Function -> Any' |
528
|
|
|
* )); //=> 'Any' |
529
|
|
|
* ``` |
530
|
|
|
* |
531
|
|
|
* @signature Operation -> String |
532
|
|
|
* @param array $operation |
533
|
|
|
* @return string |
534
|
|
|
*/ |
535
|
|
|
function _stream_return_type_of_operation($operation) { |
536
|
|
|
return last(getPath(['signatures', 0], $operation)); |
537
|
|
|
} |
538
|
|
|
|
539
|
|
|
/** |
540
|
|
|
* Returns `$type1` if types are equal and `Any` if not. |
541
|
|
|
* |
542
|
|
|
* ```php |
543
|
|
|
* F\_stream_merge_types('Number', 'Number'); //=> 'Number' |
544
|
|
|
* F\_stream_merge_types('Number', 'String'); //=> 'Any' |
545
|
|
|
* F\_stream_merge_types('Any', 'String'); //=> 'Any' |
546
|
|
|
* ``` |
547
|
|
|
* |
548
|
|
|
* @signature String -> String -> String |
549
|
|
|
* @param string $type1 |
550
|
|
|
* @param string $type2 |
551
|
|
|
* @return string |
552
|
|
|
*/ |
553
|
|
|
function _stream_merge_types($type1, $type2) { |
554
|
|
|
return ($type1 == $type2) |
555
|
|
|
? $type1 |
556
|
|
|
: 'Any'; |
557
|
|
|
} |
558
|
|
|
|
559
|
|
|
/** |
560
|
|
|
* Computes the result of a stream. |
561
|
|
|
* |
562
|
|
|
* ```php |
563
|
|
|
* $operations = [ |
564
|
|
|
* F\_stream_operation('length', 'List|Array -> Number', 'count'), |
565
|
|
|
* F\_stream_operation('length', 'String -> Number', 'strlen'), |
566
|
|
|
* F\_stream_operation('map', 'Function -> List -> List', F\map()), |
567
|
|
|
* F\_stream_operation('reduce', 'Function -> Any -> List -> Any', F\reduce()), |
568
|
|
|
* F\_stream_operation('increment', 'Number -> Number', F\plus(1)), |
569
|
|
|
* F\_stream_operation('upperCase', 'String -> String', F\upperCase()), |
570
|
|
|
* F\_stream_operation('toString', 'Any -> String', F\toString()), |
571
|
|
|
* F\_stream_operation('head', 'List -> Any', F\head()) |
572
|
|
|
* ]; |
573
|
|
|
* |
574
|
|
|
* $stream = F\_stream($operations, [1, 2, 3]); |
575
|
|
|
* $stream = F\_stream_apply_operation('length', [], $stream); |
576
|
|
|
* $stream = F\_stream_resolve($stream); |
577
|
|
|
* F\get('resolved', $stream); //=> true |
578
|
|
|
* F\get('result', $stream); //=> 3 |
579
|
|
|
* |
580
|
|
|
* $stream = F\_stream($operations, [1, 2, 3]); |
581
|
|
|
* $stream = F\_stream_apply_operation('map', [F\plus(2)], $stream); // [3, 4, 5] |
582
|
|
|
* $stream = F\_stream_apply_operation('reduce', [F\plus(), 0], $stream); // 12 |
583
|
|
|
* $stream = F\_stream_apply_operation('increment', [], $stream); // 13 |
584
|
|
|
* $stream = F\_stream_resolve($stream); |
585
|
|
|
* F\get('resolved', $stream); //=> true |
586
|
|
|
* F\get('result', $stream); //=> 13 |
587
|
|
|
* |
588
|
|
|
* $stream = F\_stream($operations, []); |
589
|
|
|
* $stream = F\_stream_apply_operation('head', [], $stream); // null |
590
|
|
|
* $stream = F\_stream_apply_operation('increment', [], $stream); // Error |
591
|
|
|
* $stream = F\_stream_apply_operation('toString', [], $stream); // Error |
592
|
|
|
* $stream = F\_stream_resolve( $stream); // throws "Stream: operation 'increment' could not be called with arguments types (Null); expected types are (Number)" |
593
|
|
|
* |
594
|
|
|
* ``` |
595
|
|
|
* |
596
|
|
|
* @signature Stream -> Stream |
597
|
|
|
* @param array $stream |
598
|
|
|
* @return array |
599
|
|
|
*/ |
600
|
|
|
function _stream_resolve($stream) { |
601
|
|
|
if (get('resolved', $stream)) |
602
|
|
|
return $stream; |
603
|
|
|
$transformations = get('transformations', $stream); |
604
|
|
|
$transformation = head($transformations); |
605
|
|
|
if (null === $transformation) { |
606
|
|
|
return _stream_make( |
607
|
|
|
get('operations', $stream), // operations |
608
|
|
|
get('data', $stream), // data |
609
|
|
|
get('transformations', $stream), // transformations |
610
|
|
|
get('type', $stream), // type |
611
|
|
|
get('data', $stream), // result |
612
|
|
|
true // resolved |
613
|
|
|
); |
614
|
|
|
} |
615
|
|
|
|
616
|
|
|
$args = append(get('data', $stream), get('args', $transformation)); |
617
|
|
|
$argsTypes = map(type(), $args); |
618
|
|
|
$operations = get('operations', $transformation); |
619
|
|
|
$applicableOperations = filter(_stream_operation_is_applicable($argsTypes), $operations); |
620
|
|
|
if (empty($applicableOperations)) { |
621
|
|
|
$types = map(pipe(get('signatures'), head(), init()), $operations); |
622
|
|
|
_stream_throw_error('wrong-transformation-args', getPath([0, 'name'], $operations), $argsTypes, $types); |
623
|
|
|
} |
624
|
|
|
|
625
|
|
|
return _stream_resolve(_stream_make( |
626
|
|
|
get('operations', $stream), // operations |
627
|
|
|
_apply(getPath([0, 'fn'], $applicableOperations), $args), // data |
628
|
|
|
tail($transformations), // transformations |
629
|
|
|
get('type', $stream), // type |
630
|
|
|
null, // result |
631
|
|
|
false // resolved |
632
|
|
|
)); |
633
|
|
|
} |
634
|
|
|
|
635
|
|
|
/** |
636
|
|
|
* Applies a function to a single argument. |
637
|
|
|
* To be used as the `then()` method of Stream. |
638
|
|
|
* |
639
|
|
|
* @signature (a -> b) -> a -> b |
640
|
|
|
* @param callable $fn |
641
|
|
|
* @param mixed $arg |
642
|
|
|
* @return mixed |
643
|
|
|
*/ |
644
|
|
|
function _stream_then($fn, $arg) { |
645
|
|
|
return _apply($fn, [$arg]); |
646
|
|
|
} |
647
|
|
|
|
648
|
|
|
|
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.