[
  {
    "path": ".gitattributes",
    "content": "build/ export-ignore\ntests/ export-ignore\nvendor/ export-ignore\n.gitattributes export-ignore\n.gitignore export-ignore\n.travis.yml export-ignore\nCHANGELOG.md export-ignore\nMakefile export-ignore\nphpunit.xml.dist export-ignore\nREADME.rst export-ignore\n"
  },
  {
    "path": ".gitignore",
    "content": "phpunit.xml\ncomposer.lock\nvendor/\nartifacts/\ndocs/_build\n.idea\n.DS_STORE\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: php\n\nphp:\n  - 5.5\n  - 5.6\n  - hhvm\n\nbefore_script:\n  - composer install\n\nscript: make test\n\nmatrix:\n  allow_failures:\n    - php: hhvm\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# CHANGELOG\n\n## 0.3.0 - 2014-01-12\n\n* Updated `transducers\\comp()` to work for any type of variadic function\n  composition.\n\n## 0.2.0 - 2014-12-07\n\n* Renamed `transducers\\seq()` to `transducers\\xform()`.\n* Renamed `transducers\\vec()` to `transducers\\to_traversable()`.\n* Renamed `transducers\\is_iterable()` to `transducers\\is_traversable()`.\n* Added `transducers\\to_fn()` so that transducers can be used with existing\n  reduce functions like `array_reduce`.\n\n## 0.1.0 - 2014-12-03\n\nInitial release.\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2014 Michael Dowling, https://github.com/mtdowling <mtdowling@gmail.com>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": "all: clean test\n\ntest:\n\tvendor/bin/phpunit\n\ncoverage:\n\tvendor/bin/phpunit --coverage-html=artifacts/coverage\n\nview-coverage:\n\topen artifacts/coverage/index.html\n\nclean:\n\trm -rf artifacts/*\n\n.PHONY: coverage\n"
  },
  {
    "path": "README.rst",
    "content": "===============\ntransducers-php\n===============\n\n.. image:: https://badges.gitter.im/Join Chat.svg\n        :target: https://gitter.im/mtdowling/transducers.php?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge\n\n`Transducers <http://clojure.org/transducers>`_ are composable algorithmic\ntransformations. They are independent from the context of their input and\noutput sources and specify only the essence of the transformation in terms of\nan individual element. Because transducers are decoupled from input or output\nsources, they can be used in many different processes - collections, streams,\nchannels, observables, etc. Transducers compose directly, without awareness of\ninput or creation of intermediate aggregates.\n\nFor more information about Clojure transducers and transducer semantics see the\nintroductory `blog post <http://blog.cognitect.com/blog/2014/8/6/transducers-are-coming>`_\nand this `video <https://www.youtube.com/watch?v=6mTbuzafcII>`_.\n\nYou can transduce anything that you can iterate over in a foreach-loop (e.g.,\narrays, ``\\Iterator``, ``Traversable``, ``Generator``, etc.). Transducers can\nbe applied **eagerly** using ``transduce()``, ``into()``, ``to_array()``,\n``to_assoc()``, ``to_string()``; and **lazily** using ``to_iter()``,\n``xform()``, or by applying a transducer stream filter.\n\n::\n\n    composer.phar require mtdowling/transducers\n\n\nDefining Transformations With Transducers\n-----------------------------------------\n\nTransducers compose with ordinary function composition. A transducer performs\nits operation before deciding whether and how many times to call the transducer\nit wraps. You can easily compose transducers to create transducer pipelines.\nThe recommended way to compose transducers is with the ``transducers\\comp()``\nfunction:\n\n.. code-block:: php\n\n    use Transducers as t;\n\n    $xf = t\\comp(\n        t\\drop(2),\n        t\\map(function ($x) { return $x + 1; }),\n        t\\filter(function ($x) { return $x % 2; }),\n        t\\take(3)\n    );\n\nThe above composed transducer is a function that creates a pipeline for\ntransforming data: it skips the first two elements of a collection,\nadds 1 to each value, filters out even numbers, then takes 3 elements from the\ncollection. This new transformation function can be used with various\ntransducer application functions, including ``xform()``.\n\n.. code-block:: php\n\n    $data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];\n    $result = t\\xform($data, $xf);\n\n    // Contains: [5, 7, 9]\n\n\nTransducers\n-----------\n\nTransducers are functions that return a function that accept a reducing\nfunction array ``$xf`` and return a new reducing function array that wraps the\nprovided ``$xf``.\n\nHere's how to create a transducer that adds ``$n`` to each value:\n\n.. code-block:: php\n\n    $inc = function ($n = 1) {\n        // Return a function that accepts a reducing function array $xf.\n        return function (array $xf) use ($n) {\n            // Return a new reducing function array that wraps $xf.\n            return [\n                'init'   => $xf['init'],\n                'result' => $xf['result'],\n                'step'   => function ($result, $input) use ($xf, $n) {\n                    return $xf['step']($result, $input + $n);\n                }\n            ];\n        }\n    };\n\n    $result = t\\xform([1, 2, 3], $inc(1));\n    // Contains: 2, 3, 4\n\n.. _reducing-link:\n\n\nReducing Function Array\n-----------------------\n\nReducing function arrays are PHP associative arrays that contain a 'init',\n'step', and 'result' key that maps to a function.\n\n+--------+-------------------------+------------------------------------------+\n|   key  |        arguments        |                  Description             |\n+========+=========================+==========================================+\n|  init  |           none          | Invoked to initialize a transducer. This |\n|        |                         | function should call the 'init' function |\n|        |                         | on the nested reducing function array    |\n|        |                         | ``$xf``, which will eventually call out  |\n|        |                         | to the transducing process. This function|\n|        |                         | is only called when an initial value is  |\n|        |                         | not provided while transducing.          |\n+--------+-------------------------+------------------------------------------+\n|  step  | ``$result``, ``$input`` | This is a standard reduction function    |\n|        |                         | but it is expected to call the           |\n|        |                         | ``$xf['step']`` function 0 or more       |\n|        |                         | times as appropriate in the transducer.  |\n|        |                         | For example, ``filter`` will choose      |\n|        |                         | (based on the predicate) whether to call |\n|        |                         | ``$xf`` or not. ``map`` will always call |\n|        |                         | it exactly once. ``cat`` may call it     |\n|        |                         | many times depending on the inputs.      |\n+--------+-------------------------+------------------------------------------+\n| result |       ``$result``       | Some processes will not end, but for     |\n|        |                         | those that do (like transduce), the      |\n|        |                         | 'result' function is used to produce     |\n|        |                         | a final value and/or flush state. This   |\n|        |                         | function must call the ``$xf['result']`` |\n|        |                         | function exactly once.                   |\n+--------+-------------------------+------------------------------------------+\n\n\nUsing Transducers\n-----------------\n\nTransducers can be used in any number of ways. This library provides several\nmethods that can be used to apply transducers.\n\n\ntransduce()\n~~~~~~~~~~~\n\n``function transduce(callable $xf, array $step, $coll, $init = null)``\n\nTransform and reduce $coll by applying $xf($step)['step'] to each value.\n\n- ``callable $xf``: Transducer function to apply.\n- ``array $step``: Transformer array that has 'init', 'result', and 'step' keys\n  that map to a callable.\n- ``$coll``: Data to transform. Can be an array, iterator, or PHP stream\n  resource.\n- ``$init``: Optional first initialization value of the reduction. If this\n  value is not provided, the ``$step['init']()`` function will be called to\n  provide a default value.\n\n.. code-block:: php\n\n    use Transducers as t;\n\n    $data = [[1, 2], [3, 4]];\n    $xf = t\\comp(\n        t\\flatten(),\n        t\\filter(function ($value) { return $value % 2; }),\n    );\n    $result = t\\transduce($xf, t\\array_reducer(), $data);\n\n    // Contains: [1, 3]\n\nWhen using this function, you can use any of the built-in reducing function\narrays as the ``$step`` argument:\n\n- ``transducers\\array_reducer()``: Creates a reducing function array that\n  appends values to an array.\n\n  .. code-block:: php\n\n      $data = [[1, 2], [3, 4]];\n      $result = t\\transduce(t\\flatten(), t\\array_reducer(), $data);\n\n      // Results contains [1, 2, 3, 4]\n\n- ``transducers\\stream_reducer()``: Creates a reducing function array that\n  writes values to a stream resource. If no ``$init`` value is provided when\n  transducing then a PHP temp stream will be used.\n\n  .. code-block:: php\n\n      $data = [[1, 2], [3, 4]];\n      $result = t\\transduce(t\\flatten(), t\\stream_reducer(), $data);\n      fseek($result, 0);\n      echo stream_get_contents($result);\n      // Outputs: 1234\n\n- ``transducers\\string_reducer()``: Creates a reducing function array that\n  concatenates each value to a string.\n\n  .. code-block:: php\n\n      $xf = t\\flatten();\n      // use an optional joiner on the string reducer.\n      $reducer = t\\string_reducer('|');\n      $data = [[1, 2], [3, 4]];\n      $result = t\\transduce($xf, $reducer, $data);\n\n      // Result is '1|2|3|4'\n\n- ``transducers\\assoc_reducer()``: Creates a reducing function array that adds\n  key value pairs to an associative array. Each value must be an array that\n  contains the array key in the first element and the array value in the second\n  element.\n\n- ``transducers\\create_reducer()``: Convenience function that can be used to\n  quickly create reducing function arrays. The first and only required argument\n  is a step function that takes the accumulated result and the new value and\n  returns a single result. The next, optional, argument is the init function\n  that takes no arguments an returns an initialized result. The next, optional,\n  argument is the result function which takes a single result argument and is\n  expected to return a final result.\n\n  .. code-block:: php\n\n      $result = t\\transduce(\n          t\\flatten(),\n          t\\create_reducer(function ($r, $x) { return $r + $x; }),\n          [[1, 2], [3, 4]]\n      );\n\n      // Result is equal to 10\n\n- ``transducers\\operator_reducer()``: Creates a reducing function array that\n  uses the provided infix operator to reduce the collection (i.e.,\n  $result <operator> $input). Supports: '.', '+', '-', '*', and '/' operators.\n\n  .. code-block:: php\n\n      $result = t\\transduce(\n          t\\flatten()\n          t\\operator_reducer('+'),\n          [[1, 2], [[3], 4]]\n      );\n\n      // Result is equal to 10\n\n\nxform()\n~~~~~~~\n\n``function xform($coll, callable $xf)``\n\nReturns the same data type passed in as ``$coll`` with ``$xf`` applied.\n\n``xform()`` using the following logic when returning values:\n\n- ``array``: Returns an array using the provided array.\n- ``associative array``: Turn the provided array into an indexed array, meaning\n  that each value passed to the ``step`` reduce function is an array where\n  the first element is the key and the second element is the value. When\n  completed, ``xform()`` returns an associative array.\n- ``\\Iterator``: Returns an iterator in which ``$xf`` is applied lazily.\n- ``resource``: Reads single bytes from the provided value and returns a new\n  fopen resource that contains the bytes from the input resource after applying\n  ``$xf``.\n- ``string``: Passes each character from the string through to each step\n  function and returns a string.\n\n.. code-block:: php\n\n    // Give an array and get back an array\n    $result = t\\xform([1, false, 3], t\\compact());\n    assert($result === [1, 3]);\n\n    // Give an iterator and get back an iterator\n    $result = t\\xform(new ArrayIterator([1, false, 3]), t\\compact());\n    assert($result instanceof \\Iterator);\n\n    // Give a stream and get back a stream.\n    $stream = fopen('php://temp', 'w+');\n    fwrite($stream, '012304');\n    rewind($stream);\n    $result = t\\xform($stream, t\\compact());\n    assert($result == '1234');\n\n    // Give a string and get back a string\n    $result = t\\xform('abc', t\\map(function ($v) { return strtoupper($v); }));\n    assert($result === 'abc');\n\n    // Give an associative array and get back an associative array.\n    $data = ['a' => 1, 'b' => 2];\n    $result = t\\xform('abc', t\\map(function ($v) {\n        return [strtoupper($v[0]), $v[1]];\n    }));\n    assert($result === ['A' => 1, 'B' => 2]);\n\n\ninto()\n~~~~~~\n\n``function into($target, $coll, callable $xf)``\n\nTransduces items from ``$coll`` into the given ``$target``, in essence\n\"pouring\" transformed data from one source into another data type.\n\nThis function does not attempt to discern between arrays and associative\narrays. Any array or ArrayAccess object provided will be treated as an\nindexed array. When a string is provided, each value will be concatenated to\nthe end of the string with no separator. When an fopen resource is provided,\ndata will be written to the end of the stream with no separator between\nwrites.\n\n.. code-block:: php\n\n    use Transducers as t;\n\n    // Compose a transducer function.\n    $transducer = t\\comp(\n        // Remove a single level of nesting.\n        'transducers\\cat',\n        // Filter out even values.\n        t\\filter(function ($value) { return $value % 2; }),\n        // Multiply each value by 2\n        t\\map(function ($value) { return $value * 2; }),\n        // Immediately stop when the value is >= 15.\n        t\\take_while(function($value) { return $value < 15; })\n    );\n\n    $data = [[1, 2, 3], [4, 5], [6], [], [7], [8, 9, 10, 11]];\n\n    // Eagerly pour the transformed data, [2, 6, 10, 14], into an array.\n    $result = t\\into([], $data, $transducer);\n\n\nto_iter()\n~~~~~~~~~\n\n``function to_iter($coll, callable $xf)``\n\nCreates an iterator that **lazily** applies the transducer ``$xf`` to the\n``$input`` iterator. Use this function when dealing with large amounts of data\nor when you want operations to occur only as needed.\n\n.. code-block:: php\n\n    // Generator that yields incrementing numbers.\n    $forever = function () {\n        $i = 0;\n        while (true) {\n            yield $i++;\n        }\n    };\n\n    // Create a transducer that multiplies each value by two and takes\n    // ony 100 values.\n    $xf = t\\comp(\n        t\\map(function ($value) { return $value * 2; }),\n        t\\take(100)\n    );\n\n    foreach (t\\to_iter($forever(), $xf) as $value) {\n        echo $value;\n    }\n\n\nto_array()\n~~~~~~~~~~\n\n``function to_array($iterable, callable $xf)``\n\nConverts a value to an array and applies a transducer function. ``$iterable``\nis passed through ``to_traversable()`` in order to convert the input value into\nan array.\n\n.. code-block:: php\n\n    .. code-block:: php\n\n    $result = t\\to_array(\n        'abc',\n        t\\map(function ($v) { return strtoupper($v); })\n    );\n\n    // Contains: ['A', 'B', 'C']\n\n\nto_assoc()\n~~~~~~~~~~\n\n``function to_assoc($iterable, callable $xf)``\n\nCreates an associative array using the provided input while applying\n``$xf`` to each value. Values are converted to arrays that contain the\narray key in the first element and the array value in the second.\n\n.. code-block:: php\n\n    $result = t\\to_assoc(\n        ['a' => 1, 'b' => 2],\n        t\\map(function ($v) { return [$v[0], $v[1] + 1]; })\n    );\n\n    assert($result == ['a' => 2, 'b' => 3]);\n\n\nto_string()\n~~~~~~~~~~~\n\n``function to_string($iterable, callable $xf)``\n\nConverts a value to a string and applies a transducer function to each\ncharacter. ``$iterable`` is passed through ``to_traversable()`` in order to\nconvert the input value into an array.\n\n.. code-block:: php\n\n    echo t\\to_string(\n        ['a', 'b', 'c'],\n        t\\map(function ($v) { return strtoupper($v); })\n    );\n\n    // Outputs: ABC\n\n\nto_fn()\n~~~~~~~\n\n``function to_fn(callable $xf, callable|array $builder = null)``\n\nConvert a transducer into a function that can be used with existing reduce\nimplementations (e.g., array_reduce).\n\n.. code-block:: php\n\n    $xf = t\\map(function ($x) { return $x + 1; });\n    $fn = t\\to_fn($xf); // $builder is optional\n    $result = array_reduce([1, 2, 3], $fn);\n    assert($result == [2, 3, 4]);\n\n    $fn = t\\to_fn($xf, t\\string_reducer());\n    $result = array_reduce([1, 2, 3], $fn);\n    assert($result == '234');\n\n\nStream Filter\n~~~~~~~~~~~~~\n\nYou can apply transducers to PHP streams using a `stream filter <http://php.net/manual/en/stream.filters.php>`_.\nThis library registers a ``transducers`` stream filter that can be appended or\nprepended to a PHP stream using the ``transducers\\append_stream_filter()`` or\n``transducers\\prepend_stream_filter()`` functions.\n\n.. code-block:: php\n\n    use transducers as t;\n\n    $f = fopen('php://temp', 'w+');\n    fwrite($f, 'testing. Can you hear me?');\n    rewind($f);\n\n    $xf = t\\comp(\n        // Split by words\n        t\\words(),\n        // Uppercase/lowercase every other word.\n        t\\keep_indexed(function ($i, $v) {\n            return $i % 2 ? strtoupper($v) : strtolower($v);\n        }),\n        // Combine words back together into a string separated by ' '.\n        t\\interpose(' ')\n    );\n\n    // Apply a transducer stream filter.\n    $filter = t\\append_stream_filter($f, $xf, STREAM_FILTER_READ);\n    echo stream_get_contents($f);\n    // Be sure to remove the filter to flush out any buffers.\n    stream_filter_remove($filter);\n    echo stream_get_contents($f);\n\n    fclose($f);\n\n    // Echoes: \"testing. CAN you HEAR me?\"\n\n\nAvailable Transducers\n---------------------\n\n\nmap()\n~~~~~\n\n``function map(callable $f)``\n\nApplies a map function ``$f`` to each value in a collection.\n\n.. code-block:: php\n\n    $data = ['a', 'b', 'c'];\n    $xf = t\\map(function ($value) { return strtoupper($value); });\n    assert(t\\xform($data, $xf) == ['A', 'B', 'C']);\n\n\nfilter()\n~~~~~~~~\n\n``function filter(callable $pred)``\n\nFilters values that do not satisfy the predicate function ``$pred``.\n\n.. code-block:: php\n\n    $data = [1, 2, 3, 4];\n    $odd = function ($value) { return $value % 2; };\n    $result = t\\xform($data, t\\filter($odd));\n    assert($result == [1, 3]);\n\n\nremove()\n~~~~~~~~\n\n``function remove(callable $pred)``\n\nRemoves anything from a sequence that satisfied ``$pred``.\n\n.. code-block:: php\n\n    $data = [1, 2, 3, 4];\n    $odd = function ($value) { return $value % 2; };\n    $result = t\\xform($data, t\\remove($odd));\n    assert($result == [2, 4]);\n\n\ncat()\n~~~~~\n\n``function cat()``\n\nTransducer that concatenates items from nested lists. Note that ``cat()`` is\nused differently than other transducers: you use cat using the string value of\nthe function name (i.e., ``'transducers\\cat'``);\n\n.. code-block:: php\n\n    $xf = 'transducers\\cat';\n    $data = [[1, 2], [3], [], [4, 5]];\n    $result = t\\xform($data, $xf);\n    assert($result == [1, 2, 3, 4, 5]);\n\n\nmapcat()\n~~~~~~~~\n\n``function mapcat(callable $f)``\n\nApplies a map function to a collection and concats them into one less level of\nnesting.\n\n.. code-block:: php\n\n    $data = [[1, 2], [3], [], [4, 5]];\n    $xf = t\\mapcat(function ($value) { return array_sum($value); });\n    $result = t\\xform($data, $xf);\n    assert($result == [3, 3, 0, 9]);\n\n\nflatten()\n~~~~~~~~~\n\n``function flatten()``\n\nTakes any nested combination of sequential things and returns their contents as\na single, flat sequence.\n\n.. code-block:: php\n\n    $data = [[1, 2], 3, [4, new ArrayObject([5, 6])]];\n    $xf = t\\flatten();\n    $result = t\\to_array($data, $xf);\n    assert($result == [1, 2, 3, 4, 5, 6]);\n\n\npartition()\n~~~~~~~~~~~\n\n``function partition($size)``\n\nPartitions the source into arrays of size ``$size``. When the reducing function\narray completes, the array will be stepped with any remaining items.\n\n.. code-block:: php\n\n    $data = [1, 2, 3, 4, 5];\n    $result = t\\xform($data, t\\partition(2));\n    assert($result == [[1, 2], [3, 4], [5]]);\n\n\npartition_by()\n~~~~~~~~~~~~~~\n\n``function partition_by(callable $pred)``\n\nSplit inputs into lists by starting a new list each time the predicate passed\nin evaluates to a different condition (true/false) than what holds for the\npresent list.\n\n.. code-block:: php\n\n    $data = [['a', 1], ['a', 2], [2, 3], ['c', 4]];\n    $xf = t\\partition_by(function ($v) { return is_string($v[0]); });\n    $result = t\\into([], $data, $xf);\n\n    assert($result == [\n        [['a', 1], ['a', 2]],\n        [[2, 3]],\n        [['c', 4]]\n    ]);\n\n\ntake()\n~~~~~~\n\n``function take($n);``\n\nTakes ``$n`` number of values from a collection.\n\n.. code-block:: php\n\n    $data = [1, 2, 3, 4, 5];\n    $result = t\\xform($data, t\\take(2));\n    assert($result == [1, 2]);\n\n\ntake_while()\n~~~~~~~~~~~~\n\n``function take_while(callable $pred)``\n\nTakes from a collection while the predicate function ``$pred`` returns true.\n\n.. code-block:: php\n\n    $data = [1, 2, 3, 4, 5];\n    $xf = t\\take_while(function ($value) { return $value < 4; });\n    $result = t\\xform($data, $xf);\n    assert($result == [1, 2, 3]);\n\n\ntake_nth()\n~~~~~~~~~~\n\n``function take_nth($nth)``\n\nTakes every nth item from a sequence of values.\n\n.. code-block:: php\n\n    $data = [1, 2, 3, 4, 5, 6];\n    $result = t\\xform($data, t\\take_nth(2));\n    assert($result == [1, 3, 5]);\n\ndrop()\n~~~~~~\n\n``function drop($n)``\n\nDrops ``$n`` items from the beginning of the input sequence.\n\n.. code-block:: php\n\n    $data = [1, 2, 3, 4, 5];\n    $result = t\\xform($data, t\\drop(2));\n    assert($result == [3, 4, 5]);\n\n\ndrop_while()\n~~~~~~~~~~~~\n\n``function drop_while(callable $pred)``\n\nDrops values from a sequence so long as the predicate function ``$pred``\nreturns true.\n\n.. code-block:: php\n\n    $data = [1, 2, 3, 4, 5];\n    $xf = t\\drop_while(function ($value) { return $value < 3; });\n    $result = t\\xform($data, $xf);\n    assert($result == [3, 4, 5]);\n\n\nreplace()\n~~~~~~~~~\n\n``function replace(array $smap)``\n\nGiven a map of replacement pairs and a collection, returns a sequence where any\nelements equal to a key in ``$smap`` are replaced with the corresponding\n``$smap`` value.\n\n.. code-block:: php\n\n    $data = ['hi', 'there', 'guy', '!'];\n    $xf = t\\replace(['hi' => 'You', '!' => '?']);\n    $result = t\\xform($data, $xf);\n    assert($result == ['You', 'there', 'guy', '?']);\n\n\nkeep()\n~~~~~~\n\n``function keep(callable $f)``\n\nKeeps ``$f`` items for which ``$f`` does not return null.\n\n.. code-block:: php\n\n    $result = t\\xform(\n        [0, false, null, true],\n        t\\keep(function ($value) { return $value; })\n    );\n\n    assert($result == [0, false, true]);\n\n\nkeep_indexed()\n~~~~~~~~~~~~~~\n\n``function keep_indexed(callable $f)``\n\nReturns a sequence of the non-null results of ``$f($index, $input)``.\n\n.. code-block:: php\n\n    $result = t\\xform(\n        [0, false, null, true],\n        t\\keep_indexed(function ($index, $input) {\n            echo $index . ':' . json_encode($input) . ', ';\n            return $input;\n        })\n    );\n\n    assert($result == [0, false, true]);\n\n    // Will echo: 0:0, 1:false, 2:null, 3:true,\n\n\ndedupe()\n~~~~~~~~\n\n``function dedupe()``\n\nRemoves duplicates that occur in order (keeping the first in a sequence of\nduplicate values).\n\n.. code-block:: php\n\n    $result = t\\xform(\n        ['a', 'b', 'b', 'c', 'c', 'c', 'b'],\n        t\\dedupe()\n    );\n\n    assert($result == ['a', 'b', 'c', 'b']);\n\n\ninterpose()\n~~~~~~~~~~~\n\n``function interpose($separator)``\n\nAdds a separator between each item in the sequence.\n\n.. code-block:: php\n\n    $result = t\\xform(['a', 'b', 'c'], t\\interpose('-'));\n    assert($result == ['a', '-', 'b', '-', 'c']);\n\n\ntap()\n~~~~~\n\n``function tap(callable $interceptor)``\n\nInvokes interceptor with each result and item, and then steps through\nunchanged.\n\nThe primary purpose of this method is to \"tap into\" a method chain, in order\nto perform operations on intermediate results within the chain. Executes\ninterceptor with current result and item.\n\n.. code-block:: php\n\n    // echo each value as it passes through the tap function.\n    $tap = t\\tap(function ($r, $x) { echo $x . ', '; });\n\n    t\\xform(\n        ['a', 'b', 'c'],\n        t\\comp(\n            $tap,\n            t\\map(function ($v) { return strtoupper($v); }),\n            $tap\n        )\n    );\n\n    // Prints: a, A, b, B, c, C,\n\n\ncompact()\n~~~~~~~~~\n\n``function compact()``\n\nTrim out all falsey values.\n\n.. code-block:: php\n\n    $result = t\\xform(['a', true, false, 'b', 0], t\\compact());\n    assert($result == ['a', true, 'b']);\n\n\nwords()\n~~~~~~~\n\n``function words($maxBuffer = 4096)``\n\nSplits the input by words. You can provide an optional max buffer length that\nwill ensure the buffer size used to find words is never exceeded. The default\nmax buffer length is 4096. To use an unbounded buffer, provide ``INF``.\n\n.. code-block:: php\n\n    $xf = t\\words();\n    $data = ['Hi. This is a test.'];\n    $result = t\\xform($data, $xf);\n    assert($result == ['Hi.', 'This', 'is', 'a', 'test.']);\n\n    $data = ['Hi. ', 'This is',  ' a test.'];\n    $result = t\\xform($data, $xf);\n    assert($result == ['Hi.', 'This', 'is', 'a', 'test.']);\n\n\nlines()\n~~~~~~~\n\n``function lines($maxBuffer = 10240000)``\n\nSplits the input by lines. You can provide an optional max buffer length that\nwill ensure the buffer size used to find lines is never exceeded. The default\nmax buffer length is 10MB. To use an unbounded buffer, provide ``INF``.\n\n.. code-block:: php\n\n    $xf = t\\lines();\n    $data = [\"Hi.\\nThis is a test.\"];\n    $result = t\\xform($data, $xf);\n    assert($result == ['Hi.', 'This is a test.']);\n\n    $data = [\"Hi.\\n\", 'This is',  ' a test.', \"\\nHear me?\"];\n    $result = t\\xform($data, $xf);\n    assert($result == ['Hi.', 'This is a test.', 'Hear me?']);\n\n\nUtility Functions\n-----------------\n\n\nidentity()\n~~~~~~~~~~\n\n``function indentity($value)``\n\nReturns the provided value. This is useful for writing reducing function arrays\nthat do not need to modify an 'init' or 'result' function. In these cases, you\ncan simply use the string ``'transducers\\identity'`` as the 'init' or 'result'\nfunction to continue to proxy to further reducers.\n\n\nassoc_iter()\n~~~~~~~~~~~~\n\n``function assoc_iter($iterable)``\n\nConverts an iterable into an indexed array iterator where each value yielded\nis an array containing the key followed by the value.\n\n.. code-block:: php\n\n    $data = ['a' => 1, 'b' => 2];\n    assert(t\\assoc_iter($data) == [['a', 1], ['b', 2]];\n\nThis can be combined with the ``assoc_reducer()`` to generate associative\narrays.\n\n.. code-block:: php\n\n    $result = t\\transduce(\n        t\\map(function ($v) { return [$v[0], $v[1] + 1]; }),\n        t\\assoc(),\n        t\\assoc_iter(['a' => 1, 'b' => 2])\n    );\n\n    assert($result == ['a' => 2, 'b' => 3]);\n\nYou should really just use the ``t\\to_assoc()`` function if you know you're\nreducing an associative array.\n\n.. code-block:: php\n\n    $result = t\\to_assoc(\n        ['a' => 1, 'b' => 2],\n        t\\map(function ($v) { return [$v[0], $v[1] + 1]; })\n    );\n\n    assert($result == ['a' => 2, 'b' => 3]);\n\n\nstream_iter()\n~~~~~~~~~~~~~\n\n``function stream_iter($stream, $size = 1)``\n\nCreates an iterator that reads from a stream using the given ``$size`` argument.\n\n.. code-block:: php\n\n    $s = fopen('php://temp', 'w+');\n    fwrite($s, 'foo');\n    rewind($s);\n\n    // outputs: foo\n    foreach (t\\stream_iter($s) as $char) {\n        echo $char;\n    }\n\n    rewind($s);\n\n    // outputs: fo-o\n    foreach (t\\stream_iter($s, 2) as $char) {\n        echo $char . '-';\n    }\n\n\nto_traversable()\n~~~~~~~~~~~~~~~~\n\n``function to_traversable($value)``\n\nConverts an input value into something this is traversable (e.g., an array or\n``\\Iterator``). This function accepts arrays, ``\\Traversable``, PHP streams,\nand strings. Arrays pass through unchanged. Associative arrays are returned as\niterators that yield arrays where each value is an array that contains the key\nof the array in the first element and the value of the array in the second\nelement. Iterators are returned as-is. Strings are split by character using\n``str_split()``. PHP streams are converted into iterators that yield a single\nbyte at a time.\n\n\nis_traversable()\n~~~~~~~~~~~~~~~~\n\n``function is_traversable($coll)``\n\nReturns true if the provided $coll is something that can be traversed in a\nforeach loop. This function treats arrays, instances of ``\\Traversable``, and\n``stdClass`` as iterable.\n\n\nreduce()\n~~~~~~~~\n\n``function reduce(callable $fn, $coll, $accum = null)``\n\nReduces the given iterable using the provided reduce function $fn. The\nreduction is short-circuited if $fn returns an instance of Reduced.\n"
  },
  {
    "path": "composer.json",
    "content": "{\n  \"name\": \"mtdowling/transducers\",\n  \"license\": \"MIT\",\n  \"authors\": [\n    {\n      \"name\": \"Michael Dowling\",\n      \"email\": \"mtdowling@gmail.com\",\n      \"homepage\": \"https://github.com/mtdowling\"\n    }\n  ],\n  \"require\": {\n    \"php\": \">=5.5.0\"\n  },\n  \"require-dev\": {\n    \"phpunit/phpunit\": \"~4.0\"\n  },\n  \"autoload\": {\n    \"files\": [\"src/transducers.php\"]\n  }\n}\n"
  },
  {
    "path": "phpunit.xml.dist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit bootstrap=\"./vendor/autoload.php\"\n         colors=\"true\">\n    <testsuites>\n        <testsuite>\n            <directory>tests</directory>\n        </testsuite>\n    </testsuites>\n    <filter>\n        <whitelist>\n            <directory suffix=\".php\">src</directory>\n        </whitelist>\n    </filter>\n</phpunit>\n"
  },
  {
    "path": "src/transducers.php",
    "content": "<?php\nnamespace transducers;\n\n/**\n * Lazily applies the transducer $xf to the $input iterator.\n *\n * @param mixed    $iterable Iterable input to transform.\n * @param callable $xf       Transducer to apply.\n *\n * @return \\Iterator Returns an iterator that lazily applies transformations.\n */\nfunction to_iter($iterable, callable $xf)\n{\n    $items = [];\n    $reducer = $xf([\n        'init'   => 'Transducers\\identity',\n        'result' => 'Transducers\\identity',\n        'step'   => function ($result, $input) use (&$items) {\n            $items[] = $input;\n            return $result;\n        }\n    ]);\n\n    $result = $reducer['init']();\n\n    foreach ($iterable as $input) {\n        $result = $reducer['step']($result, $input);\n        // Yield each queued value from the step function.\n        while ($items) {\n            yield array_shift($items);\n        }\n        // Break early if a Reduced is found.\n        if ($result instanceof Reduced) {\n            break;\n        }\n    }\n\n    // Allow reducers to step on the final result.\n    $reducer['result']($result);\n\n    while ($items) {\n        yield array_shift($items);\n    }\n}\n\n/**\n * Converts a value to an array using a transducer function.\n *\n * @param mixed    $coll Value to convert.\n * @param callable $xf   Transducer to apply.\n *\n * @return array\n * @throws \\InvalidArgumentException\n */\nfunction to_array($coll, callable $xf)\n{\n    return transduce($xf, array_reducer(), to_traversable($coll), []);\n}\n\n/**\n * Converts a value to an associative array using a transducer function.\n *\n * Do not provide an indexed array (i.e., [[0, 1], [1, 1], [2, 2]]) as this\n * function will do that for you. Note that values yielded through each\n * transducer will be an array where element 0 is the associative array key and\n * element 1 is the associative array value.\n *\n * @param mixed    $coll Value to convert.\n * @param callable $xf   Transducer to apply.\n *\n * @return array Returns an associative array.\n * @throws \\InvalidArgumentException\n */\nfunction to_assoc($coll, callable $xf)\n{\n    return transduce($xf, assoc_reducer(), assoc_iter($coll), []);\n}\n\n/**\n * Reduces a value to a string by concatenating each step value to a string.\n *\n * @param mixed    $coll Value to convert.\n * @param callable $xf   Transducer to apply.\n *\n * @return string\n * @throws \\InvalidArgumentException\n */\nfunction to_string($coll, callable $xf)\n{\n    return transduce($xf, string_reducer(), to_traversable($coll), '');\n}\n\n/**\n * Transduces items from $coll into the given $target, in essence \"pouring\"\n * transformed data from one source into another data type.\n *\n * This function does not attempt to discern between arrays and associative\n * arrays. Any array or ArrayAccess object provided will be treated as an\n * indexed array. When a string is provided, each value will be concatenated to\n * the end of the string with no separator. When an fopen resource is provided,\n * data will be written to the end of the stream with no separator between\n * writes.\n *\n * @param array|\\ArrayAccess|resource|string $target Where items are appended.\n * @param mixed                              $coll   Sequence of data\n * @param callable                           $xf     Transducer function.\n *\n * @return mixed\n * @throws \\InvalidArgumentException\n */\nfunction into($target, $coll, callable $xf)\n{\n    if (is_array($target) || $target instanceof \\ArrayAccess) {\n        return transduce($xf, array_reducer(), $coll, $target);\n    } elseif (is_resource($target)) {\n        return transduce($xf, stream_reducer(), $coll, $target);\n    } elseif (is_string($target)) {\n        return transduce($xf, string_reducer(), $coll, $target);\n    }\n\n    throw type_error('into', $coll);\n}\n\n/**\n * Returns the same data type passed in as $coll with $xf applied.\n *\n * This function will turn associative arrays into a stream of arrays that\n * contain the array key in the first element and values in the second element.\n *\n * @param array|\\Iterator|resource $coll Data to transform.\n * @param callable                 $xf   Transducer to apply.\n * @return mixed\n * @throws \\InvalidArgumentException\n */\nfunction xform($coll, callable $xf)\n{\n    if (is_array($coll)) {\n        reset($coll);\n        return key($coll) === 0\n            ? transduce($xf, array_reducer(), $coll, [])\n            : transduce($xf, assoc_reducer(), assoc_iter($coll), []);\n    } elseif ($coll instanceof \\Iterator) {\n        return to_iter($coll, $xf);\n    } elseif (is_resource($coll)) {\n        register_stream_filter();\n        stream_filter_append($coll, 'transducer', STREAM_FILTER_READ, $xf);\n        return $coll;\n    } elseif (is_string($coll)) {\n        return transduce($xf, string_reducer(), str_split($coll));\n    }\n\n    throw type_error('xform', $coll);\n}\n\n/**\n * Transform and reduce $coll by applying $xf($step)['step'] to each value.\n *\n * Returns the result of applying the transformed $xf to 'init' and the first\n * item in the $coll, then applying $xf to that result and the second item,\n * etc. If $coll contains no items, returns init and $f is not called.\n *\n * @param callable $xf   Transducer function.\n * @param array    $step Transformation array that contains an 'init', 'result',\n *                       and 'step' keys mapping to functions.\n * @param mixed    $coll The iterable collection to transform.\n * @param mixed    $init The first initialization value of the reduction.\n *\n * @return mixed\n */\nfunction transduce(callable $xf, array $step, $coll, $init = null)\n{\n    if ($init === null) {\n        $init = $step['init']();\n    }\n\n    $reducer = $xf($step);\n    return $reducer['result'](reduce($reducer['step'], $coll, $init));\n}\n\n/**\n * Convert a transducer into a function that can be used with existing reduce\n * implementations (e.g., array_reduce).\n *\n * @param callable       $xf      Transducer\n * @param callable|array $builder Reducing function array or a step function\n *                                that takes an accumulator value and the next\n *                                input and returns a new accumulator value. If\n *                                none is provided, an array_reducer is used.\n * @return mixed\n */\nfunction to_fn(callable $xf, $builder = null)\n{\n    if (!$builder) {\n        $builder = array_reducer();\n    } elseif (is_callable($builder)) {\n        $builder = create_reducer($builder);\n    }\n\n    return $xf($builder)['step'];\n}\n\n//-----------------------------------------------------------------------------\n// Transducers\n//-----------------------------------------------------------------------------\n\n/**\n * Applies a map function $f to each value in a collection.\n *\n * @param callable $f Map function to apply.\n *\n * @return callable\n */\nfunction map(callable $f)\n{\n    return function (array $xf) use ($f) {\n        return [\n            'init'   => $xf['init'],\n            'result' => $xf['result'],\n            'step'   => function ($result, $input) use ($xf, $f) {\n                return $xf['step']($result, $f($input));\n            }\n        ];\n    };\n}\n\n/**\n * Filters values that do not satisfy the predicate function $pred.\n *\n * @param callable $pred Function that accepts a value and returns true/false\n *\n * @return callable\n */\nfunction filter(callable $pred)\n{\n    return function (array $xf) use ($pred) {\n        return [\n            'init'   => $xf['init'],\n            'result' => $xf['result'],\n            'step'   => function ($result, $input) use ($pred, $xf) {\n                return $pred($input)\n                    ? $xf['step']($result, $input)\n                    : $result;\n            },\n        ];\n    };\n}\n\n/**\n * Removes anything from a sequence that satisfied $pred\n *\n * @param callable $pred Function that accepts a value and returns true/false\n *\n * @return callable\n */\nfunction remove(callable $pred)\n{\n    return filter(function ($x) use ($pred) { return !($pred($x)); });\n}\n\n/**\n * Concatenates items from nested lists.\n *\n * @param array $xf Reducing function array.\n *\n * @return callable\n */\nfunction cat(array $xf)\n{\n    return [\n        'init'   => $xf['init'],\n        'result' => $xf['result'],\n        'step'   => function ($result, $input) use ($xf) {\n            if (!is_traversable($input)) {\n                return $xf['step']($result, $input);\n            }\n            foreach ($input as $value) {\n                $result = $xf['step']($result, $value);\n            }\n            return $result;\n        }\n    ];\n}\n\n/**\n * Applies a map function to a collection and cats them into one less level of\n * nesting.\n *\n * @param callable $f Map function\n *\n * @return callable\n */\nfunction mapcat(callable $f)\n{\n    return comp(map($f), 'transducers\\cat');\n}\n\n/**\n * Takes any nested combination of sequential things and returns their contents\n * as a single, flat sequence.\n *\n * @return callable\n */\nfunction flatten()\n{\n    return function (array $xf) {\n        return [\n            'init'   => $xf['init'],\n            'result' => $xf['result'],\n            'step'   => function ($result, $input) use ($xf) {\n                if (!is_traversable($input)) {\n                    return $xf['step']($result, $input);\n                }\n                $it = new \\RecursiveIteratorIterator(new \\RecursiveArrayIterator($input));\n                foreach ($it as $value) {\n                    $result = $xf['step']($result, $value);\n                }\n                return $result;\n            }\n        ];\n    };\n}\n\n/**\n * Partitions the input sequence into partitions of the specified size.\n *\n * @param int $size Size to make each partition (except possibly the last chunk)\n *\n * @return callable\n */\nfunction partition($size)\n{\n    return function (array $xf) use ($size) {\n        $buffer = [];\n        return [\n            'init' => $xf['init'],\n            'result' => function ($result) use (&$buffer, $xf) {\n                if ($buffer) {\n                    $result = unreduced($xf['step']($result, $buffer));\n                }\n                return $xf['result']($result);\n            },\n            'step' => function ($result, $input) use ($xf, &$buffer, $size) {\n                $buffer[] = $input;\n                if (count($buffer) == $size) {\n                    $result = $xf['step']($result, $buffer);\n                    $buffer = [];\n                    return $result;\n                }\n                return $result;\n            }\n        ];\n    };\n}\n\n/**\n * Split inputs into lists by starting a new list each time the predicate\n * passed in evaluates to a different condition (true/false) than what holds\n * for the present list.\n *\n * @param callable $pred Function that returns a new value to partition by.\n *\n * @return callable\n */\nfunction partition_by(callable $pred)\n{\n    return function (array $xf) use ($pred) {\n        $ctx = [];\n        return [\n            'init' => $xf['init'],\n            'result' => function ($result) use (&$ctx, $xf) {\n                // Add any pending elements.\n                if (!empty($ctx['buffer'])) {\n                    $result = unreduced($xf['step']($result, $ctx['buffer']));\n                }\n                return $xf['result']($result);\n            },\n            'step' => function ($result, $input) use ($xf, &$ctx, $pred) {\n                $test = $pred($input);\n                if (!$ctx) {\n                    $ctx['last'] = $test;\n                    $ctx['buffer'] = [$input];\n                } elseif ($ctx['last'] !== $test) {\n                    $ctx['last'] = $test;\n                    if (!empty($ctx['buffer'])) {\n                        $buffer = $ctx['buffer'];\n                        $ctx['buffer'] = [$input];\n                        return $xf['step']($result, $buffer);\n                    }\n                } else {\n                    $ctx['buffer'][] = $input;\n                }\n                return $result;\n            }\n        ];\n    };\n}\n\n/**\n * Takes $n number of values from a collection.\n *\n * @param int $n Number of value to take\n *\n * @return callable\n */\nfunction take($n)\n{\n    return function (array $xf) use ($n) {\n        $remaining = $n;\n        return [\n            'init'   => $xf['init'],\n            'result' => $xf['result'],\n            'step'   => function ($r, $input) use (&$remaining, $xf) {\n                $r = $xf['step']($r, $input);\n                return --$remaining > 0 ? $r : ensure_reduced($r);\n            }\n        ];\n    };\n}\n\n/**\n * Takes from a collection while the predicate function $pred returns true.\n *\n * @param callable $pred Function that accepts a value and returns true/false\n *\n * @return callable\n */\nfunction take_while(callable $pred)\n{\n    return function (array $xf) use ($pred) {\n        return [\n            'init'   => $xf['init'],\n            'result' => $xf['result'],\n            'step'   => function ($result, $input) use ($pred, $xf) {\n                return $pred($input)\n                    ? $xf['step']($result, $input)\n                    : ensure_reduced($result);\n            }\n        ];\n    };\n}\n\n/**\n * Takes every nth item from a sequence of values.\n *\n * @param int $nth The nth value to take\n *\n * @return callable\n */\nfunction take_nth($nth)\n{\n    return function (array $xf) use ($nth) {\n        $i = 0;\n        return [\n            'init'   => $xf['init'],\n            'result' => $xf['result'],\n            'step'   => function ($result, $input) use ($xf, &$i, $nth) {\n                return $i++ % $nth\n                    ? $result\n                    : $xf['step']($result, $input);\n            }\n        ];\n    };\n}\n\n/**\n * Drops $n items from the beginning of the input sequence.\n *\n * @param int $n Number of items to drop\n *\n * @return callable\n */\nfunction drop($n)\n{\n    return function (array $xf) use ($n) {\n        $remaining = $n;\n        return [\n            'init'   => $xf['init'],\n            'result' => $xf['result'],\n            'step'   => function ($result, $input) use ($xf, &$remaining) {\n                return $remaining-- > 0\n                    ? $result\n                    : $xf['step']($result, $input);\n            }\n        ];\n    };\n}\n\n/**\n * Drops values from a sequence so long as the predicate function $pred\n * returns true.\n *\n * @param callable $pred Predicate that accepts a value and returns true/false\n *\n * @return callable\n */\nfunction drop_while(callable $pred)\n{\n    return function (array $xf) use ($pred) {\n        $trigger = false;\n        return [\n            'init'   => $xf['init'],\n            'result' => $xf['result'],\n            'step'   => function ($result, $input) use ($xf, $pred, &$trigger) {\n                if ($trigger) {\n                    // No longer dropping.\n                    return $xf['step']($result, $input);\n                } elseif (!$pred($input)) {\n                    // Predicate failed so stop dropping.\n                    $trigger = true;\n                    return $xf['step']($result, $input);\n                }\n                // Currently dropping\n                return $result;\n            }\n        ];\n    };\n}\n\n/**\n * Given a map of replacement pairs and a collection, returns a sequence where\n * any elements equal to a key in $smap are replaced with the corresponding\n * $smap value.\n *\n * @param array $smap Search term mapping to a replacement value.\n *\n * @return callable\n */\nfunction replace(array $smap)\n{\n    return function (array $xf) use ($smap) {\n        return [\n            'init'   => $xf['init'],\n            'result' => $xf['result'],\n            'step'   => function ($result, $input) use ($xf, $smap) {\n                return isset($smap[$input])\n                    ? $xf['step']($result, $smap[$input])\n                    : $xf['step']($result, $input);\n            }\n        ];\n    };\n}\n\n/**\n * Keeps $f items for which $f does not return null.\n *\n * @param callable $f Function that accepts a value and returns null|mixed.\n *\n * @return callable\n */\nfunction keep(callable $f)\n{\n    return function (array $xf) use ($f) {\n        return [\n            'init'   => $xf['init'],\n            'result' => $xf['result'],\n            'step'   => function ($result, $input) use ($xf, $f) {\n                $value = $f($input);\n                return $value !== null\n                    ? $xf['step']($result, $value)\n                    : $result;\n            }\n        ];\n    };\n}\n\n/**\n * Returns a sequence of the non-null results of $f($index, $input).\n *\n * @param callable $f Function that accepts an index and an item and returns\n *                    a value. Anything other than null is kept.\n * @return callable\n */\nfunction keep_indexed(callable $f)\n{\n    return function (array $xf) use ($f) {\n        $idx = 0;\n        return [\n            'init'   => $xf['init'],\n            'result' => $xf['result'],\n            'step'   => function ($result, $input) use ($xf, $f, &$idx) {\n                $value = $f($idx++, $input);\n                return $value !== null\n                    ? $xf['step']($result, $value)\n                    : $result;\n            }\n        ];\n    };\n}\n\n/**\n * Removes duplicates that occur in order (keeping the first in a sequence of\n * duplicate values).\n *\n * @return callable\n */\nfunction dedupe()\n{\n    return function (array $xf) {\n        $outer = [];\n        return [\n            'init'   => $xf['init'],\n            'result' => $xf['result'],\n            'step'   => function ($result, $input) use ($xf, &$outer) {\n                if (!array_key_exists('prev', $outer)\n                    || $outer['prev'] !== $input\n                ) {\n                    $outer['prev'] = $input;\n                    return $xf['step']($result, $input);\n                }\n                return $result;\n            }\n        ];\n    };\n}\n\n/**\n * Adds a separator between each item in the sequence.\n *\n * @param mixed $separator Separator to interpose\n *\n * @return callable\n */\nfunction interpose($separator)\n{\n    return function (array $xf) use ($separator) {\n        $triggered = 0;\n        return [\n            'init' => $xf['init'],\n            'result' => $xf['result'],\n            'step' => function ($result, $input)\n                use ($xf, $separator, &$triggered) {\n                if (!$triggered) {\n                    $triggered = true;\n                    return $xf['step']($result, $input);\n                }\n                return $xf['step']($xf['step']($result, $separator), $input);\n            }\n        ];\n    };\n}\n\n/**\n * Trim out all falsey values.\n *\n * @return callable\n */\nfunction compact()\n{\n    return function (array $xf) {\n        return [\n            'init'   => $xf['init'],\n            'result' => $xf['result'],\n            'step'   => function ($result, $input) use ($xf) {\n                return $input ? $xf['step']($result, $input) : $result;\n            }\n        ];\n    };\n}\n\n/**\n * Invokes interceptor with each result and item, and then steps through\n * unchanged.\n *\n * The primary purpose of this method is to \"tap into\" a method chain, in order\n * to perform operations on intermediate results within the chain. Executes\n * interceptor with current result and item.\n *\n * @param callable $interceptor\n *\n * @return callable\n */\nfunction tap(callable $interceptor)\n{\n    return function (array $xf) use ($interceptor) {\n        return [\n            'init'   => $xf['init'],\n            'result' => $xf['result'],\n            'step'   => function ($result, $input) use ($xf, $interceptor) {\n                $interceptor($result, $input);\n                return $xf['step']($result, $input);\n            }\n        ];\n    };\n}\n\n/**\n * Splits the input each time a character is matched. Will only buffer up to\n * $maxBuffer before flushing.\n *\n * @param array $chars     Characters to split on.\n * @param int   $maxBuffer Maximum buffer size. Defaults to 10MB.\n *\n * @return callable\n */\nfunction split(array $chars, $maxBuffer = 10240000)\n{\n    $chars = array_fill_keys($chars, true);\n    return function (array $xf) use ($chars, $maxBuffer) {\n        $buffer = '';\n        return [\n            'init'   => $xf['init'],\n            'result' => function ($result) use (&$buffer, $xf) {\n                if (strlen($buffer)) {\n                    $result = unreduced($xf['step']($result, $buffer));\n                }\n                return $xf['result']($result);\n            },\n            'step' => function ($result, $input) use ($xf, $chars, $maxBuffer, &$buffer) {\n                $input = (string) $input;\n                for ($i = 0, $t = strlen($input); $i < $t; $i++) {\n                    $c = $input[$i];\n                    if (!isset($chars[$c])) {\n                        $buffer .= $c;\n                    }\n                    if (isset($chars[$c]) || strlen($buffer) >= $maxBuffer) {\n                        $data = $buffer;\n                        $buffer = '';\n                        $result = $xf['step']($result, $data);\n                    }\n                }\n                return $result;\n            }\n        ];\n    };\n}\n\n/**\n * Splits the input by lines, and does not buffer more than $maxBuffer.\n *\n * @param int    $maxBuffer Maximum buffer size. Defaults to 10MB.\n *\n * @return callable\n */\nfunction lines($maxBuffer = 10240000)\n{\n    return split([PHP_EOL], $maxBuffer);\n}\n\n/**\n * Splits inputs by words and does not buffer more than $maxBuffer before\n * flushing.\n *\n * @param int $maxBuffer Maximum buffer size. Defaults to 4096.\n *\n * @return callable\n */\nfunction words($maxBuffer = 4096)\n{\n    static $boundary;\n    if (!$boundary) {\n        $boundary = [' ', \"\\f\", \"\\n\", \"\\r\", \"\\t\", \"\\v\",\n            json_decode('\\u00A0'),\n            json_decode('\\u2028'),\n            json_decode('\\u2029')\n        ];\n    }\n    return split($boundary, $maxBuffer);\n}\n\n//-----------------------------------------------------------------------------\n// Reducers\n//-----------------------------------------------------------------------------\n\n/**\n * Creates a reducing function array that appends values to an array or object\n * that implements {@see ArrayAccess}.\n *\n * @return array Returns a reducing function array.\n */\nfunction array_reducer()\n{\n    return [\n        'init'   => function () { return []; },\n        'result' => 'Transducers\\identity',\n        'step'   => function ($result, $input) {\n            $result[] = $input;\n            return $result;\n        }\n    ];\n}\n\n/**\n * Creates a hash map reducing function array that merges values into an\n * associative array.\n *\n * This reducer assumes that the provided value is an array where the key is\n * in the first index and the value is in the second index.\n *\n * @return array Returns a reducing function array.\n */\nfunction assoc_reducer()\n{\n    return [\n        'init'   => function () { return []; },\n        'result' => 'Transducers\\identity',\n        'step'   => function ($result, $input) {\n            $result[$input[0]] = $input[1];\n            return $result;\n        }\n    ];\n}\n\n/**\n * Creates a stream reducing function array for PHP stream resources.\n *\n * @return array Returns a reducing function array.\n */\nfunction stream_reducer()\n{\n    return [\n        'init'   => function () { return fopen('php://temp', 'w+'); },\n        'result' => 'Transducers\\identity',\n        'step' => function ($result, $input) {\n            fwrite($result, $input);\n            return $result;\n        }\n    ];\n}\n\n/**\n * Creates a string reducing function array that concatenates values into a\n * string.\n *\n * @param string $joiner Optional string to concatenate between each value.\n *\n * @return array Returns a reducing function array.\n */\nfunction string_reducer($joiner = '')\n{\n    return [\n        'init'   => function () { return ''; },\n        'result' => 'Transducers\\identity',\n        'step'   => function ($r, $x) use ($joiner) {\n            return $r . $joiner . $x;\n        }\n    ];\n}\n\n/**\n * Creates a reducing function array that uses the provided infix operator to\n * reduce the collection (i.e., $result <operator> $input).\n *\n * Supports: '.', '+', '-', '*', and '/' operators.\n *\n * @param string $operator Infix operator to use.\n *\n * @return array Returns a reducing function array.\n */\nfunction operator_reducer($operator)\n{\n    static $reducers;\n    if (!$reducers) {\n        $reducers = [\n            '.'  => function ($r, $x) { return $r . $x; },\n            '+'  => function ($r, $x) { return $r + $x; },\n            '-'  => function ($r, $x) { return $r - $x; },\n            '*'  => function ($r, $x) { return $r * $x; },\n            '/'  => function ($r, $x) { return $r / $x; }\n        ];\n    }\n\n    if (!isset($reducers[$operator])) {\n        throw new \\InvalidArgumentException(\"A reducer is not defined for {$operator}\");\n    }\n\n    return [\n        'init'   => 'Transducers\\identity',\n        'result' => 'Transducers\\identity',\n        'step'   => $reducers[$operator]\n    ];\n}\n\n/**\n * Convenience function for creating a reducing function array.\n *\n * @param callable $step   Step function that accepts $accum, $input and\n *                         returns a new reduced value.\n * @param callable $init   Optional init function invoked with no argument to\n *                         initialize the reducing function.\n * @param callable $result Optional result function invoked with a single\n *                         argument that is expected to return a result.\n *\n * @return array Returns a reducing function array.\n */\nfunction create_reducer(callable $step, callable $init = null, callable $result = null)\n{\n    return [\n        'init'   => $init ?: function () {},\n        'result' => $result ?: 'Transducers\\identity',\n        'step'   => $step\n    ];\n}\n\n//-----------------------------------------------------------------------------\n// Utility functions\n//-----------------------------------------------------------------------------\n\n/**\n * Composes the provided variadic function arguments into a single function.\n *\n *     comp($f, $g) // returns $f($g(x))\n *\n * Passing a single function will return the passed function. Passing no\n * functions will return an identity function. Passing two or more functions\n * will return a function that accepts variadic arguments for the last\n * function, and the result of this function is passed to the second to last\n * function and so on.\n *\n * @return callable\n */\nfunction comp()\n{\n    $fns = func_get_args();\n    if (!$fns) {\n        // Passing no values will return an identity function.\n        return 'transducers\\\\identity';\n    } elseif (!isset($fns[1])) {\n        // Passing a single function will return the function passed.\n        return $fns[0];\n    }\n\n    /** @var callable $fn */\n    $fn = array_pop($fns);\n    $total = count($fns);\n    return function ($a = null, $b = null) use ($fn, $fns, $total) {\n        $passed = func_num_args();\n        if ($passed === 1) {\n            $value = $fn($a);\n        } elseif ($passed === 2) {\n            $value = $fn($a, $b);\n        } elseif ($passed === 0) {\n            $value = $fn();\n        } else {\n            $value = call_user_func_array($fn, func_get_args());\n        }\n        $i = $total;\n        while (--$i > -1) {\n            $value = $fns[$i]($value);\n        }\n        return $value;\n    };\n}\n\n/**\n * Reduces the given iterable using the provided reduce function $fn. The\n * reduction is short-circuited if $fn returns an instance of Reduced.\n *\n * @param callable $fn    Reduce function.\n * @param mixed    $coll  Iterable data to transform.\n * @param mixed    $accum Initial accumulated value.\n * @return mixed Returns the reduced value\n */\nfunction reduce(callable $fn, $coll, $accum = null)\n{\n    foreach ($coll as $input) {\n        $accum = $fn($accum, $input);\n        if ($accum instanceof Reduced) {\n            return $accum->value;\n        }\n    }\n\n    return $accum;\n}\n\n/**\n * Converts a value into a sequence of data that can be foreach'ed\n *\n * When provided an indexed array, the array is returned as-is. When provided\n * an associative array, an iterator is returned where each value is an array\n * containing the [key, value]. When a stream is provided, an iterator is\n * returned that yields bytes from the stream. When an iterator is provided,\n * it is returned as-is. To force an iterator to be an indexed iterator, you\n * must use the assoc_iter() function.\n *\n * @param array|\\Iterator|resource $value Data to convert to a sequence.\n *\n * @return array|\\Iterator\n * @throws \\InvalidArgumentException\n */\nfunction to_traversable($value)\n{\n    switch (gettype($value)) {\n        case 'array':\n            reset($value);\n            return key($value) === 0 ? $value : assoc_iter($value);\n        case 'object':\n            if ($value instanceof \\Traversable || $value instanceof \\stdClass) {\n                return $value;\n            }\n            break;\n        case 'string': return str_split($value);\n        case 'resource': return stream_iter($value);\n    }\n    throw type_error('to_traversable', $value);\n}\n\n/**\n * Returns true if the provided $coll is something that can be iterated in a\n * foreach loop.\n *\n * This function treats arrays, instances of \\Traversable, and stdClass as\n * iterable.\n *\n * @param mixed $value\n *\n * @return bool\n */\nfunction is_traversable($value)\n{\n    return is_array($value)\n        || $value instanceof \\Traversable\n        || $value instanceof \\stdClass;\n}\n\n/**\n * Returns the provided Reduced or wraps the value in a Reduced.\n *\n * @param mixed|Reduced $r Value to ensure is reduced.\n *\n * @return Reduced\n */\nfunction ensure_reduced($r)\n{\n    return $r instanceof Reduced ? $r : new Reduced($r);\n}\n\n/**\n * Unwraps a reduced variable if necessary.\n *\n * @param mixed|Reduced $r Value to unwrap if needed.\n *\n * @return mixed\n */\nfunction unreduced($r)\n{\n    return $r instanceof Reduced ? $r->value : $r;\n}\n\n/**\n * Returns the provided value.\n *\n * @param mixed $value Value to return\n *\n * @return mixed\n */\nfunction identity($value = null)\n{\n    return $value;\n}\n\n/**\n * Converts an iterable into an indexed array iterator where each value yielded\n * is an array containing the key followed by the value.\n *\n * @param mixed $iterable Value to convert to an indexed iterator\n *\n * @return \\Iterator\n */\nfunction assoc_iter($iterable)\n{\n    foreach ($iterable as $key => $value) {\n        yield [$key, $value];\n    }\n}\n\n/**\n * Creates an iterator that reads from a stream.\n *\n * @param resource $stream fopen() resource.\n * @param int      $size   Number of bytes to read for each read. Defaults to 1.\n *\n * @return \\Iterator\n */\nfunction stream_iter($stream, $size = 1)\n{\n    while (!feof($stream)) {\n        yield fread($stream, $size);\n    }\n}\n\n/**\n * @param string $name Name of the function that was called.\n * @param mixed  $coll Data that was provided.\n *\n * @return \\InvalidArgumentException\n */\nfunction type_error($name, $coll)\n{\n    if (is_object($coll)) {\n        $desc = get_class($coll);\n    } else {\n        ob_start();\n        var_dump($coll);\n        $desc = ob_get_clean();\n    }\n    return new \\InvalidArgumentException(\"Do not know how to $name $desc\");\n}\n\n//-----------------------------------------------------------------------------\n// Utility classes\n//-----------------------------------------------------------------------------\n\nclass Reduced\n{\n    public $value;\n\n    public function __construct($value)\n    {\n        $this->value = $value;\n    }\n}\n\n//-----------------------------------------------------------------------------\n// Streams\n//-----------------------------------------------------------------------------\n\n/**\n * Appends a transducer filter to an open stream.\n *\n * @param resource $stream    Stream to add a filter to.\n * @param callable $xf        Transducer function.\n * @param int      $readWrite Constants available on PHP's stream_filter_append\n *\n * @return resource Returns the appended stream filter resource.\n */\nfunction append_stream_filter($stream, callable $xf, $readWrite)\n{\n    register_stream_filter();\n    return stream_filter_append($stream, 'transducer', $readWrite, $xf);\n}\n\n/**\n * Prepends a transducer filter to an open stream.\n *\n * @param resource $stream    Stream to add a filter to.\n * @param callable $xf        Transducer function.\n * @param int      $readWrite Constants available on PHP's stream_filter_prepend\n *\n * @return resource Returns the appended stream filter resource.\n */\nfunction prepend_stream_filter($stream, callable $xf, $readWrite)\n{\n    register_stream_filter();\n    return stream_filter_prepend($stream, 'transducer', $readWrite, $xf);\n}\n\n/**\n * Registers the 'transducer' stream filter.\n */\nfunction register_stream_filter()\n{\n    stream_filter_register('transducer', 'transducers\\StreamFilter');\n}\n\n/**\n * Implements transducer functionality in PHP stream filters.\n */\nclass StreamFilter extends \\php_user_filter\n{\n    private $xf;\n    private $buffer;\n    private $bufferHandle;\n\n    public function onCreate()\n    {\n        if (!is_callable($this->params)) {\n            trigger_error('Filter params arg must be a transducer function');\n            return false;\n        }\n\n        $reducer = create_reducer(function($r, $x) { $this->buffer .= $x; });\n        $this->xf = call_user_func($this->params, $reducer);\n        return true;\n    }\n\n    public function onClose()\n    {\n        if (is_resource($this->bufferHandle)) {\n            fclose($this->bufferHandle);\n        }\n    }\n\n    function filter($in, $out, &$consumed, $closing)\n    {\n        $result = '';\n\n        while ($bucket = stream_bucket_make_writeable($in)) {\n            // Stream each byte through the step function.\n            for ($i = 0, $t = strlen($bucket->data); $i < $t; $i++) {\n                $consumed++;\n                $result = $this->xf['step']($result, $bucket->data[$i]);\n                if ($result instanceof Reduced) {\n                    break;\n                }\n            }\n            // A transducer may choose to not use the provided input.\n            if (strlen($this->buffer)) {\n                $bucket->data = $this->buffer;\n                $this->buffer = '';\n                stream_bucket_append($out, $bucket);\n            }\n        }\n\n        // When closing, we allow the $xf['result'] function to add more data.\n        if ($closing) {\n            $this->xf['result']('');\n            if (strlen($this->buffer)) {\n                // The buffer is only needed when the result fn calls the step.\n                $this->bufferHandle = fopen('php://memory', 'w+');\n                $bucket = stream_bucket_new($this->bufferHandle, $this->buffer);\n                stream_bucket_append($out, $bucket);\n            }\n        }\n\n        return PSFS_PASS_ON;\n    }\n}\n"
  },
  {
    "path": "tests/transducersTest.php",
    "content": "<?php\nnamespace transducers\\Tests;\n\nuse transducers as t;\n\nclass functionsTest extends \\PHPUnit_Framework_TestCase\n{\n    public function testComposesFunctions()\n    {\n        $a = function ($x) {\n            $this->assertEquals(3, $x);\n            return $x + 1;\n        };\n\n        $b = function ($x) {\n            $this->assertEquals(1, $x);\n            return $x + 2;\n        };\n\n        $c = t\\comp($a, $b);\n        $this->assertEquals(4, $c(1));\n    }\n\n    public function testEnsuresReduced()\n    {\n        $r = t\\ensure_reduced(1);\n        $this->assertEquals(1, $r->value);\n        $r = t\\ensure_reduced($r);\n        $this->assertEquals(1, $r->value);\n    }\n\n    public function testReturnsIdentity()\n    {\n        $this->assertEquals(1, t\\identity(1));\n    }\n\n    public function testReturnsAppendXform()\n    {\n        $xf = t\\array_reducer();\n        $this->assertEquals([], $xf['init']());\n        $this->assertSame([10, 1], $xf['step']([10], 1));\n        $this->assertSame([10], $xf['result']([10]));\n    }\n\n    public function testReturnsStreamXform()\n    {\n        $xf = t\\stream_reducer();\n        $res = $xf['init']();\n        $this->assertInternalType('resource', $res);\n        $this->assertSame($res, $xf['step']($res, 'a'));\n        fseek($res, 0);\n        $this->assertEquals('a', stream_get_contents($res));\n        $this->assertSame($res, $xf['result']($res));\n        fclose($res);\n    }\n\n    public function testTransformStreamWithxform()\n    {\n        $stream = fopen('php://temp', 'w+');\n        fwrite($stream, '012304');\n        rewind($stream);\n        $result = t\\xform($stream, t\\compact());\n        rewind($result);\n        $this->assertEquals('1234', stream_get_contents($result));\n    }\n\n    public function testSeqAppliesToIterator()\n    {\n        $xf = t\\compact();\n        $data = new \\ArrayIterator([1, false, 2, null]);\n        $iter = t\\xform($data, $xf);\n        $this->assertInstanceOf('Generator', $iter);\n        $this->assertEquals([1, 2], iterator_to_array($iter));\n    }\n\n    public function testSeqAppliesToString()\n    {\n        $xf = t\\map(function ($v) { return strtoupper($v); });\n        $data = 'foo';\n        $this->assertSame('FOO', t\\xform($data, $xf));\n    }\n\n    /**\n     * @expectedExceptionMessage Do not know how to xform bool(false)\n     * @expectedException \\InvalidArgumentException\n     */\n    public function testSeqThrowsWhenUnknownDataType()\n    {\n        t\\xform(false, t\\compact());\n    }\n\n    public function testCompactTrimsFalseyValues()\n    {\n        $data = [0, false, true, 10, ' ', 'a'];\n        $result = t\\into([], $data, t\\compact());\n        $this->assertEquals([true, 10, ' ', 'a'], $result);\n    }\n\n    public function testTapsIntoReduce()\n    {\n        $data = ['a', 'b', 'c'];\n        $res = [];\n        $result = t\\into([], $data, t\\tap(function ($r, $x) use (&$res) {\n            $res[] = $x;\n        }));\n        $this->assertSame($res, $result);\n    }\n\n    public function testInterposes()\n    {\n        $data = ['a', 'b', 'c'];\n        $result = t\\into([], $data, t\\interpose('-'));\n        $this->assertEquals(['a', '-', 'b', '-', 'c'], $result);\n    }\n\n    public function testRemovesDuplicates()\n    {\n        $data = ['a', 'b', 'b', 'c', 'c', 'c', 'b'];\n        $result = t\\into([], $data, t\\dedupe());\n        $this->assertEquals(['a', 'b', 'c', 'b'], $result);\n    }\n\n    public function testMaps()\n    {\n        $data = ['a', 'b', 'c'];\n        $xf = t\\map(function ($value) { return strtoupper($value); });\n        $result = t\\into([], $data, $xf);\n        $this->assertEquals(['A', 'B', 'C'], $result);\n    }\n\n    public function testFilters()\n    {\n        $data = [1, 2, 3, 4];\n        $odd = function ($value) { return $value % 2; };\n        $result = t\\into([], $data, t\\filter($odd));\n        $this->assertEquals([1, 3], $result);\n    }\n\n    public function testRemoves()\n    {\n        $data = [1, 2, 3, 4];\n        $odd = function ($value) { return $value % 2; };\n        $result = t\\into([], $data, t\\remove($odd));\n        $this->assertEquals([2, 4], $result);\n    }\n\n    public function testCats()\n    {\n        $data = [[1, 2], 3, [], [4, 5]];\n        $result = t\\into([], $data, 'transducers\\cat');\n        $this->assertEquals($result, [1, 2, 3, 4, 5]);\n    }\n\n    public function testMapCats()\n    {\n        $data = [[1, 2], [3], [], [4, 5]];\n        $xf = t\\mapcat(function ($value) { return array_sum($value); });\n        $result = t\\into([], $data, $xf);\n        $this->assertEquals($result, [3, 3, 0, 9]);\n    }\n\n    public function testFlattensIterables()\n    {\n        $data = [[1, 2], [3, [4, 5, new \\ArrayObject([6, 7])]], [], [8, 9]];\n        $result = t\\into([], $data, t\\flatten());\n        $this->assertEquals($result, [1, 2, 3, 4, 5, 6, 7, 8, 9]);\n    }\n\n    public function testFlattenSkipsNonIterables()\n    {\n        $data = ['abc'];\n        $result = t\\into([], $data, t\\flatten());\n        $this->assertEquals($result, ['abc']);\n    }\n\n    public function testPartitions()\n    {\n        $data = [1, 2, 3, 4, 5];\n        $xf = t\\partition(2);\n        $result = t\\into([], $data, $xf);\n        $this->assertEquals($result, [[1, 2], [3, 4], [5]]);\n    }\n\n    public function testPartitionsByPredicate()\n    {\n        $data = [['a', 1], ['a', 2], ['a', 3], [2, 4], ['c', 5]];\n        $xf = t\\partition_by(function ($v) { return is_string($v[0]); });\n        $result = t\\into([], $data, $xf);\n        $this->assertEquals(\n            $result,\n            [[['a', 1], ['a', 2], ['a', 3]], [[2, 4]], [['c', 5]]]\n        );\n    }\n\n    public function testTakes()\n    {\n        $data = [1, 2, 3, 4, 5];\n        $result = t\\xform($data, t\\take(2));\n        $this->assertEquals($result, [1, 2]);\n    }\n\n    public function testDrops()\n    {\n        $data = [1, 2, 3, 4, 5];\n        $result = t\\xform($data, t\\drop(2));\n        $this->assertEquals($result, [3, 4, 5]);\n    }\n\n    public function testTakesNth()\n    {\n        $data = [1, 2, 3, 4, 5, 6];\n        $result = t\\xform($data, t\\take_nth(2));\n        $this->assertEquals($result, [1, 3, 5]);\n    }\n\n    public function testTakesWhile()\n    {\n        $data = [1, 2, 3, 4, 5];\n        $xf = t\\take_while(function ($value) { return $value < 4; });\n        $result = t\\xform($data, $xf);\n        $this->assertEquals($result, [1, 2, 3]);\n    }\n\n    public function testDropsWhile()\n    {\n        $data = [1, 2, 3, 4, 5];\n        $xf = t\\drop_while(function ($value) { return $value < 3; });\n        $result = t\\xform($data, $xf);\n        $this->assertEquals($result, [3, 4, 5]);\n    }\n\n    public function testReplaces()\n    {\n        $data = ['hi', 'there', 'guy', '!'];\n        $xf = t\\replace(['hi' => 'You', '!' => '?']);\n        $result = t\\xform($data, $xf);\n        $this->assertEquals($result, ['You', 'there', 'guy', '?']);\n    }\n\n    public function testKeeps()\n    {\n        $data = [0, false, null, true];\n        $xf = t\\keep(function ($value) { return $value; });\n        $result = t\\xform($data, $xf);\n        $this->assertEquals([0, false, true], $result);\n    }\n\n    public function testKeepsWithIndex()\n    {\n        $data = [0, false, null, true];\n        $calls = [];\n        $xf = t\\keep_indexed(function ($idx, $item) use (&$calls) {\n            $calls[] = [$idx, $item];\n            return $item;\n        });\n        $result = t\\xform($data, $xf);\n        $this->assertEquals([0, false, true], $result);\n        $this->assertEquals([[0, 0], [1, false], [2, null], [3, true]], $calls);\n    }\n\n    public function testToTraversableReturnsArrays()\n    {\n        $this->assertEquals([1, 2, 3], t\\to_traversable([1, 2, 3]));\n        $this->assertEquals(\n            [['a', 1], ['b', 2]],\n            iterator_to_array(t\\to_traversable(['a' => 1, 'b' => 2]))\n        );\n    }\n\n    public function testToTraversableReturnsStreamsIter()\n    {\n        $s = fopen('php://temp', 'w+');\n        fwrite($s, 'foo');\n        rewind($s);\n        $this->assertEquals(\n            ['f', 'o','o'],\n            iterator_to_array(t\\to_traversable($s))\n        );\n        fclose($s);\n    }\n\n    public function testToTraversableReturnsStringAsArray()\n    {\n        $this->assertEquals(['f', 'o','o'], t\\to_traversable('foo'));\n    }\n\n    public function testToTraversableReturnsIteratorAsIs()\n    {\n        $i = new \\ArrayIterator([1, 2]);\n        $this->assertSame($i, t\\to_traversable($i));\n    }\n\n    /**\n     * @expectedException \\InvalidArgumentException\n     * @expectedExceptionMessage Do not know how to to_traversable bool(false)\n     */\n    public function testToTraversableEnsuresItCanHandleType()\n    {\n        t\\to_traversable(false);\n    }\n\n    public function testConvertsToArray()\n    {\n        $this->assertEquals(\n            [1, 2],\n            t\\to_array([1, 2], t\\compact())\n        );\n        $this->assertEquals(\n            [1, 2],\n            t\\to_array(new \\ArrayIterator([1, 2]), t\\compact())\n        );\n        $this->assertEquals(\n            ['a', 'b'],\n            t\\to_array('ab', t\\compact())\n        );\n    }\n\n    /**\n     * @expectedException \\InvalidArgumentException\n     * @expectedExceptionMessage Do not know how to to_traversable int(1)\n     */\n    public function testConvertsToArrayThrowsWhenInvalidType()\n    {\n        t\\to_array(1, function () {});\n    }\n\n    public function testReducedConstructor()\n    {\n        $r = new t\\Reduced('foo');\n        $this->assertEquals('foo', $r->value);\n    }\n\n    public function testCreatesReducers()\n    {\n        $t = t\\create_reducer(\n            function ($r, $x) { return $r . $x; },\n            function () { return ''; }\n        );\n        $this->assertEquals('', $t['init']());\n        $this->assertEquals('ab', $t['step']('a', 'b'));\n        $this->assertEquals('foo', $t['result']('foo'));\n    }\n\n    public function testChecksIfTraversable()\n    {\n        $this->assertTrue(t\\is_traversable([1, 2]));\n        $this->assertTrue(t\\is_traversable(new \\ArrayObject([1, 2])));\n        $this->assertTrue(t\\is_traversable(new \\ArrayIterator([1, 2])));\n        $this->assertTrue(t\\is_traversable(new \\stdClass()));\n        $this->assertFalse(t\\is_traversable('a'));\n    }\n\n    public function testHasOperatorReducer()\n    {\n        $xf = t\\compact();\n        $data = [1, 2, 3];\n        $this->assertEquals(6, t\\transduce($xf, t\\operator_reducer('+'), $data));\n        $this->assertEquals(-6, t\\transduce($xf, t\\operator_reducer('-'), $data));\n        $this->assertEquals(0, t\\transduce($xf, t\\operator_reducer('*'), $data));\n        $this->assertEquals(6, t\\transduce($xf, t\\operator_reducer('*'), $data, 1));\n        $this->assertEquals(0.16666666666666666, t\\transduce($xf, t\\operator_reducer('/'), $data, 1));\n        $this->assertEquals('123', t\\transduce($xf, t\\operator_reducer('.'), $data));\n    }\n\n    /**\n     * @expectedException \\InvalidArgumentException\n     */\n    public function testEnsuresOperatorIsValid()\n    {\n        t\\operator_reducer('!');\n    }\n\n    public function testReducesToString()\n    {\n        $xf = t\\map(function ($v) { return strtoupper($v); });\n        $data = ['a', 'b', 'c'];\n        $this->assertEquals('ABC', t\\transduce($xf, t\\string_reducer(), $data));\n    }\n\n    public function testReducesToAssoc()\n    {\n        $xf = t\\map(function ($v) {\n            return [strtoupper($v[0]), $v[1]];\n        });\n        $data = ['a' => 1, 'b' => 2];\n        $result = t\\transduce($xf, t\\assoc_reducer(), t\\assoc_iter($data));\n        $this->assertEquals(['A' => 1, 'B' => 2], $result);\n    }\n\n    public function testSplitsWords()\n    {\n        $this->assertEquals(\n            ['hi', 'there', 'guy!'],\n            t\\xform([\"hi\\nthere\", \" guy!\"], t\\words())\n        );\n        $this->assertEquals(\n            ['hi', 'the', 're', 'guy', '!'],\n            t\\xform([\"hi\\nthere\", \" guy!\"],t\\words(3))\n        );\n    }\n\n    public function testSplitsLines()\n    {\n        $this->assertEquals(\n            ['hi', 'there guy!'],\n            t\\xform([\"hi\\nthere\", \" guy!\"], t\\lines())\n        );\n        $this->assertEquals(\n            ['hi', 'there', ' guy!'],\n            t\\xform([\"hi\\nthere\", \" guy!\"], t\\lines(5))\n        );\n    }\n\n    //-----------------------------------------------------------------------------\n    // Stream tests\n    //-----------------------------------------------------------------------------\n\n    private function stream($str)\n    {\n        $fp = fopen('php://memory', 'r+');\n        fwrite($fp, $str);\n        rewind($fp);\n        return $fp;\n    }\n\n    public function addsProvider()\n    {\n        return [[true], [false]];\n    }\n\n    /**\n     * @dataProvider addsProvider\n     */\n    public function testAddsWhenWriting($append)\n    {\n        $called = [];\n        $fp = fopen('php://memory', 'r+');\n        $xf = t\\map(function ($v) use (&$called) {\n            $called[] = $v;\n            return strtoupper($v);\n        });\n        if ($append) {\n            t\\append_stream_filter($fp, $xf, STREAM_FILTER_WRITE);\n        } else {\n            t\\prepend_stream_filter($fp, $xf, STREAM_FILTER_WRITE);\n        }\n        fwrite($fp, 'foo');\n        fwrite($fp, 'bar');\n        $this->assertEquals(['f', 'o', 'o', 'b', 'a', 'r'], $called);\n        rewind($fp);\n        $this->assertEquals('FOOBAR', stream_get_contents($fp));\n    }\n\n    public function testAddsWhenReading()\n    {\n        $called = [];\n        $fp = $this->stream('foobar');\n        $xf = t\\map(function ($v) use (&$called) {\n            $called[] = $v;\n            return strtoupper($v);\n        });\n        $filter = t\\append_stream_filter($fp, $xf, STREAM_FILTER_READ);\n        $this->assertInternalType('resource', $filter);\n        $this->assertEquals('stream filter', get_resource_type($filter));\n        $this->assertEquals('FOOBAR', stream_get_contents($fp));\n        $this->assertEquals(['f', 'o', 'o', 'b', 'a', 'r'], $called);\n    }\n\n    public function testCanEarlyTerminate()\n    {\n        $fp = $this->stream('foobar');\n        $called = [];\n        $xf = t\\comp(\n            t\\take(3),\n            t\\tap(function ($r, $x) use (&$called) {\n                $called[] = $x;\n            })\n        );\n        t\\append_stream_filter($fp, $xf, STREAM_FILTER_READ);\n        $this->assertEquals('foo', stream_get_contents($fp));\n        $this->assertEquals(['f', 'o', 'o'], $called);\n    }\n\n    public function testCanStepInClosing()\n    {\n        $fp = $this->stream('hi there guy');\n        $xf = t\\comp(\n            t\\partition_by(function ($v) { return $v !== ' '; }),\n            t\\filter(function ($v) { return $v !== [' ']; }),\n            t\\keep_indexed(function ($i, $v) {\n                $str = implode('', $v);\n                if ($i % 2) {\n                    return strtoupper($str);\n                } else {\n                    return strtolower($str);\n                }\n            }),\n            t\\interpose(' ')\n        );\n        $filter = t\\append_stream_filter($fp, $xf, STREAM_FILTER_READ);\n        $this->assertEquals('hi THERE', stream_get_contents($fp));\n        // Note that the last bit requires the filter to be removed!\n        stream_filter_remove($filter);\n        $this->assertEquals(' guy', fread($fp, 100));\n    }\n\n    /**\n     * @expectedException \\PHPUnit_Framework_Error_Notice\n     * @expectedExceptionMessage Filter params arg must be a transducer function\n     */\n    public function testEnsuresXfIscallable()\n    {\n        $fp = $this->stream('foo');\n        t\\register_stream_filter();\n        stream_filter_append($fp, 'transducer', STREAM_FILTER_READ);\n    }\n\n    public function testToFn()\n    {\n        $xf = t\\map(function ($x) { return $x + 1; });\n        $fn = t\\to_fn($xf, t\\string_reducer());\n        $result = array_reduce([1, 2, 3], $fn);\n        $this->assertEquals('234', $result);\n    }\n}\n"
  }
]