1before:
2  base = require "std.base"
3
4  this_module  = "std.functional"
5  global_table = "_G"
6
7  exported_apis = { "bind", "callable", "case", "collect", "compose",
8                    "cond", "curry", "eval", "filter", "fold", "foldl",
9                    "foldr", "id", "lambda", "map", "map_with",
10                    "memoize", "nop", "op", "reduce", "zip", "zip_with" }
11
12  M = require (this_module)
13
14
15specify std.functional:
16- context when required:
17  - context by name:
18    - it does not touch the global table:
19        expect (show_apis {added_to=global_table, by=this_module}).
20          to_equal {}
21  - it exports the documented apis:
22      t = {}
23      for k in pairs (M) do t[#t + 1] = k end
24      expect (t).to_contain.a_permutation_of (exported_apis)
25
26  - context via the std module:
27    - it does not touch the global table:
28        expect (show_apis {added_to=global_table, by="std"}).
29          to_equal {}
30
31
32- describe bind:
33  - before:
34      op = require "std.operator"
35
36      f = M.bind
37
38  - it writes an argument passing deprecation warning:
39      setdebug { deprecate = "nil" }
40      expect (capture (f, {nop, M, "bind"})).
41          to_contain_error "was deprecated"
42      setdebug { deprecate = false }
43      expect (capture (f, {nop, M, "bind"})).
44          not_to_contain_error "was deprecated"
45
46  - context with bad arguments:
47      badargs.diagnose (f, "std.functional.bind (function, ?any*)")
48
49  - it does not affect normal operation if no arguments are bound:
50      expect (f (math.min, {}) (2, 3, 4)).to_be (2)
51  - it takes the extra arguments into account:
52      expect (f (math.min, {1, 0}) (2, 3, 4)).to_be (0)
53  - it appends final call arguments:
54      expect (f (math.max, {2, 3}) (4, 5, 1)).to_be (5)
55  - it does not require all arguments in final call:
56      div = function (a, b) return a / b end
57      expect (f (div, {100}) (25)).to_be (4)
58  - it supports out of order extra arguments:
59      expect (f (op.pow, {[2] = 3}) (2)).to_be (8)
60  - it propagates nil arguments correctly:
61      expect ({f (M.id, {[2]="b", [4]="d"}) (nil, 3, 5, 6, nil)}).
62        to_equal {nil, "b", 3, "d", 5, 6, nil}
63  - it supports the legacy api:
64      expect (f (math.min) (2, 3, 4)).to_be (2)
65      expect (f (math.min, 1, 0) (2, 3, 4)).to_be (0)
66      expect (f (op.pow, nil, 3) (2)).to_be (8)
67
68
69- describe callable:
70  - before:
71      f = M.callable
72
73  - context with bad arguments:
74      badargs.diagnose (f, "std.functional.callable (?any)")
75
76  - it returns the function associated with a callable:
77      Container = require "std.container" { __call = M.nop }
78      for _, v in ipairs {
79        true,
80        42,
81        "str",
82        io.stderr,
83        {},
84        M.nop,
85        setmetatable ({}, {__call = M.nop}),
86        Container,
87      } do
88        expect (f (v)).to_be (pcall (v, {}) and M.nop or nil)
89      end
90  - it returns 'nil' for uncallable arguments:
91      expect (f ()).to_be (nil)
92      expect (f {}).to_be (nil)
93      expect (f "").to_be (nil)
94
95- describe case:
96  - before:
97      yes     = function ()  return true end
98      no      = function ()  return false end
99      default = function (s) return s end
100      branches = { yes = yes, no = no, default }
101
102      f = M.case
103
104  - context with bad arguments: |
105      badargs.diagnose (f, "std.functional.case (?any, #table)")
106
107  - it matches against branch keys:
108      expect (f ("yes", branches)).to_be (true)
109      expect (f ("no",  branches)).to_be (false)
110  - it has a default for unmatched keys:
111      expect (f ("none", branches)).to_be "none"
112  - it returns nil for unmatched keys with no default:
113      expect (f ("none", { yes = yes, no = no })).to_be (nil)
114  - it returns non-function matches:
115      expect (f ("t", {t = true})).to_be (true)
116  - it evaluates returned functions:
117      expect (f ("fn", {fn = function () return true end})).
118        to_be (true)
119  - it passes 'with' to function matches:
120      expect (f ("with", {function (s) return s end})).to_be "with"
121  - it evaluates returned functables:
122      functable = setmetatable ({}, {__call = function (t, with) return with end})
123      expect (f ("functable", {functable})).to_be "functable"
124  - it evaluates 'with' exactly once:
125      s = "prince"
126      function acc () s = s .. "s"; return s end
127      expect (f (acc (), {
128        prince   = function () return "one" end,
129        princes  = function () return "many" end,
130        princess = function () return "one" end,
131                   function () return "gibberish" end,
132      })).to_be "many"
133
134
135- describe collect:
136  - before:
137      f = M.collect
138
139  - context with bad arguments:
140      badargs.diagnose (f, "std.functional.collect ([func], any*)")
141
142  - it collects a list of single return value iterator results:
143      expect (f (base.ielems, {"a", "b", "c"})).to_equal {"a", "b", "c"}
144  - it collects a table of key:value iterator results:
145      t = {"first", second="two", last=3}
146      expect (f (pairs, t)).to_equal (t)
147  - it propagates nil arguments correctly:
148      expect (f {"a", nil, nil, "d", "e"}).to_equal {"a", [4]="d", [5]="e"}
149  - it defaults to npairs iteration:
150      expect (f {1, 2, [5]=5, a="b", c="d"}).to_equal {1, 2, [5]=5}
151
152
153- describe compose:
154  - before:
155      f = M.compose
156
157  - context with bad arguments:
158      badargs.diagnose (f, "std.functional.compose (func*)")
159
160  - it composes a single function correctly:
161      expect (f (M.id) (1)).to_be (1)
162  - it propagates nil arguments correctly:
163      expect ({f (M.id) (1, nil, nil, 4)}).to_equal {1, nil, nil, 4}
164      expect ({f (M.id, M.id) (1, nil, nil, 4)}).to_equal {1, nil, nil, 4}
165  - it composes functions in the correct order:
166      expect (f (math.sin, math.cos) (1)).
167        to_be (math.cos (math.sin (1)))
168
169
170- describe cond:
171  - before:
172      yes     = function ()  return true end
173      no      = function ()  return false end
174      default = function (s) return s end
175      branches = { yes = yes, no = no, default }
176
177      f = M.cond
178
179  - it returns nil for no arguments:
180      expect (f ()).to_be (nil)
181  - it evaluates a single function argument:
182      expect (f (function () return true end)).to_be (true)
183  - it evaluates a single functable argument:
184      functable = setmetatable ({}, {__call = function () return true end})
185      expect (f (functable)).to_be (true)
186  - it returns a non-callable single argument directly:
187      expect (f "foo").to_be "foo"
188  - it evaluates a branch function if expr is truthy:
189      expect (f ("truthy", function (s) return s end)).to_be "truthy"
190  - it returns nil if the last expr is falsey:
191      expect (f (nil, function (s) return "falsey" end)).to_be (nil)
192      expect (f (false, true, false, true)).to_be (nil)
193  - it recurses with remaining arguments if first argument is falsey:
194      expect (f (nil, true, 42, M.id)).to_be (42)
195      expect (f (nil, true, false, false, 42, M.id)).to_be (42)
196
197
198- describe curry:
199  - before:
200      op = require "std.operator"
201
202      f = M.curry
203
204  - context with bad arguments:
205      badargs.diagnose (f, "std.functional.curry (func, int)")
206
207  - it returns a zero argument function uncurried:
208      expect (f (f, 0)).to_be (f)
209  - it returns a one argument function uncurried:
210      expect (f (f, 1)).to_be (f)
211  - it curries a two argument function:
212      expect (f (f, 2)).not_to_be (f)
213  - it evaluates intermediate arguments one at a time:
214      expect (f (math.min, 3) (2) (3) (4)).to_equal (2)
215  - it returns a curried function that can be partially applied:
216      bin = f (op.pow, 2) (2)
217      expect (bin (2)).to_be (op.pow (2, 2))
218      expect (bin (10)).to_be (op.pow (2, 10))
219
220
221- describe eval:
222  - before:
223      f = M.eval
224
225  - it writes a deprecation warning:
226      setdebug { deprecate = "nil" }
227      expect (capture (f, {"42"})).to_contain_error "was deprecated"
228      setdebug { deprecate = false }
229      expect (capture (f, {"42"})).not_to_contain_error "was deprecated"
230
231  - it diagnoses invalid lua:
232      # Some internal error when eval tries to call uncompilable "=" code.
233      expect (f "=").to_raise ()
234  - it evaluates a string of lua code:
235      expect (f "math.min (2, 10)").to_be (math.min (2, 10))
236
237
238- describe filter:
239  - before:
240     elements = {"a", "b", "c", "d", "e"}
241     inverse  = {a=1, b=2, c=3, d=4, e=5}
242
243     f = M.filter
244
245  - context with bad arguments:
246      badargs.diagnose (f, "std.functional.filter (func, [func], any*)")
247
248  - it works with an empty table:
249      expect (f (M.id, pairs, {})).to_equal {}
250  - it iterates through element keys:
251      expect (f (M.id, base.ielems, elements)).to_equal {"a", "b", "c", "d", "e"}
252      expect (f (M.id, base.elems, inverse)).to_contain.a_permutation_of {1, 2, 3, 4, 5}
253  - it propagates nil arguments correctly:
254      t = {"a", nil, nil, "d", "e"}
255      expect (f (M.id, base.npairs, t)).to_equal (t)
256  - it passes all iteration result values to filter predicate:
257      t = {}
258      f (function (k, v) t[k] = v end, pairs, elements)
259      expect (t).to_equal (elements)
260  - it returns a list of filtered single return value iterator results:
261      expect (f (function (e) return e:match "[aeiou]" end, base.ielems, elements)).
262        to_equal {"a", "e"}
263  - it returns a table of filtered key:value iterator results:
264      t = {"first", second=2, last="three"}
265      expect (f (function (k, v) return type (v) == "string" end, pairs, t)).
266        to_equal {"first", last="three"}
267      expect (f (function (k, v) return k % 2 == 0 end, ipairs, elements)).
268        to_equal {[2]="b", [4]="d"}
269  - it defaults to pairs iteration:
270      t = {"first", second=2, last="three"}
271      expect (f (function (k, v) return type (v) == "string" end, t)).
272        to_equal {"first", last="three"}
273
274
275- describe fold:
276  - before:
277      op = require "std.operator"
278      f = M.fold
279
280  - it writes a deprecation warning:
281      setdebug { deprecate = "nil" }
282      expect (capture (f, {M.id, 1, ipairs, {}})).
283        to_contain_error "was deprecated"
284      setdebug { deprecate = false }
285      expect (capture (f, {M.id, 1, ipairs, {}})).
286        not_to_contain_error "was deprecated"
287
288  - it works with an empty table:
289      expect (f (op.sum, 2, ipairs, {})).to_be (2)
290  - it calls a binary function over single return value iterator results:
291      expect (f (op.sum, 2, base.ielems, {3})).
292        to_be (2 + 3)
293      expect (f (op.prod, 2, base.ielems, {3, 4})).
294        to_be (2 * 3 * 4)
295  - it calls a binary function over key:value iterator results:
296      expect (f (op.sum, 2, ipairs, {3})).to_be (2 + 3)
297      expect (f (op.prod, 2, ipairs, {3, 4})).to_be (2 * 3 * 4)
298  - it folds elements from left to right:
299      expect (f (op.pow, 2, ipairs, {3, 4})).to_be ((2 ^ 3) ^ 4)
300
301
302- describe foldl:
303  - before:
304      op = require "std.operator"
305      f = M.foldl
306
307  - context with bad arguments:
308      badargs.diagnose (f, "std.functional.foldl (func, [any], table)")
309
310  - it works with an empty table:
311      expect (f (op.sum, 10000, {})).to_be (10000)
312  - it folds a binary function through a table:
313      expect (f (op.sum, 10000, {1, 10, 100})).to_be (10111)
314  - it folds from left to right:
315      expect (f (op.pow, 2, {3, 4})).to_be ((2 ^ 3) ^ 4)
316  - it supports eliding init argument:
317      expect (f (op.pow, {2, 3, 4})).to_be ((2 ^ 3) ^ 4)
318
319
320- describe foldr:
321  - before:
322      op = require "std.operator"
323      f = M.foldr
324
325  - context with bad arguments:
326      badargs.diagnose (f, "std.functional.foldr (func, [any], table)")
327
328  - it works with an empty table:
329      expect (f (op.sum, 1, {})).to_be (1)
330  - it folds a binary function through a table:
331      expect (f (op.sum, {10000, 100, 10, 1})).to_be (10111)
332  - it folds from right to left:
333      expect (f (op.quot, 10, {10000, 100})).to_be (10000 / (100 / 10))
334  - it supports eliding init argument:
335      expect (f (op.quot, {10000, 100, 10})).to_be (10000 / (100 / 10))
336
337
338- describe id:
339  - before:
340      f = M.id
341  - it returns argument unchanged:
342      expect (f (true)).to_be (true)
343      expect (f {1, 1, 2, 3}).to_equal {1, 1, 2, 3}
344  - it returns multiple arguments unchanged:
345      expect ({f (1, "two", false)}).to_equal {1, "two", false}
346
347
348- describe lambda:
349  - before:
350      f = M.lambda
351
352  - context with bad arguments:
353      badargs.diagnose (f, "std.functional.lambda (string)")
354
355      examples {["it diagnoses bad lambda string"] = function ()
356        expect (select (2, f "foo")).to_be "invalid lambda string 'foo'"
357      end}
358      examples {["it diagnoses an uncompilable expression"] = function ()
359        expect (select (2, f "||+")).to_be "invalid lambda string '||+'"
360        expect (select (2, f "=")).to_be "invalid lambda string '='"
361      end}
362
363  - context with argument format:
364    - it returns a function:
365        expect (prototype (f "|x| 1+x")).to_be "function"
366    - it compiles to a working Lua function:
367        fn = f "||42"
368        expect (fn ()).to_be (42)
369    - it propagates argument values:
370        fn = f "|...| {...}"
371        expect (fn (1,2,3)).to_equal {1,2,3}
372  - context with expression format:
373    - it returns a function:
374        expect (prototype (f "_")).to_be "function"
375    - it compiles to a working Lua function:
376        fn = f "=42"
377        expect (fn ()).to_be (42)
378    - it sets auto-argument values:
379        fn = f "_*_"
380        expect (fn (42)).to_be (1764)
381    - it sets numeric auto-argument values:
382        fn = f "_1+_2+_3"
383        expect (fn (1, 2, 5)).to_be (8)
384
385
386- describe map:
387  - before:
388      elements = {"a", "b", "c", "d", "e"}
389      inverse  = {a=1, b=2, c=3, d=4, e=5}
390
391      f = M.map
392
393  - context with bad arguments:
394      badargs.diagnose (f, "std.functional.map (func, [func], any*)")
395
396  - it works with an empty table:
397      expect (f (M.id, ipairs, {})).to_equal {}
398  - it iterates through elements:
399      expect (f (M.id, ipairs, elements)).to_equal (elements)
400      expect (f (M.id, pairs, inverse)).to_contain.a_permutation_of (elements)
401  - it propagates nil arguments correctly:
402      t = {"a", nil, nil, "d", "e"}
403      expect (f (M.id, base.npairs, t)).to_equal (t)
404      t = {nil, nil, 3, 4}
405      expect (f (M.id, base.npairs, t)).to_equal (t)
406  - it passes all iteration result values to map function:
407      t = {}
408      f (function (k, v) t[k] = v end, pairs, elements)
409      expect (t).to_equal (elements)
410  - it returns a list of mapped single return value iterator results:
411      expect (f (function (e) return e:match "[aeiou]" end, base.ielems, elements)).
412        to_equal {"a", "e"}
413      expect (f (function (e) return e .. "x" end, base.elems, elements)).
414        to_contain.a_permutation_of {"ax", "bx", "cx", "dx", "ex"}
415  - it returns a table of mapped key:value iterator results:
416      t = {"first", second=2, last="three"}
417      expect (f (function (k, v) return type (v) == "string" end, pairs, t)).
418        to_contain.a_permutation_of {true, false, true}
419      expect (f (function (k, v) return k % 2 == 0 end, ipairs, elements)).
420        to_equal {false, true, false, true, false}
421  - it supports key:value results from mapping function:
422      expect (f (function (k, v) return v, k end, pairs, elements)).
423        to_equal (inverse)
424  - it defaults to pairs iteration:
425      t = {"first", second=2, last="three"}
426      expect (f (function (k, v) return type (v) == "string" end, t)).
427        to_contain.a_permutation_of {true, false, true}
428
429
430- describe map_with:
431  - before:
432      t = {{1, 2, 3}, {4, 5}}
433      fn = function (...) return select ("#", ...) end
434
435      f = M.map_with
436
437  - context with bad arguments:
438      badargs.diagnose (f, "std.functional.map_with (func, table of tables)")
439
440  - it works for an empty table:
441      expect (f (fn, {})).to_equal ({})
442  - it returns a table:
443      u = f (fn, t)
444      expect (type (u)).to_be "table"
445  - it creates a new table:
446      old = t
447      u = f (fn, t)
448      expect (t).to_equal (old)
449      expect (u).not_to_equal (old)
450      expect (t).to_equal {{1, 2, 3}, {4, 5}}
451  - it maps a function over a list of argument lists:
452      expect (f (fn, t)).to_equal {3, 2}
453  - it discards hash-part arguments:
454      expect (f (fn, {{1,x=2,3}, {4,5,y="z"}})).to_equal {2, 2}
455  - it maps a function over a table of argument lists:
456      expect (f (fn, {a={1,2,3}, b={4,5}})).to_equal {a=3, b=2}
457
458
459- describe memoize:
460  - before:
461      f = M.memoize
462
463      memfn = f (function (x)
464                   if x then return {x} else return nil, "bzzt" end
465                 end)
466
467  - context with bad arguments:
468      badargs.diagnose (f, "std.functional.memoize (func, ?func)")
469
470  - it propagates multiple return values:
471      expect (select (2, memfn (false))).to_be "bzzt"
472  - it returns the same object for the same arguments:
473      t = memfn (1)
474      expect (memfn (1)).to_be (t)
475  - it returns a different object for different arguments:
476      expect (memfn (1)).not_to_be (memfn (2))
477  - it returns the same object for table valued arguments:
478      t = memfn {1, 2, 3}
479      expect (memfn {1, 2, 3}).to_be (t)
480      t = memfn {foo = "bar", baz = "quux"}
481      expect (memfn {foo = "bar", baz = "quux"}).to_be (t)
482      expect (memfn {baz = "quux", foo = "bar"}).to_be (t)
483  - it returns a different object for different table arguments:
484      expect (memfn {1, 2, 3}).not_to_be (memfn {1, 2})
485      expect (memfn {1, 2, 3}).not_to_be (memfn {3, 1, 2})
486      expect (memfn {1, 2, 3}).not_to_be (memfn {1, 2, 3, 4})
487  - it accepts alternative normalization function:
488      normalize = function (...) return select ("#", ...) end
489      memfn = f (function (x) return {x} end, normalize)
490      expect (memfn "same").to_be (memfn "not same")
491      expect (memfn (1, 2)).to_be (memfn (false, "x"))
492      expect (memfn "one").not_to_be (memfn ("one", "two"))
493
494
495- describe nop:
496  - before:
497      f = M.nop
498  - it accepts any number of arguments:
499      expect (f ()).to_be (nil)
500      expect (f (false)).to_be (nil)
501      expect (f (1, 2, 3, nil, "str", {}, f)).to_be (nil)
502  - it returns no values:
503      expect (f (1, "two", false)).to_be (nil)
504
505
506- describe op:
507  - context with []:
508    - before:
509        f = M.op["[]"]
510
511    - it writes a deprecation warning:
512        setdebug { deprecate = "nil" }
513        expect (capture (f, {{2}, 1})).
514          to_contain_error "was deprecated"
515        setdebug { deprecate = false }
516        expect (capture (f, {{2}, 1})).
517          not_to_contain_error "was deprecated"
518
519    - it dereferences a table:
520        expect (f ({}, 1)).to_be (nil)
521        expect (f ({"foo", "bar"}, 1)).to_be "foo"
522        expect (f ({foo = "bar"}, "foo")).to_be "bar"
523
524  - context with +:
525    - before:
526        f = M.op["+"]
527
528    - it writes a deprecation warning:
529        setdebug { deprecate = "nil" }
530        expect (capture (f, {2, 1})).
531          to_contain_error "was deprecated"
532        setdebug { deprecate = false }
533        expect (capture (f, {2, 1})).
534          not_to_contain_error "was deprecated"
535
536    - it returns the sum of its arguments:
537        expect (f (99, 2)).to_be (99 + 2)
538
539  - context with -:
540    - before:
541        f = M.op["-"]
542
543    - it writes a deprecation warning:
544        setdebug { deprecate = "nil" }
545        expect (capture (f, {2, 1})).
546          to_contain_error "was deprecated"
547        setdebug { deprecate = false }
548        expect (capture (f, {2, 1})).
549          not_to_contain_error "was deprecated"
550
551    - it returns the difference of its arguments:
552        expect (f (99, 2)).to_be (99 - 2)
553
554  - context with *:
555    - before:
556        f = M.op["*"]
557
558    - it writes a deprecation warning:
559        setdebug { deprecate = "nil" }
560        expect (capture (f, {2, 1})).
561          to_contain_error "was deprecated"
562        setdebug { deprecate = false }
563        expect (capture (f, {2, 1})).
564          not_to_contain_error "was deprecated"
565
566    - it returns the product of its arguments:
567        expect (f (99, 2)).to_be (99 * 2)
568
569  - context with /:
570    - before:
571        f = M.op["/"]
572
573    - it writes a deprecation warning on:
574        setdebug { deprecate = "nil" }
575        expect (capture (f, {2, 1})).
576          to_contain_error "was deprecated"
577        setdebug { deprecate = false }
578        expect (capture (f, {2, 1})).
579          not_to_contain_error "was deprecated"
580
581    - it returns the quotient of its arguments:
582        expect (f (99, 2)).to_be (99 / 2)
583
584  - context with and:
585    - before:
586        f = M.op["and"]
587
588    - it writes a deprecation warning:
589        setdebug { deprecate = "nil" }
590        expect (capture (f, {true, false})).
591          to_contain_error "was deprecated"
592        setdebug { deprecate = false }
593        expect (capture (f, {true, false})).
594          not_to_contain_error "was deprecated"
595
596    - it returns the logical and of its arguments:
597        expect (f (false, false)).to_be (false)
598        expect (f (false, true)).to_be (false)
599        expect (f (true, false)).to_be (false)
600        expect (f (true, true)).to_be (true)
601    - it supports truthy and falsey arguments:
602        expect (f ()).to_be (nil)
603        expect (f (0)).to_be (nil)
604        expect (f (nil, 0)).to_be (nil)
605        expect (f (0, "false")).to_be ("false")
606
607  - context with or:
608    - before:
609        f = M.op["or"]
610
611    - it writes a deprecation warning:
612        setdebug { deprecate = "nil" }
613        expect (capture (f, {true, false})).
614          to_contain_error "was deprecated"
615        setdebug { deprecate = false }
616        expect (capture (f, {true, false})).
617          not_to_contain_error "was deprecated"
618
619    - it returns the logical or of its arguments:
620        expect (f (false, false)).to_be (false)
621        expect (f (false, true)).to_be (true)
622        expect (f (true, false)).to_be (true)
623        expect (f (true, true)).to_be (true)
624    - it supports truthy and falsey arguments:
625        expect (f ()).to_be (nil)
626        expect (f (0)).to_be (0)
627        expect (f (nil, 0)).to_be (0)
628        expect (f (0, "false")).to_be (0)
629
630  - context with not:
631    - before:
632        f = M.op["not"]
633
634    - it writes a deprecation warning:
635        setdebug { deprecate = "nil" }
636        expect (capture (f, {true})).
637          to_contain_error "was deprecated"
638        setdebug { deprecate = false }
639        expect (capture (f, {true})).
640          not_to_contain_error "was deprecated"
641
642    - it returns the logical not of its argument:
643        expect (f (false)).to_be (true)
644        expect (f (true)).to_be (false)
645    - it supports truthy and falsey arguments:
646        expect (f ()).to_be (true)
647        expect (f (0)).to_be (false)
648
649  - context with ==:
650    - before:
651        f = M.op["=="]
652
653    - it writes a deprecation warning:
654        setdebug { deprecate = "nil" }
655        expect (capture (f, {2, 1})).
656          to_contain_error "was deprecated"
657        setdebug { deprecate = false }
658        expect (capture (f, {2, 1})).
659          not_to_contain_error "was deprecated"
660
661    - it returns true if the arguments are equal:
662        expect (f ()).to_be (true)
663        expect (f ("foo", "foo")).to_be (true)
664    - it returns false if the arguments are unequal:
665        expect (f (1)).to_be (false)
666        expect (f ("foo", "bar")).to_be (false)
667
668  - context with ~=:
669    - before:
670        f = M.op["~="]
671
672    - it writes a deprecation warning:
673        setdebug { deprecate = "nil" }
674        expect (capture (f, {2, 1})).
675          to_contain_error "was deprecated"
676        setdebug { deprecate = false }
677        expect (capture (f, {2, 1})).
678          not_to_contain_error "was deprecated"
679
680    - it returns false if the arguments are equal:
681        expect (f (1, 1)).to_be (false)
682        expect (f ("foo", "foo")).to_be (false)
683    - it returns true if the arguments are unequal:
684        expect (f (1, 2)).to_be (true)
685        expect (f ("foo", "bar")).to_be (true)
686        expect (f ({}, {})).to_be (true)
687
688
689- describe reduce:
690  - before:
691      op = require "std.operator"
692
693      f = M.reduce
694
695  - context with bad arguments:
696      badargs.diagnose (f, "std.functional.reduce (func, any, [func], any*)")
697
698  - it works with an empty table:
699      expect (f (op.sum, 2, ipairs, {})).to_be (2)
700  - it calls a binary function over single return value iterator results:
701      expect (f (op.sum, 2, base.ielems, {3})).
702        to_be (2 + 3)
703      expect (f (op.prod, 2, base.ielems, {3, 4})).
704        to_be (2 * 3 * 4)
705  - it calls a binary function over key:value iterator results:
706      expect (f (op.sum, 2, base.ielems, {3})).to_be (2 + 3)
707      expect (f (op.prod, 2, base.ielems, {3, 4})).to_be (2 * 3 * 4)
708  - it propagates nil arguments correctly:
709      function set (t, k, v) t[k] = tostring (v) return t end
710      expect (f (set, {}, base.npairs, {1, nil, nil, "a", false})).
711        to_equal {"1", "nil", "nil", "a", "false"}
712      expect (f (set, {}, base.npairs, {nil, nil, "3"})).
713        to_equal {"nil", "nil", "3"}
714  - it reduces elements from left to right:
715      expect (f (op.pow, 2, base.ielems, {3, 4})).to_be ((2 ^ 3) ^ 4)
716  - it passes all iterator results to accumulator function:
717      expect (f (rawset, {}, {"one", two=5})).to_equal {"one", two=5}
718
719
720- describe zip:
721  - before:
722      tt = {{1, 2}, {3, 4}, {5, 6}}
723
724      f = M.zip
725
726  - context with bad arguments:
727      badargs.diagnose (f, "std.functional.zip (table)")
728
729  - it works for an empty table:
730      expect (f {}).to_equal {}
731  - it is the inverse of itself:
732      expect (f (f (tt))).to_equal (tt)
733  - it transposes rows and columns:
734      expect (f (tt)).to_equal {{1, 3, 5}, {2, 4, 6}}
735      expect (f {x={a=1, b=2}, y={a=3, b=4}, z={b=5}}).
736        to_equal {a={x=1, y=3}, b={x=2,y=4,z=5}}
737
738
739- describe zip_with:
740  - before:
741      tt = {{1, 2}, {3, 4}, {5}}
742      fn = function (...) return tonumber (table.concat {...}) end
743
744      f = M.zip_with
745
746  - context with bad arguments:
747      badargs.diagnose (f, "std.functional.zip_with (function, table of tables)")
748
749  - it works for an empty table:
750      expect (f (fn, {})).to_equal {}
751  - it returns a table:
752      expect (type (f (fn, tt))).to_be "table"
753  - it returns the result in a new table:
754      expect (f (fn, tt)).not_to_be (tt)
755  - it does not perturb the argument list:
756      m = f (fn, tt)
757      expect (tt).to_equal {{1, 2}, {3, 4}, {5}}
758  - it combines column entries with a function:
759      expect (f (fn, tt)).to_equal {135, 24}
760  - it discards hash-part arguments:
761      expect (f (fn, {{1,2}, x={3,4}, {[2]=5}})).to_equal {1, 25}
762  - it combines matching key entries with a function:
763      expect (f (fn, {{a=1,b=2}, {a=3,b=4}, {b=5}})).
764        to_equal {a=13, b=245}
765