[med-svn] [r-cran-purrr] 01/02: New upstream version 0.2.3

Andreas Tille tille at debian.org
Fri Oct 13 11:41:32 UTC 2017


This is an automated email from the git hooks/post-receive script.

tille pushed a commit to branch master
in repository r-cran-purrr.

commit 55f314cfe7a8b0b1f3c1c72c72c9864d3b62778f
Author: Andreas Tille <tille at debian.org>
Date:   Fri Oct 13 13:40:42 2017 +0200

    New upstream version 0.2.3
---
 DESCRIPTION                              |  25 ++
 LICENSE                                  | 674 +++++++++++++++++++++++++++++++
 MD5                                      | 128 ++++++
 NAMESPACE                                | 166 ++++++++
 NEWS.md                                  | 325 +++++++++++++++
 R/along.R                                |  29 ++
 R/arrays.R                               |  70 ++++
 R/as_mapper.R                            | 196 +++++++++
 R/coerce.R                               |  10 +
 R/coercion.R                             | 107 +++++
 R/compose.R                              |  27 ++
 R/composition.R                          | 217 ++++++++++
 R/cross.R                                | 169 ++++++++
 R/depth.R                                |  28 ++
 R/every-some.R                           |  35 ++
 R/find-position.R                        |  79 ++++
 R/flatten.R                              |  74 ++++
 R/head-tail.R                            |  31 ++
 R/imap.R                                 |  74 ++++
 R/invoke.R                               | 143 +++++++
 R/keep.R                                 |  48 +++
 R/list-modify.R                          | 103 +++++
 R/lmap.R                                 | 110 +++++
 R/map.R                                  | 198 +++++++++
 R/map2-pmap.R                            | 224 ++++++++++
 R/modify.R                               | 194 +++++++++
 R/negate.R                               |  25 ++
 R/output.R                               | 187 +++++++++
 R/partial.R                              |  87 ++++
 R/predicates.R                           | 102 +++++
 R/prepend.R                              |  28 ++
 R/purrr.R                                |   4 +
 R/reduce.R                               | 194 +++++++++
 R/rerun.R                                |  37 ++
 R/set_names.R                            |  35 ++
 R/splice.R                               |  30 ++
 R/transpose.R                            |  51 +++
 R/utils.R                                | 114 ++++++
 R/when.R                                 |  94 +++++
 README.md                                |  51 +++
 build/vignette.rds                       | Bin 0 -> 225 bytes
 inst/doc/other-langs.Rmd                 |  46 +++
 inst/doc/other-langs.html                |  64 +++
 man/accumulate.Rd                        |  62 +++
 man/along.Rd                             |  32 ++
 man/array-coercion.Rd                    |  61 +++
 man/as_mapper.Rd                         |  68 ++++
 man/as_vector.Rd                         |  51 +++
 man/compose.Rd                           |  25 ++
 man/cross.Rd                             | 115 ++++++
 man/detect.Rd                            |  78 ++++
 man/every.Rd                             |  38 ++
 man/figures/logo.png                     | Bin 0 -> 31067 bytes
 man/flatten.Rd                           |  60 +++
 man/get-attr.Rd                          |  21 +
 man/has_element.Rd                       |  21 +
 man/head_while.Rd                        |  38 ++
 man/imap.Rd                              |  78 ++++
 man/invoke.Rd                            | 103 +++++
 man/is_numeric.Rd                        |  22 +
 man/keep.Rd                              |  53 +++
 man/lift.Rd                              | 183 +++++++++
 man/list_modify.Rd                       |  57 +++
 man/lmap.Rd                              | 111 +++++
 man/map.Rd                               | 167 ++++++++
 man/map2.Rd                              | 161 ++++++++
 man/modify.Rd                            | 159 ++++++++
 man/negate.Rd                            |  38 ++
 man/null-default.Rd                      |  22 +
 man/partial.Rd                           |  72 ++++
 man/pipe.Rd                              |  12 +
 man/pluck.Rd                             |  98 +++++
 man/prepend.Rd                           |  31 ++
 man/purrr-package.Rd                     |  34 ++
 man/rbernoulli.Rd                        |  23 ++
 man/rdunif.Rd                            |  20 +
 man/reduce.Rd                            |  64 +++
 man/reexports.Rd                         |  66 +++
 man/rerun.Rd                             |  31 ++
 man/safely.Rd                            |  99 +++++
 man/set_names.Rd                         |  51 +++
 man/splice.Rd                            |  26 ++
 man/transpose.Rd                         |  61 +++
 man/vec_depth.Rd                         |  27 ++
 man/when.Rd                              |  61 +++
 src/coerce.c                             | 101 +++++
 src/coerce.h                             |   8 +
 src/extract.c                            | 158 ++++++++
 src/flatten.c                            | 126 ++++++
 src/init.c                               |  32 ++
 src/map.c                                | 198 +++++++++
 src/map.h                                |   9 +
 src/transpose.c                          | 100 +++++
 tests/testthat.R                         |   4 +
 tests/testthat/test-along.R              |  13 +
 tests/testthat/test-arrays.R             |  18 +
 tests/testthat/test-as-mapper.R          |  77 ++++
 tests/testthat/test-coerce.R             |  68 ++++
 tests/testthat/test-compose.R            |   9 +
 tests/testthat/test-composition.R        |  33 ++
 tests/testthat/test-cross.R              |  26 ++
 tests/testthat/test-depth.R              |  31 ++
 tests/testthat/test-every-some.R         |  18 +
 tests/testthat/test-find-position.R      |  34 ++
 tests/testthat/test-flatten.R            |  78 ++++
 tests/testthat/test-head-tail.R          |  16 +
 tests/testthat/test-imap.R               |  26 ++
 tests/testthat/test-invoke.R             |  52 +++
 tests/testthat/test-list-modify-update.R |  77 ++++
 tests/testthat/test-lmap.R               |   9 +
 tests/testthat/test-map.R                |  73 ++++
 tests/testthat/test-map2.R               |  44 ++
 tests/testthat/test-map_n.R              |  60 +++
 tests/testthat/test-modify.R             |  56 +++
 tests/testthat/test-negate.R             |  10 +
 tests/testthat/test-output.R             |  43 ++
 tests/testthat/test-partial.R            |  28 ++
 tests/testthat/test-pluck.R              | 156 +++++++
 tests/testthat/test-predicates.R         |  14 +
 tests/testthat/test-prepend.R            |  15 +
 tests/testthat/test-recycle_args.R       |  25 ++
 tests/testthat/test-reduce.R             |  57 +++
 tests/testthat/test-rerun.R              |  22 +
 tests/testthat/test-simplify.R           |  35 ++
 tests/testthat/test-splice.R             |  18 +
 tests/testthat/test-transpose.R          | 111 +++++
 tests/testthat/test-utils.R              |  31 ++
 tests/testthat/test-when.R               |  64 +++
 vignettes/other-langs.Rmd                |  46 +++
 129 files changed, 9462 insertions(+)

diff --git a/DESCRIPTION b/DESCRIPTION
new file mode 100644
index 0000000..45b74a4
--- /dev/null
+++ b/DESCRIPTION
@@ -0,0 +1,25 @@
+Package: purrr
+Title: Functional Programming Tools
+Version: 0.2.3
+Authors at R: c(
+    person("Lionel", "Henry", , "lionel at rstudio.com", c("aut", "cre")),
+    person("Hadley", "Wickham", , "hadley at rstudio.com", "aut"),
+    person("RStudio", role = c("cph", "fnd"))
+    )
+Description: A complete and consistent functional programming toolkit for R.
+URL: http://purrr.tidyverse.org, https://github.com/tidyverse/purrr
+BugReports: https://github.com/tidyverse/purrr/issues
+License: GPL-3 | file LICENSE
+LazyData: true
+Imports: magrittr (>= 1.5), tibble, rlang (>= 0.1)
+Suggests: covr, dplyr (>= 0.4.3), testthat, knitr, rmarkdown
+RoxygenNote: 6.0.1
+VignetteBuilder: knitr
+NeedsCompilation: yes
+Packaged: 2017-08-02 09:31:27 UTC; lionel
+Author: Lionel Henry [aut, cre],
+  Hadley Wickham [aut],
+  RStudio [cph, fnd]
+Maintainer: Lionel Henry <lionel at rstudio.com>
+Repository: CRAN
+Date/Publication: 2017-08-02 18:31:42 UTC
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..94a9ed0
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,674 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/MD5 b/MD5
new file mode 100644
index 0000000..2b5af2a
--- /dev/null
+++ b/MD5
@@ -0,0 +1,128 @@
+beed643ea16e8cbacb246961a44423d0 *DESCRIPTION
+d32239bcb673463ab874e80d47fae504 *LICENSE
+b8b691d3ec56c2995d0eed7679ea7311 *NAMESPACE
+1bf49544a9a6ce5da1f955399321b4fb *NEWS.md
+eb049f60acfeca84118ad5a6e3878c3a *R/along.R
+48b5ad68f9c3a686e0e5d78499feeec7 *R/arrays.R
+cd51a9a563477b3e25df8fd415b7daa3 *R/as_mapper.R
+f0ec1ea254e478033f6d34fb33405499 *R/coerce.R
+78c61636c1cf9295234fd97e97662b5c *R/coercion.R
+7cad8d9769011f8e8becd44c0c046905 *R/compose.R
+60109119d2926aa5eb8d0b25995b7103 *R/composition.R
+5a1819f7c6a77d026fd8a8dd85af818d *R/cross.R
+42de484120cddf450bfaefe35ea0a2be *R/depth.R
+fb21301f998ee034966f53c6a46453ad *R/every-some.R
+4de7e21a61ef6ef0fa1a00c2522d2547 *R/find-position.R
+2dd0023bfec19b388810951407f922c9 *R/flatten.R
+aa1335a2e02edc206e9388d0f002125f *R/head-tail.R
+8fcc2f91aed2123309302e0acad47189 *R/imap.R
+72aad2226d57cf651c1fc9e25af870bd *R/invoke.R
+4fc05f9f9e735ec87fb0f64bcaecde91 *R/keep.R
+bebdbf11ecf1702b245387b214e1e46c *R/list-modify.R
+312d46fc7b49e6b50add48154cdf9009 *R/lmap.R
+07c2d28b82d098a70c7a27fc190ca704 *R/map.R
+b84099b9b82c8ba315f0d8599bb3c4c1 *R/map2-pmap.R
+e420126f44f750c89a79894bef58d9fe *R/modify.R
+a848e46817874cd07d6f725c7fd112f2 *R/negate.R
+ee7ea8075ca7b0f200e8cdbb6947f303 *R/output.R
+46e6971d5cab3972e96f537c953d1ccb *R/partial.R
+29a5243a6ac22ef61660fa10619167aa *R/predicates.R
+b35704dde8e2ca4e17c830d75a1c44fd *R/prepend.R
+ab9be6571c4276e9e8260d553248d8b1 *R/purrr.R
+f09b71dc1084298ce8cbfa5eefe5ef1b *R/reduce.R
+8e41a48c6995da83d85d3407bde4477d *R/rerun.R
+421ca7ab2db6ed008fba8b5558674827 *R/set_names.R
+35b5580292c6c4227b218128bdd7da85 *R/splice.R
+f6afdc22a2adf80cede2a04ec0536488 *R/transpose.R
+b5ac331a5179d8aeae4b9e70cf003177 *R/utils.R
+3011f46c733271400987666dc871373d *R/when.R
+905bd231c84f1eb83498323757d1c627 *README.md
+ba009ec33d50810392dbb70cf5b9b5dc *build/vignette.rds
+d0a12786bea51ec8b9d57563e6edacd8 *inst/doc/other-langs.Rmd
+6a93487e8c6c101952681d6d920ea844 *inst/doc/other-langs.html
+5e52173daf46dcd6f817e465640e70e9 *man/accumulate.Rd
+a918e44486ee9a04d8ecd9ade2a68883 *man/along.Rd
+a5fb7427a5582b7f26fb4628b0285fb3 *man/array-coercion.Rd
+dcbeafd99f65f30e6c6b55f785263851 *man/as_mapper.Rd
+e7f26ac9b0e6831a8e74e8bb6c4c0849 *man/as_vector.Rd
+ebc8d00b74c57e535a75097d5b3efb73 *man/compose.Rd
+1079d4e27459673cef82d40c1c3ff770 *man/cross.Rd
+95177a137c3a458dd30a897e5527486a *man/detect.Rd
+ed53201c56c68c61eaa8cba9836027dc *man/every.Rd
+1d60cfe447b30937913953382e194602 *man/figures/logo.png
+f936d540f0f4c19df702b72e57b1db0f *man/flatten.Rd
+2d83b8c056befed23e23c5fa0a7e2901 *man/get-attr.Rd
+45f2c89c9029e869a44213e3ecfdb393 *man/has_element.Rd
+99e9c4dc80463cc0440f53cd30950239 *man/head_while.Rd
+fb3e1696bb0c6092ee206d982e0b9972 *man/imap.Rd
+8d8d59bf99551df5138fdc384f0764bb *man/invoke.Rd
+f4654f144511be7d26d7e88f01c5c5c4 *man/is_numeric.Rd
+ed663b2e2ea63c371e2aae84526b8bbf *man/keep.Rd
+aaf7edad66c589562b6542de0ab1fa14 *man/lift.Rd
+91c2686573f852772455c3173a1d0730 *man/list_modify.Rd
+8d71abc37291505d62372642d527b7c3 *man/lmap.Rd
+b26b111b610cfad97f57ef667cad58c6 *man/map.Rd
+27864281fb67c4d6a81b501f0760c3cc *man/map2.Rd
+3cf9fe4009caa54f9061e3f79ac3cece *man/modify.Rd
+c6f50cc4df88e89d974ae859659ac48a *man/negate.Rd
+41960071b98ae08db2626bf0176474ad *man/null-default.Rd
+fff26baafa6d12a021a1eccc418b98d1 *man/partial.Rd
+a64a7ea44fcaa33c2d3ad0f7909cbc3e *man/pipe.Rd
+a7d3140205673734c39ff12ebcbfb018 *man/pluck.Rd
+c2e38b7a30f26c4ea0435141446254e4 *man/prepend.Rd
+1d2d8fea562ac7cf11eca1c642a81a6b *man/purrr-package.Rd
+f34d8370301c799a28aa8307b6394911 *man/rbernoulli.Rd
+2df5a87d08db6c4b73447fa625ea444d *man/rdunif.Rd
+a899f7774548591e1c94c14c593b42cc *man/reduce.Rd
+50df6a47659d7e3831a580ff7b008b27 *man/reexports.Rd
+028392c9b567bf0e18039256b65d54df *man/rerun.Rd
+e545a455d6e8c9a2b7113cc15e778ce9 *man/safely.Rd
+1833c1e1d1e45f2a8fa6843672c92a8e *man/set_names.Rd
+d198d9d3ffebb0bdca5968a34b70ccf4 *man/splice.Rd
+987b0b3f1704943ddbad57e9e2c21e8d *man/transpose.Rd
+5cf4da2f617677a3ab17aea0e6dcfcfd *man/vec_depth.Rd
+476045071aee1d250fccf59f5bab87f9 *man/when.Rd
+74a87782ba250e74b5d1830e8864fb84 *src/coerce.c
+6c50acdeb1bbbf26649665739256d211 *src/coerce.h
+0ca1770e2a59ea7459690214e3db50eb *src/extract.c
+e7ea15c193e5f5146090b7c652fe0c6c *src/flatten.c
+829d93cfd9ef1890b0c06c402f5cb8ce *src/init.c
+4db7ddfaa264f7f0323f6c53d6d5beb5 *src/map.c
+daa7efc01a8489ded96d8c0863e69b0f *src/map.h
+6152e5b8602a9e993d798cc65ce0366c *src/transpose.c
+8e9d16c5c6aedcc157783b13df5b9db0 *tests/testthat.R
+ef7812928b1473706a682fd6948e9f98 *tests/testthat/test-along.R
+dcbf48a2fbdba7f91e3880f7a0064701 *tests/testthat/test-arrays.R
+f27cabfbbf69b64d662b72644ea39538 *tests/testthat/test-as-mapper.R
+59bad9250777fd79da0f44408d7191d2 *tests/testthat/test-coerce.R
+297f88674faf22665a960389c8fda450 *tests/testthat/test-compose.R
+b3708b23403b14b7c15db43e810029e8 *tests/testthat/test-composition.R
+ca3d5a991b34b91ec6cff68a6884cad1 *tests/testthat/test-cross.R
+c548d3237286977e949b2b665b8a5792 *tests/testthat/test-depth.R
+a22e930c9691c0655ba9c5fe94866adc *tests/testthat/test-every-some.R
+14532ea09ed8dfca0d19e65a4f1d7c41 *tests/testthat/test-find-position.R
+6857d64342f75696d507790e01efef80 *tests/testthat/test-flatten.R
+fb8adbe7e936c917da7db33858f2df4a *tests/testthat/test-head-tail.R
+d4c61639a083a3ec2dddc8a7cf632343 *tests/testthat/test-imap.R
+5ffc01118f1e0f07b9c0d90cfcdce1cb *tests/testthat/test-invoke.R
+1aad6ffcce7a01de2c838b1f475bd78d *tests/testthat/test-list-modify-update.R
+325bad5b7fa5a5a642da9ce50753e437 *tests/testthat/test-lmap.R
+c70e2e34a59f3a5986b1f2ba2a0128a3 *tests/testthat/test-map.R
+2b67c975d77b2e433f0ad8ea693e0e47 *tests/testthat/test-map2.R
+8c865f413f11c6a265d15e1f842df9b4 *tests/testthat/test-map_n.R
+93bd710e49b0ae1af8dd27d021ddc39d *tests/testthat/test-modify.R
+ce1638acb8d41d5bfe2fc29877484b8a *tests/testthat/test-negate.R
+192dfcaa7553057ad63e307c8a474e5b *tests/testthat/test-output.R
+d772931501f353938cd4f19fef56df05 *tests/testthat/test-partial.R
+a3d9e4801e73a775b96e6e3c433285e5 *tests/testthat/test-pluck.R
+ea532e7f3b213aff3ad4f66c6a23b7b4 *tests/testthat/test-predicates.R
+b91718777144b4a6ad6d8ca71585a67c *tests/testthat/test-prepend.R
+98068929576937ae3a307a5daa19a9fb *tests/testthat/test-recycle_args.R
+273e7c7e8c27c6d25e8dae194f435de6 *tests/testthat/test-reduce.R
+e34255b41581de0f6faf55eadd8cfbdb *tests/testthat/test-rerun.R
+0eb43c0afc550daeead9401117b73f03 *tests/testthat/test-simplify.R
+3f87bdf05305f94b2b5012ecdaabbb30 *tests/testthat/test-splice.R
+0d12dc2b093f55b36f46c31a4c69dacb *tests/testthat/test-transpose.R
+6069a05aa8cef11b1ca0a42d6067e7cb *tests/testthat/test-utils.R
+7125b68b593f2e86140faabd4e8586ee *tests/testthat/test-when.R
+d0a12786bea51ec8b9d57563e6edacd8 *vignettes/other-langs.Rmd
diff --git a/NAMESPACE b/NAMESPACE
new file mode 100644
index 0000000..d2ca5e5
--- /dev/null
+++ b/NAMESPACE
@@ -0,0 +1,166 @@
+# Generated by roxygen2: do not edit by hand
+
+S3method(as_mapper,character)
+S3method(as_mapper,default)
+S3method(as_mapper,list)
+S3method(as_mapper,numeric)
+S3method(modify,default)
+S3method(modify_at,default)
+S3method(modify_depth,default)
+S3method(modify_if,default)
+export("%>%")
+export("%@%")
+export("%||%")
+export(accumulate)
+export(accumulate_right)
+export(array_branch)
+export(array_tree)
+export(as_function)
+export(as_mapper)
+export(as_vector)
+export(at_depth)
+export(attr_getter)
+export(auto_browse)
+export(compact)
+export(compose)
+export(cross)
+export(cross2)
+export(cross3)
+export(cross_d)
+export(cross_df)
+export(cross_n)
+export(detect)
+export(detect_index)
+export(discard)
+export(every)
+export(flatten)
+export(flatten_chr)
+export(flatten_dbl)
+export(flatten_df)
+export(flatten_dfc)
+export(flatten_dfr)
+export(flatten_int)
+export(flatten_lgl)
+export(has_element)
+export(head_while)
+export(imap)
+export(imap_chr)
+export(imap_dbl)
+export(imap_dfc)
+export(imap_dfr)
+export(imap_int)
+export(imap_lgl)
+export(invoke)
+export(invoke_map)
+export(invoke_map_chr)
+export(invoke_map_dbl)
+export(invoke_map_df)
+export(invoke_map_dfc)
+export(invoke_map_dfr)
+export(invoke_map_int)
+export(invoke_map_lgl)
+export(is_atomic)
+export(is_bare_atomic)
+export(is_bare_character)
+export(is_bare_double)
+export(is_bare_integer)
+export(is_bare_list)
+export(is_bare_logical)
+export(is_bare_numeric)
+export(is_bare_vector)
+export(is_character)
+export(is_double)
+export(is_empty)
+export(is_formula)
+export(is_function)
+export(is_integer)
+export(is_list)
+export(is_logical)
+export(is_null)
+export(is_numeric)
+export(is_scalar_atomic)
+export(is_scalar_character)
+export(is_scalar_double)
+export(is_scalar_integer)
+export(is_scalar_list)
+export(is_scalar_logical)
+export(is_scalar_numeric)
+export(is_scalar_vector)
+export(is_vector)
+export(iwalk)
+export(keep)
+export(lift)
+export(lift_dl)
+export(lift_dv)
+export(lift_ld)
+export(lift_lv)
+export(lift_vd)
+export(lift_vl)
+export(list_along)
+export(list_merge)
+export(list_modify)
+export(lmap)
+export(lmap_at)
+export(lmap_if)
+export(map)
+export(map2)
+export(map2_chr)
+export(map2_dbl)
+export(map2_df)
+export(map2_dfc)
+export(map2_dfr)
+export(map2_int)
+export(map2_lgl)
+export(map_at)
+export(map_call)
+export(map_chr)
+export(map_dbl)
+export(map_df)
+export(map_dfc)
+export(map_dfr)
+export(map_if)
+export(map_int)
+export(map_lgl)
+export(modify)
+export(modify_at)
+export(modify_depth)
+export(modify_if)
+export(negate)
+export(partial)
+export(pluck)
+export(pmap)
+export(pmap_chr)
+export(pmap_dbl)
+export(pmap_df)
+export(pmap_dfc)
+export(pmap_dfr)
+export(pmap_int)
+export(pmap_lgl)
+export(possibly)
+export(prepend)
+export(pwalk)
+export(quietly)
+export(rbernoulli)
+export(rdunif)
+export(reduce)
+export(reduce2)
+export(reduce2_right)
+export(reduce_right)
+export(rep_along)
+export(rerun)
+export(safely)
+export(set_names)
+export(simplify)
+export(simplify_all)
+export(some)
+export(splice)
+export(tail_while)
+export(transpose)
+export(update_list)
+export(vec_depth)
+export(walk)
+export(walk2)
+export(when)
+import(rlang)
+importFrom(magrittr,"%>%")
+useDynLib(purrr, .registration = TRUE)
diff --git a/NEWS.md b/NEWS.md
new file mode 100644
index 0000000..4cbf3b0
--- /dev/null
+++ b/NEWS.md
@@ -0,0 +1,325 @@
+# purrr 0.2.3
+
+## Breaking changes
+
+We noticed the following issues during reverse dependencies checks:
+
+* If `reduce()` fails with this message: ``Error: `.x` is empty, and
+  no `.init` supplied``, this is because `reduce()` now returns
+  `.init` when `.x` is empty. Fix the problem by supplying an
+  appropriate argument to `.init`, or by providing special behaviour
+  when `.x` has length 0.
+
+* The type predicates have been migrated to rlang. Consequently the
+  `bare-type-predicates` documentation topic is no longer in purrr,
+  which might cause a warning if you cross-reference it.
+
+
+## Dependencies
+
+purrr no longer depends on lazyeval or Rcpp (or dplyr, as of the
+previous version). This makes the dependency graph of the tidyverse
+simpler, and makes purrr more suitable as a dependency of lower-level
+packages.
+
+There have also been two changes to eliminate name conflicts between
+purrr and dplyr:
+
+* `order_by()`, `sort_by()` and `split_by()` have been removed. `order_by()`
+  conflicted with `dplyr::order_by()` and the complete family doesn't feel that
+  useful. Use tibbles instead (#217).
+
+* `contains()` has been renamed to `has_element()` to avoid conflicts with
+  dplyr (#217).
+
+
+## pluck()
+
+The plucking mechanism used for indexing into data structures with
+`map()` has been extracted into the function `pluck()`. Plucking is
+often more readable to extract an element buried in a deep data
+structure. Compare this syntax-heavy extraction which reads
+non-linearly:
+
+```
+accessor(x[[1]])$foo
+```
+
+to the equivalent pluck:
+
+```
+x %>% pluck(1, accessor, "foo")
+```
+
+
+## Map helpers
+
+* `as_function()` is now `as_mapper()` because it is a tranformation that
+  makes sense primarily for mapping functions, not in general (#298).
+  `.null` has been renamed to `.default` to better reflect its intent (#298).
+  `.default` is returned whenever an element is absent or empty (#231, #254).
+
+  `as_mapper()` sanitises primitive functions by transforming them to
+  closures with standardised argument names (using `rlang::as_closure()`).
+  For instance `+` is transformed to `function(.x, .y) .x + .y`. This
+  results in proper argument matching so that `map(1:10, partial(`-`,
+  .x = 5))` produces `list(5 - 1, 5 - 2, ...)`.
+
+* Recursive indexing can now extract objects out of environments (#213) and
+  S4 objects (#200), as well as lists.
+
+* `attr_getter()` makes it possible to extract from attributes
+  like `map(list(iris, mtcars), attr_getter("row.names"))`.
+
+* The argument list for formula-functions has been tweaked so that you can
+  refer to arguments by position with `..1`, `..2`, and so on. This makes it
+  possible to use the formula shorthand for functions with more than two
+  arguments (#289).
+
+* `possibly()`, `safely()` and friends no longer capture interrupts: this
+  means that you can now terminate a mapper using one of these with
+  Escape or Ctrl + C (#314)
+
+
+## Map functions
+
+* All map functions now treat `NULL` the same way as an empty vector (#199),
+  and return an empty vector if any input is an empty vector.
+
+* All `map()` functions now force their arguments in the same way that base R
+  does for `lapply()` (#191). This makes `map()` etc easier to use when
+  generating functions.
+
+* A new family of "indexed" map functions, `imap()`, `imap_lgl()` etc,
+  provide a short-hand for `map2(x, names(x))` or `map2(x, seq_along(x))`
+  (#240).
+
+* The data frame suffix `_df` has been (soft) deprecated in favour of
+  `_dfr` to more clearly indicate that it's a row-bind. All variants now
+  also have a `_dfc` for column binding (#167). (These will not be terribly
+  useful until `dplyr::bind_rows()`/`dplyr::bind_cols()` have better
+  semantics for vectors.)
+
+
+## Modify functions
+
+A new `modify()` family returns the same output of the type as the
+input `.x`. This is in contrast to the `map()` family which always
+returns a list, regardless of the input type.
+
+The modify functions are S3 generics. However their default methods
+should be sufficient for most classes since they rely on the semantics
+of `[<-`.  `modify.default()` is thus a shorthand for `x[] <- map(x, f)`.
+
+* `at_depth()` has been renamed to `modify_depth()`.
+
+* `modify_depth()` gains new `.ragged` argument, and negative depths are
+  now computed relative to the deepest component of the list (#236).
+
+
+## New functions
+
+* `auto_browse(f)` returns a new function that automatically calls `browser()`
+  if `f` throws an error (#281).
+
+* `vec_depth()` computes the depth (i.e. the number of levels of indexing)
+  or a vector (#243).
+
+* `reduce2()` and `reduce2_right()` make it possible to reduce with a
+  3 argument function where the first argument is the accumulated value, the
+  second argument is `.x`, and the third argument is `.y` (#163).
+
+* `list_modify()` extends `stats::modifyList()` to replace by position
+  if the list is not named.(#201). `list_merge()` operates similarly
+  to `list_modify()` but combines instead of replacing (#322).
+
+* The legacy function `update_list()` is basically a version of
+  `list_modify` that evaluates formulas within the list. It is likely
+  to be deprecated in the future in favour of a tidyeval interface
+  such as a list method for `dplyr::mutate()`.
+
+
+## Minor improvements and bug fixes
+
+* Thanks to @dchiu911, the unit test coverage of purrr is now much greater.
+
+* All predicate functions are re-exported from rlang (#124).
+
+* `compact()` now works with standard mapper conventions (#282).
+
+* `cross_n()` has been renamed to `cross()`. The `_n` suffix was
+  removed for consistency with `pmap()` (originally called `map_n()`
+  at the start of the project) and `transpose()` (originally called
+  `zip_n()`). Similarly, `cross_d()` has been renamed to `cross_df()`
+  for consistency with `map_df()`.
+
+* `every()` and `some()` now return `NA` if present in the input (#174).
+
+* `invoke()` uses a more robust approach to generate the argument list (#249)
+  It no longer uses lazyeval to figure out which enviroment a character `f`
+  comes from.
+
+* `is_numeric()` and `is_scalar_numeric()` are deprecated because they
+  don't test for what you might expect at first sight.
+
+* `reduce()` now throws an error if `.x` is empty and `.init` is not
+  supplied.
+
+* Deprecated functions `flatmap()`, `map3()`, `map_n()`, `walk3()`,
+  `walk_n()`, `zip2()`, `zip3()`, `zip_n()` have been removed.
+
+* `pmap()` coerces data frames to lists to avoid the expensive `[.data.frame`
+  which provides security that is unneeded here (#220).
+
+* `rdunif()` checks its inputs for validity (#211).
+
+* `set_names()` can now take a function to tranform the names programmatically
+  (#276), and you can supply names in `...` to reduce typing even more
+  more (#316). `set_names()` is now powered by `rlang::set_names()`.
+
+* `safely()` now actually uses the `quiet` argument (#296).
+
+* `transpose()` now matches by name if available (#164). You can
+  override the default choice with the new `.names` argument.
+
+* The function argument of `detect()` and `detect_index()` have been
+  renamed from `.p` to `.f`. This is because they have mapper
+  semantics rather than predicate semantics.
+
+
+# purrr 0.2.2.1
+
+This is a compatibility release with dplyr 0.6.0.
+
+* All data-frame based mappers have been removed in favour of new
+  functions and idioms in the tidyverse. `dmap()`, `dmap_at()`,
+  `dmap_if()`, `invoke_rows()`, `slice_rows()`, `map_rows()`,
+  `by_slice()`, `by_row()`, and `unslice()` have been moved to
+  purrrlyr. This is a bit of an aggresive change but it allows us to
+  make the dependencies much lighter.
+
+
+# purrr 0.2.2
+
+* Fix for dev tibble support.
+
+* `as_function()` now supports list arguments which allow recursive indexing
+   using either names or positions. They now always stop when encountering
+   the first NULL (#173).
+
+* `accumulate` and `reduce` correctly pass extra arguments to the
+   worker function.
+
+
+# purrr 0.2.1
+
+* `as_function()` gains a `.null` argument that for character and numeric
+  values allows you to specify what to return for null/absent elements (#110).
+  This can be used with any map function, e.g. `map_int(x, 1, .null = NA)`
+
+* `as_function()` is now generic.
+
+* New `is_function()` that returns `TRUE` only for regular functions.
+
+* Fix crash on GCC triggered by `invoke_rows()`.
+
+
+# purrr 0.2.0
+
+## New functions
+
+* There are two handy infix functions:
+
+    * `x %||% y` is shorthand for `if (is.null(x)) y else x` (#109).
+    * `x %@% "a"` is shorthand for `attr(x, "a", exact = TRUE)` (#69).
+
+* `accumulate()` has been added to handle recursive folding. It is shortand
+  for `Reduce(f, .x, accumulate = TRUE)` and follows a similar syntax to
+  `reduce()` (#145). A right-hand version `accumulate_right()` was also added.
+
+* `map_df()` row-binds output together. It's the equivalent of `plyr::ldply()`
+  (#127)
+
+* `flatten()` is now type-stable and always returns a list. To return a simpler
+  vector, use `flatten_lgl()`, `flatten_int()`, `flatten_dbl()`,
+  `flatten_chr()`, or `flatten_df()`.
+
+* `invoke()` has been overhauled to be more useful: it now works similarly
+  to `map_call()` when `.x` is NULL, and hence `map_call()` has been
+  deprecated. `invoke_map()` is a vectorised complement to `invoke()` (#125),
+  and comes with typed variants `invoke_map_lgl()`, `invoke_map_int()`,
+  `invoke_map_dbl()`, `invoke_map_chr()`, and `invoke_map_df()`.
+
+* `transpose()` replaces `zip2()`, `zip3()`, and `zip_n()` (#128).
+  The name more clearly reflects the intent (transposing the first and second
+  levels of list). It no longer has fields argument or the `.simplify` argument;
+  instead use the new `simplify_all()` function.
+
+* `safely()`, `quietly()`, and `possibly()` are experimental functions
+  for working with functions with side-effects (e.g. printed output,
+  messages, warnings, and errors) (#120). `safely()` is a version of `try()`
+  that modifies a function (rather than an expression), and always returns a
+  list with two components, `result` and `error`.
+
+* `list_along()` and `rep_along()` generalise the idea of `seq_along()`.
+  (#122).
+
+* `is_null()` is the snake-case version of `is.null()`.
+
+* `pmap()` (parallel map) replaces `map_n()` (#132), and has typed-variants
+  suffixed `pmap_lgl()`, `pmap_int()`, `pmap_dbl()`, `pmap_chr()`, and
+  `pmap_df()`.
+
+* `set_names()` is a snake-case alternative to `setNames()` with stricter
+  equality checking, and more convenient defaults for pipes:
+  `x %>% set_names()` is equivalent to `setNames(x, x)` (#119).
+
+
+## Row based functionals
+
+We are still figuring out what belongs in dplyr and what belongs in
+purrr. Expect much experimentation and many changes with these
+functions.
+
+* `map()` now always returns a list. Data frame support has been moved
+  to `map_df()` and `dmap()`. The latter supports sliced data frames
+  as a shortcut for the combination of `by_slice()` and `dmap()`:
+  `x %>% by_slice(dmap, fun, .collate = "rows")`. The conditional
+  variants `dmap_at()` and `dmap_if()` also support sliced data frames
+  and will recycle scalar results to the slice size.
+
+* `map_rows()` has been renamed to `invoke_rows()`. As other
+  rows-based functionals, it collates results inside lists by default,
+  but with column collation this function is equivalent to
+  `plyr::mdply()`.
+
+* The rows-based functionals gain a `.to` option to name the output
+  column as well as a `.collate` argument. The latter allows to
+  collate the output in lists (by default), on columns or on
+  rows. This makes these functions more flexible and more predictable.
+
+## Bug fixes and minor changes
+
+* `as_function()`, which converts formulas etc to functions, is now
+  exported (#123).
+
+* `rerun()` is correctly scoped (#95)
+
+* `update_list()` can now modify an element called `x` (#98).
+
+* `map*()` now use custom C code, rather than relying on `lapply()`, `mapply()`
+  etc. The performance characteristcs are very similar, but it allows us greater
+  control over the output (#118).
+
+* `map_lgl()` now has second argument `.f`, not `.p` (#134).
+
+
+## Deprecated functions
+
+* `flatmap()` -> use `map()` followed by the appropriate `flatten()`.
+
+* `map_call()` -> `invoke()`.
+
+* `map_n()` -> `pmap()`; `walk_n()` -> `pwalk()`.
+
+* `map3(x, y, z)` -> `map_n(list(x, y, z))`; `walk3(x, y, z) -> `pwalk(list(x, y, z))`
diff --git a/R/along.R b/R/along.R
new file mode 100644
index 0000000..1167161
--- /dev/null
+++ b/R/along.R
@@ -0,0 +1,29 @@
+#' Helper to create vectors with matching length.
+#'
+#' These functions take the idea of [seq_along()] and generalise
+#' it to creating lists (`list_along`) and repeating values
+#' (`rep_along`).
+#'
+#' @param x A vector.
+#' @param y Values to repeat.
+#' @return An vectors the same length as `.x`.
+#' @keywords internal
+#' @examples
+#' x <- 1:5
+#' rep_along(x, 1:2)
+#' rep_along(x, 1)
+#' list_along(x)
+#' @name along
+NULL
+
+#' @export
+#' @rdname along
+list_along <- function(x) {
+  vector("list", length(x))
+}
+
+#' @export
+#' @rdname along
+rep_along <- function(x, y) {
+  rep(y, length.out = length(x))
+}
diff --git a/R/arrays.R b/R/arrays.R
new file mode 100644
index 0000000..bfa4291
--- /dev/null
+++ b/R/arrays.R
@@ -0,0 +1,70 @@
+#' Coerce array to list
+#'
+#' `array_branch()` and `array_tree()` enable arrays to be
+#' used with purrr's functionals by turning them into lists. The
+#' details of the coercion are controlled by the `margin`
+#' argument. `array_tree()` creates an hierarchical list (a tree)
+#' that has as many levels as dimensions specified in `margin`,
+#' while `array_branch()` creates a flat list (by analogy, a
+#' branch) along all mentioned dimensions.
+#'
+#' When no margin is specified, all dimensions are used by
+#' default. When `margin` is a numeric vector of length zero, the
+#' whole array is wrapped in a list.
+#' @param array An array to coerce into a list.
+#' @param margin A numeric vector indicating the positions of the
+#'   indices to be to be enlisted. If `NULL`, a full margin is
+#'   used. If `numeric(0)`, the array as a whole is wrapped in a
+#'   list.
+#' @name array-coercion
+#' @export
+#' @examples
+#' # We create an array with 3 dimensions
+#' x <- array(1:12, c(2, 2, 3))
+#'
+#' # A full margin for such an array would be the vector 1:3. This is
+#' # the default if you don't specify a margin
+#'
+#' # Creating a branch along the full margin is equivalent to
+#' # as.list(array) and produces a list of size length(x):
+#' array_branch(x) %>% str()
+#'
+#' # A branch along the first dimension yields a list of length 2
+#' # with each element containing a 2x3 array:
+#' array_branch(x, 1) %>% str()
+#'
+#' # A branch along the first and third dimensions yields a list of
+#' # length 2x3 whose elements contain a vector of length 2:
+#' array_branch(x, c(1, 3)) %>% str()
+#'
+#' # Creating a tree from the full margin creates a list of lists of
+#' # lists:
+#' array_tree(x) %>% str()
+#'
+#' # The ordering and the depth of the tree are controlled by the
+#' # margin argument:
+#' array_tree(x, c(3, 1)) %>% str()
+array_branch <- function(array, margin = NULL) {
+    dim(array) <- dim(array) %||% length(array)
+    margin <- margin %||% seq_along(dim(array))
+
+  if (length(margin) == 0) {
+    list(array)
+  } else {
+    apply(array, margin, list) %>% flatten()
+  }
+}
+
+#' @rdname array-coercion
+#' @export
+array_tree <- function(array, margin = NULL) {
+  dim(array) <- dim(array) %||% length(array)
+  margin <- margin %||% seq_along(dim(array))
+
+  if (length(margin) > 1) {
+    new_margin <- ifelse(margin[-1] > margin[[1]], margin[-1] - 1, margin[-1])
+    apply(array, margin[[1]], . %>% array_tree(new_margin))
+  } else {
+    array_branch(array, margin)
+  }
+}
diff --git a/R/as_mapper.R b/R/as_mapper.R
new file mode 100644
index 0000000..6f973e5
--- /dev/null
+++ b/R/as_mapper.R
@@ -0,0 +1,196 @@
+#' Convert an object into a mapper function
+#'
+#' `as_mapper` is the powerhouse behind the varied function
+#' specifications that most purrr functions allow. It is an S3
+#' generic. The default method forwards its arguments to
+#' [rlang::as_function()].
+#'
+#' @param .f A function, formula, or atomic vector.
+#'
+#'   If a __function__, it is used as is.
+#'
+#'   If a __formula__, e.g. `~ .x + 2`, it is converted to a function. There
+#'   are three ways to refer to the arguments:
+#'
+#'   * For a single argument function, use `.`
+#'   * For a two argument function, use `.x` and `.y`
+#'   * For more arguments, use `..1`, `..2`, `..3` etc
+#'
+#'   This syntax allows you to create very compact anonymous functions.
+#'
+#'   If __character vector__, __numeric vector__, or __list__, it
+#'   is converted to an extractor function. Character vectors index by name
+#'   and numeric vectors index by position; use a list to index by position
+#'   and name at different levels. Within a list, wrap strings in `get_attr()`
+#'   to extract named attributes. If a component is not present, the value of
+#'   `.default` will be returned.
+#' @param .default,.null Optional additional argument for extractor functions
+#'   (i.e. when `.f` is character, integer, or list). Returned when
+#'   value is absent (does not exist) or empty (has length 0).
+#'   `.null` is deprecated; please use `.default` instead.
+#' @param ... Additional arguments passed on to methods.
+#' @export
+#' @examples
+#' as_mapper(~ . + 1)
+#' as_mapper(1)
+#'
+#' as_mapper(c("a", "b", "c"))
+#' # Equivalent to function(x) x[["a"]][["b"]][["c"]]
+#'
+#' as_mapper(list(1, "a", 2))
+#' # Equivalent to function(x) x[[1]][["a"]][[2]]
+#'
+#' as_mapper(list(1, attr_getter("a")))
+#' # Equivalent to function(x) attr(x[[1]], "a")
+#'
+#' as_mapper(c("a", "b", "c"), .null = NA)
+as_mapper <- function(.f, ...) {
+  UseMethod("as_mapper")
+}
+
+#' @export
+#' @rdname as_mapper
+#' @usage NULL
+as_function <- function(...) {
+  warning(
+    "`as_function()` is deprecated; please use `as_mapper()` or `rlang::as_function()` instead",
+    call. = FALSE
+  )
+  as_mapper(...)
+}
+
+#' Pluck out a single an element from a vector or environment
+#'
+#' @description
+#'
+#' This is a generalised form of `[[` which allows you to index deeply
+#' and flexibly into data structures. It supports R standard accessors
+#' like integer positions and string names, and also accepts arbitrary
+#' accessor functions, i.e. functions that take an object and return
+#' some internal piece.
+#'
+#' `pluck()` is often more readable than a mix of operators and
+#' accessors because it reads linearly and is free of syntactic
+#' cruft. Compare: `accessor(x[[1]])$foo` to `pluck(x, 1, accessor,
+#' "foo")`.
+#'
+#' Furthermore, `pluck()` never partial-matches unlike `$` which will
+#' select the `disp` object if you write `mtcars$di`.
+#'
+#' @details
+#'
+#' Since it handles arbitrary accessor functions, `pluck()` is a type
+#' of composition operator. However, it is indexing-oriented thanks to
+#' its handling of strings and integers. By the same token is also
+#' explicit regarding the intent of the composition (e.g. extraction).
+#'
+#' @param .x A vector or environment
+#' @param ... A list of accessors for indexing into the object. Can be
+#'   an integer position, a string name, or an accessor function. If
+#'   the object being indexed is an S4 object, accessing it by name
+#'   will return the corresponding slot.
+#'
+#'   These dots [splice lists automatically][rlang::dots_splice]. This
+#'   means you can supply arguments and lists of arguments
+#'   indistinctly.
+#' @param .default Value to use if target is empty or absent.
+#' @keywords internal
+#' @export
+#' @examples
+#' # pluck() supports integer positions, string names, and functions.
+#' # Using functions, you can easily extend pluck(). Let's create a
+#' # list of data structures:
+#' obj1 <- list("a", list(1, elt = "foobar"))
+#' obj2 <- list("b", list(2, elt = "foobaz"))
+#' x <- list(obj1, obj2)
+#'
+#' # And now an accessor for these complex data structures:
+#' my_element <- function(x) x[[2]]$elt
+#'
+#' # The accessor can then be passed to pluck:
+#' pluck(x, 1, my_element)
+#' pluck(x, 2, my_element)
+#'
+#' # Even for this simple data structure, this is more readable than
+#' # the alternative form because it requires you to read both from
+#' # right-to-left and from left-to-right in different parts of the
+#' # expression:
+#' my_element(x[[1]])
+#'
+#'
+#' # This technique is used for plucking into attributes with
+#' # attr_getter(). It takes an attribute name and returns a function
+#' # to access the attribute:
+#' obj1 <- structure("obj", obj_attr = "foo")
+#' obj2 <- structure("obj", obj_attr = "bar")
+#' x <- list(obj1, obj2)
+#'
+#' # pluck() is handy for extracting deeply into a data structure.
+#' # Here we'll first extract by position, then by attribute:
+#' pluck(x, 1, attr_getter("obj_attr"))  # From first object
+#' pluck(x, 2, attr_getter("obj_attr"))  # From second object
+#'
+#'
+#' # pluck() splices lists of arguments automatically. The following
+#' # pluck is equivalent to the one above:
+#' idx <- list(1, attr_getter("obj_attr"))
+#' pluck(x, idx)
+pluck <- function(.x, ..., .default = NULL) {
+  .Call(extract_impl, .x, dots_splice(...), .default)
+}
+
+#' @export
+#' @rdname pluck
+#' @param attr An attribute name as string.
+attr_getter <- function(attr) {
+  force(attr)
+  function(x) attr(x, attr)
+}
+
+
+# Vectors -----------------------------------------------------------------
+
+#' @export
+#' @rdname as_mapper
+as_mapper.character <- function(.f, ..., .null, .default = NULL) {
+  .default <- find_extract_default(.null, .default)
+  plucker(as.list(.f), .default)
+}
+
+#' @export
+#' @rdname as_mapper
+as_mapper.numeric <- function(.f, ..., .null, .default = NULL) {
+  .default <- find_extract_default(.null, .default)
+  plucker(as.list(.f), .default)
+}
+
+#' @export
+#' @rdname as_mapper
+as_mapper.list <- function(.f, ..., .null, .default = NULL) {
+  .default <- find_extract_default(.null, .default)
+  plucker(.f, .default)
+}
+
+find_extract_default <- function(.null, .default) {
+  if (!missing(.null)) {
+    # warning("`.null` is deprecated; please use `.default` instead", call. = FALSE)
+    .null
+  } else {
+    .default
+  }
+}
+
+plucker <- function(i, default) {
+  # Interpolation creates a closure with a more readable source
+  expr_interp(function(x, ...)
+    pluck(x, !! i, .default = !! default)
+  )
+}
+
+
+# Default -----------------------------------------------------------------
+
+#' @export
+as_mapper.default <- function(.f, ...) {
+  rlang::as_closure(.f)
+}
diff --git a/R/coerce.R b/R/coerce.R
new file mode 100644
index 0000000..613631d
--- /dev/null
+++ b/R/coerce.R
@@ -0,0 +1,10 @@
+# Used internally by map and flatten.
+# Exposed here for testing
+coerce <- function(x, type) {
+  .Call(coerce_impl, x, type)
+}
+
+coerce_lgl <- function(x) coerce(x, "logical")
+coerce_int <- function(x) coerce(x, "integer")
+coerce_dbl <- function(x) coerce(x, "double")
+coerce_chr <- function(x) coerce(x, "character")
diff --git a/R/coercion.R b/R/coercion.R
new file mode 100644
index 0000000..fee9fdd
--- /dev/null
+++ b/R/coercion.R
@@ -0,0 +1,107 @@
+#' Coerce a list to a vector
+#'
+#' `as_vector()` collapses a list of vectors into one vector. It
+#' checks that the type of each vector is consistent with
+#' `.type`. If the list can not be simplified, it throws an error.
+#' `simplify` will simplify a vector if possible; `simplify_all`
+#' will apply `simplify` to every element of a list.
+#'
+#' `.type` can be a vector mold specifying both the type and the
+#' length of the vectors to be concatenated, such as `numeric(1)`
+#' or `integer(4)`. Alternatively, it can be a string describing
+#' the type, one of: "logical", "integer", "double", "complex",
+#' "character" or "raw".
+#'
+#' @param .x A list of vectors
+#' @param .type A vector mold or a string describing the type of the
+#'   input vectors. The latter can be any of the types returned by
+#'   [typeof()], or "numeric" as a shorthand for either
+#'   "double" or "integer".
+#' @export
+#' @examples
+#' # Supply the type either with a string:
+#' as.list(letters) %>% as_vector("character")
+#'
+#' # Or with a vector mold:
+#' as.list(letters) %>% as_vector(character(1))
+#'
+#' # Vector molds are more flexible because they also specify the
+#' # length of the concatenated vectors:
+#' list(1:2, 3:4, 5:6) %>% as_vector(integer(2))
+#'
+#' # Note that unlike vapply(), as_vector() never adds dimension
+#' # attributes. So when you specify a vector mold of size > 1, you
+#' # always get a vector and not a matrix
+as_vector <- function(.x, .type = NULL) {
+  if (can_simplify(.x, .type)) {
+    unlist(.x)
+  } else {
+    stop("Cannot coerce .x to a vector", call. = FALSE)
+  }
+}
+
+#' @export
+#' @rdname as_vector
+simplify <- function(.x, .type = NULL) {
+  if (can_simplify(.x, .type)) {
+    unlist(.x)
+  } else {
+    .x
+  }
+}
+
+#' @export
+#' @rdname as_vector
+simplify_all <- function(.x, .type = NULL) {
+  map(.x, simplify, .type = .type)
+}
+
+
+# Simplify a list of atomic vectors of the same type to a vector
+#
+# simplify_list(list(1, 2, 3))
+can_simplify <- function(x, type = NULL) {
+  is_atomic <- vapply(x, is.atomic, logical(1))
+  if (!all(is_atomic)) return(FALSE)
+
+  mode <- unique(vapply(x, typeof, character(1)))
+  if (length(mode) > 1 &&
+        !all(c("double", "integer") %in% mode)) {
+    return(FALSE)
+  }
+
+  # This can be coerced safely. If type is supplied, perform
+  # additional check
+  is.null(type) || can_coerce(x, type)
+}
+
+can_coerce <- function(x, type) {
+  actual <- typeof(x[[1]])
+
+  if (is_mold(type)) {
+    lengths <- unique(map_int(x, length))
+    if (length(lengths) > 1 || !(lengths == length(type))) {
+      return(FALSE)
+    } else {
+      type <- typeof(type)
+    }
+  }
+
+  if (actual == "integer" && type %in% c("integer", "double", "numeric")) {
+    return(TRUE)
+  }
+
+  if (actual %in% c("integer", "double") && type == "numeric") {
+    return(TRUE)
+  }
+
+  actual == type
+}
+
+
+# is a mold? As opposed to a string
+is_mold <- function(type) {
+  modes <- c("numeric", "logical", "integer", "double", "complex",
+    "character", "raw")
+  length(type) > 1 || (!type %in% modes)
+}
diff --git a/R/compose.R b/R/compose.R
new file mode 100644
index 0000000..e559691
--- /dev/null
+++ b/R/compose.R
@@ -0,0 +1,27 @@
+#' Compose multiple functions
+#'
+#' @param ... n functions to apply in order from right to left.
+#' @return A function
+#' @export
+#' @examples
+#' not_null <- compose(`!`, is.null)
+#' not_null(4)
+#' not_null(NULL)
+#'
+#' add1 <- function(x) x + 1
+#' compose(add1, add1)(8)
+compose <- function(...) {
+  fs <- lapply(list(...), match.fun)
+  n <- length(fs)
+
+  last <- fs[[n]]
+  rest <- fs[-n]
+
+  function(...) {
+    out <- last(...)
+    for (f in rev(rest)) {
+      out <- f(out)
+    }
+    out
+  }
+}
diff --git a/R/composition.R b/R/composition.R
new file mode 100644
index 0000000..6ff3e6f
--- /dev/null
+++ b/R/composition.R
@@ -0,0 +1,217 @@
+#' Lift the domain of a function
+#'
+#' `lift_xy()` is a composition helper. It helps you compose
+#' functions by lifting their domain from a kind of input to another
+#' kind. The domain can be changed from and to a list (l), a vector
+#' (v) and dots (d). For example, `lift_ld(fun)` transforms a
+#' function taking a list to a function taking dots.
+#'
+#' The most important of those helpers is probably `lift_dl()`
+#' because it allows you to transform a regular function to one that
+#' takes a list. This is often essential for composition with purrr
+#' functional tools. Since this is such a common function,
+#' `lift()` is provided as an alias for that operation.
+#'
+#' @inheritParams as_vector
+#' @param ..f A function to lift.
+#' @param ... Default arguments for `..f`. These will be
+#'   evaluated only once, when the lifting factory is called.
+#' @return A function.
+#' @name lift
+#' @seealso [invoke()]
+NULL
+
+#' @rdname lift
+#' @section from ... to `list(...)` or `c(...)`:
+#'   Here dots should be taken here in a figurative way. The lifted
+#'   functions does not need to take dots per se. The function is
+#'   simply wrapped a function in [do.call()], so instead
+#'   of taking multiple arguments, it takes a single named list or
+#'   vector which will be interpreted as its arguments.  This is
+#'   particularly useful when you want to pass a row of a data frame
+#'   or a list to a function and don't want to manually pull it apart
+#'   in your function.
+#' @param .unnamed If `TRUE`, `ld` or `lv` will not
+#'   name the parameters in the lifted function signature. This
+#'   prevents matching of arguments by name and match by position
+#'   instead.
+#' @export
+#' @examples
+#' ### Lifting from ... to list(...) or c(...)
+#'
+#' x <- list(x = c(1:100, NA, 1000), na.rm = TRUE, trim = 0.9)
+#' lift_dl(mean)(x)
+#'
+#' # Or in a pipe:
+#' mean %>% lift_dl() %>% invoke(x)
+#'
+#' # You can also use the lift() alias for this common operation:
+#' lift(mean)(x)
+#'
+#' # Default arguments can also be specified directly in lift_dl()
+#' list(c(1:100, NA, 1000)) %>% lift_dl(mean, na.rm = TRUE)()
+#'
+#' # lift_dl() and lift_ld() are inverse of each other.
+#' # Here we transform sum() so that it takes a list
+#' fun <- sum %>% lift_dl()
+#' fun(list(3, NA, 4, na.rm = TRUE))
+#'
+#' # Now we transform it back to a variadic function
+#' fun2 <- fun %>% lift_ld()
+#' fun2(3, NA, 4, na.rm = TRUE)
+#'
+#' # It can sometimes be useful to make sure the lifted function's
+#' # signature has no named parameters, as would be the case for a
+#' # function taking only dots. The lifted function will take a list
+#' # or vector but will not match its arguments to the names of the
+#' # input. For instance, if you give a data frame as input to your
+#' # lifted function, the names of the columns are probably not
+#' # related to the function signature and should be discarded.
+#' lifted_identical <- lift_dl(identical, .unnamed = TRUE)
+#' mtcars[c(1, 1)] %>% lifted_identical()
+#' mtcars[c(1, 2)] %>% lifted_identical()
+lift <- function(..f, ..., .unnamed = FALSE) {
+  force(..f)
+  defaults <- list(...)
+  function(.x = list(), ...) {
+    if (.unnamed) {
+      .x <- unname(.x)
+    }
+    do.call("..f", c(.x, defaults, list(...)))
+  }
+}
+
+#' @rdname lift
+#' @export
+lift_dl <- lift
+
+#' @rdname lift
+#' @export
+lift_dv <- function(..f, ..., .unnamed = FALSE) {
+  force(..f)
+  defaults <- list(...)
+
+  function(.x, ...) {
+    if (.unnamed) {
+      .x <- unname(.x)
+    }
+    .x <- as.list(.x)
+    do.call("..f", c(.x, defaults, list(...)))
+  }
+}
+
+#' @rdname lift
+#' @section from `c(...)` to `list(...)` or `...`:
+#'   These factories allow a function taking a vector to take a list
+#'   or dots instead. The lifted function internally transforms its
+#'   inputs back to an atomic vector. purrr does not obey the usual R
+#'   casting rules (e.g., `c(1, "2")` produces a character
+#'   vector) and will produce an error if the types are not
+#'   compatible. Additionally, you can enforce a particular vector
+#'   type by supplying `.type`.
+#' @export
+#' @examples
+#' #
+#'
+#'
+#' ### Lifting from c(...) to list(...) or ...
+#'
+#' # In other situations we need the vector-valued function to take a
+#' # variable number of arguments as with pmap(). This is a job for
+#' # lift_vd():
+#' pmap(mtcars, lift_vd(mean))
+#'
+#' # lift_vd() will collect the arguments and concatenate them to a
+#' # vector before passing them to ..f. You can add a check to assert
+#' # the type of vector you expect:
+#' lift_vd(tolower, .type = character(1))("this", "is", "ok")
+lift_vl <- function(..f, ..., .type) {
+  force(..f)
+  defaults <- list(...)
+  if (missing(.type)) .type <- NULL
+
+  function(.x = list(), ...) {
+    x <- as_vector(.x, .type)
+    do.call("..f", c(list(x), defaults, list(...)))
+  }
+}
+
+#' @rdname lift
+#' @export
+lift_vd <- function(..f, ..., .type) {
+  force(..f)
+  defaults <- list(...)
+  if (missing(.type)) .type <- NULL
+
+  function(...) {
+    x <- as_vector(list(...), .type)
+    do.call("..f", c(list(x), defaults))
+  }
+}
+
+#' @rdname lift
+#' @section from list(...) to c(...) or ...:
+#' `lift_ld()` turns a function that takes a list into a
+#' function that takes dots. `lift_vd()` does the same with a
+#' function that takes an atomic vector. These factory functions are
+#' the inverse operations of `lift_dl()` and `lift_dv()`.
+#'
+#' `lift_vd()` internally coerces the inputs of `..f` to
+#' an atomic vector. The details of this coercion can be controlled
+#' with `.type`.
+#'
+#' @export
+#' @examples
+#' #
+#'
+#'
+#' ### Lifting from list(...) to c(...) or ...
+#'
+#' # cross() normally takes a list of elements and returns their
+#' # cartesian product. By lifting it you can supply the arguments as
+#' # if it was a function taking dots:
+#' cross_dots <- lift_ld(cross)
+#' out1 <- cross(list(a = 1:2, b = c("a", "b", "c")))
+#' out2 <- cross_dots(a = 1:2, b = c("a", "b", "c"))
+#' identical(out1, out2)
+#'
+#' # This kind of lifting is sometimes needed for function
+#' # composition. An example would be to use pmap() with a function
+#' # that takes a list. In the following, we use some() on each row of
+#' # a data frame to check they each contain at least one element
+#' # satisfying a condition:
+#' mtcars %>% pmap(lift_ld(some, partial(`<`, 200)))
+#'
+#' # Default arguments for ..f can be specified in the call to
+#' # lift_ld()
+#' lift_ld(cross, .filter = `==`)(1:3, 1:3) %>% str()
+#'
+#'
+#' # Here is another function taking a list and that we can update to
+#' # take a vector:
+#' glue <- function(l) {
+#'   if (!is.list(l)) stop("not a list")
+#'   l %>% invoke(paste, .)
+#' }
+#'
+#' \dontrun{
+#' letters %>% glue()           # fails because glue() expects a list}
+#'
+#' letters %>% lift_lv(glue)()  # succeeds
+lift_ld <- function(..f, ...) {
+  force(..f)
+  defaults <- list(...)
+  function(...) {
+    do.call("..f", c(list(list(...)), defaults))
+  }
+}
+
+#' @rdname lift
+#' @export
+lift_lv <- function(..f, ...) {
+  force(..f)
+  defaults <- list(...)
+  function(.x, ...) {
+    do.call("..f", c(list(as.list(.x)), defaults, list(...)))
+  }
+}
diff --git a/R/cross.R b/R/cross.R
new file mode 100644
index 0000000..040c1cb
--- /dev/null
+++ b/R/cross.R
@@ -0,0 +1,169 @@
+#' Produce all combinations of list elements
+#'
+#' `cross2()` returns the product set of the elements of
+#' `.x` and `.y`. `cross3()` takes an additional
+#' `.z` argument. `cross()` takes a list `.l` and
+#' returns the cartesian product of all its elements in a list, with
+#' one combination by element. `cross_df()` is like
+#' `cross()` but returns a data frame, with one combination by
+#' row.
+#'
+#' `cross()`, `cross2()` and `cross3()` return the
+#' cartesian product is returned in wide format. This makes it more
+#' amenable to mapping operations. `cross_df()` returns the output
+#' in long format just as `expand.grid()` does. This is adapted
+#' to rowwise operations.
+#'
+#' When the number of combinations is large and the individual
+#' elements are heavy memory-wise, it is often useful to filter
+#' unwanted combinations on the fly with `.filter`. It must be
+#' a predicate function that takes the same number of arguments as the
+#' number of crossed objects (2 for `cross2()`, 3 for
+#' `cross3()`, `length(.l)` for `cross()`) and
+#' returns `TRUE` or `FALSE`. The combinations where the
+#' predicate function returns `TRUE` will be removed from the
+#' result.
+#' @seealso [expand.grid()]
+#' @param .x,.y,.z Lists or atomic vectors.
+#' @param .l A list of lists or atomic vectors. Alternatively, a data
+#'   frame. `cross_df()` requires all elements to be named.
+#' @param .filter A predicate function that takes the same number of
+#'   arguments as the number of variables to be combined.
+#' @return `cross2()`, `cross3()` and `cross()`
+#'   always return a list. `cross_df()` always returns a data
+#'   frame. `cross()` returns a list where each element is one
+#'   combination so that the list can be directly mapped
+#'   over. `cross_df()` returns a data frame where each row is one
+#'   combination.
+#' @export
+#' @examples
+#' # We build all combinations of names, greetings and separators from our
+#' # list of data and pass each one to paste()
+#' data <- list(
+#'   id = c("John", "Jane"),
+#'   greeting = c("Hello.", "Bonjour."),
+#'   sep = c("! ", "... ")
+#' )
+#'
+#' data %>%
+#'   cross() %>%
+#'   map(lift(paste))
+#'
+#' # cross() returns the combinations in long format: many elements,
+#' # each representing one combination. With cross_df() we'll get a
+#' # data frame in long format: crossing three objects produces a data
+#' # frame of three columns with each row being a particular
+#' # combination. This is the same format that expand.grid() returns.
+#' args <- data %>% cross_df()
+#'
+#' # In case you need a list in long format (and not a data frame)
+#' # just run as.list() after cross_df()
+#' args %>% as.list()
+#'
+#' # This format is often less pratical for functional programming
+#' # because applying a function to the combinations requires a loop
+#' out <- vector("list", length = nrow(args))
+#' for (i in seq_along(out))
+#'   out[[i]] <- map(args, i) %>% invoke(paste, .)
+#' out
+#'
+#' # It's easier to transpose and then use invoke_map()
+#' args %>% transpose() %>% map_chr(~ invoke(paste, .))
+#'
+#' # Unwanted combinations can be filtered out with a predicate function
+#' filter <- function(x, y) x >= y
+#' cross2(1:5, 1:5, .filter = filter) %>% str()
+#'
+#' # To give names to the components of the combinations, we map
+#' # setNames() on the product:
+#' seq_len(3) %>%
+#'   cross2(., ., .filter = `==`) %>%
+#'   map(setNames, c("x", "y"))
+#'
+#' # Alternatively we can encapsulate the arguments in a named list
+#' # before crossing to get named components:
+#' seq_len(3) %>%
+#'   list(x = ., y = .) %>%
+#'   cross(.filter = `==`)
+cross <- function(.l, .filter = NULL) {
+  if (is_empty(.l)) {
+    return(.l)
+  }
+
+  if (!is.null(.filter)) {
+    .filter <- as_mapper(.filter)
+  }
+
+  n <- length(.l)
+  lengths <- lapply(.l, length)
+  names <- names(.l)
+
+  factors <- cumprod(lengths)
+  total_length <- factors[n]
+  factors <- c(1, factors[-n])
+
+  out <- replicate(total_length, vector("list", n), simplify = FALSE)
+
+  for (i in seq_along(out)) {
+    for (j in seq_len(n)) {
+      index <- floor((i - 1) / factors[j]) %% length(.l[[j]]) + 1
+      out[[i]][[j]] <- .l[[j]][[index]]
+    }
+    names(out[[i]]) <- names
+
+    # Filter out unwanted elements. We set them to NULL instead of
+    # completely removing them so we don't mess up the loop indexing.
+    # NULL elements are removed later on.
+    if (!is.null(.filter)) {
+      is_to_filter <- do.call(".filter", unname(out[[i]]))
+      if (!is.logical(is_to_filter) || !length(is_to_filter) == 1) {
+        stop("The filter function must return TRUE or FALSE", call. = FALSE)
+      }
+      if (is_to_filter) {
+        out[i] <- list(NULL)
+      }
+    }
+  }
+
+  # Remove filtered elements
+  compact(out)
+}
+
+#' @export
+#' @rdname cross
+cross2 <- function(.x, .y, .filter = NULL) {
+  cross(list(.x, .y), .filter = .filter)
+}
+
+#' @export
+#' @rdname cross
+cross3 <- function(.x, .y, .z, .filter = NULL) {
+  cross(list(.x, .y, .z), .filter = .filter)
+}
+
+#' @rdname cross
+#' @export
+cross_df <- function(.l, .filter = NULL) {
+  cross(.l, .filter = .filter) %>%
+    transpose() %>%
+    simplify_all() %>%
+    tibble::as_tibble()
+}
+
+#' @export
+#' @usage NULL
+#' @rdname cross
+cross_n <- function(...) {
+  warning("`cross_n()` is deprecated; please use `cross()` instead.",
+    call. = FALSE)
+  cross(...)
+}
+
+#' @export
+#' @usage NULL
+#' @rdname cross
+cross_d <- function(...) {
+  warning("`cross_d()` is deprecated; please use `cross_df()` instead.",
+    call. = FALSE)
+  cross_df(...)
+}
diff --git a/R/depth.R b/R/depth.R
new file mode 100644
index 0000000..3624a4f
--- /dev/null
+++ b/R/depth.R
@@ -0,0 +1,28 @@
+#' Compute the depth of a vector
+#'
+#' The depth of a vector is basically how many levels that you can index
+#' into it.
+#'
+#' @param x A vector
+#' @return An integer.
+#' @export
+#' @examples
+#' x <- list(
+#'   list(),
+#'   list(list()),
+#'   list(list(list(1)))
+#' )
+#' vec_depth(x)
+#' x %>% map_int(vec_depth)
+vec_depth <- function(x) {
+  if (is_null(x)) {
+    0L
+  } else if (is_atomic(x)) {
+    1L
+  } else if (is_list(x)) {
+    depths <- map_int(x, vec_depth)
+    1L + max(depths, 0L)
+  } else {
+    abort("`x` must be a vector")
+  }
+}
diff --git a/R/every-some.R b/R/every-some.R
new file mode 100644
index 0000000..0faf968
--- /dev/null
+++ b/R/every-some.R
@@ -0,0 +1,35 @@
+#' Do every or some elements of a list satisfy a predicate?
+#'
+#' @inheritParams map_if
+#' @inheritParams map
+#' @return A logical vector of length 1.
+#' @export
+#' @examples
+#' x <- list(0, 1, TRUE)
+#' x %>% every(identity)
+#' x %>% some(identity)
+#'
+#' y <- list(0:10, 5.5)
+#' y %>% every(is.numeric)
+#' y %>% every(is.integer)
+every <- function(.x, .p, ...) {
+  .p <- as_mapper(.p, ...)
+  for (i in seq_along(.x)) {
+    val <- .p(.x[[i]], ...)
+    if (is_false(val)) return(FALSE)
+    if (anyNA(val)) return(NA)
+  }
+  TRUE
+}
+
+#' @export
+#' @rdname every
+some <- function(.x, .p, ...) {
+  .p <- as_mapper(.p, ...)
+  for (i in seq_along(.x)) {
+    val <- .p(.x[[i]], ...)
+    if (is_true(val)) return(TRUE)
+    if (anyNA(val)) return(NA)
+  }
+  FALSE
+}
diff --git a/R/find-position.R b/R/find-position.R
new file mode 100644
index 0000000..6ae2e29
--- /dev/null
+++ b/R/find-position.R
@@ -0,0 +1,79 @@
+#' Find the value or position of the first match.
+#'
+#' @inheritParams every
+#' @inheritParams map
+#' @param .right If `FALSE`, the default, starts at the beginning
+#'   of the vector and move towards the end; if `TRUE`, starts at the end
+#'   of the vector and moves towards the beginning.
+#' @return `detect` the value of the first item that matches the
+#'  predicate; `detect_index` the position of the matching item.
+#'  If not found, `detect` returns `NULL` and `detect_index`
+#'  returns 0.
+#' @export
+#' @examples
+#' is_even <- function(x) x %% 2 == 0
+#'
+#' 3:10 %>% detect(is_even)
+#' 3:10 %>% detect_index(is_even)
+#'
+#' 3:10 %>% detect(is_even, .right = TRUE)
+#' 3:10 %>% detect_index(is_even, .right = TRUE)
+#'
+#'
+#' # Since `.f` is passed to as_mapper(), you can supply a
+#' # lambda-formula or a pluck object:
+#' x <- list(
+#'   list(1, foo = FALSE),
+#'   list(2, foo = TRUE),
+#'   list(3, foo = TRUE)
+#' )
+#'
+#' detect(x, "foo")
+#' detect_index(x, "foo")
+detect <- function(.x, .f, ..., .right = FALSE, .p) {
+  if (!missing(.p)) {
+    warn("`.p` has been renamed to `.f`", "purrr_2.2.3")
+    .f <- .p
+  }
+  .f <- as_mapper(.f, ...)
+
+  for (i in index(.x, .right)) {
+    if (is_true(.f(.x[[i]], ...))) return(.x[[i]])
+  }
+  NULL
+}
+
+#' @export
+#' @rdname detect
+detect_index <- function(.x, .f, ..., .right = FALSE, .p) {
+  if (!missing(.p)) {
+    warn("`.p` has been renamed to `.f`", "purrr_2.2.3")
+    .f <- .p
+  }
+  .f <- as_mapper(.f, ...)
+
+  for (i in index(.x, .right)) {
+    if (is_true(.f(.x[[i]], ...))) return(i)
+  }
+  0L
+}
+
+index <- function(x, right = FALSE) {
+  idx <- seq_along(x)
+  if (right)
+    idx <- rev(idx)
+  idx
+}
+
+#' Does a list contain an object?
+#'
+#' @inheritParams map
+#' @param .y Object to test for
+#' @export
+#' @examples
+#' x <- list(1:10, 5, 9.9)
+#' x %>% has_element(1:10)
+#' x %>% has_element(3)
+has_element <- function(.x, .y) {
+  some(.x, identical, .y)
+}
diff --git a/R/flatten.R b/R/flatten.R
new file mode 100644
index 0000000..8033810
--- /dev/null
+++ b/R/flatten.R
@@ -0,0 +1,74 @@
+#' Flatten a list of lists into a simple vector.
+#'
+#' These functions remove a level hierarchy from a list. They are similar to
+#' [unlist()], only ever remove a single layer of hierarchy, and
+#' are type-stable so you always know what the type of the output is.
+#'
+#' @param .x A list of flatten. The contents of the list can be anything for
+#'   `flatten` (as a list is returned), but the contents must match the
+#'   type for the other functions.
+#' @return `flatten()` returns a list, `flatten_lgl()` a logical
+#'   vector, `flatten_int()` an integer vector, `flatten_dbl()` a
+#'   double vector, and `flatten_chr()` a character vector.
+#'
+#'   `flatten_dfr()` and `flatten_dfc()` return data frames created by
+#'   row-binding and column-binding respectively. They require dplyr to
+#'   be installed.
+#' @inheritParams map
+#' @export
+#' @examples
+#' x <- rerun(2, sample(4))
+#' x
+#' x %>% flatten()
+#' x %>% flatten_int()
+#'
+#' # You can use flatten in conjunction with map
+#' x %>% map(1L) %>% flatten_int()
+#' # But it's more efficient to use the typed map instead.
+#' x %>% map_int(1L)
+flatten <- function(.x) {
+  .Call(flatten_impl, .x)
+}
+
+#' @export
+#' @rdname flatten
+flatten_lgl <- function(.x) {
+  .Call(vflatten_impl, .x, "logical")
+}
+
+#' @export
+#' @rdname flatten
+flatten_int <- function(.x) {
+  .Call(vflatten_impl, .x, "integer")
+}
+
+#' @export
+#' @rdname flatten
+flatten_dbl <- function(.x) {
+  .Call(vflatten_impl, .x, "double")
+}
+
+#' @export
+#' @rdname flatten
+flatten_chr <- function(.x) {
+  .Call(vflatten_impl, .x, "character")
+}
+
+#' @export
+#' @rdname flatten
+flatten_dfr <- function(.x, .id = NULL) {
+  res <- .Call(flatten_impl, .x)
+  dplyr::bind_rows(res, .id = .id)
+}
+
+#' @export
+#' @rdname flatten
+flatten_dfc <- function(.x) {
+  res <- .Call(flatten_impl, .x)
+  dplyr::bind_cols(res)
+}
+
+#' @export
+#' @rdname flatten
+#' @usage NULL
+flatten_df <- flatten_dfr
diff --git a/R/head-tail.R b/R/head-tail.R
new file mode 100644
index 0000000..45fbe6b
--- /dev/null
+++ b/R/head-tail.R
@@ -0,0 +1,31 @@
+#' Find head/tail that all satisfies a predicate.
+#'
+#' @inheritParams map_if
+#' @inheritParams map
+#' @return A vector the same type as `.x`.
+#' @export
+#' @examples
+#' pos <- function(x) x >= 0
+#' head_while(5:-5, pos)
+#' tail_while(5:-5, negate(pos))
+#'
+#' big <- function(x) x > 100
+#' head_while(0:10, big)
+#' tail_while(0:10, big)
+head_while <- function(.x, .p, ...) {
+  # Find location of first FALSE
+  loc <- detect_index(.x, negate(.p), ...)
+  if (loc == 0) return(.x)
+
+  .x[seq_len(loc - 1)]
+}
+
+#' @export
+#' @rdname head_while
+tail_while <- function(.x, .p, ...) {
+  # Find location of last FALSE
+  loc <- detect_index(.x, negate(.p), ..., .right = TRUE)
+  if (loc == 0) return(.x)
+
+  .x[-seq_len(loc)]
+}
diff --git a/R/imap.R b/R/imap.R
new file mode 100644
index 0000000..27219c7
--- /dev/null
+++ b/R/imap.R
@@ -0,0 +1,74 @@
+#' Apply a function to each element of a vector, and its index
+#'
+#' `imap_xxx(x, ...)`, an indexed map, is short hand for
+#' `map2(x, names(x), ...)` if `x` has names, or `map2(x, seq_along(x), ...)`
+#' if it does not. This is useful if you need to compute on both the value
+#' and the position of an element.
+#'
+#' @inheritParams map
+#' @return A vector the same length as `.x`.
+#' @export
+#' @family map variants
+#' @examples
+#' # Note that when using the formula shortcut, the first argument
+#' # is the value, and the second is the position
+#' imap_chr(sample(10), ~ paste0(.y, ": ", .x))
+#' iwalk(mtcars, ~ cat(.y, ": ", median(.x), "\n", sep = ""))
+imap <- function(.x, .f, ...) {
+  .f <- as_mapper(.f, ...)
+  map2(.x, vec_index(.x), .f, ...)
+}
+
+#' @rdname imap
+#' @export
+imap_lgl <- function(.x, .f, ...) {
+  .f <- as_mapper(.f, ...)
+  map2_lgl(.x, vec_index(.x), .f, ...)
+}
+
+#' @rdname imap
+#' @export
+imap_chr <- function(.x, .f, ...) {
+  .f <- as_mapper(.f, ...)
+  map2_chr(.x, vec_index(.x), .f, ...)
+}
+
+#' @rdname imap
+#' @export
+imap_int <- function(.x, .f, ...) {
+  .f <- as_mapper(.f, ...)
+  map2_int(.x, vec_index(.x), .f, ...)
+}
+
+#' @rdname imap
+#' @export
+imap_dbl <- function(.x, .f, ...) {
+  .f <- as_mapper(.f, ...)
+  map2_dbl(.x, vec_index(.x), .f, ...)
+}
+
+#' @rdname imap
+#' @export
+imap_dfr <- function(.x, .f, ..., .id = NULL) {
+  .f <- as_mapper(.f, ...)
+  map2_dfr(.x, vec_index(.x), .f, ...)
+}
+
+#' @rdname imap
+#' @export
+imap_dfc <- function(.x, .f, ..., .id = NULL) {
+  .f <- as_mapper(.f, ...)
+  map2_dfc(.x, vec_index(.x), .f, ...)
+}
+
+#' @export
+#' @rdname imap
+iwalk <- function(.x, .f, ...) {
+  .f <- as_mapper(.f, ...)
+  walk2(.x, vec_index(.x), .f, ...)
+}
+
+
+vec_index <- function(x) {
+  names(x) %||% seq_along(x)
+}
diff --git a/R/invoke.R b/R/invoke.R
new file mode 100644
index 0000000..9873233
--- /dev/null
+++ b/R/invoke.R
@@ -0,0 +1,143 @@
+#' Invoke functions.
+#'
+#' This pair of functions make it easier to combine a function and list
+#' of parameters to get a result. `invoke` is a wrapper around
+#' `do.call` that makes it easy to use in a pipe. `invoke_map`
+#' makes it easier to call lists of functions with lists of parameters.
+#'
+#' @param .f For `invoke`, a function; for `invoke_map` a
+#'   list of functions.
+#' @param .x For `invoke`, an argument-list; for `invoke_map` a
+#'   list of argument-lists the same length as `.f` (or length 1).
+#'   The default argument, `list(NULL)`, will be recycled to the
+#'   same length as `.f`, and will call each function with no
+#'   arguments (apart from any supplied in `...`.
+#' @param ... Additional arguments passed to each function.
+#' @param .env Environment in which [do.call()] should
+#'   evaluate a constructed expression. This only matters if you pass
+#'   as `.f` the name of a function rather than its value, or as
+#'   `.x` symbols of objects rather than their values.
+#' @inheritParams map
+#' @export
+#' @family map variants
+#' @examples
+#' # Invoke a function with a list of arguments
+#' invoke(runif, list(n = 10))
+#' # Invoke a function with named arguments
+#' invoke(runif, n = 10)
+#'
+#' # Combine the two:
+#' invoke(paste, list("01a", "01b"), sep = "-")
+#' # That's more natural as part of a pipeline:
+#' list("01a", "01b") %>%
+#'   invoke(paste, ., sep = "-")
+#'
+#' # Invoke a list of functions, each with different arguments
+#' invoke_map(list(runif, rnorm), list(list(n = 10), list(n = 5)))
+#' # Or with the same inputs:
+#' invoke_map(list(runif, rnorm), list(list(n = 5)))
+#' invoke_map(list(runif, rnorm), n = 5)
+#' # Or the same function with different inputs:
+#' invoke_map("runif", list(list(n = 5), list(n = 10)))
+#'
+#' # Or as a pipeline
+#' list(m1 = mean, m2 = median) %>% invoke_map(x = rcauchy(100))
+#' list(m1 = mean, m2 = median) %>% invoke_map_dbl(x = rcauchy(100))
+#'
+#' # Note that you can also match by position by explicitly omitting `.x`.
+#' # This can be useful when the argument names of the functions are not
+#' # identical
+#' list(m1 = mean, m2 = median) %>%
+#'   invoke_map(, rcauchy(100))
+#'
+#' # If you have pairs of function name and arguments, it's natural
+#' # to store them in a data frame. Here we use a tibble because
+#' # it has better support for list-columns
+#' df <- tibble::tibble(
+#'   f = c("runif", "rpois", "rnorm"),
+#'   params = list(
+#'     list(n = 10),
+#'     list(n = 5, lambda = 10),
+#'     list(n = 10, mean = -3, sd = 10)
+#'   )
+#' )
+#' df
+#' invoke_map(df$f, df$params)
+invoke <- function(.f, .x = NULL, ..., .env = NULL) {
+  .env <- .env %||% parent.frame()
+
+  args <- c(as.list(.x), list(...))
+  do.call(.f, args, envir = .env)
+}
+
+as_invoke_function <- function(f) {
+  if (is.function(f)) {
+    list(f)
+  } else {
+    f
+  }
+}
+
+#' @rdname invoke
+#' @export
+invoke_map <- function(.f, .x = list(NULL), ..., .env = NULL) {
+  .env <- .env %||% parent.frame()
+  .f <- as_invoke_function(.f)
+  map2(.f, .x, invoke, ..., .env = .env)
+}
+#' @rdname invoke
+#' @export
+invoke_map_lgl <- function(.f, .x = list(NULL), ..., .env = NULL) {
+  .env <- .env %||% parent.frame()
+  .f <- as_invoke_function(.f)
+  map2_lgl(.f, .x, invoke, ..., .env = .env)
+}
+#' @rdname invoke
+#' @export
+invoke_map_int <- function(.f, .x = list(NULL), ..., .env = NULL) {
+  .env <- .env %||% parent.frame()
+  .f <- as_invoke_function(.f)
+  map2_int(.f, .x, invoke, ..., .env = .env)
+}
+#' @rdname invoke
+#' @export
+invoke_map_dbl <- function(.f, .x = list(NULL), ..., .env = NULL) {
+  .env <- .env %||% parent.frame()
+  .f <- as_invoke_function(.f)
+  map2_dbl(.f, .x, invoke, ..., .env = .env)
+}
+#' @rdname invoke
+#' @export
+invoke_map_chr <- function(.f, .x = list(NULL), ..., .env = NULL) {
+  .env <- .env %||% parent.frame()
+  .f <- as_invoke_function(.f)
+  map2_chr(.f, .x, invoke, ..., .env = .env)
+}
+
+#' @rdname invoke
+#' @export
+invoke_map_dfr <- function(.f, .x = list(NULL), ..., .env = NULL) {
+  .env <- .env %||% parent.frame()
+  .f <- as_invoke_function(.f)
+  map2_dfr(.f, .x, invoke, ..., .env = .env)
+}
+#' @rdname invoke
+#' @export
+invoke_map_dfc <- function(.f, .x = list(NULL), ..., .env = NULL) {
+  .env <- .env %||% parent.frame()
+  .f <- as_invoke_function(.f)
+  map2_dfc(.f, .x, invoke, ..., .env = .env)
+}
+#' @rdname invoke
+#' @export
+#' @usage NULL
+invoke_map_df <- invoke_map_dfr
+
+
+#' @rdname invoke
+#' @export
+#' @usage NULL
+map_call <- function(.x, .f, ...) {
+  warning("`map_call()` is deprecated. Please use `invoke()` instead.")
+  invoke(.f, .x, ...)
+}
diff --git a/R/keep.R b/R/keep.R
new file mode 100644
index 0000000..9f2a62f
--- /dev/null
+++ b/R/keep.R
@@ -0,0 +1,48 @@
+#' Keep or discard elements using a predicate function.
+#'
+#' `keep` and `discard` are opposites. `compact` is a handy
+#' wrapper that removes all elements that are `NULL`.
+#'
+#' These are usually called `select` or `filter` and `reject` or
+#' `drop`, but those names are already taken. `keep` is similar to
+#' [Filter()] but the argument order is more convenient, and the
+#' evaluation of `.f` is stricter.
+#'
+#' @param .x A list or vector.
+#' @param ... Additional arguments passed on to `.p`.
+#' @inheritParams map_if
+#' @export
+#' @examples
+#' rep(10, 10) %>%
+#'   map(sample, 5) %>%
+#'   keep(function(x) mean(x) > 6)
+#'
+#' # Or use a formula
+#' rep(10, 10) %>%
+#'   map(sample, 5) %>%
+#'   keep(~ mean(.x) > 6)
+#'
+#' # Using a string instead of a function will select all list elements
+#' # where that subelement is TRUE
+#' x <- rerun(5, a = rbernoulli(1), b = sample(10))
+#' x
+#' x %>% keep("a")
+#' x %>% discard("a")
+keep <- function(.x, .p, ...) {
+  sel <- probe(.x, .p, ...)
+  .x[!is.na(sel) & sel]
+}
+
+#' @export
+#' @rdname keep
+discard <- function(.x, .p, ...) {
+  sel <- probe(.x, .p, ...)
+  .x[is.na(sel) | !sel]
+}
+
+#' @export
+#' @rdname keep
+compact <- function(.x, .p = identity) {
+  .f <- as_mapper(.p)
+  .x %>% discard(function(x) is_empty(.f(x)))
+}
diff --git a/R/list-modify.R b/R/list-modify.R
new file mode 100644
index 0000000..7ec28f3
--- /dev/null
+++ b/R/list-modify.R
@@ -0,0 +1,103 @@
+#' Modify a list
+#'
+#' @description
+#'
+#' `list_modify()` and `list_merge()` recursively combine two lists, matching
+#' elements either by name or position. If an sub-element is present in
+#' both lists `list_modify()` takes the value from `y`, and `list_merge()`
+#' concatenates the values together.
+#'
+#' `update_list()` handles formulas and quosures that can refer to
+#' values existing within the input list. Note that this function
+#' might be deprecated in the future in favour of a `dplyr::mutate()`
+#' method for lists.
+#'
+#' @param .x List to modify.
+#' @param ... New values of a list. Use `NULL` to remove values.
+#'   Use a formula to evaluate in the context of the list values.
+#'   These dots have [splicing semantics][rlang::dots_list].
+#' @export
+#' @examples
+#' x <- list(x = 1:10, y = 4, z = list(a = 1, b = 2))
+#' str(x)
+#'
+#' # Update values
+#' str(list_modify(x, a = 1))
+#' # Replace values
+#' str(list_modify(x, z = 5))
+#' str(list_modify(x, z = list(a = 1:5)))
+#' # Remove values
+#' str(list_modify(x, z = NULL))
+#'
+#' # Combine values
+#' str(list_merge(x, x = 11, z = list(a = 2:5, c = 3)))
+#'
+#'
+#' # All these functions take dots with splicing. Use !!! or UQS() to
+#' # splice a list of arguments:
+#' l <- list(new = 1, y = NULL, z = 5)
+#' str(list_modify(x, !!! l))
+#'
+#' # In update_list() you can also use quosures and formulas to
+#' # compute new values. This function is likely to be deprecated in
+#' # the future
+#' update_list(x, z1 = ~z[[1]])
+#' update_list(x, z = rlang::quo(x + y))
+list_modify <- function(.x, ...) {
+  dots <- dots_list(...)
+  list_recurse(.x, dots, function(x, y) y)
+}
+#' @export
+#' @rdname list_modify
+list_merge <- function(.x, ...) {
+  dots <- dots_list(...)
+  list_recurse(.x, dots, c)
+}
+
+list_recurse <- function(x, y, base_case) {
+  stopifnot(is.list(x), is.list(y))
+
+  if (is_empty(x)) {
+    return(y)
+  } else if (is_empty(y)) {
+    return(x)
+  }
+
+  x_names <- names(x)
+  y_names <- names(y)
+
+  if (!is_names(x_names) && !is_names(y_names)) {
+    for (i in rev(seq_along(y))) {
+      if (i <= length(x) && is_list(x[[i]]) && is_list(y[[i]])) {
+        x[[i]] <- list_recurse(x[[i]], y[[i]], base_case)
+      } else {
+        x[[i]] <- base_case(x[[i]], y[[i]])
+      }
+    }
+  } else if (is_names(x_names) && is_names(y_names)) {
+    for (nm in y_names) {
+      if (has_name(x, nm) && is_list(x[[nm]]) && is_list(y[[nm]])) {
+        x[[nm]] <- list_recurse(x[[nm]], y[[nm]], base_case)
+      } else {
+        x[[nm]] <- base_case(x[[nm]], y[[nm]])
+      }
+    }
+  } else {
+    stop("`x` and `y` must be either both named or both unnamed", call. = FALSE)
+  }
+
+  x
+}
+
+#' @rdname list_modify
+#' @export
+#' @usage NULL
+update_list <- function(.x, ...) {
+  dots <- dots_list(...)
+
+  formulas <- map_lgl(dots, is_bare_formula, lhs = FALSE, scoped = TRUE)
+  dots <- map_if(dots, formulas, as_quosure)
+  dots <- map_if(dots, is_quosure, eval_tidy, data = .x)
+
+  list_recurse(.x, dots, function(x, y) y)
+}
diff --git a/R/lmap.R b/R/lmap.R
new file mode 100644
index 0000000..37d64c3
--- /dev/null
+++ b/R/lmap.R
@@ -0,0 +1,110 @@
+#' Apply a function to list-elements of a list
+#'
+#' `lmap()`, `lmap_at()` and `lmap_if()` are similar to
+#' `map()`, `map_at()` and `map_if()`, with the
+#' difference that they operate exclusively on functions that take
+#' \emph{and} return a list (or data frame). Thus, instead of mapping
+#' the elements of a list (as in \code{.x[[i]]}), they apply a
+#' function `.f` to each subset of size 1 of that list (as in
+#' `.x[i]`). We call those those elements `list-elements').
+#'
+#' Mapping the list-elements `.x[i]` has several advantages. It
+#' makes it possible to work with functions that exclusively take a
+#' list or data frame. It enables `.f` to access the attributes
+#' of the encapsulating list, like the name of the components it
+#' receives. It also enables `.f` to return a larger list than
+#' the list-element of size 1 it got as input. Conversely, `.f`
+#' can also return empty lists. In these cases, the output list is
+#' reshaped with a different size than the input list `.x`.
+#' @param .x A list or data frame.
+#' @param .f A function that takes and returns a list or data frame.
+#' @inheritParams map_if
+#' @inheritParams map_at
+#' @inheritParams map
+#' @return If `.x` is a list, a list. If `.x` is a data
+#'   frame, a data frame.
+#' @family map variants
+#' @export
+#' @examples
+#' # Let's write a function that returns a larger list or an empty list
+#' # depending on some condition. This function also uses the names
+#' # metadata available in the attributes of the list-element
+#' maybe_rep <- function(x) {
+#'   n <- rpois(1, 2)
+#'   out <- rep_len(x, n)
+#'   if (length(out) > 0) {
+#'     names(out) <- paste0(names(x), seq_len(n))
+#'   }
+#'   out
+#' }
+#'
+#' # The output size varies each time we map f()
+#' x <- list(a = 1:4, b = letters[5:7], c = 8:9, d = letters[10])
+#' x %>% lmap(maybe_rep)
+#'
+#' # We can apply f() on a selected subset of x
+#' x %>% lmap_at(c("a", "d"), maybe_rep)
+#'
+#' # Or only where a condition is satisfied
+#' x %>% lmap_if(is.character, maybe_rep)
+#'
+#'
+#' # A more realistic example would be a function that takes discrete
+#' # variables in a dataset and turns them into disjunctive tables, a
+#' # form that is amenable to fitting some types of models.
+#'
+#' # A disjunctive table contains only 0 and 1 but has as many columns
+#' # as unique values in the original variable. Ideally, we want to
+#' # combine the names of each level with the name of the discrete
+#' # variable in order to identify them. Given these requirements, it
+#' # makes sense to have a function that takes a data frame of size 1
+#' # and returns a data frame of variable size.
+#' disjoin <- function(x, sep = "_") {
+#'   name <- names(x)
+#'   x <- as.factor(x[[1]])
+#'
+#'   out <- lapply(levels(x), function(level) {
+#'     as.numeric(x == level)
+#'   })
+#'
+#'   names(out) <- paste(name, levels(x), sep = sep)
+#'   tibble::as_tibble(out)
+#' }
+#'
+#' # Now, we are ready to map disjoin() on each categorical variable of a
+#' # data frame:
+#' iris %>% lmap_if(is.factor, disjoin)
+#' mtcars %>% lmap_at(c("cyl", "vs", "am"), disjoin)
+lmap <- function(.x, .f, ...) {
+  .x %>% lmap_at(seq_along(.x), .f, ...)
+}
+
+#' @rdname lmap
+#' @export
+lmap_if <- function(.x, .p, .f, ...) {
+  sel <- probe(.x, .p) %>% which()
+  .x %>% lmap_at(sel, .f, ...)
+}
+
+#' @rdname lmap
+#' @export
+lmap_at <- function(.x, .at, .f, ...) {
+  if (is_formula(.f)) {
+    .f <- as_mapper(.f, ...)
+  }
+  sel <- inv_which(.x, .at)
+
+  out <- vector("list", length(.x))
+  for (i in seq_along(.x)) {
+    res <-
+      if (sel[[i]]) {
+        .f(.x[i], ...)
+      } else {
+        .x[i]
+      }
+    stopifnot(is.list(res))
+    out[[i]] <- res
+  }
+
+  flatten(out) %>% maybe_as_data_frame(.x)
+}
diff --git a/R/map.R b/R/map.R
new file mode 100644
index 0000000..b8b75d9
--- /dev/null
+++ b/R/map.R
@@ -0,0 +1,198 @@
+#' Apply a function to each element of a vector
+#'
+#' @description
+#'
+#' The map functions transform their input by applying a function to
+#' each element and returning a vector the same length as the input.
+#'
+#' * `map()`, `map_if()` and `map_at()` always return a list. See the
+#'   [modify()] family for versions that return an object of the same
+#'   type as the input.
+#'
+#'   The `_if` and `_at` variants take a predicate function `.p` that
+#'   determines which elements of `.x` are transformed with `.f`.
+#'   transform.
+#'
+#' * `map_lgl()`, `map_int()`, `map_dbl()` and `map_chr()` return
+#'   vectors of the corresponding type (or die trying).
+#'
+#' * `map_dfr()` and `map_dfc()` return data frames created by
+#'   row-binding and column-binding respectively. They require dplyr
+#'   to be installed.
+#'
+#' * `walk()` calls `.f` for its side-effect and returns the input `.x`.
+#'
+#' @inheritParams as_mapper
+#' @param .x A list or atomic vector.
+#' @param .p A single predicate function, a formula describing such a
+#'   predicate function, or a logical vector of the same length as `.x`.
+#'   Alternatively, if the elements of `.x` are themselves lists of
+#'   objects, a string indicating the name of a logical element in the
+#'   inner lists. Only those elements where `.p` evaluates to
+#'   `TRUE` will be modified.
+#' @param .at A character vector of names or a numeric vector of
+#'   positions. Only those elements corresponding to `.at` will be
+#'   modified.
+#' @param ... Additional arguments passed on to `.f`.
+#' @return All functions return a vector the same length as `.x`.
+#'
+#'   `map()` returns a list, `map_lgl()` a logical vector, `map_int()` an
+#'   integer vector, `map_dbl()` a double vector, and `map_chr()` a character
+#'   vector. The output of `.f` will be automatically typed upwards,
+#'   e.g. logical -> integer -> double -> character.
+#'
+#'   `walk()` returns the input `.x` (invisibly). This makes it easy to
+#'   use in pipe.
+#' @export
+#' @family map variants
+#' @examples
+#' 1:10 %>%
+#'   map(rnorm, n = 10) %>%
+#'   map_dbl(mean)
+#'
+#' # Or use an anonymous function
+#' 1:10 %>%
+#'   map(function(x) rnorm(10, x))
+#'
+#' # Or a formula
+#' 1:10 %>%
+#'   map(~ rnorm(10, .x))
+#'
+#' # Extract by name or position
+#' # .default specifies value for elements that are missing or NULL
+#' l1 <- list(list(a = 1L), list(a = NULL, b = 2L), list(b = 3L))
+#' l1 %>% map("a", .default = "???")
+#' l1 %>% map_int("b", .default = NA)
+#' l1 %>% map_int(2, .default = NA)
+#'
+#' # Supply multiple values to index deeply into a list
+#' l2 <- list(
+#'   list(num = 1:3,     letters[1:3]),
+#'   list(num = 101:103, letters[4:6]),
+#'   list()
+#' )
+#' l2 %>% map(c(2, 2))
+#'
+#' # Use a list to build an extractor that mixes numeric indices and names,
+#' # and .default to provide a default value if the element does not exist
+#' l2 %>% map(list("num", 3))
+#' l2 %>% map_int(list("num", 3), .default = NA)
+#'
+#' # A more realistic example: split a data frame into pieces, fit a
+#' # model to each piece, summarise and extract R^2
+#' mtcars %>%
+#'   split(.$cyl) %>%
+#'   map(~ lm(mpg ~ wt, data = .x)) %>%
+#'   map(summary) %>%
+#'   map_dbl("r.squared")
+#'
+#' # Use map_lgl(), map_dbl(), etc to reduce to a vector.
+#' # * list
+#' mtcars %>% map(sum)
+#' # * vector
+#' mtcars %>% map_dbl(sum)
+#'
+#' # If each element of the output is a data frame, use
+#' # map_df to row-bind them together:
+#' mtcars %>%
+#'   split(.$cyl) %>%
+#'   map(~ lm(mpg ~ wt, data = .x)) %>%
+#'   map_df(~ as.data.frame(t(as.matrix(coef(.)))))
+#' # (if you also want to preserve the variable names see
+#' # the broom package)
+map <- function(.x, .f, ...) {
+  .f <- as_mapper(.f, ...)
+  .Call(map_impl, environment(), ".x", ".f", "list")
+}
+#' @rdname map
+#' @export
+map_if <- function(.x, .p, .f, ...) {
+  sel <- probe(.x, .p)
+
+  out <- list_along(.x)
+  out[sel]  <- map(.x[sel], .f, ...)
+  out[!sel] <- .x[!sel]
+
+  set_names(out, names(.x))
+}
+#' @rdname map
+#' @export
+map_at <- function(.x, .at, .f, ...) {
+  sel <- inv_which(.x, .at)
+
+  out <- list_along(.x)
+  out[sel]  <- map(.x[sel], .f, ...)
+  out[!sel] <- .x[!sel]
+
+  set_names(out, names(.x))
+}
+
+
+#' @rdname map
+#' @export
+map_lgl <- function(.x, .f, ...) {
+  .f <- as_mapper(.f, ...)
+  .Call(map_impl, environment(), ".x", ".f", "logical")
+}
+
+#' @rdname map
+#' @export
+map_chr <- function(.x, .f, ...) {
+  .f <- as_mapper(.f, ...)
+  .Call(map_impl, environment(), ".x", ".f", "character")
+}
+
+#' @rdname map
+#' @export
+map_int <- function(.x, .f, ...) {
+  .f <- as_mapper(.f, ...)
+  .Call(map_impl, environment(), ".x", ".f", "integer")
+}
+
+#' @rdname map
+#' @export
+map_dbl <- function(.x, .f, ...) {
+  .f <- as_mapper(.f, ...)
+  .Call(map_impl, environment(), ".x", ".f", "double")
+}
+
+#' @rdname map
+#' @param .id If not `NULL` a variable with this name will be created
+#'   giving either the name or the index of the data frame.
+#' @export
+map_dfr <- function(.x, .f, ..., .id = NULL) {
+  if (!is_installed("dplyr")) {
+    abort("`map_df()` requires dplyr")
+  }
+
+  .f <- as_mapper(.f, ...)
+  res <- map(.x, .f, ...)
+  dplyr::bind_rows(res, .id = .id)
+}
+
+#' @rdname map
+#' @export
+#' @usage NULL
+map_df <- map_dfr
+
+#' @rdname map
+#' @export
+map_dfc <- function(.x, .f, ...) {
+  if (!is_installed("dplyr")) {
+    abort("`map_dfc()` requires dplyr")
+  }
+
+  .f <- as_mapper(.f, ...)
+  res <- map(.x, .f, ...)
+  dplyr::bind_cols(res)
+}
+
+#' @export
+#' @rdname map
+walk <- function(.x, .f, ...) {
+  .f <- as_mapper(.f, ...)
+  for (i in seq_along(.x)) {
+    .f(.x[[i]], ...)
+  }
+  invisible(.x)
+}
diff --git a/R/map2-pmap.R b/R/map2-pmap.R
new file mode 100644
index 0000000..e4bba65
--- /dev/null
+++ b/R/map2-pmap.R
@@ -0,0 +1,224 @@
+#' Map over multiple inputs simultaneously.
+#'
+#' These functions are variants of `map()` iterate over multiple
+#' arguments in parallel. `map2()` and `walk2()` are specialised for the two
+#' argument case; `pmap()` and `pwalk()` allow you to provide any number of
+#' arguments in a list.
+#'
+#' Note that arguments to be vectorised over come before the `.f`,
+#' and arguments that are supplied to every call come after `.f`.
+#'
+#' @inheritParams map
+#' @param .x,.y Vectors of the same length. A vector of length 1 will
+#'   be recycled.
+#' @param .l A list of lists. The length of `.l` determines the
+#'   number of arguments that `.f` will be called with. List
+#'   names will be used if present.
+#' @return An atomic vector, list, or data frame, depending on the suffix.
+#'   Atomic vectors and lists will be named if `.x` or the first
+#'   element of `.l` is named.
+#'
+#'   If all input is length 0, the output will be length 0. If any
+#'   input is length 1, it will be recycled to the length of the longest.
+#' @export
+#' @family map variants
+#' @examples
+#' x <- list(1, 10, 100)
+#' y <- list(1, 2, 3)
+#' z <- list(5, 50, 500)
+#'
+#' map2(x, y, ~ .x + .y)
+#' # Or just
+#' map2(x, y, `+`)
+#'
+#' # Split into pieces, fit model to each piece, then predict
+#' by_cyl <- mtcars %>% split(.$cyl)
+#' mods <- by_cyl %>% map(~ lm(mpg ~ wt, data = .))
+#' map2(mods, by_cyl, predict)
+#'
+#' pmap(list(x, y, z), sum)
+#'
+#' # Matching arguments by position
+#' pmap(list(x, y, z), function(a, b ,c) a / (b + c))
+#'
+#' # Matching arguments by name
+#' l <- list(a = x, b = y, c = z)
+#' pmap(l, function(c, b, a) a / (b + c))
+#'
+#' # Vectorizing a function over multiple arguments
+#' df <- data.frame(
+#'   x = c("apple", "banana", "cherry"),
+#'   pattern = c("p", "n", "h"),
+#'   replacement = c("x", "f", "q"),
+#'   stringsAsFactors = FALSE
+#'   )
+#' pmap(df, gsub)
+#' pmap_chr(df, gsub)
+#'
+#' ## Use `...` to absorb unused components of input list .l
+#' df <- data.frame(
+#'   x = 1:3 + 0.1,
+#'   y = 3:1 - 0.1,
+#'   z = letters[1:3]
+#' )
+#' plus <- function(x, y) x + y
+#' \dontrun{
+#' ## this won't work
+#' pmap(df, plus)
+#' }
+#' ## but this will
+#' plus2 <- function(x, y, ...) x + y
+#' pmap_dbl(df, plus2)
+#'
+map2 <- function(.x, .y, .f, ...) {
+  .f <- as_mapper(.f, ...)
+  .Call(map2_impl, environment(), ".x", ".y", ".f", "list")
+}
+#' @export
+#' @rdname map2
+map2_lgl <- function(.x, .y, .f, ...) {
+  .f <- as_mapper(.f, ...)
+  .Call(map2_impl, environment(), ".x", ".y", ".f", "logical")
+}
+#' @export
+#' @rdname map2
+map2_int <- function(.x, .y, .f, ...) {
+  .f <- as_mapper(.f, ...)
+  .Call(map2_impl, environment(), ".x", ".y", ".f", "integer")
+}
+#' @export
+#' @rdname map2
+map2_dbl <- function(.x, .y, .f, ...) {
+  .f <- as_mapper(.f, ...)
+  .Call(map2_impl, environment(), ".x", ".y", ".f", "double")
+}
+#' @export
+#' @rdname map2
+map2_chr <- function(.x, .y, .f, ...) {
+  .f <- as_mapper(.f, ...)
+  .Call(map2_impl, environment(), ".x", ".y", ".f", "character")
+}
+#' @rdname map2
+#' @export
+map2_dfr <- function(.x, .y, .f, ..., .id = NULL) {
+  if (!is_installed("dplyr")) {
+    abort("`map2_dfr()` requires dplyr")
+  }
+
+  .f <- as_mapper(.f, ...)
+  res <- map2(.x, .y, .f, ...)
+  dplyr::bind_rows(res, .id = .id)
+}
+#' @rdname map2
+#' @export
+map2_dfc <- function(.x, .y, .f, ...) {
+  if (!is_installed("dplyr")) {
+    abort("`map2_dfc()` requires dplyr")
+  }
+
+  .f <- as_mapper(.f, ...)
+  res <- map2(.x, .y, .f, ...)
+  dplyr::bind_cols(res)
+}
+#' @rdname map2
+#' @export
+#' @usage NULL
+map2_df <- map2_dfr
+#' @export
+#' @rdname map2
+walk2 <- function(.x, .y, .f, ...) {
+  pwalk(list(.x, .y), .f, ...)
+  invisible(.x)
+}
+
+#' @export
+#' @rdname map2
+pmap <- function(.l, .f, ...) {
+  .f <- as_mapper(.f, ...)
+  if (is.data.frame(.l)) {
+    .l <- as.list(.l)
+  }
+
+  .Call(pmap_impl, environment(), ".l", ".f", "list")
+}
+
+#' @export
+#' @rdname map2
+pmap_lgl <- function(.l, .f, ...) {
+  .f <- as_mapper(.f, ...)
+  if (is.data.frame(.l)) {
+    .l <- as.list(.l)
+  }
+
+  .Call(pmap_impl, environment(), ".l", ".f", "logical")
+}
+#' @export
+#' @rdname map2
+pmap_int <- function(.l, .f, ...) {
+  .f <- as_mapper(.f, ...)
+  if (is.data.frame(.l)) {
+    .l <- as.list(.l)
+  }
+
+  .Call(pmap_impl, environment(), ".l", ".f", "integer")
+}
+#' @export
+#' @rdname map2
+pmap_dbl <- function(.l, .f, ...) {
+  .f <- as_mapper(.f, ...)
+  if (is.data.frame(.l)) {
+    .l <- as.list(.l)
+  }
+
+  .Call(pmap_impl, environment(), ".l", ".f", "double")
+}
+#' @export
+#' @rdname map2
+pmap_chr <- function(.l, .f, ...) {
+  .f <- as_mapper(.f, ...)
+  if (is.data.frame(.l)) {
+    .l <- as.list(.l)
+  }
+
+  .Call(pmap_impl, environment(), ".l", ".f", "character")
+}
+
+#' @rdname map2
+#' @export
+pmap_dfr <- function(.l, .f, ..., .id = NULL) {
+  if (!is_installed("dplyr")) {
+    abort("`pmap_dfr()` requires dplyr")
+  }
+
+  .f <- as_mapper(.f, ...)
+  res <- pmap(.l, .f, ...)
+  dplyr::bind_rows(res, .id = .id)
+}
+
+#' @rdname map2
+#' @export
+pmap_dfc <- function(.l, .f, ...) {
+  if (!is_installed("dplyr")) {
+    abort("`pmap_dfc()` requires dplyr")
+  }
+
+  .f <- as_mapper(.f, ...)
+  res <- pmap(.l, .f, ...)
+  dplyr::bind_cols(res)
+}
+
+#' @rdname map2
+#' @export
+#' @usage NULL
+pmap_df <- pmap_dfr
+
+#' @export
+#' @rdname map2
+pwalk <- function(.l, .f, ...) {
+  .f <- as_mapper(.f, ...)
+  args_list <- recycle_args(.l) %>% transpose()
+  for (args in args_list) {
+    do.call(".f", c(args, list(...)))
+  }
+  invisible(.l)
+}
diff --git a/R/modify.R b/R/modify.R
new file mode 100644
index 0000000..39bc842
--- /dev/null
+++ b/R/modify.R
@@ -0,0 +1,194 @@
+#' Modify elements selectively
+#'
+#' `modify()` is a short-cut for `x[] <- map(x, .f); return(x)`. `modify_if()`
+#' only modifies the elements of `x` that satisfy a predicate and leaves the
+#' others unchanged. `modify_at()` only modifies elements given by names or
+#' positions. `modify_depth()` only modifies elements at a given level of a
+#' nested data structure.
+#'
+#' Since the transformation can alter the structure of the input; it's
+#' your responsibility to ensure that the transformation produces a
+#' valid output. For example, if you're modifying a data frame, `.f`
+#' must preserve the length of the input.
+#'
+#'
+#' @section Genericity:
+#'
+#' All these functions are S3 generic. However, the default method is
+#' sufficient in many cases. It should be suitable for any data type
+#' that implements the subset-assignment method `[<-`.
+#'
+#' In some cases it may make sense to provide a custom implementation
+#' with a method suited to your S3 class. For example, a `grouped_df`
+#' method might take into account the grouped nature of a data frame.
+#'
+#' @inheritParams map
+#' @param .depth Level of `.x` to map on. Use a negative value to count up
+#'  from the lowest level of the list.
+#'
+#'  * `modify_depth(x, 0, fun)` is equivalent to `x[] <- fun(x)`
+#'  * `modify_depth(x, 1, fun)` is equivalent to `x[] <- map(x, fun)`
+#'  * `modify_depth(x, 2, fun)` is equivalent to `x[] <- map(x, ~ map(., fun))`
+#' @return An object the same class as `.x`
+#' @family map variants
+#' @export
+#' @examples
+#' # Convert factors to characters
+#' iris %>%
+#'   modify_if(is.factor, as.character) %>%
+#'   str()
+#'
+#' # Specify which columns to map with a numeric vector of positions:
+#' mtcars %>% modify_at(c(1, 4, 5), as.character) %>% str()
+#'
+#' # Or with a vector of names:
+#' mtcars %>% modify_at(c("cyl", "am"), as.character) %>% str()
+#'
+#' list(x = rbernoulli(100), y = 1:100) %>%
+#'   transpose() %>%
+#'   modify_if("x", ~ update_list(., y = ~ y * 100)) %>%
+#'   transpose() %>%
+#'   simplify_all()
+#'
+#' # Modify at specified depth ---------------------------
+#' l1 <- list(
+#'   obj1 = list(
+#'     prop1 = list(param1 = 1:2, param2 = 3:4),
+#'     prop2 = list(param1 = 5:6, param2 = 7:8)
+#'   ),
+#'   obj2 = list(
+#'     prop1 = list(param1 = 9:10, param2 = 11:12),
+#'     prop2 = list(param1 = 12:14, param2 = 15:17)
+#'   )
+#' )
+#'
+#' # In the above list, "obj" is level 1, "prop" is level 2 and "param"
+#' # is level 3. To apply sum() on all params, we map it at depth 3:
+#' l1 %>% modify_depth(3, sum) %>% str()
+#'
+#' # modify() lets us pluck the elements prop1/param2 in obj1 and obj2:
+#' l1 %>% modify(c("prop1", "param2")) %>% str()
+#'
+#' # But what if we want to pluck all param2 elements? Then we need to
+#' # act at a lower level:
+#' l1 %>% modify_depth(2, "param2") %>% str()
+#'
+#' # modify_depth() can be with other purrr functions to make them operate at
+#' # a lower level. Here we ask pmap() to map paste() simultaneously over all
+#' # elements of the objects at the second level. paste() is effectively
+#' # mapped at level 3.
+#' l1 %>% modify_depth(2, ~ pmap(., paste, sep = " / ")) %>% str()
+modify <- function(.x, .f, ...) {
+  UseMethod("modify")
+}
+#' @rdname modify
+#' @export
+modify.default <- function(.x, .f, ...) {
+  .x[] <- map(.x, .f, ...)
+  .x
+}
+
+#' @rdname modify
+#' @export
+modify_if <- function(.x, .p, .f, ...) {
+  UseMethod("modify_if")
+}
+#' @rdname modify
+#' @export
+modify_if.default <- function(.x, .p, .f, ...) {
+  sel <- probe(.x, .p)
+  .x[sel] <- map(.x[sel], .f, ...)
+  .x
+}
+
+#' @rdname modify
+#' @export
+modify_at <- function(.x, .at, .f, ...) {
+  UseMethod("modify_at")
+}
+#' @rdname modify
+#' @export
+modify_at.default <- function(.x, .at, .f, ...) {
+  sel <- inv_which(.x, .at)
+  .x[sel] <- map(.x[sel], .f, ...)
+  .x
+}
+
+#' @rdname modify
+#' @export
+#' @param .ragged If `TRUE`, will apply to leaves, even if they're not
+#'   at depth `.depth`. If `FALSE`, will throw an error if there are
+#'   no elements at depth `.depth`.
+modify_depth <- function(.x, .depth, .f, ..., .ragged = .depth < 0) {
+  UseMethod("modify_depth")
+}
+#' @rdname modify
+#' @export
+modify_depth.default <- function(.x, .depth, .f, ..., .ragged = .depth < 0) {
+  stopifnot(is_integerish(.depth, n = 1))
+
+  if (.depth < 0) {
+    .depth <- vec_depth(.x) + .depth
+  }
+
+  .f <- as_mapper(.f, ...)
+  modify_depth_rec(.x, .depth, .f, ..., .ragged = .ragged)
+}
+
+modify_depth_rec <- function(.x, .depth, .f, ..., .ragged = FALSE) {
+  if (.depth == 0) {
+    .x[] <- .f(.x, ...)
+  } else if (.depth == 1) {
+    if (!is.list(.x)) {
+      if (.ragged) {
+        .x[] <- .f(.x, ...)
+      } else {
+        stop("List not deep enough", call. = FALSE)
+      }
+    } else {
+      .x[] <- map(.x, .f, ...)
+    }
+  } else if (.depth > 1) {
+    .x[] <- map(.x, function(x) {
+      modify_depth_rec(x, .depth - 1, .f, ..., .ragged = .ragged)
+    })
+  } else {
+    stop("Invalid `depth`", call. = FALSE)
+  }
+  .x
+}
+
+#' @export
+#' @usage NULL
+#' @rdname modify
+at_depth <- function(.x, .depth, .f, ...) {
+  warning(
+    "at_depth() is deprecated, please use `modify_depth()` instead",
+    call. = FALSE
+  )
+  modify_depth(.x, .depth, .f, ...)
+}
+
+# Internal version of map_lgl() that works with logical vectors
+probe <- function(.x, .p, ...) {
+  if (is_logical(.p)) {
+    stopifnot(length(.p) == length(.x))
+    .p
+  } else {
+    map_lgl(.x, .p, ...)
+  }
+}
+
+inv_which <- function(x, sel) {
+  if (is.character(sel)) {
+    names <- names(x)
+    if (is.null(names)) {
+      stop("character indexing requires a named object", call. = FALSE)
+    }
+    names %in% sel
+  } else if (is.numeric(sel)) {
+    seq_along(x) %in% sel
+  } else {
+    stop("unrecognised index type", call. = FALSE)
+  }
+}
diff --git a/R/negate.R b/R/negate.R
new file mode 100644
index 0000000..65e3bb6
--- /dev/null
+++ b/R/negate.R
@@ -0,0 +1,25 @@
+#' Negate a predicate function.
+#'
+#' @inheritParams map_if
+#' @inheritParams as_mapper
+#' @return A new predicate function.
+#' @export
+#' @examples
+#' negate("x")
+#' negate(is.null)
+#' negate(~ .x > 0)
+#'
+#' x <- transpose(list(x = 1:10, y = rbernoulli(10)))
+#' x %>% keep("y") %>% length()
+#' x %>% keep(negate("y")) %>% length()
+#' # Same as
+#' x %>% discard("y") %>% length()
+negate <- function(.p, .default = FALSE) {
+  .p <- as_mapper(.p)
+
+  body(.p) <- expr({
+    ! ( !! body(.p) )
+  })
+
+  .p
+}
diff --git a/R/output.R b/R/output.R
new file mode 100644
index 0000000..4378907
--- /dev/null
+++ b/R/output.R
@@ -0,0 +1,187 @@
+#' Capture side effects.
+#'
+#' These functions wrap functions so that instead of generating side effects
+#' through printed output, messages, warnings, and errors, they return enhanced
+#' output. They are all adverbs because they modify the action of a verb (a
+#' function).
+#'
+#' @inheritParams map
+#' @param quiet Hide errors (`TRUE`, the default), or display them
+#'   as they occur?
+#' @param otherwise Default value to use when an error occurs.
+#'
+#' @return `safely`: wrapped function instead returns a list with
+#'   components `result` and `error`. One value is always `NULL`.
+#'
+#'   `quietly`: wrapped function instead returns a list with components
+#'   `result`, `output`, `messages` and `warnings`.
+#'
+#'   `possibly`: wrapped function uses a default value (`otherwise`)
+#'   whenever an error occurs.
+#'
+#' @export
+#' @examples
+#' safe_log <- safely(log)
+#' safe_log(10)
+#' safe_log("a")
+#'
+#' list("a", 10, 100) %>%
+#'   map(safe_log) %>%
+#'   transpose()
+#'
+#' # This is a bit easier to work with if you supply a default value
+#' # of the same type and use the simplify argument to transpose():
+#' safe_log <- safely(log, otherwise = NA_real_)
+#' list("a", 10, 100) %>%
+#'   map(safe_log) %>%
+#'   transpose() %>%
+#'   simplify_all()
+#'
+#' # To replace errors with a default value, use possibly().
+#' list("a", 10, 100) %>%
+#'   map_dbl(possibly(log, NA_real_))
+#'
+#' # For interactive usage, auto_browse() is useful because it automatically
+#' # starts a browser() in the right place.
+#' f <- function(x) {
+#'   y <- 20
+#'   if (x > 5) {
+#'     stop("!")
+#'   } else {
+#'     x
+#'   }
+#' }
+#' if (interactive()) {
+#'   map(1:6, auto_browse(f))
+#' }
+#'
+#' # It doesn't make sense to use auto_browse with primitive functions,
+#' # because they are implemented in C so there's no useful environment
+#' # for you to interact with.
+safely <- function(.f, otherwise = NULL, quiet = TRUE) {
+  .f <- as_mapper(.f)
+  function(...) capture_error(.f(...), otherwise, quiet)
+}
+
+#' @export
+#' @rdname safely
+quietly <- function(.f) {
+  .f <- as_mapper(.f)
+  function(...) capture_output(.f(...))
+}
+
+#' @export
+#' @rdname safely
+possibly <- function(.f, otherwise, quiet = TRUE) {
+  .f <- as_mapper(.f)
+  force(otherwise)
+
+  function(...) {
+    tryCatch(.f(...),
+      error = function(e) {
+        if (!quiet)
+          message("Error: ", e$message)
+        otherwise
+      },
+      interrupt = function(e) {
+        stop("Terminated by user", call. = FALSE)
+      }
+    )
+  }
+}
+
+#' @export
+#' @rdname safely
+auto_browse <- function(.f) {
+  if (is_primitive(.f)) {
+    abort("Can not auto_browse() primitive functions")
+  }
+
+  function(...) {
+    withCallingHandlers(
+      .f(...),
+      error = function(e) {
+        # 1: h(simpleError(msg, call))
+        # 2: .handleSimpleError(function (e)  <...>
+        # 3: stop(...)
+        frame <- ctxt_frame(4)
+        browse_in_frame(frame)
+      },
+      warning = function(e) {
+        if (getOption("warn") >= 2) {
+          frame <- ctxt_frame(7)
+          browse_in_frame(frame)
+        }
+      },
+      interrupt = function(e) {
+        stop("Terminated by user", call. = FALSE)
+      }
+    )
+  }
+}
+
+browse_in_frame <- function(frame) {
+  # ESS should problably set `.Platform$GUI == "ESS"`
+  # In the meantime, check that ESSR is attached
+  if (is_scoped("ESSR")) {
+    # Workaround ESS issue
+    with_env(frame$env, on.exit({
+      browser()
+      NULL
+    }))
+    return_from(frame)
+  } else {
+    eval_bare(quote(browser()), env = frame$env)
+  }
+}
+
+capture_error <- function(code, otherwise = NULL, quiet = TRUE) {
+  tryCatch(
+    list(result = code, error = NULL),
+    error = function(e) {
+      if (!quiet)
+        message("Error: ", e$message)
+
+      list(result = otherwise, error = e)
+    },
+    interrupt = function(e) {
+      stop("Terminated by user", call. = FALSE)
+    }
+  )
+}
+
+capture_output <- function(code) {
+  warnings <- character()
+  wHandler <- function(w) {
+    warnings <<- c(warnings, w$message)
+    invokeRestart("muffleWarning")
+  }
+
+  messages <- character()
+  mHandler <- function(m) {
+    messages <<- c(messages, m$message)
+    invokeRestart("muffleMessage")
+  }
+
+  temp <- file()
+  sink(temp)
+  on.exit({
+    sink()
+    close(temp)
+  })
+
+  result <- withCallingHandlers(
+    code,
+    warning = wHandler,
+    message = mHandler
+  )
+
+  output <- paste0(readLines(temp, warn = FALSE), collapse = "\n")
+
+  list(
+    result = result,
+    output = output,
+    warnings = warnings,
+    messages = messages
+  )
+}
diff --git a/R/partial.R b/R/partial.R
new file mode 100644
index 0000000..16b5330
--- /dev/null
+++ b/R/partial.R
@@ -0,0 +1,87 @@
+#' Partial apply a function, filling in some arguments.
+#'
+#' Partial function application allows you to modify a function by pre-filling
+#' some of the arguments.  It is particularly useful in conjunction with
+#' functionals and other function operators.
+#'
+#' @section Design choices:
+#'
+#' There are many ways to implement partial function application in R.
+#' (see e.g. `dots` in \url{https://github.com/crowding/ptools} for another
+#' approach.)  This implementation is based on creating functions that are as
+#' similar as possible to the anonymous functions that you'd create by hand,
+#' if you weren't using `partial`.
+#'
+#' @param ...f a function. For the output source to read well, this should be a
+#'   named function.
+#' @param ... named arguments to `...f` that should be partially applied.
+#' @param .env the environment of the created function. Defaults to
+#'   [parent.frame()] and you should rarely need to modify this.
+#' @param .lazy If `TRUE` arguments evaluated lazily, if `FALSE`,
+#'   evaluated when `partial` is called.
+#' @param .first If `TRUE`, the partialized arguments are placed
+#'   to the front of the function signature. If `FALSE`, they are
+#'   moved to the back. Only useful to control position matching of
+#'   arguments when the partialized arguments are not named.
+#' @export
+#' @examples
+#' # Partial is designed to replace the use of anonymous functions for
+#' # filling in function arguments. Instead of:
+#' compact1 <- function(x) discard(x, is.null)
+#'
+#' # we can write:
+#' compact2 <- partial(discard, .p = is.null)
+#'
+#' # and the generated source code is very similar to what we made by hand
+#' compact1
+#' compact2
+#'
+#' # Note that the evaluation occurs "lazily" so that arguments will be
+#' # repeatedly evaluated
+#' f <- partial(runif, n = rpois(1, 5))
+#' f
+#' f()
+#' f()
+#'
+#' # You can override this by saying .lazy = FALSE
+#' f <- partial(runif, n = rpois(1, 5), .lazy = FALSE)
+#' f
+#' f()
+#' f()
+#'
+#' # This also means that partial works fine with functions that do
+#' # non-standard evaluation
+#' my_long_variable <- 1:10
+#' plot2 <- partial(plot, my_long_variable)
+#' plot2()
+#' plot2(runif(10), type = "l")
+partial <- function(...f, ..., .env = parent.frame(), .lazy = TRUE,
+                    .first = TRUE) {
+  stopifnot(is.function(...f))
+
+  if (.lazy) {
+    fcall <- substitute(...f(...))
+  } else {
+    fcall <- make_call(substitute(...f), .args = list(...))
+  }
+
+  # Pass on ... from parent function
+  n <- length(fcall)
+  if (!.first && n > 1) {
+    tmp <- fcall[1]
+    tmp[[2]] <- quote(...)
+    tmp[seq(3, n + 1)] <- fcall[seq(2, n)]
+    names(tmp)[seq(3, n + 1)] <- names2(fcall)[seq(2, n)]
+    fcall <- tmp
+  } else {
+    fcall[[n + 1]] <- quote(...)
+  }
+
+  args <- list("..." = quote(expr = ))
+  new_function(args, fcall, .env)
+}
+
+make_call <- function(f, ..., .args = list()) {
+  if (is.character(f)) f <- as.name(f)
+  as.call(c(f, ..., .args))
+}
diff --git a/R/predicates.R b/R/predicates.R
new file mode 100644
index 0000000..ce8d315
--- /dev/null
+++ b/R/predicates.R
@@ -0,0 +1,102 @@
+#' Test is an object is integer or double
+#'
+#' Numeric is used in three different ways in base R:
+#' * as an alias for double (as in [as.numeric()])
+#' * to mean either integer or double (as in [mode()])
+#' * for something representable as numeric (as in [as.numeric()])
+#' This function tests for the second, which is often not what you want
+#' so these functions are deprecated.
+#'
+#' @export
+#' @keywords internal
+is_numeric <- function(x) {
+  warning("Deprecated", call. = FALSE)
+  is_integer(x) || is_double(x)
+}
+
+#' @export
+#' @rdname is_numeric
+is_scalar_numeric <- function(x) {
+  warning("Deprecated", call. = FALSE)
+  is_scalar_integer(x) || is_scalar_double(x)
+}
+
+# Re-exports from purrr ---------------------------------------------------
+
+#' @export
+rlang::is_bare_list
+
+#' @export
+rlang::is_bare_atomic
+
+#' @export
+rlang::is_bare_vector
+
+#' @export
+rlang::is_bare_double
+
+#' @export
+rlang::is_bare_integer
+
+#' @export
+rlang::is_bare_numeric
+
+#' @export
+rlang::is_bare_character
+
+#' @export
+rlang::is_bare_logical
+
+#' @export
+rlang::is_list
+
+#' @export
+rlang::is_atomic
+
+#' @export
+rlang::is_vector
+
+#' @export
+rlang::is_integer
+
+#' @export
+rlang::is_double
+
+#' @export
+rlang::is_character
+
+#' @export
+rlang::is_logical
+
+#' @export
+rlang::is_null
+
+#' @export
+rlang::is_function
+
+#' @export
+rlang::is_scalar_list
+
+#' @export
+rlang::is_scalar_atomic
+
+#' @export
+rlang::is_scalar_vector
+
+#' @export
+rlang::is_scalar_double
+
+#' @export
+rlang::is_scalar_character
+
+#' @export
+rlang::is_scalar_logical
+
+#' @export
+rlang::is_scalar_integer
+
+#' @export
+rlang::is_empty
+
+#' @export
+rlang::is_formula
diff --git a/R/prepend.R b/R/prepend.R
new file mode 100644
index 0000000..2132836
--- /dev/null
+++ b/R/prepend.R
@@ -0,0 +1,28 @@
+#' Prepend a vector
+#'
+#' This is a companion to [append()] to help merging two
+#' lists or atomic vectors. `prepend()` is a clearer semantic
+#' signal than `c()` that a vector is to be merged at the beginning of
+#' another, especially in a pipe chain.
+#'
+#' @param x the vector to be modified.
+#' @param values to be included in the modified vector.
+#' @param before a subscript, before which the values are to be appended.
+#' @return A merged vector.
+#' @export
+#' @examples
+#' x <- as.list(1:3)
+#'
+#' x %>% append("a")
+#' x %>% prepend("a")
+#' x %>% prepend(list("a", "b"), before = 3)
+prepend <- function(x, values, before = 1) {
+  n <- length(x)
+  stopifnot(before > 0 && before <= n)
+
+  if (before == 1) {
+    c(values, x)
+  } else {
+    c(x[1:(before - 1)], values, x[before:n])
+  }
+}
diff --git a/R/purrr.R b/R/purrr.R
new file mode 100644
index 0000000..1ed3c82
--- /dev/null
+++ b/R/purrr.R
@@ -0,0 +1,4 @@
+#' @keywords internal
+#' @import rlang
+#' @useDynLib purrr, .registration = TRUE
+"_PACKAGE"
diff --git a/R/reduce.R b/R/reduce.R
new file mode 100644
index 0000000..4ef5eb7
--- /dev/null
+++ b/R/reduce.R
@@ -0,0 +1,194 @@
+#' Reduce a list to a single value by iteratively applying a binary function.
+#'
+#' `reduce()` combines from the left, `reduce_right()` combines from
+#' the right. `reduce(list(x1, x2, x3), f)` is equivalent to
+#' `f(f(x1, x2), x3)`; `reduce_right(list(x1, x2, x3), f)` is equivalent to
+#' `f(f(x3, x2), x1)`.
+#'
+#' @inheritParams map
+#' @param .y For `reduce2()`, an additional argument that is passed to
+#'   `.f`. If `init` is not set, `.y` should be 1 element shorter than
+#'   `.x`.
+#' @param .f For `reduce()`, a 2-argument function. The function will be
+#'   passed the accumulated value as the first argument and the "next" value
+#'   as the second argument.
+#'
+#'   For `reduce2()`, a 3-argument function. The function will be passed the
+#'   accumulated value as the first argument, the next value of `.x` as the
+#'   second argument, and the next value of `.y` as the third argument.
+#'
+#' @param .init If supplied, will be used as the first value to start
+#'   the accumulation, rather than using \code{x[[1]]}. This is useful if
+#'   you want to ensure that `reduce` returns a correct value when `.x`
+#'   is empty. If missing, and `x` is empty, will throw an error.
+#' @export
+#' @examples
+#' 1:3 %>% reduce(`+`)
+#' 1:10 %>% reduce(`*`)
+#'
+#' paste2 <- function(x, y, sep = ".") paste(x, y, sep = sep)
+#' letters[1:4] %>% reduce(paste2)
+#' letters[1:4] %>% reduce2(c("-", ".", "-"), paste2)
+#'
+#' samples <- rerun(2, sample(10, 5))
+#' samples
+#' reduce(samples, union)
+#' reduce(samples, intersect)
+#'
+#' x <- list(c(0, 1), c(2, 3), c(4, 5))
+#' x %>% reduce(c)
+#' x %>% reduce_right(c)
+#' # Equivalent to:
+#' x %>% rev() %>% reduce(c)
+reduce <- function(.x, .f, ..., .init) {
+  reduce_impl(.x, .f, ..., .init = .init, .left = TRUE)
+}
+
+#' @export
+#' @rdname reduce
+reduce_right <- function(.x, .f, ..., .init) {
+  reduce_impl(.x, .f, ..., .init = .init, .left = FALSE)
+}
+
+#' @export
+#' @rdname reduce
+reduce2 <- function(.x, .y, .f, ..., .init) {
+  reduce2_impl(.x, .y, .f, ..., .init = .init, .left = TRUE)
+}
+
+#' @export
+#' @rdname reduce
+reduce2_right <- function(.x, .y, .f, ..., .init) {
+  reduce2_impl(.x, .f, .y, ..., .init = .init, .left = FALSE)
+}
+
+reduce2_impl <- function(.x, .y, .f, ..., .init, .left = TRUE) {
+  out <- reduce_init(.x, .init, left = .left)
+  x_idx <- reduce_index(.x, .init, left = .left)
+  y_idx <- reduce_index(.y, NULL, left = .left)
+
+  if (length(x_idx) != length(y_idx)) {
+    stop("`.y` does not have length ", length(x_idx))
+  }
+
+  .f <- as_mapper(.f, ...)
+  for (i in seq_along(x_idx)) {
+    x_i <- x_idx[[i]]
+    y_i <- y_idx[[i]]
+
+    out <- .f(out, .x[[x_i]], .y[[y_i]], ...)
+  }
+
+  out
+}
+
+
+reduce_impl <- function(.x, .f, ..., .init, .left = TRUE) {
+  out <- reduce_init(.x, .init, left = .left)
+  idx <- reduce_index(.x, .init, left = .left)
+
+  .f <- as_mapper(.f, ...)
+  for (i in idx) {
+    out <- .f(out, .x[[i]], ...)
+  }
+
+  out
+}
+
+reduce_init <- function(x, init, left = TRUE) {
+  if (!missing(init)) {
+    init
+  } else {
+    if (is_empty(x)) {
+      stop("`.x` is empty, and no `.init` supplied", call. = FALSE)
+    } else if (left) {
+      x[[1]]
+    } else {
+      x[[length(x)]]
+    }
+  }
+}
+
+reduce_index <- function(x, init, left = TRUE) {
+  n <- length(x)
+
+  if (!missing(init)) {
+    if (left) {
+      seq_len(n)
+    } else {
+      rev(seq_len(n))
+    }
+  } else {
+    if (left) {
+      seq_len2(2L, n)
+    } else {
+      rev(seq_len2(1L, n - 1L))
+    }
+  }
+}
+
+seq_len2 <- function(start, end) {
+  if (start > end) {
+    return(integer(0))
+  }
+
+  start:end
+}
+
+#' Accumulate recursive folds across a list
+#'
+#' `accumulate` applies a function recursively over a list from the left, while
+#' `accumulate_right` applies the function from the right. Unlike `reduce`
+#' both functions keep the intermediate results.
+#'
+#' @inheritParams reduce
+#' @export
+#' @examples
+#' 1:3 %>% accumulate(`+`)
+#' 1:10 %>% accumulate_right(`*`)
+#'
+#' # From Haskell's scanl documentation
+#' 1:10 %>% accumulate(max, .init = 5)
+#'
+#' # Understanding the arguments .x and .y when .f
+#' # is a lambda function
+#' # .x is the accumulating value
+#' 1:10 %>% accumulate(~ .x)
+#' # .y is element in the list
+#' 1:10 %>% accumulate(~ .y)
+#'
+#' # Simulating stochastic processes with drift
+#' \dontrun{
+#' library(dplyr)
+#' library(ggplot2)
+#'
+#' rerun(5, rnorm(100)) %>%
+#'   set_names(paste0("sim", 1:5)) %>%
+#'   map(~ accumulate(., ~ .05 + .x + .y)) %>%
+#'   map_df(~ data_frame(value = .x, step = 1:100), .id = "simulation") %>%
+#'   ggplot(aes(x = step, y = value)) +
+#'     geom_line(aes(color = simulation)) +
+#'     ggtitle("Simulations of a random walk with drift")
+#' }
+accumulate <- function(.x, .f, ..., .init) {
+  .f <- as_mapper(.f, ...)
+
+  f <- function(x, y) {
+    .f(x, y, ...)
+  }
+
+  Reduce(f, .x, init = .init, accumulate = TRUE)
+}
+
+#' @export
+#' @rdname accumulate
+accumulate_right <- function(.x, .f, ..., .init) {
+  .f <- as_mapper(.f, ...)
+
+  # Note the order of arguments is switched
+  f <- function(x, y) {
+    .f(y, x, ...)
+  }
+
+  Reduce(f, .x, init = .init, right = TRUE, accumulate = TRUE)
+}
diff --git a/R/rerun.R b/R/rerun.R
new file mode 100644
index 0000000..ef289ba
--- /dev/null
+++ b/R/rerun.R
@@ -0,0 +1,37 @@
+#' Re-run expressions multiple times.
+#'
+#' This is a convenient way of generating sample data. It works similarly to
+#' \code{\link{replicate}(..., simplify = FALSE)}.
+#'
+#' @param .n Number of times to run expressions
+#' @param ... Expressions to re-run.
+#' @return A list of length `.n`. Each element of `...` will be
+#'   re-run once for each `.n`. It
+#'
+#'   There is one special case: if there's a single unnamed input, the second
+#'   level list will be dropped. In this case, `rerun(n, x)` behaves like
+#'   `replicate(n, x, simplify = FALSE)`.
+#' @export
+#' @examples
+#' 10 %>% rerun(rnorm(5))
+#' 10 %>%
+#'   rerun(x = rnorm(5), y = rnorm(5)) %>%
+#'   map_dbl(~ cor(.x$x, .x$y))
+rerun <- function(.n, ...) {
+  dots <- quos(...)
+
+  # Special case: if single unnamed argument, insert directly into the output
+  # rather than wrapping in a list.
+  if (length(dots) == 1 && !has_names(dots)) {
+    dots <- dots[[1]]
+    eval_dots <- eval_tidy
+  } else {
+    eval_dots <- function(x) lapply(x, eval_tidy)
+  }
+
+  out <- vector("list", .n)
+  for (i in seq_len(.n)) {
+    out[[i]] <- eval_dots(dots)
+  }
+  out
+}
diff --git a/R/set_names.R b/R/set_names.R
new file mode 100644
index 0000000..3c7c578
--- /dev/null
+++ b/R/set_names.R
@@ -0,0 +1,35 @@
+#' @title Set names in a vector
+#'
+#' @details
+#' This is a snake case wrapper for [stats::setNames()], with
+#' tweaked defaults, and stricter argument checking.
+#'
+#' @usage set_names(x, nm = x, ...)
+#' @param x Vector to name
+#' @param nm,... Vector of names, the same length as `x`.
+#'
+#'   You can specify names in three ways:
+#'
+#'   * If you do nothing, `x` will be named with itself
+#'
+#'   * You can supply either a character vector to `nm` or individual
+#'     strings in to `...``
+#'
+#'   * If `x` already has names, you can provide a function or formula
+#'     to transform the existing names.
+#'
+#' @return `.x` with the names attribute set.
+#' @export
+#' @examples
+#' set_names(1:4, c("a", "b", "c", "d"))
+#' set_names(1:4, letters[1:4])
+#' set_names(1:4, "a", "b", "c", "d")
+#'
+#' # If the second argument is ommitted a vector is named with itself
+#' set_names(letters[1:5])
+#'
+#' # Alternatively you can supply a function
+#' set_names(1:10, ~ letters[seq_along(.)])
+#' set_names(head(mtcars), toupper)
+#' @name set_names
+rlang::set_names
diff --git a/R/splice.R b/R/splice.R
new file mode 100644
index 0000000..58a388c
--- /dev/null
+++ b/R/splice.R
@@ -0,0 +1,30 @@
+#' Splice objects and lists of objects into a list
+#'
+#' This splices all arguments into a list. Non-list objects and lists
+#' with a S3 class are encapsulated in a list before concatenation.
+#'
+#' @param ... Objects to concatenate.
+#' @return A list.
+#' @export
+#' @examples
+#' inputs <- list(arg1 = "a", arg2 = "b")
+#'
+#' # splice() concatenates the elements of inputs with arg3
+#' splice(inputs, arg3 = c("c1", "c2")) %>% str()
+#' list(inputs, arg3 = c("c1", "c2")) %>% str()
+#' c(inputs, arg3 = c("c1", "c2")) %>% str()
+splice <- function(...) {
+  splice_if(list(...), is_bare_list)
+}
+
+splice_if <- function(.x, .p) {
+  unspliced <- !probe(.x, .p)
+  out <- modify_if(.x, unspliced, list)
+
+  # Copy outer names to inner
+  if (!is.null(names(.x))) {
+    out[unspliced] <- map2(out[unspliced], names(out)[unspliced], set_names)
+  }
+
+  flatten(out)
+}
diff --git a/R/transpose.R b/R/transpose.R
new file mode 100644
index 0000000..e398797
--- /dev/null
+++ b/R/transpose.R
@@ -0,0 +1,51 @@
+#' Transpose a list.
+#'
+#' Transpose turns a list-of-lists "inside-out"; it turns a pair of lists into a
+#' list of pairs, or a list of pairs into pair of lists. For example,
+#' if you had a list of length n where each component had values `a` and
+#' `b`, `transpose()` would make a list with elements `a` and
+#' `b` that contained lists of length n. It's called transpose because
+#' \code{x[[1]][[2]]} is equivalent to \code{transpose(x)[[2]][[1]]}.
+#'
+#' Note that `transpose()` is its own inverse, much like the
+#' transpose operation on a matrix. You can get back the original
+#' input by transposing it twice.
+#'
+#' @param .l A list of vectors to zip. The first element is used as the
+#'   template; you'll get a warning if a sub-list is not the same length as
+#'   the first element.
+#' @param .names For efficiency, `transpose()` usually inspects the
+#'   first component of `.l` to determine the structure. Use `.names`
+#'   if you want to override this default.
+#' @return A list with indexing transposed compared to `.l`.
+#' @export
+#' @examples
+#' x <- rerun(5, x = runif(1), y = runif(5))
+#' x %>% str()
+#' x %>% transpose() %>% str()
+#' # Back to where we started
+#' x %>% transpose() %>% transpose() %>% str()
+#'
+#' # transpose() is useful in conjunction with safely() & quietly()
+#' x <- list("a", 1, 2)
+#' y <- x %>% map(safely(log))
+#' y %>% str()
+#' y %>% transpose() %>% str()
+#'
+#' # Use simplify_all() to reduce to atomic vectors where possible
+#' x <- list(list(a = 1, b = 2), list(a = 3, b = 4), list(a = 5, b = 6))
+#' x %>% transpose()
+#' x %>% transpose() %>% simplify_all()
+#'
+#' # Provide explicit component names to prevent loss of those that don't
+#' # appear in first component
+#' ll <- list(
+#'   list(x = 1, y = "one"),
+#'   list(z = "deux", x = 2)
+#' )
+#' ll %>% transpose()
+#' nms <- ll %>% map(names) %>% reduce(union)
+#' ll %>% transpose(.names = nms)
+transpose <- function(.l, .names = NULL) {
+  .Call(transpose_impl, .l, .names)
+}
diff --git a/R/utils.R b/R/utils.R
new file mode 100644
index 0000000..77740a4
--- /dev/null
+++ b/R/utils.R
@@ -0,0 +1,114 @@
+#' Pipe operator
+#'
+#' @name %>%
+#' @rdname pipe
+#' @keywords internal
+#' @export
+#' @importFrom magrittr %>%
+#' @usage lhs \%>\% rhs
+NULL
+
+maybe_as_data_frame <- function(out, x) {
+  if (is.data.frame(x)) {
+    tibble::as_tibble(out)
+  } else {
+    out
+  }
+}
+
+recycle_args <- function(args) {
+  lengths <- map_int(args, length)
+  n <- max(lengths)
+
+  stopifnot(all(lengths == 1L | lengths == n))
+  to_recycle <- lengths == 1L
+  args[to_recycle] <- lapply(args[to_recycle], function(x) rep.int(x, n))
+  args
+}
+
+names2 <- function(x) {
+  names(x) %||% rep("", length(x))
+}
+
+#' Default value for `NULL`.
+#'
+#' This infix function makes it easy to replace `NULL`s with a
+#' default value. It's inspired by the way that Ruby's or operation (`||`)
+#' works.
+#'
+#' @param x,y If `x` is NULL, will return `y`; otherwise returns
+#'   `x`.
+#' @export
+#' @name null-default
+#' @examples
+#' 1 %||% 2
+#' NULL %||% 2
+`%||%` <- function(x, y) {
+  if (is.null(x)) {
+    y
+  } else {
+    x
+  }
+}
+
+#' Infix attribute accessor
+#'
+#' @param x Object
+#' @param name Attribute name
+#' @export
+#' @name get-attr
+#' @examples
+#' factor(1:3) %@% "levels"
+#' mtcars %@% "class"
+`%@%` <- function(x, name) attr(x, name, exact = TRUE)
+
+
+#' Generate random sample from a Bernoulli distribution
+#'
+#' @param n Number of samples
+#' @param p Probability of getting `TRUE`
+#' @return A logical vector
+#' @export
+#' @examples
+#' rbernoulli(10)
+#' rbernoulli(100, 0.1)
+rbernoulli <- function(n, p = 0.5) {
+  stats::runif(n) > (1 - p)
+}
+
+#' Generate random sample from a discrete uniform distribution
+#'
+#' @param n Number of samples to draw.
+#' @param a,b Range of the distribution (inclusive).
+#' @export
+#' @examples
+#' table(rdunif(1e3, 10))
+#' table(rdunif(1e3, 10, -5))
+rdunif <- function(n, b, a = 1) {
+  stopifnot(is.numeric(a), length(a) == 1)
+  stopifnot(is.numeric(b), length(b) == 1)
+
+  a1 <- min(a, b)
+  b1 <- max(a, b)
+
+  sample(b1 - a1 + 1, n, replace = TRUE) + a1 - 1
+}
+
+# magrittr placeholder
+globalVariables(".")
+
+
+has_names <- function(x) {
+  nms <- names(x)
+  if (is.null(nms)) {
+    rep_along(x, FALSE)
+  } else {
+    !(is.na(nms) | nms == "")
+  }
+}
+
+ndots <- function(...) nargs()
+
+is_names <- function(nms) {
+  is_character(nms) && !any(is.na(nms) | nms == "")
+}
diff --git a/R/when.R b/R/when.R
new file mode 100644
index 0000000..7ca6d31
--- /dev/null
+++ b/R/when.R
@@ -0,0 +1,94 @@
+#' Match/validate a set of conditions for an object and continue with the action
+#' associated with the first valid match.
+#'
+#' `when` is a flavour of pattern matching (or an if-else abstraction) in
+#' which a value is matched against a sequence of condition-action sets. When a
+#' valid match/condition is found the action is executed and the result of the
+#' action is returned.
+#'
+#' @param .   the value to match against
+#' @param ... formulas; each containing a condition as LHS and an action as RHS.
+#'   named arguments will define additional values.
+#' @keywords internal
+#' @return The value resulting from the action of the first valid
+#'   match/condition is returned. If no matches are found, and no default is
+#'   given, NULL will be returned.
+#'
+# @details condition-action sets are written as formulas with conditions as
+#   left-hand sides and actions as right-hand sides. A formula with only a
+#   right-hand will be treated as a condition which is always satisfied. For
+#   such a default case one can also omit the `~` symbol, but note that its
+#   value will then be evaluated. Any named argument will be made available in
+#   all conditions and actions, which is useful in avoiding repeated temporary
+#   computations or temporary assignments.
+#
+#' Validity of the conditions are tested with `isTRUE`, or equivalently
+#' with `identical(condition, TRUE)`.
+#' In other words conditions resulting in more than one logical will never
+#' be valid. Note that the input value is always treated as a single object,
+#' as opposed to the `ifelse` function.
+#'
+#' @examples
+#' 1:10 %>%
+#'   when(
+#'     sum(.) <=  50 ~ sum(.),
+#'     sum(.) <= 100 ~ sum(.)/2,
+#'     ~ 0
+#'   )
+#'
+#' 1:10 %>%
+#'   when(
+#'     sum(.) <=   x ~ sum(.),
+#'     sum(.) <= 2*x ~ sum(.)/2,
+#'     ~ 0,
+#'     x = 60
+#'   )
+#'
+#' iris %>%
+#'   subset(Sepal.Length > 10) %>%
+#'   when(
+#'     nrow(.) > 0 ~ .,
+#'     ~ iris %>% head(10)
+#'   )
+#'
+#' iris %>%
+#'   head %>%
+#'   when(nrow(.) < 10 ~ .,
+#'        ~ stop("Expected fewer than 10 rows."))
+#' @export
+when <- function(., ...) {
+  dots   <- list(...)
+  names  <- names(dots)
+  named  <- if (is.null(names)) rep(FALSE, length(dots)) else names != ""
+
+  if (sum(!named) == 0)
+    stop("At least one matching condition is needed.",
+         call. = FALSE)
+
+  is_formula <-
+    vapply(dots,
+           function(dot) identical(class(dot), "formula"),
+           logical(1L))
+
+  env <- new.env(parent = parent.frame())
+  env[["."]] <- .
+
+  if (sum(named) > 0)
+    for (i in which(named))
+      env[[names[i]]] <- dots[[i]]
+
+  result <- NULL
+  for (i in which(!named)) {
+    if (is_formula[i]) {
+      action <- length(dots[[i]])
+      if (action == 2 || is_true(eval(dots[[i]][[2]], env, env))) {
+        result <- eval(dots[[i]][[action]], env, env)
+        break
+      }
+    } else {
+      result <- dots[[i]]
+    }
+  }
+
+  result
+}
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..f3f1d20
--- /dev/null
+++ b/README.md
@@ -0,0 +1,51 @@
+
+<!-- README.md is generated from README.Rmd. Please edit that file -->
+purrr <img src="man/figures/logo.png" align="right" />
+======================================================
+
+[![CRAN\_Status\_Badge](http://www.r-pkg.org/badges/version/purrr)](http://cran.r-project.org/package=purrr) [![Build Status](https://travis-ci.org/tidyverse/purrr.svg?branch=master)](https://travis-ci.org/tidyverse/purrr) <!-- [![Coverage Status](https://img.shields.io/codecov/c/github/tidyverse/purrr/master.svg)](https://codecov.io/github/tidyverse/purrr?branch=master) -->
+
+Overview
+--------
+
+purrr enhances R's functional programming (FP) toolkit by providing a complete and consistent set of tools for working with functions and vectors. If you've never heard of FP before, the best place to start is the family of `map()` functions which allow you to replace many for loops with code that is both more succinct and easier to read. The best place to learn about the `map()` functions is the [iteration chapter](http://r4ds.had.co.nz/iteration.html) in R for data science.
+
+Installation
+------------
+
+``` r
+# The easiest way to get purrr is to install the whole tidyverse:
+install.packages("tidyverse")
+
+# Alternatively, install just purrr:
+install.packages("purrr")
+
+# Or the the development version from GitHub:
+# install.packages("devtools")
+devtools::install_github("tidyverse/purrr")
+```
+
+Usage
+-----
+
+The following example uses purrr to solve a fairly realistic problem: split a data frame into pieces, fit a model to each piece, compute the summary, then extract the R<sup>2</sup>.
+
+``` r
+library(purrr)
+
+mtcars %>%
+  split(.$cyl) %>% # from base R
+  map(~ lm(mpg ~ wt, data = .)) %>%
+  map(summary) %>%
+  map_dbl("r.squared")
+#>         4         6         8 
+#> 0.5086326 0.4645102 0.4229655
+```
+
+This example illustrates some of the advantages of purrr functions over the equivalents in base R:
+
+-   The first argument is always the data, so purrr works naturally with the pipe.
+
+-   All purrr functions are type-stable. They always return the advertised output type (`map()` returns lists; `map_dbl()` returns double vectors), or they throw an errror.
+
+-   All `map()` functions either accept function, formulas (used for succinctly generating anonymous functions), a character vector (used to extract components by name), or a numeric vector (used to extract by position).
diff --git a/build/vignette.rds b/build/vignette.rds
new file mode 100644
index 0000000..8b1719d
Binary files /dev/null and b/build/vignette.rds differ
diff --git a/inst/doc/other-langs.Rmd b/inst/doc/other-langs.Rmd
new file mode 100644
index 0000000..e029eff
--- /dev/null
+++ b/inst/doc/other-langs.Rmd
@@ -0,0 +1,46 @@
+---
+title: "Functional programming in other languages"
+output: rmarkdown::html_vignette
+vignette: >
+  %\VignetteIndexEntry{Functional programming in other languages}
+  %\VignetteEngine{knitr::rmarkdown}
+  %\VignetteEncoding{UTF-8}
+---
+
+purrr draws inspiration from many related tools:
+
+* List operations defined in the Haskell [prelude][haskell]
+
+* Scala's [list methods][scala].
+
+* Functional programming librarys for javascript: 
+  [underscore.js](http://underscorejs.org), 
+  [lodash](https://lodash.com) and 
+  [lazy.js](http://danieltao.com/lazy.js/).
+
+* [rlist](http://renkun.me/rlist/), another R package to support working
+  with lists. Similar goals but somewhat different philosophy.
+
+However, the goal of purrr is not to try and simulate a purer functional programming language in R; we don't want to implement a second-class version of Haskell in R. The goal is to give you similar expressiveness to an FP language, while allowing you to write code that looks and works like R:
+
+* Instead of point free (tacit) style, we use the pipe, `%>%`, to write code 
+  that can be read from left to right.
+
+* Instead of currying, we use `...` to pass in extra arguments.
+
+* Anonymous functions are verbose in R, so we provide two convenient shorthands.
+  For unary functions, `~ .x + 1` is equivalent to `function(.x) .x + 1`.
+  For chains of transformations functions, `. %>% f() %>% g()` is
+  equivalent to `function(.) . %>% f() %>% g()` (this shortcut is provided
+  by magrittr).
+
+* R is weakly typed, so we need `map` variants that describe the output type 
+  (like `map_int()`, `map_dbl()`, etc) because don't the return type of `.f`.
+
+* R has named arguments, so instead of providing different functions for
+  minor variations (e.g. `detect()` and `detectLast()`) we use a named
+  argument, `.right`. Type-stable functions are easy to reason about so
+  additional arguments will never change the type of the output.
+
+[scala]:http://www.scala-lang.org/api/current/index.html#scala.collection.immutable.List
+[haskell]:http://hackage.haskell.org/package/base-4.7.0.1/docs/Prelude.html#g:11
diff --git a/inst/doc/other-langs.html b/inst/doc/other-langs.html
new file mode 100644
index 0000000..d1696a2
--- /dev/null
+++ b/inst/doc/other-langs.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+
+<meta charset="utf-8" />
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<meta name="generator" content="pandoc" />
+
+<meta name="viewport" content="width=device-width, initial-scale=1">
+
+
+
+<title>Functional programming in other languages</title>
+
+
+
+
+
+
+<link href="data:text/css,body%20%7B%0A%20%20background%2Dcolor%3A%20%23fff%3B%0A%20%20margin%3A%201em%20auto%3B%0A%20%20max%2Dwidth%3A%20700px%3B%0A%20%20overflow%3A%20visible%3B%0A%20%20padding%2Dleft%3A%202em%3B%0A%20%20padding%2Dright%3A%202em%3B%0A%20%20font%2Dfamily%3A%20%22Open%20Sans%22%2C%20%22Helvetica%20Neue%22%2C%20Helvetica%2C%20Arial%2C%20sans%2Dserif%3B%0A%20%20font%2Dsize%3A%2014px%3B%0A%20%20line%2Dheight%3A%201%2E35%3B%0A%7D%0A%0A%23header%20%7B%0A%20%20text%2Dalign%3A% [...]
+
+</head>
+
+<body>
+
+
+
+
+<h1 class="title toc-ignore">Functional programming in other languages</h1>
+
+
+
+<p>purrr draws inspiration from many related tools:</p>
+<ul>
+<li><p>List operations defined in the Haskell <a href="http://hackage.haskell.org/package/base-4.7.0.1/docs/Prelude.html#g:11">prelude</a></p></li>
+<li><p>Scala’s <a href="http://www.scala-lang.org/api/current/index.html#scala.collection.immutable.List">list methods</a>.</p></li>
+<li><p>Functional programming librarys for javascript: <a href="http://underscorejs.org">underscore.js</a>, <a href="https://lodash.com">lodash</a> and <a href="http://danieltao.com/lazy.js/">lazy.js</a>.</p></li>
+<li><p><a href="http://renkun.me/rlist/">rlist</a>, another R package to support working with lists. Similar goals but somewhat different philosophy.</p></li>
+</ul>
+<p>However, the goal of purrr is not to try and simulate a purer functional programming language in R; we don’t want to implement a second-class version of Haskell in R. The goal is to give you similar expressiveness to an FP language, while allowing you to write code that looks and works like R:</p>
+<ul>
+<li><p>Instead of point free (tacit) style, we use the pipe, <code>%>%</code>, to write code that can be read from left to right.</p></li>
+<li><p>Instead of currying, we use <code>...</code> to pass in extra arguments.</p></li>
+<li><p>Anonymous functions are verbose in R, so we provide two convenient shorthands. For unary functions, <code>~ .x + 1</code> is equivalent to <code>function(.x) .x + 1</code>. For chains of transformations functions, <code>. %>% f() %>% g()</code> is equivalent to <code>function(.) . %>% f() %>% g()</code> (this shortcut is provided by magrittr).</p></li>
+<li><p>R is weakly typed, so we need <code>map</code> variants that describe the output type (like <code>map_int()</code>, <code>map_dbl()</code>, etc) because don’t the return type of <code>.f</code>.</p></li>
+<li><p>R has named arguments, so instead of providing different functions for minor variations (e.g. <code>detect()</code> and <code>detectLast()</code>) we use a named argument, <code>.right</code>. Type-stable functions are easy to reason about so additional arguments will never change the type of the output.</p></li>
+</ul>
+
+
+
+<!-- dynamically load mathjax for compatibility with self-contained -->
+<script>
+  (function () {
+    var script = document.createElement("script");
+    script.type = "text/javascript";
+    script.src  = "https://mathjax.rstudio.com/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML";
+    document.getElementsByTagName("head")[0].appendChild(script);
+  })();
+</script>
+
+</body>
+</html>
diff --git a/man/accumulate.Rd b/man/accumulate.Rd
new file mode 100644
index 0000000..6e38153
--- /dev/null
+++ b/man/accumulate.Rd
@@ -0,0 +1,62 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/reduce.R
+\name{accumulate}
+\alias{accumulate}
+\alias{accumulate_right}
+\title{Accumulate recursive folds across a list}
+\usage{
+accumulate(.x, .f, ..., .init)
+
+accumulate_right(.x, .f, ..., .init)
+}
+\arguments{
+\item{.x}{A list or atomic vector.}
+
+\item{.f}{For \code{reduce()}, a 2-argument function. The function will be
+passed the accumulated value as the first argument and the "next" value
+as the second argument.
+
+For \code{reduce2()}, a 3-argument function. The function will be passed the
+accumulated value as the first argument, the next value of \code{.x} as the
+second argument, and the next value of \code{.y} as the third argument.}
+
+\item{...}{Additional arguments passed on to \code{.f}.}
+
+\item{.init}{If supplied, will be used as the first value to start
+the accumulation, rather than using \code{x[[1]]}. This is useful if
+you want to ensure that \code{reduce} returns a correct value when \code{.x}
+is empty. If missing, and \code{x} is empty, will throw an error.}
+}
+\description{
+\code{accumulate} applies a function recursively over a list from the left, while
+\code{accumulate_right} applies the function from the right. Unlike \code{reduce}
+both functions keep the intermediate results.
+}
+\examples{
+1:3 \%>\% accumulate(`+`)
+1:10 \%>\% accumulate_right(`*`)
+
+# From Haskell's scanl documentation
+1:10 \%>\% accumulate(max, .init = 5)
+
+# Understanding the arguments .x and .y when .f
+# is a lambda function
+# .x is the accumulating value
+1:10 \%>\% accumulate(~ .x)
+# .y is element in the list
+1:10 \%>\% accumulate(~ .y)
+
+# Simulating stochastic processes with drift
+\dontrun{
+library(dplyr)
+library(ggplot2)
+
+rerun(5, rnorm(100)) \%>\%
+  set_names(paste0("sim", 1:5)) \%>\%
+  map(~ accumulate(., ~ .05 + .x + .y)) \%>\%
+  map_df(~ data_frame(value = .x, step = 1:100), .id = "simulation") \%>\%
+  ggplot(aes(x = step, y = value)) +
+    geom_line(aes(color = simulation)) +
+    ggtitle("Simulations of a random walk with drift")
+}
+}
diff --git a/man/along.Rd b/man/along.Rd
new file mode 100644
index 0000000..9177d17
--- /dev/null
+++ b/man/along.Rd
@@ -0,0 +1,32 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/along.R
+\name{along}
+\alias{along}
+\alias{list_along}
+\alias{rep_along}
+\title{Helper to create vectors with matching length.}
+\usage{
+list_along(x)
+
+rep_along(x, y)
+}
+\arguments{
+\item{x}{A vector.}
+
+\item{y}{Values to repeat.}
+}
+\value{
+An vectors the same length as \code{.x}.
+}
+\description{
+These functions take the idea of \code{\link[=seq_along]{seq_along()}} and generalise
+it to creating lists (\code{list_along}) and repeating values
+(\code{rep_along}).
+}
+\examples{
+x <- 1:5
+rep_along(x, 1:2)
+rep_along(x, 1)
+list_along(x)
+}
+\keyword{internal}
diff --git a/man/array-coercion.Rd b/man/array-coercion.Rd
new file mode 100644
index 0000000..a194f27
--- /dev/null
+++ b/man/array-coercion.Rd
@@ -0,0 +1,61 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/arrays.R
+\name{array-coercion}
+\alias{array-coercion}
+\alias{array_branch}
+\alias{array_tree}
+\title{Coerce array to list}
+\usage{
+array_branch(array, margin = NULL)
+
+array_tree(array, margin = NULL)
+}
+\arguments{
+\item{array}{An array to coerce into a list.}
+
+\item{margin}{A numeric vector indicating the positions of the
+indices to be to be enlisted. If \code{NULL}, a full margin is
+used. If \code{numeric(0)}, the array as a whole is wrapped in a
+list.}
+}
+\description{
+\code{array_branch()} and \code{array_tree()} enable arrays to be
+used with purrr's functionals by turning them into lists. The
+details of the coercion are controlled by the \code{margin}
+argument. \code{array_tree()} creates an hierarchical list (a tree)
+that has as many levels as dimensions specified in \code{margin},
+while \code{array_branch()} creates a flat list (by analogy, a
+branch) along all mentioned dimensions.
+}
+\details{
+When no margin is specified, all dimensions are used by
+default. When \code{margin} is a numeric vector of length zero, the
+whole array is wrapped in a list.
+}
+\examples{
+# We create an array with 3 dimensions
+x <- array(1:12, c(2, 2, 3))
+
+# A full margin for such an array would be the vector 1:3. This is
+# the default if you don't specify a margin
+
+# Creating a branch along the full margin is equivalent to
+# as.list(array) and produces a list of size length(x):
+array_branch(x) \%>\% str()
+
+# A branch along the first dimension yields a list of length 2
+# with each element containing a 2x3 array:
+array_branch(x, 1) \%>\% str()
+
+# A branch along the first and third dimensions yields a list of
+# length 2x3 whose elements contain a vector of length 2:
+array_branch(x, c(1, 3)) \%>\% str()
+
+# Creating a tree from the full margin creates a list of lists of
+# lists:
+array_tree(x) \%>\% str()
+
+# The ordering and the depth of the tree are controlled by the
+# margin argument:
+array_tree(x, c(3, 1)) \%>\% str()
+}
diff --git a/man/as_mapper.Rd b/man/as_mapper.Rd
new file mode 100644
index 0000000..1110c24
--- /dev/null
+++ b/man/as_mapper.Rd
@@ -0,0 +1,68 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/as_mapper.R
+\name{as_mapper}
+\alias{as_mapper}
+\alias{as_function}
+\alias{as_mapper.character}
+\alias{as_mapper.numeric}
+\alias{as_mapper.list}
+\title{Convert an object into a mapper function}
+\usage{
+as_mapper(.f, ...)
+
+\method{as_mapper}{character}(.f, ..., .null, .default = NULL)
+
+\method{as_mapper}{numeric}(.f, ..., .null, .default = NULL)
+
+\method{as_mapper}{list}(.f, ..., .null, .default = NULL)
+}
+\arguments{
+\item{.f}{A function, formula, or atomic vector.
+
+If a \strong{function}, it is used as is.
+
+If a \strong{formula}, e.g. \code{~ .x + 2}, it is converted to a function. There
+are three ways to refer to the arguments:
+\itemize{
+\item For a single argument function, use \code{.}
+\item For a two argument function, use \code{.x} and \code{.y}
+\item For more arguments, use \code{..1}, \code{..2}, \code{..3} etc
+}
+
+This syntax allows you to create very compact anonymous functions.
+
+If \strong{character vector}, \strong{numeric vector}, or \strong{list}, it
+is converted to an extractor function. Character vectors index by name
+and numeric vectors index by position; use a list to index by position
+and name at different levels. Within a list, wrap strings in \code{get_attr()}
+to extract named attributes. If a component is not present, the value of
+\code{.default} will be returned.}
+
+\item{...}{Additional arguments passed on to methods.}
+
+\item{.default, .null}{Optional additional argument for extractor functions
+(i.e. when \code{.f} is character, integer, or list). Returned when
+value is absent (does not exist) or empty (has length 0).
+\code{.null} is deprecated; please use \code{.default} instead.}
+}
+\description{
+\code{as_mapper} is the powerhouse behind the varied function
+specifications that most purrr functions allow. It is an S3
+generic. The default method forwards its arguments to
+\code{\link[rlang:as_function]{rlang::as_function()}}.
+}
+\examples{
+as_mapper(~ . + 1)
+as_mapper(1)
+
+as_mapper(c("a", "b", "c"))
+# Equivalent to function(x) x[["a"]][["b"]][["c"]]
+
+as_mapper(list(1, "a", 2))
+# Equivalent to function(x) x[[1]][["a"]][[2]]
+
+as_mapper(list(1, attr_getter("a")))
+# Equivalent to function(x) attr(x[[1]], "a")
+
+as_mapper(c("a", "b", "c"), .null = NA)
+}
diff --git a/man/as_vector.Rd b/man/as_vector.Rd
new file mode 100644
index 0000000..84038d4
--- /dev/null
+++ b/man/as_vector.Rd
@@ -0,0 +1,51 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/coercion.R
+\name{as_vector}
+\alias{as_vector}
+\alias{simplify}
+\alias{simplify_all}
+\title{Coerce a list to a vector}
+\usage{
+as_vector(.x, .type = NULL)
+
+simplify(.x, .type = NULL)
+
+simplify_all(.x, .type = NULL)
+}
+\arguments{
+\item{.x}{A list of vectors}
+
+\item{.type}{A vector mold or a string describing the type of the
+input vectors. The latter can be any of the types returned by
+\code{\link[=typeof]{typeof()}}, or "numeric" as a shorthand for either
+"double" or "integer".}
+}
+\description{
+\code{as_vector()} collapses a list of vectors into one vector. It
+checks that the type of each vector is consistent with
+\code{.type}. If the list can not be simplified, it throws an error.
+\code{simplify} will simplify a vector if possible; \code{simplify_all}
+will apply \code{simplify} to every element of a list.
+}
+\details{
+\code{.type} can be a vector mold specifying both the type and the
+length of the vectors to be concatenated, such as \code{numeric(1)}
+or \code{integer(4)}. Alternatively, it can be a string describing
+the type, one of: "logical", "integer", "double", "complex",
+"character" or "raw".
+}
+\examples{
+# Supply the type either with a string:
+as.list(letters) \%>\% as_vector("character")
+
+# Or with a vector mold:
+as.list(letters) \%>\% as_vector(character(1))
+
+# Vector molds are more flexible because they also specify the
+# length of the concatenated vectors:
+list(1:2, 3:4, 5:6) \%>\% as_vector(integer(2))
+
+# Note that unlike vapply(), as_vector() never adds dimension
+# attributes. So when you specify a vector mold of size > 1, you
+# always get a vector and not a matrix
+}
diff --git a/man/compose.Rd b/man/compose.Rd
new file mode 100644
index 0000000..8fdf13f
--- /dev/null
+++ b/man/compose.Rd
@@ -0,0 +1,25 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/compose.R
+\name{compose}
+\alias{compose}
+\title{Compose multiple functions}
+\usage{
+compose(...)
+}
+\arguments{
+\item{...}{n functions to apply in order from right to left.}
+}
+\value{
+A function
+}
+\description{
+Compose multiple functions
+}
+\examples{
+not_null <- compose(`!`, is.null)
+not_null(4)
+not_null(NULL)
+
+add1 <- function(x) x + 1
+compose(add1, add1)(8)
+}
diff --git a/man/cross.Rd b/man/cross.Rd
new file mode 100644
index 0000000..da99306
--- /dev/null
+++ b/man/cross.Rd
@@ -0,0 +1,115 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/cross.R
+\name{cross}
+\alias{cross}
+\alias{cross2}
+\alias{cross3}
+\alias{cross_df}
+\alias{cross_n}
+\alias{cross_d}
+\title{Produce all combinations of list elements}
+\usage{
+cross(.l, .filter = NULL)
+
+cross2(.x, .y, .filter = NULL)
+
+cross3(.x, .y, .z, .filter = NULL)
+
+cross_df(.l, .filter = NULL)
+}
+\arguments{
+\item{.l}{A list of lists or atomic vectors. Alternatively, a data
+frame. \code{cross_df()} requires all elements to be named.}
+
+\item{.filter}{A predicate function that takes the same number of
+arguments as the number of variables to be combined.}
+
+\item{.x, .y, .z}{Lists or atomic vectors.}
+}
+\value{
+\code{cross2()}, \code{cross3()} and \code{cross()}
+always return a list. \code{cross_df()} always returns a data
+frame. \code{cross()} returns a list where each element is one
+combination so that the list can be directly mapped
+over. \code{cross_df()} returns a data frame where each row is one
+combination.
+}
+\description{
+\code{cross2()} returns the product set of the elements of
+\code{.x} and \code{.y}. \code{cross3()} takes an additional
+\code{.z} argument. \code{cross()} takes a list \code{.l} and
+returns the cartesian product of all its elements in a list, with
+one combination by element. \code{cross_df()} is like
+\code{cross()} but returns a data frame, with one combination by
+row.
+}
+\details{
+\code{cross()}, \code{cross2()} and \code{cross3()} return the
+cartesian product is returned in wide format. This makes it more
+amenable to mapping operations. \code{cross_df()} returns the output
+in long format just as \code{expand.grid()} does. This is adapted
+to rowwise operations.
+
+When the number of combinations is large and the individual
+elements are heavy memory-wise, it is often useful to filter
+unwanted combinations on the fly with \code{.filter}. It must be
+a predicate function that takes the same number of arguments as the
+number of crossed objects (2 for \code{cross2()}, 3 for
+\code{cross3()}, \code{length(.l)} for \code{cross()}) and
+returns \code{TRUE} or \code{FALSE}. The combinations where the
+predicate function returns \code{TRUE} will be removed from the
+result.
+}
+\examples{
+# We build all combinations of names, greetings and separators from our
+# list of data and pass each one to paste()
+data <- list(
+  id = c("John", "Jane"),
+  greeting = c("Hello.", "Bonjour."),
+  sep = c("! ", "... ")
+)
+
+data \%>\%
+  cross() \%>\%
+  map(lift(paste))
+
+# cross() returns the combinations in long format: many elements,
+# each representing one combination. With cross_df() we'll get a
+# data frame in long format: crossing three objects produces a data
+# frame of three columns with each row being a particular
+# combination. This is the same format that expand.grid() returns.
+args <- data \%>\% cross_df()
+
+# In case you need a list in long format (and not a data frame)
+# just run as.list() after cross_df()
+args \%>\% as.list()
+
+# This format is often less pratical for functional programming
+# because applying a function to the combinations requires a loop
+out <- vector("list", length = nrow(args))
+for (i in seq_along(out))
+  out[[i]] <- map(args, i) \%>\% invoke(paste, .)
+out
+
+# It's easier to transpose and then use invoke_map()
+args \%>\% transpose() \%>\% map_chr(~ invoke(paste, .))
+
+# Unwanted combinations can be filtered out with a predicate function
+filter <- function(x, y) x >= y
+cross2(1:5, 1:5, .filter = filter) \%>\% str()
+
+# To give names to the components of the combinations, we map
+# setNames() on the product:
+seq_len(3) \%>\%
+  cross2(., ., .filter = `==`) \%>\%
+  map(setNames, c("x", "y"))
+
+# Alternatively we can encapsulate the arguments in a named list
+# before crossing to get named components:
+seq_len(3) \%>\%
+  list(x = ., y = .) \%>\%
+  cross(.filter = `==`)
+}
+\seealso{
+\code{\link[=expand.grid]{expand.grid()}}
+}
diff --git a/man/detect.Rd b/man/detect.Rd
new file mode 100644
index 0000000..32a6552
--- /dev/null
+++ b/man/detect.Rd
@@ -0,0 +1,78 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/find-position.R
+\name{detect}
+\alias{detect}
+\alias{detect_index}
+\title{Find the value or position of the first match.}
+\usage{
+detect(.x, .f, ..., .right = FALSE, .p)
+
+detect_index(.x, .f, ..., .right = FALSE, .p)
+}
+\arguments{
+\item{.x}{A list or atomic vector.}
+
+\item{.f}{A function, formula, or atomic vector.
+
+If a \strong{function}, it is used as is.
+
+If a \strong{formula}, e.g. \code{~ .x + 2}, it is converted to a function. There
+are three ways to refer to the arguments:
+\itemize{
+\item For a single argument function, use \code{.}
+\item For a two argument function, use \code{.x} and \code{.y}
+\item For more arguments, use \code{..1}, \code{..2}, \code{..3} etc
+}
+
+This syntax allows you to create very compact anonymous functions.
+
+If \strong{character vector}, \strong{numeric vector}, or \strong{list}, it
+is converted to an extractor function. Character vectors index by name
+and numeric vectors index by position; use a list to index by position
+and name at different levels. Within a list, wrap strings in \code{get_attr()}
+to extract named attributes. If a component is not present, the value of
+\code{.default} will be returned.}
+
+\item{...}{Additional arguments passed on to \code{.f}.}
+
+\item{.right}{If \code{FALSE}, the default, starts at the beginning
+of the vector and move towards the end; if \code{TRUE}, starts at the end
+of the vector and moves towards the beginning.}
+
+\item{.p}{A single predicate function, a formula describing such a
+predicate function, or a logical vector of the same length as \code{.x}.
+Alternatively, if the elements of \code{.x} are themselves lists of
+objects, a string indicating the name of a logical element in the
+inner lists. Only those elements where \code{.p} evaluates to
+\code{TRUE} will be modified.}
+}
+\value{
+\code{detect} the value of the first item that matches the
+predicate; \code{detect_index} the position of the matching item.
+If not found, \code{detect} returns \code{NULL} and \code{detect_index}
+returns 0.
+}
+\description{
+Find the value or position of the first match.
+}
+\examples{
+is_even <- function(x) x \%\% 2 == 0
+
+3:10 \%>\% detect(is_even)
+3:10 \%>\% detect_index(is_even)
+
+3:10 \%>\% detect(is_even, .right = TRUE)
+3:10 \%>\% detect_index(is_even, .right = TRUE)
+
+
+# Since `.f` is passed to as_mapper(), you can supply a
+# lambda-formula or a pluck object:
+x <- list(
+  list(1, foo = FALSE),
+  list(2, foo = TRUE),
+  list(3, foo = TRUE)
+)
+
+detect(x, "foo")
+detect_index(x, "foo")
+}
diff --git a/man/every.Rd b/man/every.Rd
new file mode 100644
index 0000000..cd1ab69
--- /dev/null
+++ b/man/every.Rd
@@ -0,0 +1,38 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/every-some.R
+\name{every}
+\alias{every}
+\alias{some}
+\title{Do every or some elements of a list satisfy a predicate?}
+\usage{
+every(.x, .p, ...)
+
+some(.x, .p, ...)
+}
+\arguments{
+\item{.x}{A list or atomic vector.}
+
+\item{.p}{A single predicate function, a formula describing such a
+predicate function, or a logical vector of the same length as \code{.x}.
+Alternatively, if the elements of \code{.x} are themselves lists of
+objects, a string indicating the name of a logical element in the
+inner lists. Only those elements where \code{.p} evaluates to
+\code{TRUE} will be modified.}
+
+\item{...}{Additional arguments passed on to \code{.f}.}
+}
+\value{
+A logical vector of length 1.
+}
+\description{
+Do every or some elements of a list satisfy a predicate?
+}
+\examples{
+x <- list(0, 1, TRUE)
+x \%>\% every(identity)
+x \%>\% some(identity)
+
+y <- list(0:10, 5.5)
+y \%>\% every(is.numeric)
+y \%>\% every(is.integer)
+}
diff --git a/man/figures/logo.png b/man/figures/logo.png
new file mode 100644
index 0000000..d109b96
Binary files /dev/null and b/man/figures/logo.png differ
diff --git a/man/flatten.Rd b/man/flatten.Rd
new file mode 100644
index 0000000..3f893c9
--- /dev/null
+++ b/man/flatten.Rd
@@ -0,0 +1,60 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/flatten.R
+\name{flatten}
+\alias{flatten}
+\alias{flatten_lgl}
+\alias{flatten_int}
+\alias{flatten_dbl}
+\alias{flatten_chr}
+\alias{flatten_dfr}
+\alias{flatten_dfc}
+\alias{flatten_df}
+\title{Flatten a list of lists into a simple vector.}
+\usage{
+flatten(.x)
+
+flatten_lgl(.x)
+
+flatten_int(.x)
+
+flatten_dbl(.x)
+
+flatten_chr(.x)
+
+flatten_dfr(.x, .id = NULL)
+
+flatten_dfc(.x)
+}
+\arguments{
+\item{.x}{A list of flatten. The contents of the list can be anything for
+\code{flatten} (as a list is returned), but the contents must match the
+type for the other functions.}
+
+\item{.id}{If not \code{NULL} a variable with this name will be created
+giving either the name or the index of the data frame.}
+}
+\value{
+\code{flatten()} returns a list, \code{flatten_lgl()} a logical
+vector, \code{flatten_int()} an integer vector, \code{flatten_dbl()} a
+double vector, and \code{flatten_chr()} a character vector.
+
+\code{flatten_dfr()} and \code{flatten_dfc()} return data frames created by
+row-binding and column-binding respectively. They require dplyr to
+be installed.
+}
+\description{
+These functions remove a level hierarchy from a list. They are similar to
+\code{\link[=unlist]{unlist()}}, only ever remove a single layer of hierarchy, and
+are type-stable so you always know what the type of the output is.
+}
+\examples{
+x <- rerun(2, sample(4))
+x
+x \%>\% flatten()
+x \%>\% flatten_int()
+
+# You can use flatten in conjunction with map
+x \%>\% map(1L) \%>\% flatten_int()
+# But it's more efficient to use the typed map instead.
+x \%>\% map_int(1L)
+}
diff --git a/man/get-attr.Rd b/man/get-attr.Rd
new file mode 100644
index 0000000..96a2f7c
--- /dev/null
+++ b/man/get-attr.Rd
@@ -0,0 +1,21 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/utils.R
+\name{get-attr}
+\alias{get-attr}
+\alias{\%@\%}
+\title{Infix attribute accessor}
+\usage{
+x \%@\% name
+}
+\arguments{
+\item{x}{Object}
+
+\item{name}{Attribute name}
+}
+\description{
+Infix attribute accessor
+}
+\examples{
+factor(1:3) \%@\% "levels"
+mtcars \%@\% "class"
+}
diff --git a/man/has_element.Rd b/man/has_element.Rd
new file mode 100644
index 0000000..5627dd8
--- /dev/null
+++ b/man/has_element.Rd
@@ -0,0 +1,21 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/find-position.R
+\name{has_element}
+\alias{has_element}
+\title{Does a list contain an object?}
+\usage{
+has_element(.x, .y)
+}
+\arguments{
+\item{.x}{A list or atomic vector.}
+
+\item{.y}{Object to test for}
+}
+\description{
+Does a list contain an object?
+}
+\examples{
+x <- list(1:10, 5, 9.9)
+x \%>\% has_element(1:10)
+x \%>\% has_element(3)
+}
diff --git a/man/head_while.Rd b/man/head_while.Rd
new file mode 100644
index 0000000..9a82849
--- /dev/null
+++ b/man/head_while.Rd
@@ -0,0 +1,38 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/head-tail.R
+\name{head_while}
+\alias{head_while}
+\alias{tail_while}
+\title{Find head/tail that all satisfies a predicate.}
+\usage{
+head_while(.x, .p, ...)
+
+tail_while(.x, .p, ...)
+}
+\arguments{
+\item{.x}{A list or atomic vector.}
+
+\item{.p}{A single predicate function, a formula describing such a
+predicate function, or a logical vector of the same length as \code{.x}.
+Alternatively, if the elements of \code{.x} are themselves lists of
+objects, a string indicating the name of a logical element in the
+inner lists. Only those elements where \code{.p} evaluates to
+\code{TRUE} will be modified.}
+
+\item{...}{Additional arguments passed on to \code{.f}.}
+}
+\value{
+A vector the same type as \code{.x}.
+}
+\description{
+Find head/tail that all satisfies a predicate.
+}
+\examples{
+pos <- function(x) x >= 0
+head_while(5:-5, pos)
+tail_while(5:-5, negate(pos))
+
+big <- function(x) x > 100
+head_while(0:10, big)
+tail_while(0:10, big)
+}
diff --git a/man/imap.Rd b/man/imap.Rd
new file mode 100644
index 0000000..95d56e7
--- /dev/null
+++ b/man/imap.Rd
@@ -0,0 +1,78 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/imap.R
+\name{imap}
+\alias{imap}
+\alias{imap_lgl}
+\alias{imap_chr}
+\alias{imap_int}
+\alias{imap_dbl}
+\alias{imap_dfr}
+\alias{imap_dfc}
+\alias{iwalk}
+\title{Apply a function to each element of a vector, and its index}
+\usage{
+imap(.x, .f, ...)
+
+imap_lgl(.x, .f, ...)
+
+imap_chr(.x, .f, ...)
+
+imap_int(.x, .f, ...)
+
+imap_dbl(.x, .f, ...)
+
+imap_dfr(.x, .f, ..., .id = NULL)
+
+imap_dfc(.x, .f, ..., .id = NULL)
+
+iwalk(.x, .f, ...)
+}
+\arguments{
+\item{.x}{A list or atomic vector.}
+
+\item{.f}{A function, formula, or atomic vector.
+
+If a \strong{function}, it is used as is.
+
+If a \strong{formula}, e.g. \code{~ .x + 2}, it is converted to a function. There
+are three ways to refer to the arguments:
+\itemize{
+\item For a single argument function, use \code{.}
+\item For a two argument function, use \code{.x} and \code{.y}
+\item For more arguments, use \code{..1}, \code{..2}, \code{..3} etc
+}
+
+This syntax allows you to create very compact anonymous functions.
+
+If \strong{character vector}, \strong{numeric vector}, or \strong{list}, it
+is converted to an extractor function. Character vectors index by name
+and numeric vectors index by position; use a list to index by position
+and name at different levels. Within a list, wrap strings in \code{get_attr()}
+to extract named attributes. If a component is not present, the value of
+\code{.default} will be returned.}
+
+\item{...}{Additional arguments passed on to \code{.f}.}
+
+\item{.id}{If not \code{NULL} a variable with this name will be created
+giving either the name or the index of the data frame.}
+}
+\value{
+A vector the same length as \code{.x}.
+}
+\description{
+\code{imap_xxx(x, ...)}, an indexed map, is short hand for
+\code{map2(x, names(x), ...)} if \code{x} has names, or \code{map2(x, seq_along(x), ...)}
+if it does not. This is useful if you need to compute on both the value
+and the position of an element.
+}
+\examples{
+# Note that when using the formula shortcut, the first argument
+# is the value, and the second is the position
+imap_chr(sample(10), ~ paste0(.y, ": ", .x))
+iwalk(mtcars, ~ cat(.y, ": ", median(.x), "\\n", sep = ""))
+}
+\seealso{
+Other map variants: \code{\link{invoke}},
+  \code{\link{lmap}}, \code{\link{map2}},
+  \code{\link{map}}, \code{\link{modify}}
+}
diff --git a/man/invoke.Rd b/man/invoke.Rd
new file mode 100644
index 0000000..676a9c5
--- /dev/null
+++ b/man/invoke.Rd
@@ -0,0 +1,103 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/invoke.R
+\name{invoke}
+\alias{invoke}
+\alias{invoke_map}
+\alias{invoke_map_lgl}
+\alias{invoke_map_int}
+\alias{invoke_map_dbl}
+\alias{invoke_map_chr}
+\alias{invoke_map_dfr}
+\alias{invoke_map_dfc}
+\alias{invoke_map_df}
+\alias{map_call}
+\title{Invoke functions.}
+\usage{
+invoke(.f, .x = NULL, ..., .env = NULL)
+
+invoke_map(.f, .x = list(NULL), ..., .env = NULL)
+
+invoke_map_lgl(.f, .x = list(NULL), ..., .env = NULL)
+
+invoke_map_int(.f, .x = list(NULL), ..., .env = NULL)
+
+invoke_map_dbl(.f, .x = list(NULL), ..., .env = NULL)
+
+invoke_map_chr(.f, .x = list(NULL), ..., .env = NULL)
+
+invoke_map_dfr(.f, .x = list(NULL), ..., .env = NULL)
+
+invoke_map_dfc(.f, .x = list(NULL), ..., .env = NULL)
+}
+\arguments{
+\item{.f}{For \code{invoke}, a function; for \code{invoke_map} a
+list of functions.}
+
+\item{.x}{For \code{invoke}, an argument-list; for \code{invoke_map} a
+list of argument-lists the same length as \code{.f} (or length 1).
+The default argument, \code{list(NULL)}, will be recycled to the
+same length as \code{.f}, and will call each function with no
+arguments (apart from any supplied in \code{...}.}
+
+\item{...}{Additional arguments passed to each function.}
+
+\item{.env}{Environment in which \code{\link[=do.call]{do.call()}} should
+evaluate a constructed expression. This only matters if you pass
+as \code{.f} the name of a function rather than its value, or as
+\code{.x} symbols of objects rather than their values.}
+}
+\description{
+This pair of functions make it easier to combine a function and list
+of parameters to get a result. \code{invoke} is a wrapper around
+\code{do.call} that makes it easy to use in a pipe. \code{invoke_map}
+makes it easier to call lists of functions with lists of parameters.
+}
+\examples{
+# Invoke a function with a list of arguments
+invoke(runif, list(n = 10))
+# Invoke a function with named arguments
+invoke(runif, n = 10)
+
+# Combine the two:
+invoke(paste, list("01a", "01b"), sep = "-")
+# That's more natural as part of a pipeline:
+list("01a", "01b") \%>\%
+  invoke(paste, ., sep = "-")
+
+# Invoke a list of functions, each with different arguments
+invoke_map(list(runif, rnorm), list(list(n = 10), list(n = 5)))
+# Or with the same inputs:
+invoke_map(list(runif, rnorm), list(list(n = 5)))
+invoke_map(list(runif, rnorm), n = 5)
+# Or the same function with different inputs:
+invoke_map("runif", list(list(n = 5), list(n = 10)))
+
+# Or as a pipeline
+list(m1 = mean, m2 = median) \%>\% invoke_map(x = rcauchy(100))
+list(m1 = mean, m2 = median) \%>\% invoke_map_dbl(x = rcauchy(100))
+
+# Note that you can also match by position by explicitly omitting `.x`.
+# This can be useful when the argument names of the functions are not
+# identical
+list(m1 = mean, m2 = median) \%>\%
+  invoke_map(, rcauchy(100))
+
+# If you have pairs of function name and arguments, it's natural
+# to store them in a data frame. Here we use a tibble because
+# it has better support for list-columns
+df <- tibble::tibble(
+  f = c("runif", "rpois", "rnorm"),
+  params = list(
+    list(n = 10),
+    list(n = 5, lambda = 10),
+    list(n = 10, mean = -3, sd = 10)
+  )
+)
+df
+invoke_map(df$f, df$params)
+}
+\seealso{
+Other map variants: \code{\link{imap}}, \code{\link{lmap}},
+  \code{\link{map2}}, \code{\link{map}},
+  \code{\link{modify}}
+}
diff --git a/man/is_numeric.Rd b/man/is_numeric.Rd
new file mode 100644
index 0000000..ce45b79
--- /dev/null
+++ b/man/is_numeric.Rd
@@ -0,0 +1,22 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/predicates.R
+\name{is_numeric}
+\alias{is_numeric}
+\alias{is_scalar_numeric}
+\title{Test is an object is integer or double}
+\usage{
+is_numeric(x)
+
+is_scalar_numeric(x)
+}
+\description{
+Numeric is used in three different ways in base R:
+\itemize{
+\item as an alias for double (as in \code{\link[=as.numeric]{as.numeric()}})
+\item to mean either integer or double (as in \code{\link[=mode]{mode()}})
+\item for something representable as numeric (as in \code{\link[=as.numeric]{as.numeric()}})
+This function tests for the second, which is often not what you want
+so these functions are deprecated.
+}
+}
+\keyword{internal}
diff --git a/man/keep.Rd b/man/keep.Rd
new file mode 100644
index 0000000..b5e20ba
--- /dev/null
+++ b/man/keep.Rd
@@ -0,0 +1,53 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/keep.R
+\name{keep}
+\alias{keep}
+\alias{discard}
+\alias{compact}
+\title{Keep or discard elements using a predicate function.}
+\usage{
+keep(.x, .p, ...)
+
+discard(.x, .p, ...)
+
+compact(.x, .p = identity)
+}
+\arguments{
+\item{.x}{A list or vector.}
+
+\item{.p}{A single predicate function, a formula describing such a
+predicate function, or a logical vector of the same length as \code{.x}.
+Alternatively, if the elements of \code{.x} are themselves lists of
+objects, a string indicating the name of a logical element in the
+inner lists. Only those elements where \code{.p} evaluates to
+\code{TRUE} will be modified.}
+
+\item{...}{Additional arguments passed on to \code{.p}.}
+}
+\description{
+\code{keep} and \code{discard} are opposites. \code{compact} is a handy
+wrapper that removes all elements that are \code{NULL}.
+}
+\details{
+These are usually called \code{select} or \code{filter} and \code{reject} or
+\code{drop}, but those names are already taken. \code{keep} is similar to
+\code{\link[=Filter]{Filter()}} but the argument order is more convenient, and the
+evaluation of \code{.f} is stricter.
+}
+\examples{
+rep(10, 10) \%>\%
+  map(sample, 5) \%>\%
+  keep(function(x) mean(x) > 6)
+
+# Or use a formula
+rep(10, 10) \%>\%
+  map(sample, 5) \%>\%
+  keep(~ mean(.x) > 6)
+
+# Using a string instead of a function will select all list elements
+# where that subelement is TRUE
+x <- rerun(5, a = rbernoulli(1), b = sample(10))
+x
+x \%>\% keep("a")
+x \%>\% discard("a")
+}
diff --git a/man/lift.Rd b/man/lift.Rd
new file mode 100644
index 0000000..e63a9a6
--- /dev/null
+++ b/man/lift.Rd
@@ -0,0 +1,183 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/composition.R
+\name{lift}
+\alias{lift}
+\alias{lift}
+\alias{lift_dl}
+\alias{lift_dv}
+\alias{lift_vl}
+\alias{lift_vd}
+\alias{lift_ld}
+\alias{lift_lv}
+\title{Lift the domain of a function}
+\usage{
+lift(..f, ..., .unnamed = FALSE)
+
+lift_dl(..f, ..., .unnamed = FALSE)
+
+lift_dv(..f, ..., .unnamed = FALSE)
+
+lift_vl(..f, ..., .type)
+
+lift_vd(..f, ..., .type)
+
+lift_ld(..f, ...)
+
+lift_lv(..f, ...)
+}
+\arguments{
+\item{..f}{A function to lift.}
+
+\item{...}{Default arguments for \code{..f}. These will be
+evaluated only once, when the lifting factory is called.}
+
+\item{.unnamed}{If \code{TRUE}, \code{ld} or \code{lv} will not
+name the parameters in the lifted function signature. This
+prevents matching of arguments by name and match by position
+instead.}
+
+\item{.type}{A vector mold or a string describing the type of the
+input vectors. The latter can be any of the types returned by
+\code{\link[=typeof]{typeof()}}, or "numeric" as a shorthand for either
+"double" or "integer".}
+}
+\value{
+A function.
+}
+\description{
+\code{lift_xy()} is a composition helper. It helps you compose
+functions by lifting their domain from a kind of input to another
+kind. The domain can be changed from and to a list (l), a vector
+(v) and dots (d). For example, \code{lift_ld(fun)} transforms a
+function taking a list to a function taking dots.
+}
+\details{
+The most important of those helpers is probably \code{lift_dl()}
+because it allows you to transform a regular function to one that
+takes a list. This is often essential for composition with purrr
+functional tools. Since this is such a common function,
+\code{lift()} is provided as an alias for that operation.
+}
+\section{from ... to \code{list(...)} or \code{c(...)}}{
+
+Here dots should be taken here in a figurative way. The lifted
+functions does not need to take dots per se. The function is
+simply wrapped a function in \code{\link[=do.call]{do.call()}}, so instead
+of taking multiple arguments, it takes a single named list or
+vector which will be interpreted as its arguments.  This is
+particularly useful when you want to pass a row of a data frame
+or a list to a function and don't want to manually pull it apart
+in your function.
+}
+
+\section{from \code{c(...)} to \code{list(...)} or \code{...}}{
+
+These factories allow a function taking a vector to take a list
+or dots instead. The lifted function internally transforms its
+inputs back to an atomic vector. purrr does not obey the usual R
+casting rules (e.g., \code{c(1, "2")} produces a character
+vector) and will produce an error if the types are not
+compatible. Additionally, you can enforce a particular vector
+type by supplying \code{.type}.
+}
+
+\section{from list(...) to c(...) or ...}{
+
+\code{lift_ld()} turns a function that takes a list into a
+function that takes dots. \code{lift_vd()} does the same with a
+function that takes an atomic vector. These factory functions are
+the inverse operations of \code{lift_dl()} and \code{lift_dv()}.
+
+\code{lift_vd()} internally coerces the inputs of \code{..f} to
+an atomic vector. The details of this coercion can be controlled
+with \code{.type}.
+}
+
+\examples{
+### Lifting from ... to list(...) or c(...)
+
+x <- list(x = c(1:100, NA, 1000), na.rm = TRUE, trim = 0.9)
+lift_dl(mean)(x)
+
+# Or in a pipe:
+mean \%>\% lift_dl() \%>\% invoke(x)
+
+# You can also use the lift() alias for this common operation:
+lift(mean)(x)
+
+# Default arguments can also be specified directly in lift_dl()
+list(c(1:100, NA, 1000)) \%>\% lift_dl(mean, na.rm = TRUE)()
+
+# lift_dl() and lift_ld() are inverse of each other.
+# Here we transform sum() so that it takes a list
+fun <- sum \%>\% lift_dl()
+fun(list(3, NA, 4, na.rm = TRUE))
+
+# Now we transform it back to a variadic function
+fun2 <- fun \%>\% lift_ld()
+fun2(3, NA, 4, na.rm = TRUE)
+
+# It can sometimes be useful to make sure the lifted function's
+# signature has no named parameters, as would be the case for a
+# function taking only dots. The lifted function will take a list
+# or vector but will not match its arguments to the names of the
+# input. For instance, if you give a data frame as input to your
+# lifted function, the names of the columns are probably not
+# related to the function signature and should be discarded.
+lifted_identical <- lift_dl(identical, .unnamed = TRUE)
+mtcars[c(1, 1)] \%>\% lifted_identical()
+mtcars[c(1, 2)] \%>\% lifted_identical()
+#
+
+
+### Lifting from c(...) to list(...) or ...
+
+# In other situations we need the vector-valued function to take a
+# variable number of arguments as with pmap(). This is a job for
+# lift_vd():
+pmap(mtcars, lift_vd(mean))
+
+# lift_vd() will collect the arguments and concatenate them to a
+# vector before passing them to ..f. You can add a check to assert
+# the type of vector you expect:
+lift_vd(tolower, .type = character(1))("this", "is", "ok")
+#
+
+
+### Lifting from list(...) to c(...) or ...
+
+# cross() normally takes a list of elements and returns their
+# cartesian product. By lifting it you can supply the arguments as
+# if it was a function taking dots:
+cross_dots <- lift_ld(cross)
+out1 <- cross(list(a = 1:2, b = c("a", "b", "c")))
+out2 <- cross_dots(a = 1:2, b = c("a", "b", "c"))
+identical(out1, out2)
+
+# This kind of lifting is sometimes needed for function
+# composition. An example would be to use pmap() with a function
+# that takes a list. In the following, we use some() on each row of
+# a data frame to check they each contain at least one element
+# satisfying a condition:
+mtcars \%>\% pmap(lift_ld(some, partial(`<`, 200)))
+
+# Default arguments for ..f can be specified in the call to
+# lift_ld()
+lift_ld(cross, .filter = `==`)(1:3, 1:3) \%>\% str()
+
+
+# Here is another function taking a list and that we can update to
+# take a vector:
+glue <- function(l) {
+  if (!is.list(l)) stop("not a list")
+  l \%>\% invoke(paste, .)
+}
+
+\dontrun{
+letters \%>\% glue()           # fails because glue() expects a list}
+
+letters \%>\% lift_lv(glue)()  # succeeds
+}
+\seealso{
+\code{\link[=invoke]{invoke()}}
+}
diff --git a/man/list_modify.Rd b/man/list_modify.Rd
new file mode 100644
index 0000000..de5de50
--- /dev/null
+++ b/man/list_modify.Rd
@@ -0,0 +1,57 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/list-modify.R
+\name{list_modify}
+\alias{list_modify}
+\alias{list_merge}
+\alias{update_list}
+\title{Modify a list}
+\usage{
+list_modify(.x, ...)
+
+list_merge(.x, ...)
+}
+\arguments{
+\item{.x}{List to modify.}
+
+\item{...}{New values of a list. Use \code{NULL} to remove values.
+Use a formula to evaluate in the context of the list values.
+These dots have \link[rlang:dots_list]{splicing semantics}.}
+}
+\description{
+\code{list_modify()} and \code{list_merge()} recursively combine two lists, matching
+elements either by name or position. If an sub-element is present in
+both lists \code{list_modify()} takes the value from \code{y}, and \code{list_merge()}
+concatenates the values together.
+
+\code{update_list()} handles formulas and quosures that can refer to
+values existing within the input list. Note that this function
+might be deprecated in the future in favour of a \code{dplyr::mutate()}
+method for lists.
+}
+\examples{
+x <- list(x = 1:10, y = 4, z = list(a = 1, b = 2))
+str(x)
+
+# Update values
+str(list_modify(x, a = 1))
+# Replace values
+str(list_modify(x, z = 5))
+str(list_modify(x, z = list(a = 1:5)))
+# Remove values
+str(list_modify(x, z = NULL))
+
+# Combine values
+str(list_merge(x, x = 11, z = list(a = 2:5, c = 3)))
+
+
+# All these functions take dots with splicing. Use !!! or UQS() to
+# splice a list of arguments:
+l <- list(new = 1, y = NULL, z = 5)
+str(list_modify(x, !!! l))
+
+# In update_list() you can also use quosures and formulas to
+# compute new values. This function is likely to be deprecated in
+# the future
+update_list(x, z1 = ~z[[1]])
+update_list(x, z = rlang::quo(x + y))
+}
diff --git a/man/lmap.Rd b/man/lmap.Rd
new file mode 100644
index 0000000..cdbbf3e
--- /dev/null
+++ b/man/lmap.Rd
@@ -0,0 +1,111 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/lmap.R
+\name{lmap}
+\alias{lmap}
+\alias{lmap_if}
+\alias{lmap_at}
+\title{Apply a function to list-elements of a list}
+\usage{
+lmap(.x, .f, ...)
+
+lmap_if(.x, .p, .f, ...)
+
+lmap_at(.x, .at, .f, ...)
+}
+\arguments{
+\item{.x}{A list or data frame.}
+
+\item{.f}{A function that takes and returns a list or data frame.}
+
+\item{...}{Additional arguments passed on to \code{.f}.}
+
+\item{.p}{A single predicate function, a formula describing such a
+predicate function, or a logical vector of the same length as \code{.x}.
+Alternatively, if the elements of \code{.x} are themselves lists of
+objects, a string indicating the name of a logical element in the
+inner lists. Only those elements where \code{.p} evaluates to
+\code{TRUE} will be modified.}
+
+\item{.at}{A character vector of names or a numeric vector of
+positions. Only those elements corresponding to \code{.at} will be
+modified.}
+}
+\value{
+If \code{.x} is a list, a list. If \code{.x} is a data
+frame, a data frame.
+}
+\description{
+\code{lmap()}, \code{lmap_at()} and \code{lmap_if()} are similar to
+\code{map()}, \code{map_at()} and \code{map_if()}, with the
+difference that they operate exclusively on functions that take
+\emph{and} return a list (or data frame). Thus, instead of mapping
+the elements of a list (as in \code{.x[[i]]}), they apply a
+function \code{.f} to each subset of size 1 of that list (as in
+\code{.x[i]}). We call those those elements `list-elements').
+}
+\details{
+Mapping the list-elements \code{.x[i]} has several advantages. It
+makes it possible to work with functions that exclusively take a
+list or data frame. It enables \code{.f} to access the attributes
+of the encapsulating list, like the name of the components it
+receives. It also enables \code{.f} to return a larger list than
+the list-element of size 1 it got as input. Conversely, \code{.f}
+can also return empty lists. In these cases, the output list is
+reshaped with a different size than the input list \code{.x}.
+}
+\examples{
+# Let's write a function that returns a larger list or an empty list
+# depending on some condition. This function also uses the names
+# metadata available in the attributes of the list-element
+maybe_rep <- function(x) {
+  n <- rpois(1, 2)
+  out <- rep_len(x, n)
+  if (length(out) > 0) {
+    names(out) <- paste0(names(x), seq_len(n))
+  }
+  out
+}
+
+# The output size varies each time we map f()
+x <- list(a = 1:4, b = letters[5:7], c = 8:9, d = letters[10])
+x \%>\% lmap(maybe_rep)
+
+# We can apply f() on a selected subset of x
+x \%>\% lmap_at(c("a", "d"), maybe_rep)
+
+# Or only where a condition is satisfied
+x \%>\% lmap_if(is.character, maybe_rep)
+
+
+# A more realistic example would be a function that takes discrete
+# variables in a dataset and turns them into disjunctive tables, a
+# form that is amenable to fitting some types of models.
+
+# A disjunctive table contains only 0 and 1 but has as many columns
+# as unique values in the original variable. Ideally, we want to
+# combine the names of each level with the name of the discrete
+# variable in order to identify them. Given these requirements, it
+# makes sense to have a function that takes a data frame of size 1
+# and returns a data frame of variable size.
+disjoin <- function(x, sep = "_") {
+  name <- names(x)
+  x <- as.factor(x[[1]])
+
+  out <- lapply(levels(x), function(level) {
+    as.numeric(x == level)
+  })
+
+  names(out) <- paste(name, levels(x), sep = sep)
+  tibble::as_tibble(out)
+}
+
+# Now, we are ready to map disjoin() on each categorical variable of a
+# data frame:
+iris \%>\% lmap_if(is.factor, disjoin)
+mtcars \%>\% lmap_at(c("cyl", "vs", "am"), disjoin)
+}
+\seealso{
+Other map variants: \code{\link{imap}},
+  \code{\link{invoke}}, \code{\link{map2}},
+  \code{\link{map}}, \code{\link{modify}}
+}
diff --git a/man/map.Rd b/man/map.Rd
new file mode 100644
index 0000000..dce4d45
--- /dev/null
+++ b/man/map.Rd
@@ -0,0 +1,167 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/map.R
+\name{map}
+\alias{map}
+\alias{map_if}
+\alias{map_at}
+\alias{map_lgl}
+\alias{map_chr}
+\alias{map_int}
+\alias{map_dbl}
+\alias{map_dfr}
+\alias{map_df}
+\alias{map_dfc}
+\alias{walk}
+\title{Apply a function to each element of a vector}
+\usage{
+map(.x, .f, ...)
+
+map_if(.x, .p, .f, ...)
+
+map_at(.x, .at, .f, ...)
+
+map_lgl(.x, .f, ...)
+
+map_chr(.x, .f, ...)
+
+map_int(.x, .f, ...)
+
+map_dbl(.x, .f, ...)
+
+map_dfr(.x, .f, ..., .id = NULL)
+
+map_dfc(.x, .f, ...)
+
+walk(.x, .f, ...)
+}
+\arguments{
+\item{.x}{A list or atomic vector.}
+
+\item{.f}{A function, formula, or atomic vector.
+
+If a \strong{function}, it is used as is.
+
+If a \strong{formula}, e.g. \code{~ .x + 2}, it is converted to a function. There
+are three ways to refer to the arguments:
+\itemize{
+\item For a single argument function, use \code{.}
+\item For a two argument function, use \code{.x} and \code{.y}
+\item For more arguments, use \code{..1}, \code{..2}, \code{..3} etc
+}
+
+This syntax allows you to create very compact anonymous functions.
+
+If \strong{character vector}, \strong{numeric vector}, or \strong{list}, it
+is converted to an extractor function. Character vectors index by name
+and numeric vectors index by position; use a list to index by position
+and name at different levels. Within a list, wrap strings in \code{get_attr()}
+to extract named attributes. If a component is not present, the value of
+\code{.default} will be returned.}
+
+\item{...}{Additional arguments passed on to \code{.f}.}
+
+\item{.p}{A single predicate function, a formula describing such a
+predicate function, or a logical vector of the same length as \code{.x}.
+Alternatively, if the elements of \code{.x} are themselves lists of
+objects, a string indicating the name of a logical element in the
+inner lists. Only those elements where \code{.p} evaluates to
+\code{TRUE} will be modified.}
+
+\item{.at}{A character vector of names or a numeric vector of
+positions. Only those elements corresponding to \code{.at} will be
+modified.}
+
+\item{.id}{If not \code{NULL} a variable with this name will be created
+giving either the name or the index of the data frame.}
+}
+\value{
+All functions return a vector the same length as \code{.x}.
+
+\code{map()} returns a list, \code{map_lgl()} a logical vector, \code{map_int()} an
+integer vector, \code{map_dbl()} a double vector, and \code{map_chr()} a character
+vector. The output of \code{.f} will be automatically typed upwards,
+e.g. logical -> integer -> double -> character.
+
+\code{walk()} returns the input \code{.x} (invisibly). This makes it easy to
+use in pipe.
+}
+\description{
+The map functions transform their input by applying a function to
+each element and returning a vector the same length as the input.
+\itemize{
+\item \code{map()}, \code{map_if()} and \code{map_at()} always return a list. See the
+\code{\link[=modify]{modify()}} family for versions that return an object of the same
+type as the input.
+
+The \code{_if} and \code{_at} variants take a predicate function \code{.p} that
+determines which elements of \code{.x} are transformed with \code{.f}.
+transform.
+\item \code{map_lgl()}, \code{map_int()}, \code{map_dbl()} and \code{map_chr()} return
+vectors of the corresponding type (or die trying).
+\item \code{map_dfr()} and \code{map_dfc()} return data frames created by
+row-binding and column-binding respectively. They require dplyr
+to be installed.
+\item \code{walk()} calls \code{.f} for its side-effect and returns the input \code{.x}.
+}
+}
+\examples{
+1:10 \%>\%
+  map(rnorm, n = 10) \%>\%
+  map_dbl(mean)
+
+# Or use an anonymous function
+1:10 \%>\%
+  map(function(x) rnorm(10, x))
+
+# Or a formula
+1:10 \%>\%
+  map(~ rnorm(10, .x))
+
+# Extract by name or position
+# .default specifies value for elements that are missing or NULL
+l1 <- list(list(a = 1L), list(a = NULL, b = 2L), list(b = 3L))
+l1 \%>\% map("a", .default = "???")
+l1 \%>\% map_int("b", .default = NA)
+l1 \%>\% map_int(2, .default = NA)
+
+# Supply multiple values to index deeply into a list
+l2 <- list(
+  list(num = 1:3,     letters[1:3]),
+  list(num = 101:103, letters[4:6]),
+  list()
+)
+l2 \%>\% map(c(2, 2))
+
+# Use a list to build an extractor that mixes numeric indices and names,
+# and .default to provide a default value if the element does not exist
+l2 \%>\% map(list("num", 3))
+l2 \%>\% map_int(list("num", 3), .default = NA)
+
+# A more realistic example: split a data frame into pieces, fit a
+# model to each piece, summarise and extract R^2
+mtcars \%>\%
+  split(.$cyl) \%>\%
+  map(~ lm(mpg ~ wt, data = .x)) \%>\%
+  map(summary) \%>\%
+  map_dbl("r.squared")
+
+# Use map_lgl(), map_dbl(), etc to reduce to a vector.
+# * list
+mtcars \%>\% map(sum)
+# * vector
+mtcars \%>\% map_dbl(sum)
+
+# If each element of the output is a data frame, use
+# map_df to row-bind them together:
+mtcars \%>\%
+  split(.$cyl) \%>\%
+  map(~ lm(mpg ~ wt, data = .x)) \%>\%
+  map_df(~ as.data.frame(t(as.matrix(coef(.)))))
+# (if you also want to preserve the variable names see
+# the broom package)
+}
+\seealso{
+Other map variants: \code{\link{imap}},
+  \code{\link{invoke}}, \code{\link{lmap}},
+  \code{\link{map2}}, \code{\link{modify}}
+}
diff --git a/man/map2.Rd b/man/map2.Rd
new file mode 100644
index 0000000..065ce72
--- /dev/null
+++ b/man/map2.Rd
@@ -0,0 +1,161 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/map2-pmap.R
+\name{map2}
+\alias{map2}
+\alias{map2_lgl}
+\alias{map2_int}
+\alias{map2_dbl}
+\alias{map2_chr}
+\alias{map2_dfr}
+\alias{map2_dfc}
+\alias{map2_df}
+\alias{walk2}
+\alias{pmap}
+\alias{pmap_lgl}
+\alias{pmap_int}
+\alias{pmap_dbl}
+\alias{pmap_chr}
+\alias{pmap_dfr}
+\alias{pmap_dfc}
+\alias{pmap_df}
+\alias{pwalk}
+\title{Map over multiple inputs simultaneously.}
+\usage{
+map2(.x, .y, .f, ...)
+
+map2_lgl(.x, .y, .f, ...)
+
+map2_int(.x, .y, .f, ...)
+
+map2_dbl(.x, .y, .f, ...)
+
+map2_chr(.x, .y, .f, ...)
+
+map2_dfr(.x, .y, .f, ..., .id = NULL)
+
+map2_dfc(.x, .y, .f, ...)
+
+walk2(.x, .y, .f, ...)
+
+pmap(.l, .f, ...)
+
+pmap_lgl(.l, .f, ...)
+
+pmap_int(.l, .f, ...)
+
+pmap_dbl(.l, .f, ...)
+
+pmap_chr(.l, .f, ...)
+
+pmap_dfr(.l, .f, ..., .id = NULL)
+
+pmap_dfc(.l, .f, ...)
+
+pwalk(.l, .f, ...)
+}
+\arguments{
+\item{.x, .y}{Vectors of the same length. A vector of length 1 will
+be recycled.}
+
+\item{.f}{A function, formula, or atomic vector.
+
+If a \strong{function}, it is used as is.
+
+If a \strong{formula}, e.g. \code{~ .x + 2}, it is converted to a function. There
+are three ways to refer to the arguments:
+\itemize{
+\item For a single argument function, use \code{.}
+\item For a two argument function, use \code{.x} and \code{.y}
+\item For more arguments, use \code{..1}, \code{..2}, \code{..3} etc
+}
+
+This syntax allows you to create very compact anonymous functions.
+
+If \strong{character vector}, \strong{numeric vector}, or \strong{list}, it
+is converted to an extractor function. Character vectors index by name
+and numeric vectors index by position; use a list to index by position
+and name at different levels. Within a list, wrap strings in \code{get_attr()}
+to extract named attributes. If a component is not present, the value of
+\code{.default} will be returned.}
+
+\item{...}{Additional arguments passed on to \code{.f}.}
+
+\item{.id}{If not \code{NULL} a variable with this name will be created
+giving either the name or the index of the data frame.}
+
+\item{.l}{A list of lists. The length of \code{.l} determines the
+number of arguments that \code{.f} will be called with. List
+names will be used if present.}
+}
+\value{
+An atomic vector, list, or data frame, depending on the suffix.
+Atomic vectors and lists will be named if \code{.x} or the first
+element of \code{.l} is named.
+
+If all input is length 0, the output will be length 0. If any
+input is length 1, it will be recycled to the length of the longest.
+}
+\description{
+These functions are variants of \code{map()} iterate over multiple
+arguments in parallel. \code{map2()} and \code{walk2()} are specialised for the two
+argument case; \code{pmap()} and \code{pwalk()} allow you to provide any number of
+arguments in a list.
+}
+\details{
+Note that arguments to be vectorised over come before the \code{.f},
+and arguments that are supplied to every call come after \code{.f}.
+}
+\examples{
+x <- list(1, 10, 100)
+y <- list(1, 2, 3)
+z <- list(5, 50, 500)
+
+map2(x, y, ~ .x + .y)
+# Or just
+map2(x, y, `+`)
+
+# Split into pieces, fit model to each piece, then predict
+by_cyl <- mtcars \%>\% split(.$cyl)
+mods <- by_cyl \%>\% map(~ lm(mpg ~ wt, data = .))
+map2(mods, by_cyl, predict)
+
+pmap(list(x, y, z), sum)
+
+# Matching arguments by position
+pmap(list(x, y, z), function(a, b ,c) a / (b + c))
+
+# Matching arguments by name
+l <- list(a = x, b = y, c = z)
+pmap(l, function(c, b, a) a / (b + c))
+
+# Vectorizing a function over multiple arguments
+df <- data.frame(
+  x = c("apple", "banana", "cherry"),
+  pattern = c("p", "n", "h"),
+  replacement = c("x", "f", "q"),
+  stringsAsFactors = FALSE
+  )
+pmap(df, gsub)
+pmap_chr(df, gsub)
+
+## Use `...` to absorb unused components of input list .l
+df <- data.frame(
+  x = 1:3 + 0.1,
+  y = 3:1 - 0.1,
+  z = letters[1:3]
+)
+plus <- function(x, y) x + y
+\dontrun{
+## this won't work
+pmap(df, plus)
+}
+## but this will
+plus2 <- function(x, y, ...) x + y
+pmap_dbl(df, plus2)
+
+}
+\seealso{
+Other map variants: \code{\link{imap}},
+  \code{\link{invoke}}, \code{\link{lmap}},
+  \code{\link{map}}, \code{\link{modify}}
+}
diff --git a/man/modify.Rd b/man/modify.Rd
new file mode 100644
index 0000000..cd9390d
--- /dev/null
+++ b/man/modify.Rd
@@ -0,0 +1,159 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/modify.R
+\name{modify}
+\alias{modify}
+\alias{modify.default}
+\alias{modify_if}
+\alias{modify_if.default}
+\alias{modify_at}
+\alias{modify_at.default}
+\alias{modify_depth}
+\alias{modify_depth.default}
+\alias{at_depth}
+\title{Modify elements selectively}
+\usage{
+modify(.x, .f, ...)
+
+\method{modify}{default}(.x, .f, ...)
+
+modify_if(.x, .p, .f, ...)
+
+\method{modify_if}{default}(.x, .p, .f, ...)
+
+modify_at(.x, .at, .f, ...)
+
+\method{modify_at}{default}(.x, .at, .f, ...)
+
+modify_depth(.x, .depth, .f, ..., .ragged = .depth < 0)
+
+\method{modify_depth}{default}(.x, .depth, .f, ..., .ragged = .depth < 0)
+}
+\arguments{
+\item{.x}{A list or atomic vector.}
+
+\item{.f}{A function, formula, or atomic vector.
+
+If a \strong{function}, it is used as is.
+
+If a \strong{formula}, e.g. \code{~ .x + 2}, it is converted to a function. There
+are three ways to refer to the arguments:
+\itemize{
+\item For a single argument function, use \code{.}
+\item For a two argument function, use \code{.x} and \code{.y}
+\item For more arguments, use \code{..1}, \code{..2}, \code{..3} etc
+}
+
+This syntax allows you to create very compact anonymous functions.
+
+If \strong{character vector}, \strong{numeric vector}, or \strong{list}, it
+is converted to an extractor function. Character vectors index by name
+and numeric vectors index by position; use a list to index by position
+and name at different levels. Within a list, wrap strings in \code{get_attr()}
+to extract named attributes. If a component is not present, the value of
+\code{.default} will be returned.}
+
+\item{...}{Additional arguments passed on to \code{.f}.}
+
+\item{.p}{A single predicate function, a formula describing such a
+predicate function, or a logical vector of the same length as \code{.x}.
+Alternatively, if the elements of \code{.x} are themselves lists of
+objects, a string indicating the name of a logical element in the
+inner lists. Only those elements where \code{.p} evaluates to
+\code{TRUE} will be modified.}
+
+\item{.at}{A character vector of names or a numeric vector of
+positions. Only those elements corresponding to \code{.at} will be
+modified.}
+
+\item{.depth}{Level of \code{.x} to map on. Use a negative value to count up
+from the lowest level of the list.
+\itemize{
+\item \code{modify_depth(x, 0, fun)} is equivalent to \code{x[] <- fun(x)}
+\item \code{modify_depth(x, 1, fun)} is equivalent to \code{x[] <- map(x, fun)}
+\item \code{modify_depth(x, 2, fun)} is equivalent to \code{x[] <- map(x, ~ map(., fun))}
+}}
+
+\item{.ragged}{If \code{TRUE}, will apply to leaves, even if they're not
+at depth \code{.depth}. If \code{FALSE}, will throw an error if there are
+no elements at depth \code{.depth}.}
+}
+\value{
+An object the same class as \code{.x}
+}
+\description{
+\code{modify()} is a short-cut for \code{x[] <- map(x, .f); return(x)}. \code{modify_if()}
+only modifies the elements of \code{x} that satisfy a predicate and leaves the
+others unchanged. \code{modify_at()} only modifies elements given by names or
+positions. \code{modify_depth()} only modifies elements at a given level of a
+nested data structure.
+}
+\details{
+Since the transformation can alter the structure of the input; it's
+your responsibility to ensure that the transformation produces a
+valid output. For example, if you're modifying a data frame, \code{.f}
+must preserve the length of the input.
+}
+\section{Genericity}{
+
+
+All these functions are S3 generic. However, the default method is
+sufficient in many cases. It should be suitable for any data type
+that implements the subset-assignment method \code{[<-}.
+
+In some cases it may make sense to provide a custom implementation
+with a method suited to your S3 class. For example, a \code{grouped_df}
+method might take into account the grouped nature of a data frame.
+}
+
+\examples{
+# Convert factors to characters
+iris \%>\%
+  modify_if(is.factor, as.character) \%>\%
+  str()
+
+# Specify which columns to map with a numeric vector of positions:
+mtcars \%>\% modify_at(c(1, 4, 5), as.character) \%>\% str()
+
+# Or with a vector of names:
+mtcars \%>\% modify_at(c("cyl", "am"), as.character) \%>\% str()
+
+list(x = rbernoulli(100), y = 1:100) \%>\%
+  transpose() \%>\%
+  modify_if("x", ~ update_list(., y = ~ y * 100)) \%>\%
+  transpose() \%>\%
+  simplify_all()
+
+# Modify at specified depth ---------------------------
+l1 <- list(
+  obj1 = list(
+    prop1 = list(param1 = 1:2, param2 = 3:4),
+    prop2 = list(param1 = 5:6, param2 = 7:8)
+  ),
+  obj2 = list(
+    prop1 = list(param1 = 9:10, param2 = 11:12),
+    prop2 = list(param1 = 12:14, param2 = 15:17)
+  )
+)
+
+# In the above list, "obj" is level 1, "prop" is level 2 and "param"
+# is level 3. To apply sum() on all params, we map it at depth 3:
+l1 \%>\% modify_depth(3, sum) \%>\% str()
+
+# modify() lets us pluck the elements prop1/param2 in obj1 and obj2:
+l1 \%>\% modify(c("prop1", "param2")) \%>\% str()
+
+# But what if we want to pluck all param2 elements? Then we need to
+# act at a lower level:
+l1 \%>\% modify_depth(2, "param2") \%>\% str()
+
+# modify_depth() can be with other purrr functions to make them operate at
+# a lower level. Here we ask pmap() to map paste() simultaneously over all
+# elements of the objects at the second level. paste() is effectively
+# mapped at level 3.
+l1 \%>\% modify_depth(2, ~ pmap(., paste, sep = " / ")) \%>\% str()
+}
+\seealso{
+Other map variants: \code{\link{imap}},
+  \code{\link{invoke}}, \code{\link{lmap}},
+  \code{\link{map2}}, \code{\link{map}}
+}
diff --git a/man/negate.Rd b/man/negate.Rd
new file mode 100644
index 0000000..c7a14fa
--- /dev/null
+++ b/man/negate.Rd
@@ -0,0 +1,38 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/negate.R
+\name{negate}
+\alias{negate}
+\title{Negate a predicate function.}
+\usage{
+negate(.p, .default = FALSE)
+}
+\arguments{
+\item{.p}{A single predicate function, a formula describing such a
+predicate function, or a logical vector of the same length as \code{.x}.
+Alternatively, if the elements of \code{.x} are themselves lists of
+objects, a string indicating the name of a logical element in the
+inner lists. Only those elements where \code{.p} evaluates to
+\code{TRUE} will be modified.}
+
+\item{.default}{Optional additional argument for extractor functions
+(i.e. when \code{.f} is character, integer, or list). Returned when
+value is absent (does not exist) or empty (has length 0).
+\code{.null} is deprecated; please use \code{.default} instead.}
+}
+\value{
+A new predicate function.
+}
+\description{
+Negate a predicate function.
+}
+\examples{
+negate("x")
+negate(is.null)
+negate(~ .x > 0)
+
+x <- transpose(list(x = 1:10, y = rbernoulli(10)))
+x \%>\% keep("y") \%>\% length()
+x \%>\% keep(negate("y")) \%>\% length()
+# Same as
+x \%>\% discard("y") \%>\% length()
+}
diff --git a/man/null-default.Rd b/man/null-default.Rd
new file mode 100644
index 0000000..9b09913
--- /dev/null
+++ b/man/null-default.Rd
@@ -0,0 +1,22 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/utils.R
+\name{null-default}
+\alias{null-default}
+\alias{\%||\%}
+\title{Default value for \code{NULL}.}
+\usage{
+x \%||\% y
+}
+\arguments{
+\item{x, y}{If \code{x} is NULL, will return \code{y}; otherwise returns
+\code{x}.}
+}
+\description{
+This infix function makes it easy to replace \code{NULL}s with a
+default value. It's inspired by the way that Ruby's or operation (\code{||})
+works.
+}
+\examples{
+1 \%||\% 2
+NULL \%||\% 2
+}
diff --git a/man/partial.Rd b/man/partial.Rd
new file mode 100644
index 0000000..eef6006
--- /dev/null
+++ b/man/partial.Rd
@@ -0,0 +1,72 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/partial.R
+\name{partial}
+\alias{partial}
+\title{Partial apply a function, filling in some arguments.}
+\usage{
+partial(...f, ..., .env = parent.frame(), .lazy = TRUE, .first = TRUE)
+}
+\arguments{
+\item{...f}{a function. For the output source to read well, this should be a
+named function.}
+
+\item{...}{named arguments to \code{...f} that should be partially applied.}
+
+\item{.env}{the environment of the created function. Defaults to
+\code{\link[=parent.frame]{parent.frame()}} and you should rarely need to modify this.}
+
+\item{.lazy}{If \code{TRUE} arguments evaluated lazily, if \code{FALSE},
+evaluated when \code{partial} is called.}
+
+\item{.first}{If \code{TRUE}, the partialized arguments are placed
+to the front of the function signature. If \code{FALSE}, they are
+moved to the back. Only useful to control position matching of
+arguments when the partialized arguments are not named.}
+}
+\description{
+Partial function application allows you to modify a function by pre-filling
+some of the arguments.  It is particularly useful in conjunction with
+functionals and other function operators.
+}
+\section{Design choices}{
+
+
+There are many ways to implement partial function application in R.
+(see e.g. \code{dots} in \url{https://github.com/crowding/ptools} for another
+approach.)  This implementation is based on creating functions that are as
+similar as possible to the anonymous functions that you'd create by hand,
+if you weren't using \code{partial}.
+}
+
+\examples{
+# Partial is designed to replace the use of anonymous functions for
+# filling in function arguments. Instead of:
+compact1 <- function(x) discard(x, is.null)
+
+# we can write:
+compact2 <- partial(discard, .p = is.null)
+
+# and the generated source code is very similar to what we made by hand
+compact1
+compact2
+
+# Note that the evaluation occurs "lazily" so that arguments will be
+# repeatedly evaluated
+f <- partial(runif, n = rpois(1, 5))
+f
+f()
+f()
+
+# You can override this by saying .lazy = FALSE
+f <- partial(runif, n = rpois(1, 5), .lazy = FALSE)
+f
+f()
+f()
+
+# This also means that partial works fine with functions that do
+# non-standard evaluation
+my_long_variable <- 1:10
+plot2 <- partial(plot, my_long_variable)
+plot2()
+plot2(runif(10), type = "l")
+}
diff --git a/man/pipe.Rd b/man/pipe.Rd
new file mode 100644
index 0000000..f9ca34a
--- /dev/null
+++ b/man/pipe.Rd
@@ -0,0 +1,12 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/utils.R
+\name{\%>\%}
+\alias{\%>\%}
+\title{Pipe operator}
+\usage{
+lhs \%>\% rhs
+}
+\description{
+Pipe operator
+}
+\keyword{internal}
diff --git a/man/pluck.Rd b/man/pluck.Rd
new file mode 100644
index 0000000..b088e47
--- /dev/null
+++ b/man/pluck.Rd
@@ -0,0 +1,98 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/as_mapper.R
+\name{pluck}
+\alias{pluck}
+\alias{attr_getter}
+\title{Pluck out a single an element from a vector or environment}
+\usage{
+pluck(.x, ..., .default = NULL)
+
+attr_getter(attr)
+}
+\arguments{
+\item{.x}{A vector or environment}
+
+\item{...}{A list of accessors for indexing into the object. Can be
+an integer position, a string name, or an accessor function. If
+the object being indexed is an S4 object, accessing it by name
+will return the corresponding slot.
+
+These dots \link[rlang:dots_splice]{splice lists automatically}. This
+means you can supply arguments and lists of arguments
+indistinctly.}
+
+\item{.default}{Value to use if target is empty or absent.}
+
+\item{attr}{An attribute name as string.}
+}
+\description{
+This is a generalised form of \code{[[} which allows you to index deeply
+and flexibly into data structures. It supports R standard accessors
+like integer positions and string names, and also accepts arbitrary
+accessor functions, i.e. functions that take an object and return
+some internal piece.
+
+\code{pluck()} is often more readable than a mix of operators and
+accessors because it reads linearly and is free of syntactic
+cruft. Compare: \code{accessor(x[[1]])$foo} to \code{pluck(x, 1, accessor, "foo")}.
+
+Furthermore, \code{pluck()} never partial-matches unlike \code{$} which will
+select the \code{disp} object if you write \code{mtcars$di}.
+
+[[` which allows you to index deeply
+and flexibly into data structures. It supports R standard accessors
+like integer positions and string names, and also accepts arbitrary
+accessor functions, i.e. functions that take an object and return
+some internal piece.
+
+\code{pluck()} is often more readable than a mix of operators and
+accessors because it reads linearly and is free of syntactic
+cruft. Compare: `accessor(x[[1]: R:[%60%20which%20allows%20you%20to%20index%20deeply%0Aand%20flexibly%20into%20data%20structures.%20It%20supports%20R%20standard%20accessors%0Alike%20integer%20positions%20and%20string%20names,%20and%20also%20accepts%20arbitrary%0Aaccessor%20functions,%20i.e.%20functions%20that%20take%20an%20object%20and%20return%0Asome%20internal%20piece.%0A%0A%60pluck()%60%20is%20often%20more%20readable%20than%20a%20mix%20of%20operators%20and%0Aaccessors%20because%20it%2 [...]
+}
+\details{
+Since it handles arbitrary accessor functions, \code{pluck()} is a type
+of composition operator. However, it is indexing-oriented thanks to
+its handling of strings and integers. By the same token is also
+explicit regarding the intent of the composition (e.g. extraction).
+}
+\examples{
+# pluck() supports integer positions, string names, and functions.
+# Using functions, you can easily extend pluck(). Let's create a
+# list of data structures:
+obj1 <- list("a", list(1, elt = "foobar"))
+obj2 <- list("b", list(2, elt = "foobaz"))
+x <- list(obj1, obj2)
+
+# And now an accessor for these complex data structures:
+my_element <- function(x) x[[2]]$elt
+
+# The accessor can then be passed to pluck:
+pluck(x, 1, my_element)
+pluck(x, 2, my_element)
+
+# Even for this simple data structure, this is more readable than
+# the alternative form because it requires you to read both from
+# right-to-left and from left-to-right in different parts of the
+# expression:
+my_element(x[[1]])
+
+
+# This technique is used for plucking into attributes with
+# attr_getter(). It takes an attribute name and returns a function
+# to access the attribute:
+obj1 <- structure("obj", obj_attr = "foo")
+obj2 <- structure("obj", obj_attr = "bar")
+x <- list(obj1, obj2)
+
+# pluck() is handy for extracting deeply into a data structure.
+# Here we'll first extract by position, then by attribute:
+pluck(x, 1, attr_getter("obj_attr"))  # From first object
+pluck(x, 2, attr_getter("obj_attr"))  # From second object
+
+
+# pluck() splices lists of arguments automatically. The following
+# pluck is equivalent to the one above:
+idx <- list(1, attr_getter("obj_attr"))
+pluck(x, idx)
+}
+\keyword{internal}
diff --git a/man/prepend.Rd b/man/prepend.Rd
new file mode 100644
index 0000000..6ba07df
--- /dev/null
+++ b/man/prepend.Rd
@@ -0,0 +1,31 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/prepend.R
+\name{prepend}
+\alias{prepend}
+\title{Prepend a vector}
+\usage{
+prepend(x, values, before = 1)
+}
+\arguments{
+\item{x}{the vector to be modified.}
+
+\item{values}{to be included in the modified vector.}
+
+\item{before}{a subscript, before which the values are to be appended.}
+}
+\value{
+A merged vector.
+}
+\description{
+This is a companion to \code{\link[=append]{append()}} to help merging two
+lists or atomic vectors. \code{prepend()} is a clearer semantic
+signal than \code{c()} that a vector is to be merged at the beginning of
+another, especially in a pipe chain.
+}
+\examples{
+x <- as.list(1:3)
+
+x \%>\% append("a")
+x \%>\% prepend("a")
+x \%>\% prepend(list("a", "b"), before = 3)
+}
diff --git a/man/purrr-package.Rd b/man/purrr-package.Rd
new file mode 100644
index 0000000..04ee1a5
--- /dev/null
+++ b/man/purrr-package.Rd
@@ -0,0 +1,34 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/purrr.R
+\docType{package}
+\name{purrr-package}
+\alias{purrr}
+\alias{purrr-package}
+\title{purrr: Functional Programming Tools}
+\description{
+A complete and consistent functional programming toolkit for R.
+}
+\seealso{
+Useful links:
+\itemize{
+  \item \url{http://purrr.tidyverse.org}
+  \item \url{https://github.com/tidyverse/purrr}
+  \item Report bugs at \url{https://github.com/tidyverse/purrr/issues}
+}
+
+}
+\author{
+\strong{Maintainer}: Lionel Henry \email{lionel at rstudio.com}
+
+Authors:
+\itemize{
+  \item Hadley Wickham \email{hadley at rstudio.com}
+}
+
+Other contributors:
+\itemize{
+  \item RStudio [copyright holder, funder]
+}
+
+}
+\keyword{internal}
diff --git a/man/rbernoulli.Rd b/man/rbernoulli.Rd
new file mode 100644
index 0000000..c00e575
--- /dev/null
+++ b/man/rbernoulli.Rd
@@ -0,0 +1,23 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/utils.R
+\name{rbernoulli}
+\alias{rbernoulli}
+\title{Generate random sample from a Bernoulli distribution}
+\usage{
+rbernoulli(n, p = 0.5)
+}
+\arguments{
+\item{n}{Number of samples}
+
+\item{p}{Probability of getting \code{TRUE}}
+}
+\value{
+A logical vector
+}
+\description{
+Generate random sample from a Bernoulli distribution
+}
+\examples{
+rbernoulli(10)
+rbernoulli(100, 0.1)
+}
diff --git a/man/rdunif.Rd b/man/rdunif.Rd
new file mode 100644
index 0000000..ae5fcf6
--- /dev/null
+++ b/man/rdunif.Rd
@@ -0,0 +1,20 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/utils.R
+\name{rdunif}
+\alias{rdunif}
+\title{Generate random sample from a discrete uniform distribution}
+\usage{
+rdunif(n, b, a = 1)
+}
+\arguments{
+\item{n}{Number of samples to draw.}
+
+\item{a, b}{Range of the distribution (inclusive).}
+}
+\description{
+Generate random sample from a discrete uniform distribution
+}
+\examples{
+table(rdunif(1e3, 10))
+table(rdunif(1e3, 10, -5))
+}
diff --git a/man/reduce.Rd b/man/reduce.Rd
new file mode 100644
index 0000000..ea1d28a
--- /dev/null
+++ b/man/reduce.Rd
@@ -0,0 +1,64 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/reduce.R
+\name{reduce}
+\alias{reduce}
+\alias{reduce_right}
+\alias{reduce2}
+\alias{reduce2_right}
+\title{Reduce a list to a single value by iteratively applying a binary function.}
+\usage{
+reduce(.x, .f, ..., .init)
+
+reduce_right(.x, .f, ..., .init)
+
+reduce2(.x, .y, .f, ..., .init)
+
+reduce2_right(.x, .y, .f, ..., .init)
+}
+\arguments{
+\item{.x}{A list or atomic vector.}
+
+\item{.f}{For \code{reduce()}, a 2-argument function. The function will be
+passed the accumulated value as the first argument and the "next" value
+as the second argument.
+
+For \code{reduce2()}, a 3-argument function. The function will be passed the
+accumulated value as the first argument, the next value of \code{.x} as the
+second argument, and the next value of \code{.y} as the third argument.}
+
+\item{...}{Additional arguments passed on to \code{.f}.}
+
+\item{.init}{If supplied, will be used as the first value to start
+the accumulation, rather than using \code{x[[1]]}. This is useful if
+you want to ensure that \code{reduce} returns a correct value when \code{.x}
+is empty. If missing, and \code{x} is empty, will throw an error.}
+
+\item{.y}{For \code{reduce2()}, an additional argument that is passed to
+\code{.f}. If \code{init} is not set, \code{.y} should be 1 element shorter than
+\code{.x}.}
+}
+\description{
+\code{reduce()} combines from the left, \code{reduce_right()} combines from
+the right. \code{reduce(list(x1, x2, x3), f)} is equivalent to
+\code{f(f(x1, x2), x3)}; \code{reduce_right(list(x1, x2, x3), f)} is equivalent to
+\code{f(f(x3, x2), x1)}.
+}
+\examples{
+1:3 \%>\% reduce(`+`)
+1:10 \%>\% reduce(`*`)
+
+paste2 <- function(x, y, sep = ".") paste(x, y, sep = sep)
+letters[1:4] \%>\% reduce(paste2)
+letters[1:4] \%>\% reduce2(c("-", ".", "-"), paste2)
+
+samples <- rerun(2, sample(10, 5))
+samples
+reduce(samples, union)
+reduce(samples, intersect)
+
+x <- list(c(0, 1), c(2, 3), c(4, 5))
+x \%>\% reduce(c)
+x \%>\% reduce_right(c)
+# Equivalent to:
+x \%>\% rev() \%>\% reduce(c)
+}
diff --git a/man/reexports.Rd b/man/reexports.Rd
new file mode 100644
index 0000000..4086dec
--- /dev/null
+++ b/man/reexports.Rd
@@ -0,0 +1,66 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/predicates.R
+\docType{import}
+\name{reexports}
+\alias{reexports}
+\alias{is_bare_list}
+\alias{reexports}
+\alias{is_bare_atomic}
+\alias{reexports}
+\alias{is_bare_vector}
+\alias{reexports}
+\alias{is_bare_double}
+\alias{reexports}
+\alias{is_bare_integer}
+\alias{reexports}
+\alias{is_bare_numeric}
+\alias{reexports}
+\alias{is_bare_character}
+\alias{reexports}
+\alias{is_bare_logical}
+\alias{reexports}
+\alias{is_list}
+\alias{reexports}
+\alias{is_atomic}
+\alias{reexports}
+\alias{is_vector}
+\alias{reexports}
+\alias{is_integer}
+\alias{reexports}
+\alias{is_double}
+\alias{reexports}
+\alias{is_character}
+\alias{reexports}
+\alias{is_logical}
+\alias{reexports}
+\alias{is_null}
+\alias{reexports}
+\alias{is_function}
+\alias{reexports}
+\alias{is_scalar_list}
+\alias{reexports}
+\alias{is_scalar_atomic}
+\alias{reexports}
+\alias{is_scalar_vector}
+\alias{reexports}
+\alias{is_scalar_double}
+\alias{reexports}
+\alias{is_scalar_character}
+\alias{reexports}
+\alias{is_scalar_logical}
+\alias{reexports}
+\alias{is_scalar_integer}
+\alias{reexports}
+\alias{is_empty}
+\alias{reexports}
+\alias{is_formula}
+\title{Objects exported from other packages}
+\keyword{internal}
+\description{
+These objects are imported from other packages. Follow the links
+below to see their documentation.
+
+\describe{
+  \item{rlang}{\code{\link[rlang]{is_bare_list}}, \code{\link[rlang]{is_bare_atomic}}, \code{\link[rlang]{is_bare_vector}}, \code{\link[rlang]{is_bare_double}}, \code{\link[rlang]{is_bare_integer}}, \code{\link[rlang]{is_bare_numeric}}, \code{\link[rlang]{is_bare_character}}, \code{\link[rlang]{is_bare_logical}}, \code{\link[rlang]{is_list}}, \code{\link[rlang]{is_atomic}}, \code{\link[rlang]{is_vector}}, \code{\link[rlang]{is_integer}}, \code{\link[rlang]{is_double}}, \code{\link[rlang] [...]
+}}
+
diff --git a/man/rerun.Rd b/man/rerun.Rd
new file mode 100644
index 0000000..dec80d9
--- /dev/null
+++ b/man/rerun.Rd
@@ -0,0 +1,31 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/rerun.R
+\name{rerun}
+\alias{rerun}
+\title{Re-run expressions multiple times.}
+\usage{
+rerun(.n, ...)
+}
+\arguments{
+\item{.n}{Number of times to run expressions}
+
+\item{...}{Expressions to re-run.}
+}
+\value{
+A list of length \code{.n}. Each element of \code{...} will be
+re-run once for each \code{.n}. It
+
+There is one special case: if there's a single unnamed input, the second
+level list will be dropped. In this case, \code{rerun(n, x)} behaves like
+\code{replicate(n, x, simplify = FALSE)}.
+}
+\description{
+This is a convenient way of generating sample data. It works similarly to
+\code{\link{replicate}(..., simplify = FALSE)}.
+}
+\examples{
+10 \%>\% rerun(rnorm(5))
+10 \%>\%
+  rerun(x = rnorm(5), y = rnorm(5)) \%>\%
+  map_dbl(~ cor(.x$x, .x$y))
+}
diff --git a/man/safely.Rd b/man/safely.Rd
new file mode 100644
index 0000000..44bedfc
--- /dev/null
+++ b/man/safely.Rd
@@ -0,0 +1,99 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/output.R
+\name{safely}
+\alias{safely}
+\alias{quietly}
+\alias{possibly}
+\alias{auto_browse}
+\title{Capture side effects.}
+\usage{
+safely(.f, otherwise = NULL, quiet = TRUE)
+
+quietly(.f)
+
+possibly(.f, otherwise, quiet = TRUE)
+
+auto_browse(.f)
+}
+\arguments{
+\item{.f}{A function, formula, or atomic vector.
+
+If a \strong{function}, it is used as is.
+
+If a \strong{formula}, e.g. \code{~ .x + 2}, it is converted to a function. There
+are three ways to refer to the arguments:
+\itemize{
+\item For a single argument function, use \code{.}
+\item For a two argument function, use \code{.x} and \code{.y}
+\item For more arguments, use \code{..1}, \code{..2}, \code{..3} etc
+}
+
+This syntax allows you to create very compact anonymous functions.
+
+If \strong{character vector}, \strong{numeric vector}, or \strong{list}, it
+is converted to an extractor function. Character vectors index by name
+and numeric vectors index by position; use a list to index by position
+and name at different levels. Within a list, wrap strings in \code{get_attr()}
+to extract named attributes. If a component is not present, the value of
+\code{.default} will be returned.}
+
+\item{otherwise}{Default value to use when an error occurs.}
+
+\item{quiet}{Hide errors (\code{TRUE}, the default), or display them
+as they occur?}
+}
+\value{
+\code{safely}: wrapped function instead returns a list with
+components \code{result} and \code{error}. One value is always \code{NULL}.
+
+\code{quietly}: wrapped function instead returns a list with components
+\code{result}, \code{output}, \code{messages} and \code{warnings}.
+
+\code{possibly}: wrapped function uses a default value (\code{otherwise})
+whenever an error occurs.
+}
+\description{
+These functions wrap functions so that instead of generating side effects
+through printed output, messages, warnings, and errors, they return enhanced
+output. They are all adverbs because they modify the action of a verb (a
+function).
+}
+\examples{
+safe_log <- safely(log)
+safe_log(10)
+safe_log("a")
+
+list("a", 10, 100) \%>\%
+  map(safe_log) \%>\%
+  transpose()
+
+# This is a bit easier to work with if you supply a default value
+# of the same type and use the simplify argument to transpose():
+safe_log <- safely(log, otherwise = NA_real_)
+list("a", 10, 100) \%>\%
+  map(safe_log) \%>\%
+  transpose() \%>\%
+  simplify_all()
+
+# To replace errors with a default value, use possibly().
+list("a", 10, 100) \%>\%
+  map_dbl(possibly(log, NA_real_))
+
+# For interactive usage, auto_browse() is useful because it automatically
+# starts a browser() in the right place.
+f <- function(x) {
+  y <- 20
+  if (x > 5) {
+    stop("!")
+  } else {
+    x
+  }
+}
+if (interactive()) {
+  map(1:6, auto_browse(f))
+}
+
+# It doesn't make sense to use auto_browse with primitive functions,
+# because they are implemented in C so there's no useful environment
+# for you to interact with.
+}
diff --git a/man/set_names.Rd b/man/set_names.Rd
new file mode 100644
index 0000000..b086ac3
--- /dev/null
+++ b/man/set_names.Rd
@@ -0,0 +1,51 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/set_names.R
+\docType{import}
+\name{set_names}
+\alias{set_names}
+\title{Set names in a vector}
+\usage{
+set_names(x, nm = x, ...)
+}
+\arguments{
+\item{x}{Vector to name}
+
+\item{nm, ...}{Vector of names, the same length as \code{x}.
+
+You can specify names in three ways:
+\itemize{
+\item If you do nothing, \code{x} will be named with itself
+\item You can supply either a character vector to \code{nm} or individual
+strings in to `...``
+\item If \code{x} already has names, you can provide a function or formula
+to transform the existing names.
+}}
+}
+\value{
+\code{.x} with the names attribute set.
+}
+\details{
+This is a snake case wrapper for \code{\link[stats:setNames]{stats::setNames()}}, with
+tweaked defaults, and stricter argument checking.
+}
+\examples{
+set_names(1:4, c("a", "b", "c", "d"))
+set_names(1:4, letters[1:4])
+set_names(1:4, "a", "b", "c", "d")
+
+# If the second argument is ommitted a vector is named with itself
+set_names(letters[1:5])
+
+# Alternatively you can supply a function
+set_names(1:10, ~ letters[seq_along(.)])
+set_names(head(mtcars), toupper)
+}
+\keyword{internal}
+\description{
+These objects are imported from other packages. Follow the links
+below to see their documentation.
+
+\describe{
+  \item{rlang}{\code{\link[rlang]{set_names}}}
+}}
+
diff --git a/man/splice.Rd b/man/splice.Rd
new file mode 100644
index 0000000..2b600f1
--- /dev/null
+++ b/man/splice.Rd
@@ -0,0 +1,26 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/splice.R
+\name{splice}
+\alias{splice}
+\title{Splice objects and lists of objects into a list}
+\usage{
+splice(...)
+}
+\arguments{
+\item{...}{Objects to concatenate.}
+}
+\value{
+A list.
+}
+\description{
+This splices all arguments into a list. Non-list objects and lists
+with a S3 class are encapsulated in a list before concatenation.
+}
+\examples{
+inputs <- list(arg1 = "a", arg2 = "b")
+
+# splice() concatenates the elements of inputs with arg3
+splice(inputs, arg3 = c("c1", "c2")) \%>\% str()
+list(inputs, arg3 = c("c1", "c2")) \%>\% str()
+c(inputs, arg3 = c("c1", "c2")) \%>\% str()
+}
diff --git a/man/transpose.Rd b/man/transpose.Rd
new file mode 100644
index 0000000..0733166
--- /dev/null
+++ b/man/transpose.Rd
@@ -0,0 +1,61 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/transpose.R
+\name{transpose}
+\alias{transpose}
+\title{Transpose a list.}
+\usage{
+transpose(.l, .names = NULL)
+}
+\arguments{
+\item{.l}{A list of vectors to zip. The first element is used as the
+template; you'll get a warning if a sub-list is not the same length as
+the first element.}
+
+\item{.names}{For efficiency, \code{transpose()} usually inspects the
+first component of \code{.l} to determine the structure. Use \code{.names}
+if you want to override this default.}
+}
+\value{
+A list with indexing transposed compared to \code{.l}.
+}
+\description{
+Transpose turns a list-of-lists "inside-out"; it turns a pair of lists into a
+list of pairs, or a list of pairs into pair of lists. For example,
+if you had a list of length n where each component had values \code{a} and
+\code{b}, \code{transpose()} would make a list with elements \code{a} and
+\code{b} that contained lists of length n. It's called transpose because
+\code{x[[1]][[2]]} is equivalent to \code{transpose(x)[[2]][[1]]}.
+}
+\details{
+Note that \code{transpose()} is its own inverse, much like the
+transpose operation on a matrix. You can get back the original
+input by transposing it twice.
+}
+\examples{
+x <- rerun(5, x = runif(1), y = runif(5))
+x \%>\% str()
+x \%>\% transpose() \%>\% str()
+# Back to where we started
+x \%>\% transpose() \%>\% transpose() \%>\% str()
+
+# transpose() is useful in conjunction with safely() & quietly()
+x <- list("a", 1, 2)
+y <- x \%>\% map(safely(log))
+y \%>\% str()
+y \%>\% transpose() \%>\% str()
+
+# Use simplify_all() to reduce to atomic vectors where possible
+x <- list(list(a = 1, b = 2), list(a = 3, b = 4), list(a = 5, b = 6))
+x \%>\% transpose()
+x \%>\% transpose() \%>\% simplify_all()
+
+# Provide explicit component names to prevent loss of those that don't
+# appear in first component
+ll <- list(
+  list(x = 1, y = "one"),
+  list(z = "deux", x = 2)
+)
+ll \%>\% transpose()
+nms <- ll \%>\% map(names) \%>\% reduce(union)
+ll \%>\% transpose(.names = nms)
+}
diff --git a/man/vec_depth.Rd b/man/vec_depth.Rd
new file mode 100644
index 0000000..1753016
--- /dev/null
+++ b/man/vec_depth.Rd
@@ -0,0 +1,27 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/depth.R
+\name{vec_depth}
+\alias{vec_depth}
+\title{Compute the depth of a vector}
+\usage{
+vec_depth(x)
+}
+\arguments{
+\item{x}{A vector}
+}
+\value{
+An integer.
+}
+\description{
+The depth of a vector is basically how many levels that you can index
+into it.
+}
+\examples{
+x <- list(
+  list(),
+  list(list()),
+  list(list(list(1)))
+)
+vec_depth(x)
+x \%>\% map_int(vec_depth)
+}
diff --git a/man/when.Rd b/man/when.Rd
new file mode 100644
index 0000000..7a1ac30
--- /dev/null
+++ b/man/when.Rd
@@ -0,0 +1,61 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/when.R
+\name{when}
+\alias{when}
+\title{Match/validate a set of conditions for an object and continue with the action
+associated with the first valid match.}
+\usage{
+when(., ...)
+}
+\arguments{
+\item{.}{the value to match against}
+
+\item{...}{formulas; each containing a condition as LHS and an action as RHS.
+named arguments will define additional values.}
+}
+\value{
+The value resulting from the action of the first valid
+match/condition is returned. If no matches are found, and no default is
+given, NULL will be returned.
+
+Validity of the conditions are tested with \code{isTRUE}, or equivalently
+with \code{identical(condition, TRUE)}.
+In other words conditions resulting in more than one logical will never
+be valid. Note that the input value is always treated as a single object,
+as opposed to the \code{ifelse} function.
+}
+\description{
+\code{when} is a flavour of pattern matching (or an if-else abstraction) in
+which a value is matched against a sequence of condition-action sets. When a
+valid match/condition is found the action is executed and the result of the
+action is returned.
+}
+\examples{
+1:10 \%>\%
+  when(
+    sum(.) <=  50 ~ sum(.),
+    sum(.) <= 100 ~ sum(.)/2,
+    ~ 0
+  )
+
+1:10 \%>\%
+  when(
+    sum(.) <=   x ~ sum(.),
+    sum(.) <= 2*x ~ sum(.)/2,
+    ~ 0,
+    x = 60
+  )
+
+iris \%>\%
+  subset(Sepal.Length > 10) \%>\%
+  when(
+    nrow(.) > 0 ~ .,
+    ~ iris \%>\% head(10)
+  )
+
+iris \%>\%
+  head \%>\%
+  when(nrow(.) < 10 ~ .,
+       ~ stop("Expected fewer than 10 rows."))
+}
+\keyword{internal}
diff --git a/src/coerce.c b/src/coerce.c
new file mode 100644
index 0000000..b6831d1
--- /dev/null
+++ b/src/coerce.c
@@ -0,0 +1,101 @@
+#define R_NO_REMAP
+#include <R.h>
+#include <Rinternals.h>
+#include <stdio.h>
+
+double logical_to_real(int x) {
+  return (x == NA_LOGICAL) ? NA_REAL : x;
+}
+double integer_to_real(int x) {
+  return (x == NA_INTEGER) ? NA_REAL : x;
+}
+SEXP logical_to_char(int x) {
+  if (x == NA_LOGICAL)
+    return NA_STRING;
+
+  return Rf_mkChar(x ? "TRUE" : "FALSE");
+}
+SEXP integer_to_char(int x) {
+  if (x == NA_INTEGER)
+    return NA_STRING;
+
+  char buf[100];
+  snprintf(buf, 100, "%d", x);
+  return Rf_mkChar(buf);
+}
+SEXP double_to_char(double x) {
+  if (!R_finite(x)) {
+    if (R_IsNA(x)) {
+      return NA_STRING;
+    } else if (R_IsNaN(x)) {
+      return Rf_mkChar("NaN");
+    } else if (x > 0) {
+      return Rf_mkChar("Inf");
+    } else {
+      return Rf_mkChar("-Inf");
+    }
+  }
+
+  char buf[100];
+  snprintf(buf, 100, "%f", x);
+  return Rf_mkChar(buf);
+}
+
+void cant_coerce(SEXP from, SEXP to, int i) {
+  Rf_errorcall(R_NilValue, "Can't coerce element %i from a %s to a %s",
+    i + 1, Rf_type2char(TYPEOF(from)), Rf_type2char(TYPEOF(to)));
+}
+
+void set_vector_value(SEXP to, int i, SEXP from, int j) {
+  switch(TYPEOF(to)) {
+  case LGLSXP:
+    switch(TYPEOF(from)) {
+    case LGLSXP: LOGICAL(to)[i] = LOGICAL(from)[j]; break;
+    default: cant_coerce(from, to, i);
+    }
+    break;
+  case INTSXP:
+    switch(TYPEOF(from)) {
+    case LGLSXP: INTEGER(to)[i] = LOGICAL(from)[j]; break;
+    case INTSXP: INTEGER(to)[i] = INTEGER(from)[j]; break;
+    default: cant_coerce(from, to, i);
+    }
+    break;
+  case REALSXP:
+    switch(TYPEOF(from)) {
+    case LGLSXP:  REAL(to)[i] = logical_to_real(LOGICAL(from)[j]); break;
+    case INTSXP:  REAL(to)[i] = integer_to_real(INTEGER(from)[j]); break;
+    case REALSXP: REAL(to)[i] = REAL(from)[j]; break;
+    default: cant_coerce(from, to, i);
+    }
+    break;
+  case STRSXP:
+    switch(TYPEOF(from)) {
+    case LGLSXP:  SET_STRING_ELT(to, i, logical_to_char(LOGICAL(from)[j])); break;
+    case INTSXP:  SET_STRING_ELT(to, i, integer_to_char(INTEGER(from)[j])); break;
+    case REALSXP: SET_STRING_ELT(to, i, double_to_char(REAL(from)[j])); break;
+    case STRSXP:  SET_STRING_ELT(to, i, STRING_ELT(from, j)); break;
+    default: cant_coerce(from, to, i);
+    }
+    break;
+  case VECSXP:
+    SET_VECTOR_ELT(to, i, from);
+    break;
+  default: cant_coerce(from, to, i);
+  }
+}
+
+
+SEXP coerce_impl(SEXP x, SEXP type_) {
+  int n = Rf_length(x);
+
+  SEXPTYPE type = Rf_str2type(CHAR(Rf_asChar(type_)));
+  SEXP out = PROTECT(Rf_allocVector(type, n));
+
+  for (int i = 0; i < n; ++i) {
+    set_vector_value(out, i, x, i);
+  }
+
+  UNPROTECT(1);
+  return out;
+}
diff --git a/src/coerce.h b/src/coerce.h
new file mode 100644
index 0000000..00c9aee
--- /dev/null
+++ b/src/coerce.h
@@ -0,0 +1,8 @@
+#ifndef UTILS_H
+#define UTILS_H
+
+// Set value of to[i] to from[j], coercing vectors using usual rules.
+void set_vector_value(SEXP to, int i, SEXP from, int j);
+
+
+#endif
diff --git a/src/extract.c b/src/extract.c
new file mode 100644
index 0000000..9cda3fd
--- /dev/null
+++ b/src/extract.c
@@ -0,0 +1,158 @@
+#define R_NO_REMAP
+#include <R.h>
+#include <Rinternals.h>
+#include "coerce.h"
+#include <string.h>
+
+int find_offset(SEXP x, SEXP index, int i) {
+  if (Rf_length(index) > 1) {
+    Rf_errorcall(R_NilValue, "Index %i must have length 1", i + 1);
+  }
+
+  int n = Rf_length(x);
+  if (n == 0)
+    return -1;
+
+  if (TYPEOF(index) == INTSXP) {
+    int val = INTEGER(index)[0];
+
+    if (val == NA_INTEGER)
+      return -1;
+
+    val--;
+    if (val < 0 || val >= n)
+      return -1;
+
+    return val;
+  } if (TYPEOF(index) == REALSXP) {
+    double val = REAL(index)[0];
+
+    if (!R_finite(val))
+      return -1;
+
+    val--;
+    if (val < 0 || val >= n)
+      return -1;
+
+    return val;
+  } else if (TYPEOF(index) == STRSXP) {
+    SEXP names = Rf_getAttrib(x, R_NamesSymbol);
+    if (names == R_NilValue) // vector doesn't have names
+      return -1;
+
+    if (STRING_ELT(index, 0) == NA_STRING)
+      return -1;
+
+    const char* val = Rf_translateCharUTF8(STRING_ELT(index, 0));
+    if (val[0] == '\0') // "" matches nothing
+      return -1;
+
+    for (int j = 0; j < Rf_length(names); ++j) {
+      if (STRING_ELT(names, j) == NA_STRING)
+        continue;
+
+      const char* names_j = Rf_translateCharUTF8(STRING_ELT(names, j));
+      if (strcmp(names_j, val) == 0)
+        return j;
+
+    }
+    return -1;
+  } else {
+    Rf_errorcall(
+      R_NilValue,
+      "Index %i must be a character or numeric vector", i + 1
+    );
+  }
+}
+
+SEXP extract_vector(SEXP x, SEXP index_i, int i) {
+  int offset = find_offset(x, index_i, i);
+  if (offset < 0)
+    return R_NilValue;
+
+  switch(TYPEOF(x)) {
+  case LGLSXP:  return Rf_ScalarLogical(LOGICAL(x)[offset]);
+  case INTSXP:  return Rf_ScalarInteger(INTEGER(x)[offset]);
+  case REALSXP: return Rf_ScalarReal(REAL(x)[offset]);
+  case STRSXP:  return Rf_ScalarString(STRING_ELT(x, offset));
+  case VECSXP:  return VECTOR_ELT(x, offset);
+  default:
+    Rf_errorcall(R_NilValue,
+      "Don't know how to index object of type %s at level %i",
+      Rf_type2char(TYPEOF(x)), i + 1
+    );
+  }
+
+  return R_NilValue;
+}
+
+SEXP extract_env(SEXP x, SEXP index_i, int i) {
+  if (TYPEOF(index_i) != STRSXP || Rf_length(index_i) != 1) {
+    Rf_errorcall(R_NilValue, "Index %i is not a string", i + 1);
+  }
+
+  SEXP index = STRING_ELT(index_i, 0);
+  if (index == NA_STRING)
+    return R_NilValue;
+
+  SEXP sym = Rf_installChar(index);
+  SEXP out = Rf_findVarInFrame3(x, sym, TRUE);
+
+  return (out == R_UnboundValue) ? R_NilValue : out;
+}
+
+SEXP extract_attr(SEXP x, SEXP index_i, int i) {
+  if (TYPEOF(index_i) != STRSXP || Rf_length(index_i) != 1) {
+    Rf_errorcall(R_NilValue, "Index %i is not a string", i + 1);
+  }
+
+  SEXP index = STRING_ELT(index_i, 0);
+  if (index == NA_STRING)
+    return R_NilValue;
+
+  SEXP sym = Rf_installChar(index);
+  return Rf_getAttrib(x, sym);
+}
+
+SEXP extract_clo(SEXP x, SEXP clo) {
+  SEXP expr = PROTECT(Rf_lang2(clo, x));
+  SEXP out = Rf_eval(expr, R_EmptyEnv);
+
+  UNPROTECT(1);
+  return out;
+}
+
+SEXP extract_impl(SEXP x, SEXP index, SEXP missing) {
+  if (TYPEOF(index) != VECSXP) {
+    Rf_errorcall(R_NilValue, "`index` must be a list (not a %s)",
+      Rf_type2char(TYPEOF(index)));
+  }
+
+  int n = Rf_length(index);
+
+  for (int i = 0; i < n; ++i) {
+    SEXP index_i = VECTOR_ELT(index, i);
+
+    if (TYPEOF(index_i) == CLOSXP) {
+      x = extract_clo(x, index_i);
+    } else {
+      if (Rf_isNull(x)) {
+        return missing;
+      } else if (Rf_isVector(x)) {
+        x = extract_vector(x, index_i, i);
+      } else if (Rf_isEnvironment(x)) {
+        x = extract_env(x, index_i, i);
+      } else if (Rf_isS4(x)) {
+        x = extract_attr(x, index_i, i);
+      } else {
+        Rf_errorcall(R_NilValue,
+          "Don't know how to pluck from a %s", Rf_type2char(TYPEOF(x))
+        );
+      }
+    }
+
+  }
+
+  return (Rf_length(x) == 0) ? missing : x;
+}
+
diff --git a/src/flatten.c b/src/flatten.c
new file mode 100644
index 0000000..b24beb3
--- /dev/null
+++ b/src/flatten.c
@@ -0,0 +1,126 @@
+#define R_NO_REMAP
+#include <R.h>
+#include <Rinternals.h>
+#include "coerce.h"
+
+const char* objtype(SEXP x) {
+  return Rf_type2char(TYPEOF(x));
+}
+
+SEXP flatten_impl(SEXP x) {
+  if (TYPEOF(x) != VECSXP)
+    Rf_errorcall(R_NilValue, "`.x` must be a list (%s)", objtype(x));
+  int m = Rf_length(x);
+
+  // Determine output size and check type
+  int n = 0;
+  int has_names = 0;
+  SEXP x_names = Rf_getAttrib(x, R_NamesSymbol);
+
+  for (int j = 0; j < m; ++j) {
+    SEXP x_j = VECTOR_ELT(x, j);
+    if (!Rf_isVector(x_j) && !Rf_isNull(x_j))
+      Rf_errorcall(R_NilValue, "Element %i is not a vector (%s)", j + 1, objtype(x_j));
+
+    n += Rf_length(x_j);
+    if (!has_names) {
+      if (!Rf_isNull(Rf_getAttrib(x_j, R_NamesSymbol))) {
+        // Sub-element is named
+        has_names = 1;
+      } else if (Rf_length(x_j) == 1 && !Rf_isNull(x_names)) {
+        // Element is a "scalar" and has name in parent
+        SEXP name = STRING_ELT(x_names, j);
+        if (name != NA_STRING && strcmp(CHAR(name), "") != 0)
+          has_names = 1;
+      }
+    }
+  }
+
+  SEXP out = PROTECT(Rf_allocVector(VECSXP, n));
+  SEXP names = PROTECT(Rf_allocVector(STRSXP, n));
+  if (has_names)
+    Rf_setAttrib(out, R_NamesSymbol, names);
+  UNPROTECT(1);
+
+  int i = 0;
+  for (int j = 0; j < m; ++j) {
+    SEXP x_j = VECTOR_ELT(x, j);
+    int n_j = Rf_length(x_j);
+
+    SEXP names_j = Rf_getAttrib(x_j, R_NamesSymbol);
+    int has_names_j = !Rf_isNull(names_j);
+
+    for (int k = 0; k < n_j; ++k, ++i) {
+      switch(TYPEOF(x_j)) {
+      case LGLSXP:   SET_VECTOR_ELT(out, i, Rf_ScalarLogical(LOGICAL(x_j)[k])); break;
+      case INTSXP:   SET_VECTOR_ELT(out, i, Rf_ScalarInteger(INTEGER(x_j)[k])); break;
+      case REALSXP:  SET_VECTOR_ELT(out, i, Rf_ScalarReal(REAL(x_j)[k])); break;
+      case STRSXP:   SET_VECTOR_ELT(out, i, Rf_ScalarString(STRING_ELT(x_j, k))); break;
+      case VECSXP:   SET_VECTOR_ELT(out, i, VECTOR_ELT(x_j, k)); break;
+      default:
+        Rf_errorcall(R_NilValue, "Unsupported type at element %i (%s)", j + 1, objtype(x_j));
+      }
+      if (has_names) {
+        if (has_names_j) {
+          SET_STRING_ELT(names, i, has_names_j ? STRING_ELT(names_j, k) : Rf_mkChar(""));
+        } else if (n_j == 1) {
+          SET_STRING_ELT(names, i, !Rf_isNull(x_names) ? STRING_ELT(x_names, j) : Rf_mkChar(""));
+        }
+      }
+      if (i % 1000 == 0)
+        R_CheckUserInterrupt();
+    }
+  }
+
+
+
+  UNPROTECT(1);
+  return out;
+}
+
+SEXP vflatten_impl(SEXP x, SEXP type_) {
+  if (TYPEOF(x) != VECSXP)
+    Rf_errorcall(R_NilValue, "`.x` must be a list (%s)", objtype(x));
+  int m = Rf_length(x);
+
+  SEXPTYPE type = Rf_str2type(CHAR(Rf_asChar(type_)));
+
+  // Determine output size and type
+  int n = 0;
+  int has_names = 0;
+  for (int j = 0; j < m; ++j) {
+    SEXP x_j = VECTOR_ELT(x, j);
+
+    n += Rf_length(x_j);
+    if (!has_names && !Rf_isNull(Rf_getAttrib(x_j, R_NamesSymbol))) {
+      has_names = 1;
+    }
+  }
+
+  SEXP out = PROTECT(Rf_allocVector(type, n));
+  SEXP names = PROTECT(Rf_allocVector(STRSXP, n));
+  if (has_names)
+    Rf_setAttrib(out, R_NamesSymbol, names);
+  UNPROTECT(1);
+
+  int i = 0;
+  for (int j = 0; j < m; ++j) {
+    SEXP x_j = VECTOR_ELT(x, j);
+    int n_j = Rf_length(x_j);
+
+    SEXP names_j = Rf_getAttrib(x_j, R_NamesSymbol);
+    int has_names_j = !Rf_isNull(names_j);
+
+    for (int k = 0; k < n_j; ++k, ++i) {
+      set_vector_value(out, i, x_j, k);
+
+      if (has_names)
+        SET_STRING_ELT(names, i, has_names_j ? STRING_ELT(names_j, k) : Rf_mkChar(""));
+      if (i % 1000 == 0)
+        R_CheckUserInterrupt();
+    }
+  }
+
+  UNPROTECT(1);
+  return out;
+}
diff --git a/src/init.c b/src/init.c
new file mode 100644
index 0000000..6720dc8
--- /dev/null
+++ b/src/init.c
@@ -0,0 +1,32 @@
+#include <R.h>
+#include <Rinternals.h>
+#include <stdlib.h> // for NULL
+#include <R_ext/Rdynload.h>
+
+/* .Call calls */
+extern SEXP coerce_impl(SEXP, SEXP);
+extern SEXP extract_impl(SEXP, SEXP, SEXP);
+extern SEXP flatten_impl(SEXP);
+extern SEXP map_impl(SEXP, SEXP, SEXP, SEXP);
+extern SEXP map2_impl(SEXP, SEXP, SEXP, SEXP, SEXP);
+extern SEXP pmap_impl(SEXP, SEXP, SEXP, SEXP);
+extern SEXP transpose_impl(SEXP, SEXP);
+extern SEXP vflatten_impl(SEXP, SEXP);
+
+static const R_CallMethodDef CallEntries[] = {
+    {"coerce_impl",    (DL_FUNC) &coerce_impl,    2},
+    {"extract_impl",   (DL_FUNC) &extract_impl,   3},
+    {"flatten_impl",   (DL_FUNC) &flatten_impl,   1},
+    {"map_impl",       (DL_FUNC) &map_impl,       4},
+    {"map2_impl",      (DL_FUNC) &map2_impl,      5},
+    {"pmap_impl",      (DL_FUNC) &pmap_impl,      4},
+    {"transpose_impl", (DL_FUNC) &transpose_impl, 2},
+    {"vflatten_impl",  (DL_FUNC) &vflatten_impl,  2},
+    {NULL, NULL, 0}
+};
+
+void R_init_purrr(DllInfo *dll)
+{
+    R_registerRoutines(dll, NULL, CallEntries, NULL, NULL);
+    R_useDynamicSymbols(dll, FALSE);
+}
diff --git a/src/map.c b/src/map.c
new file mode 100644
index 0000000..6584cdb
--- /dev/null
+++ b/src/map.c
@@ -0,0 +1,198 @@
+#define R_NO_REMAP
+#include <R.h>
+#include <Rversion.h>
+#include <Rinternals.h>
+#include "coerce.h"
+
+void copy_names(SEXP from, SEXP to) {
+  if (Rf_length(from) != Rf_length(to))
+    return;
+
+  SEXP names = Rf_getAttrib(from, R_NamesSymbol);
+  if (Rf_isNull(names))
+    return;
+
+  Rf_setAttrib(to, R_NamesSymbol, names);
+}
+
+// call must involve i
+SEXP call_loop(SEXP env, SEXP call, int n, SEXPTYPE type, int force_args) {
+  // Create variable "i" and map to scalar integer
+  SEXP i_val = PROTECT(Rf_ScalarInteger(1));
+  SEXP i = Rf_install("i");
+  Rf_defineVar(i, i_val, env);
+  UNPROTECT(1);
+
+  SEXP out = PROTECT(Rf_allocVector(type, n));
+  for (int i = 0; i < n; ++i) {
+    if (i % 1000 == 0)
+      R_CheckUserInterrupt();
+
+    INTEGER(i_val)[0] = i + 1;
+
+#if defined(R_VERSION) && R_VERSION >= R_Version(3, 2, 3)
+    SEXP res = R_forceAndCall(call, force_args, env);
+#else
+    SEXP res = Rf_eval(call, env);
+#endif
+    if (type != VECSXP && Rf_length(res) != 1)
+      Rf_errorcall(R_NilValue, "Result %i is not a length 1 atomic vector", i + 1);
+
+    set_vector_value(out, i, res, 0);
+  }
+
+  UNPROTECT(1);
+  return out;
+}
+
+SEXP map_impl(SEXP env, SEXP x_name_, SEXP f_name_, SEXP type_) {
+  const char* x_name = CHAR(Rf_asChar(x_name_));
+  const char* f_name = CHAR(Rf_asChar(f_name_));
+
+  SEXP x = Rf_install(x_name);
+  SEXP f = Rf_install(f_name);
+  SEXP i = Rf_install("i");
+  SEXPTYPE type = Rf_str2type(CHAR(Rf_asChar(type_)));
+
+  SEXP x_val = Rf_eval(x, env);
+
+  if (Rf_isNull(x_val)) {
+    return Rf_allocVector(type, 0);
+  } else if (!Rf_isVector(x_val)) {
+    Rf_errorcall(R_NilValue, "`.x` is not a vector (%s)", Rf_type2char(TYPEOF(x_val)));
+  }
+  int n = Rf_length(x_val);
+
+  // Constructs a call like f(x[[i]], ...) - don't want to substitute
+  // actual values for f or x, because they may be long, which creates
+  // bad tracebacks()
+  SEXP Xi = PROTECT(Rf_lang3(R_Bracket2Symbol, x, i));
+  SEXP f_call = PROTECT(Rf_lang3(f, Xi, R_DotsSymbol));
+
+  SEXP out = PROTECT(call_loop(env, f_call, n, type, 1));
+  copy_names(x_val, out);
+
+  UNPROTECT(3);
+
+  return out;
+}
+
+SEXP map2_impl(SEXP env, SEXP x_name_, SEXP y_name_, SEXP f_name_, SEXP type_) {
+  const char* x_name = CHAR(Rf_asChar(x_name_));
+  const char* y_name = CHAR(Rf_asChar(y_name_));
+  const char* f_name = CHAR(Rf_asChar(f_name_));
+
+  SEXP x = Rf_install(x_name);
+  SEXP y = Rf_install(y_name);
+  SEXP f = Rf_install(f_name);
+  SEXP i = Rf_install("i");
+  SEXPTYPE type = Rf_str2type(CHAR(Rf_asChar(type_)));
+
+  SEXP x_val = Rf_eval(x, env);
+  SEXP y_val = Rf_eval(y, env);
+
+  if (!Rf_isVector(x_val) && !Rf_isNull(x_val))
+    Rf_errorcall(R_NilValue, "`.x` is not a vector (%s)", Rf_type2char(TYPEOF(x_val)));
+  if (!Rf_isVector(y_val) && !Rf_isNull(y_val))
+    Rf_errorcall(R_NilValue, "`.y` is not a vector (%s)", Rf_type2char(TYPEOF(y_val)));
+
+  int nx = Rf_length(x_val), ny = Rf_length(y_val);
+  if (nx == 0 || ny == 0) {
+    return Rf_allocVector(type, 0);
+  }
+  if (nx != ny && !(nx == 1 || ny == 1)) {
+    Rf_errorcall(R_NilValue, "`.x` (%i) and `.y` (%i) are different lengths", nx, ny);
+  }
+  int n = (nx > ny) ? nx : ny;
+
+  // Constructs a call like f(x[[i]], y[[i]], ...)
+  SEXP one = PROTECT(Rf_ScalarInteger(1));
+  SEXP Xi = PROTECT(Rf_lang3(R_Bracket2Symbol, x, nx == 1 ? one : i));
+  SEXP Yi = PROTECT(Rf_lang3(R_Bracket2Symbol, y, ny == 1 ? one : i));
+  SEXP f_call = PROTECT(Rf_lang4(f, Xi, Yi, R_DotsSymbol));
+
+  SEXP out = PROTECT(call_loop(env, f_call, n, type, 2));
+  copy_names(x_val, out);
+
+  UNPROTECT(5);
+  return out;
+}
+
+SEXP pmap_impl(SEXP env, SEXP l_name_, SEXP f_name_, SEXP type_) {
+  const char* l_name = CHAR(Rf_asChar(l_name_));
+  SEXP l = Rf_install(l_name);
+  SEXP l_val = Rf_eval(l, env);
+  SEXPTYPE type = Rf_str2type(CHAR(Rf_asChar(type_)));
+
+  if (!Rf_isVectorList(l_val))
+    Rf_errorcall(R_NilValue, "`.x` is not a list (%s)", Rf_type2char(TYPEOF(l_val)));
+
+  // Check all elements are lists and find maximum length
+  int m = Rf_length(l_val);
+  int n = 0;
+  for (int j = 0; j < m; ++j) {
+    SEXP j_val = VECTOR_ELT(l_val, j);
+
+    if (!Rf_isVector(j_val) && !Rf_isNull(j_val)) {
+      Rf_errorcall(R_NilValue, "Element %i is not a vector (%s)", j + 1, Rf_type2char(TYPEOF(j_val)));
+    }
+
+    int nj = Rf_length(j_val);
+
+    if (nj == 0) {
+      return Rf_allocVector(type, 0);
+    } else if (nj > n) {
+      n = nj;
+    }
+
+  }
+
+  // Check length of all elements
+  for (int j = 0; j < m; ++j) {
+    SEXP j_val = VECTOR_ELT(l_val, j);
+    int nj = Rf_length(j_val);
+
+    if (nj != 1 && nj != n)
+      Rf_errorcall(R_NilValue, "Element %i has length %i, not 1 or %i.", j + 1, nj, n);
+  }
+
+  SEXP l_names = Rf_getAttrib(l_val, R_NamesSymbol);
+  int has_names = !Rf_isNull(l_names);
+
+  const char* f_name = CHAR(Rf_asChar(f_name_));
+  SEXP f = Rf_install(f_name);
+  SEXP i = Rf_install("i");
+  SEXP one = PROTECT(Rf_ScalarInteger(1));
+
+  // Construct call like f(.x[[c(1, i)]], .x[[c(2, i)]], ...)
+  // We construct the call backwards because can only add to the front of a
+  // linked list. That makes PROTECTion tricky because we need to update it
+  // each time to point to the start of the linked list.
+
+  SEXP f_call = Rf_lang1(R_DotsSymbol);
+  PROTECT_INDEX fi;
+  PROTECT_WITH_INDEX(f_call, &fi);
+
+  for (int j = m - 1; j >= 0; --j) {
+    int nj = Rf_length(VECTOR_ELT(l_val, j));
+
+    // Construct call like .l[[c(j, i)]]
+    SEXP j_ = PROTECT(Rf_ScalarInteger(j + 1));
+    SEXP ji_ = PROTECT(Rf_lang3(Rf_install("c"), j_, nj == 1 ? one : i));
+    SEXP l_ji = PROTECT(Rf_lang3(R_Bracket2Symbol, l, ji_));
+
+    REPROTECT(f_call = Rf_lcons(l_ji, f_call), fi);
+    if (has_names && CHAR(STRING_ELT(l_names, j))[0] != '\0')
+      SET_TAG(f_call, Rf_install(CHAR(STRING_ELT(l_names, j))));
+
+    UNPROTECT(3);
+  }
+
+  REPROTECT(f_call = Rf_lcons(f, f_call), fi);
+
+  SEXP out = PROTECT(call_loop(env, f_call, n, type, m));
+  copy_names(VECTOR_ELT(l_val, 0), out);
+
+  UNPROTECT(3);
+  return out;
+}
diff --git a/src/map.h b/src/map.h
new file mode 100644
index 0000000..3912001
--- /dev/null
+++ b/src/map.h
@@ -0,0 +1,9 @@
+#ifndef MAP_H
+#define MAP_H
+
+extern "C" {
+  SEXP map_impl(SEXP env, SEXP x_name_, SEXP f_name_, SEXP type_);
+  SEXP pmap_impl(SEXP env, SEXP l_name_, SEXP f_name_, SEXP type_);
+}
+
+#endif
diff --git a/src/transpose.c b/src/transpose.c
new file mode 100644
index 0000000..251ffed
--- /dev/null
+++ b/src/transpose.c
@@ -0,0 +1,100 @@
+#define R_NO_REMAP
+#include <R.h>
+#include <Rinternals.h>
+
+SEXP transpose_impl(SEXP x, SEXP names_template) {
+  if (TYPEOF(x) != VECSXP)
+    Rf_errorcall(R_NilValue, "`.l` is not a list (%s)", Rf_type2char(TYPEOF(x)));
+
+  int n = Rf_length(x);
+  if (n == 0) {
+    return Rf_allocVector(VECSXP, 0);
+  }
+
+  int has_template = !Rf_isNull(names_template);
+
+  SEXP x1 = VECTOR_ELT(x, 0);
+  if (!Rf_isVector(x1))
+    Rf_errorcall(R_NilValue, "Element 1 is not a vector (%s)", Rf_type2char(TYPEOF(x1)));
+  int m = has_template ? Rf_length(names_template) : Rf_length(x1);
+
+  // Create space for output
+  SEXP out = PROTECT(Rf_allocVector(VECSXP, m));
+  SEXP names1 = Rf_getAttrib(x, R_NamesSymbol);
+
+  for (int j = 0; j < m; ++j) {
+    SEXP xj = PROTECT(Rf_allocVector(VECSXP, n));
+    if (!Rf_isNull(names1)) {
+      Rf_setAttrib(xj, R_NamesSymbol, names1);
+    }
+    SET_VECTOR_ELT(out, j, xj);
+    UNPROTECT(1);
+  }
+
+  SEXP names2 = has_template ? names_template : Rf_getAttrib(x1, R_NamesSymbol);
+  if (!Rf_isNull(names2)) {
+    Rf_setAttrib(out, R_NamesSymbol, names2);
+  }
+
+  // Fill output
+  for (int i = 0; i < n; ++i) {
+    SEXP xi = VECTOR_ELT(x, i);
+    if (!Rf_isVector(xi))
+      Rf_errorcall(R_NilValue, "Element %i is not a vector (%s)", i + 1, Rf_type2char(TYPEOF(x1)));
+
+
+    // find mapping between names and index. Use -1 to indicate not found
+    SEXP names_i = Rf_getAttrib(xi, R_NamesSymbol);
+    SEXP index;
+    if (!Rf_isNull(names2) && !Rf_isNull(names_i)) {
+      index = PROTECT(Rf_match(names_i, names2, 0));
+      // Rf_match returns 1-based index; convert to 0-based for C
+      for (int i = 0; i < m; ++i) {
+        INTEGER(index)[i] = INTEGER(index)[i] - 1;
+      }
+    } else {
+      index = PROTECT(Rf_allocVector(INTSXP, m));
+      int mi = Rf_length(xi);
+
+      if (m != mi) {
+        Rf_warningcall(R_NilValue, "Element %i has length %i not %i", i + 1, mi, m);
+      }
+      for (int i = 0; i < m; ++i) {
+        INTEGER(index)[i] = (i < mi) ? i : -1;
+      }
+
+    }
+    int* pIndex = INTEGER(index);
+
+    for (int j = 0; j < m; ++j) {
+      int pos = pIndex[j];
+      if (pos == -1)
+        continue;
+
+      switch(TYPEOF(xi)) {
+      case LGLSXP:
+        SET_VECTOR_ELT(VECTOR_ELT(out, j), i, Rf_ScalarLogical(LOGICAL(xi)[pos]));
+        break;
+      case INTSXP:
+        SET_VECTOR_ELT(VECTOR_ELT(out, j), i, Rf_ScalarInteger(INTEGER(xi)[pos]));
+        break;
+      case REALSXP:
+        SET_VECTOR_ELT(VECTOR_ELT(out, j), i, Rf_ScalarReal(REAL(xi)[pos]));
+        break;
+      case STRSXP:
+        SET_VECTOR_ELT(VECTOR_ELT(out, j), i, Rf_ScalarString(STRING_ELT(xi, pos)));
+        break;
+      case VECSXP:
+        SET_VECTOR_ELT(VECTOR_ELT(out, j), i, VECTOR_ELT(xi, pos));
+        break;
+      default:
+        Rf_errorcall(R_NilValue, "Unsupported type %s", Rf_type2char(TYPEOF(xi)));
+      }
+    }
+
+    UNPROTECT(1);
+  }
+
+  UNPROTECT(1);
+  return out;
+}
diff --git a/tests/testthat.R b/tests/testthat.R
new file mode 100644
index 0000000..63c2e2e
--- /dev/null
+++ b/tests/testthat.R
@@ -0,0 +1,4 @@
+library(testthat)
+library(purrr)
+
+test_check("purrr")
diff --git a/tests/testthat/test-along.R b/tests/testthat/test-along.R
new file mode 100644
index 0000000..0bbef3c
--- /dev/null
+++ b/tests/testthat/test-along.R
@@ -0,0 +1,13 @@
+context("along")
+
+test_that("list_along works", {
+  x <- 1:5
+  expect_identical(list_along(x), vector("list", 5))
+})
+
+test_that("rep_along works", {
+  expect_equal(
+    rep_along(c("c", "b", "a"), 1:3),
+    rep_along(c("d", "f", "e"), 1:3)
+  )
+})
diff --git a/tests/testthat/test-arrays.R b/tests/testthat/test-arrays.R
new file mode 100644
index 0000000..75ca0ff
--- /dev/null
+++ b/tests/testthat/test-arrays.R
@@ -0,0 +1,18 @@
+context("arrays")
+
+x <- array(1:12, c(2, 2, 3))
+
+test_that("array_branch creates a flat list when no margin specified", {
+  expect_length(array_branch(x), 12)
+})
+
+test_that("array_branch wraps array in list when margin has length 0", {
+  expect_identical(array_branch(x, numeric(0)), list(x))
+})
+
+test_that("length depends on whether list is flattened or not", {
+  m1 <- c(3, 1)
+  m2 <- 3
+  expect_length(array_branch(x, m1), prod(dim(x)[m1]))
+  expect_length(array_tree(x, m1), prod(dim(x)[m2]))
+})
diff --git a/tests/testthat/test-as-mapper.R b/tests/testthat/test-as-mapper.R
new file mode 100644
index 0000000..7cfcf1e
--- /dev/null
+++ b/tests/testthat/test-as-mapper.R
@@ -0,0 +1,77 @@
+context("as_mapper")
+
+
+# formulas ----------------------------------------------------------------
+
+test_that("can refer to first argument in three ways", {
+  expect_equal(map_dbl(1, ~ . + 1), 2)
+  expect_equal(map_dbl(1, ~ .x + 1), 2)
+  expect_equal(map_dbl(1, ~ ..1 + 1), 2)
+})
+
+test_that("can refer to second arg in two ways", {
+  expect_equal(map2_dbl(1, 2, ~ .x + .y + 1), 4)
+  expect_equal(map2_dbl(1, 2, ~ ..1 + ..2 + 1), 4)
+})
+
+# vectors --------------------------------------------------------------
+
+# test_that(".null generates warning", {
+#   expect_warning(map(1, 2, .null = NA), "`.null` is deprecated")
+# })
+
+test_that(".default replaces absent values", {
+  x <- list(
+    list(a = 1, b = 2, c = 3),
+    list(a = 1, c = 2),
+    NULL
+  )
+
+  expect_equal(map_dbl(x, 3, .default = NA), c(3, NA, NA))
+  expect_equal(map_dbl(x, "b", .default = NA), c(2, NA, NA))
+})
+
+test_that(".default replaces elements with length 0", {
+  x <- list(
+    list(a = 1),
+    list(a = NULL),
+    list(a = numeric())
+  )
+  expect_equal(map_dbl(x, "a", .default = NA), c(1, NA, NA))
+})
+
+test_that("Additional arguments are ignored", {
+  expect_equal(as_mapper(function() NULL, foo = "bar", foobar), function() NULL)
+})
+
+test_that("can supply length > 1 vectors", {
+  expect_identical(as_mapper(1:2)(list(list("a", "b"))), "b")
+  expect_identical(as_mapper(c("a", "b"))(list(a = list("a", b = "b"))), "b")
+})
+
+
+# primitive functions --------------------------------------------------
+
+test_that("primitive functions are wrapped", {
+  expect_identical(as_mapper(`-`)(.y = 10, .x = 5), -5)
+  expect_identical(as_mapper(`c`)(1, 3, 5), c(1, 3, 5))
+})
+
+test_that("syntactic primitives are wrapped", {
+  expect_identical(as_mapper(`[[`)(mtcars, "cyl"), mtcars$cyl)
+  expect_identical(as_mapper(`$`)(mtcars, cyl), mtcars$cyl)
+})
+
+
+# lists ------------------------------------------------------------------
+
+test_that("lists are wrapped", {
+  mapper_list <- as_mapper(list("mpg", 5))(mtcars)
+  base_list <- mtcars[["mpg"]][[5]]
+  expect_identical(mapper_list, base_list)
+})
+
+test_that("raw and complex types aren't supported for indexing", {
+  expect_error(as_mapper(1)(raw(2)))
+  expect_error(as_mapper(1)(complex(2)))
+})
diff --git a/tests/testthat/test-coerce.R b/tests/testthat/test-coerce.R
new file mode 100644
index 0000000..10e1f9b
--- /dev/null
+++ b/tests/testthat/test-coerce.R
@@ -0,0 +1,68 @@
+context("coerce")
+
+test_that("missing values converted to new type", {
+  expect_equal(coerce_int(NA), NA_integer_)
+  expect_equal(coerce_dbl(NA), NA_real_)
+  expect_equal(coerce_chr(NA), NA_character_)
+
+  expect_equal(coerce_dbl(NA_integer_), NA_real_)
+  expect_equal(coerce_chr(NA_integer_), NA_character_)
+
+  expect_equal(coerce_chr(NA_real_), NA_character_)
+})
+
+test_that("can't coerce downwards", {
+  expect_error(coerce_chr(list(1)), "Can't coerce")
+  expect_error(coerce_dbl(list(1)), "Can't coerce")
+  expect_error(coerce_int(list(1)), "Can't coerce")
+  expect_error(coerce_lgl(list(1)), "Can't coerce")
+
+  expect_error(coerce_dbl("a"), "Can't coerce")
+  expect_error(coerce_int("a"), "Can't coerce")
+  expect_error(coerce_lgl("a"), "Can't coerce")
+
+  expect_error(coerce_int(1), "Can't coerce")
+  expect_error(coerce_lgl(1), "Can't coerce")
+
+  expect_error(coerce_lgl(1L), "Can't coerce")
+})
+
+test_that("coercing to same type returns input", {
+  expect_equal(coerce_lgl(c(TRUE, FALSE)), c(TRUE, FALSE))
+  expect_equal(coerce_dbl(c(1, 2)), c(1, 2))
+  expect_equal(coerce_int(c(1L, 2L)), c(1L, 2L))
+  expect_equal(coerce_chr(c("a", "b")), c("a", "b"))
+})
+
+test_that("types automatically coerced upwards", {
+  expect_identical(coerce_int(c(FALSE, TRUE)), c(0L, 1L))
+  expect_identical(coerce_dbl(c(FALSE, TRUE)), c(0, 1))
+  expect_identical(coerce_dbl(c(1L, 2L)), c(1, 2))
+  expect_identical(coerce_chr(c(FALSE, TRUE)), c("FALSE", "TRUE"))
+  expect_identical(coerce_chr(c(1L, 2L)), c("1", "2"))
+  expect_identical(coerce_chr(c(1.5, 2.5)), c("1.500000", "2.500000"))
+})
+
+test_that("coercing to character values correctly", {
+  expect_equal(coerce_chr(c(FALSE, TRUE)), c("FALSE", "TRUE"))
+  expect_equal(coerce_chr(c(1L, 2L)), c("1", "2"))
+  expect_equal(coerce_chr(c(1.5, 2.5)), c("1.500000", "2.500000"))
+  expect_equal(coerce_chr(c("a", "b")), c("a", "b"))
+
+  x <- c(NA, NaN, Inf, -Inf)
+  expect_equal(coerce(x, "character"), as.character(x))
+})
+
+test_that("can't coerce to expressions", {
+  expect_error(coerce(list(1), "expression"))
+})
+
+test_that("as_vector can be type-specifc", {
+  expect_identical(as_vector(as.list(letters), "character"), letters)
+})
+
+test_that("as_vector cannot coerce lists with zero-length elements", {
+  x <- list(a = 1, b = c(list(), 3))
+  expect_error(as_vector(x))
+  expect_identical(x, simplify(x))
+})
diff --git a/tests/testthat/test-compose.R b/tests/testthat/test-compose.R
new file mode 100644
index 0000000..2c8eb38
--- /dev/null
+++ b/tests/testthat/test-compose.R
@@ -0,0 +1,9 @@
+context("compose")
+
+test_that("composed functions are applied right to left", {
+  expect_identical(!is.null(4), compose(`!`, is.null)(4))
+
+  set.seed(1)
+  x <- sample(1:4, 100, replace = TRUE)
+  expect_identical(unname(sort(table(x))), compose(unname, sort, table)(x))
+})
diff --git a/tests/testthat/test-composition.R b/tests/testthat/test-composition.R
new file mode 100644
index 0000000..340eb14
--- /dev/null
+++ b/tests/testthat/test-composition.R
@@ -0,0 +1,33 @@
+context("composition")
+
+test_that("lift_dl and lift_ld are inverses of each other", {
+  expect_identical(
+    sum %>%
+      lift_dl(.unnamed = TRUE) %>%
+      invoke(list(3, NA, 4, na.rm = TRUE)),
+    sum %>%
+      lift_dl() %>%
+      lift_ld() %>%
+      invoke(3, NA, 4, na.rm = TRUE)
+  )
+})
+
+test_that("lift_dv is from ... to c(...)", {
+  expect_equal(lift_dv(range, .unnamed = TRUE)(1:10), c(1, 10))
+})
+
+test_that("lift_vd is from c(...) to ...", {
+  expect_equal(lift_vd(mean)(1, 2), 1.5)
+})
+
+test_that("lift_vl is from c(...) to list(...)", {
+  expect_equal(lift_vl(mean)(list(1, 2)), 1.5)
+})
+
+test_that("lift_lv is from list(...) to c(...)", {
+  glue <- function(l) {
+    if (!is.list(l)) stop("not a list")
+    l %>% invoke(paste, .)
+  }
+  expect_identical(lift_lv(glue)(letters), paste(letters, collapse = " "))
+})
diff --git a/tests/testthat/test-cross.R b/tests/testthat/test-cross.R
new file mode 100644
index 0000000..41e4f0c
--- /dev/null
+++ b/tests/testthat/test-cross.R
@@ -0,0 +1,26 @@
+context("cross")
+
+test_that("long format corresponds to expand.grid output", {
+  x <- list(a = 1:3, b = 4:9)
+
+  out1 <- cross_df(x)
+  out2 <- expand.grid(x, KEEP.OUT.ATTRS = FALSE) %>% dplyr::as_data_frame()
+
+  expect_equal(out1, out2)
+})
+
+test_that("filtering works", {
+  filter <- function(x, y) x >= y
+  out <- cross2(1:3, 1:3, .filter = filter)
+  expect_equal(out, list(list(1, 2), list(1, 3), list(2, 3)))
+})
+
+test_that("filtering fails when filter function doesn't return a logical", {
+  filter <- function(x, y, z) x + y + z
+  expect_error(cross3(1:3, 1:3, 1:3, .filter = filter))
+})
+
+test_that("works with empty input", {
+  expect_equal(cross(list()), list())
+  expect_equal(cross(NULL), NULL)
+})
diff --git a/tests/testthat/test-depth.R b/tests/testthat/test-depth.R
new file mode 100644
index 0000000..c66dd62
--- /dev/null
+++ b/tests/testthat/test-depth.R
@@ -0,0 +1,31 @@
+context("depth")
+
+test_that("depth of NULL is 0", {
+  expect_equal(vec_depth(NULL), 0L)
+})
+
+test_that("depth of atomic vector is 1", {
+  expect_equal(vec_depth(1:10), 1)
+  expect_equal(vec_depth(letters), 1)
+  expect_equal(vec_depth(c(TRUE, FALSE)), 1)
+})
+
+test_that("depth of empty list is 1", {
+  expect_equal(vec_depth(list()), 1)
+})
+
+test_that("depth of nested is depth of deepest element + 1", {
+  x <- list(
+    NULL,
+    list(),
+    list(list())
+  )
+
+  depths <- map_int(x, vec_depth)
+  expect_equal(depths, c(0, 1, 2))
+  expect_equal(vec_depth(x), max(depths) + 1)
+})
+
+test_that("depth throws an error if input is not a vector", {
+  expect_error(vec_depth(as.formula(y ~ x)))
+})
diff --git a/tests/testthat/test-every-some.R b/tests/testthat/test-every-some.R
new file mode 100644
index 0000000..852860a
--- /dev/null
+++ b/tests/testthat/test-every-some.R
@@ -0,0 +1,18 @@
+context("every-some")
+
+test_that("return NA if present", {
+  expect_equal(some(1:10, ~ NA), NA)
+  expect_equal(every(1:10, ~ NA), NA)
+})
+
+test_that("every returns TRUE if all elements are TRUE", {
+  x <- list(0, 1, TRUE)
+  expect_false(every(x, isTRUE))
+  expect_true(every(x[3], isTRUE))
+})
+
+test_that("some returns FALSE if all elements are FALSE", {
+  x <- list(1, 0, FALSE)
+  expect_false(some(x, isTRUE))
+  expect_true(some(x[1], negate(isTRUE)))
+})
diff --git a/tests/testthat/test-find-position.R b/tests/testthat/test-find-position.R
new file mode 100644
index 0000000..5d29b49
--- /dev/null
+++ b/tests/testthat/test-find-position.R
@@ -0,0 +1,34 @@
+context("find-position")
+
+y <- 4:10
+
+test_that("detect functions work", {
+  is_odd <- function(x) x %% 2 == 1
+  expect_equal(detect(y, is_odd), 5)
+  expect_equal(detect_index(y, is_odd), 2)
+  expect_equal(detect(y, is_odd, .right = TRUE), 9)
+  expect_equal(detect_index(y, is_odd, .right = TRUE), 6)
+})
+
+test_that("detect returns NULL when match not found", {
+  expect_null(detect(y, function(x) x > 11))
+})
+
+test_that("detect_index returns 0 when match not found", {
+  expect_equal(detect_index(y, function(x) x > 11), 0)
+})
+
+test_that("has_element checks whether a list contains an object", {
+  expect_true(has_element(list(1, 2), 1))
+  expect_false(has_element(list(1, 2), 3))
+})
+
+test_that("detect functions still work with `.p`", {
+  is_even <- function(x) x %% 2 == 0
+  expect_warning(regex = "renamed to `.f`",
+    expect_identical(detect(1:3, .p = is_even), 2L)
+  )
+  expect_warning(regex = "renamed to `.f`",
+    expect_identical(detect_index(1:3, .p = is_even), 2L)
+  )
+})
diff --git a/tests/testthat/test-flatten.R b/tests/testthat/test-flatten.R
new file mode 100644
index 0000000..d807a1d
--- /dev/null
+++ b/tests/testthat/test-flatten.R
@@ -0,0 +1,78 @@
+context("flatten")
+
+test_that("input must be a list", {
+  expect_error(flatten(1), "must be a list")
+})
+
+test_that("contents of list must be supported types", {
+  expect_error(flatten(list(quote(a))), "not a vector")
+  expect_error(flatten(list(expression(a))), "Unsupported type")
+})
+
+test_that("each second level element becomes first level element", {
+  expect_equal(flatten(list(1:2)), list(1, 2))
+  expect_equal(flatten(list(1, 2)), list(1, 2))
+})
+
+test_that("can flatten all atomic vectors", {
+  expect_equal(flatten(list(F)), list(F))
+  expect_equal(flatten(list(1L)), list(1L))
+  expect_equal(flatten(list(1)), list(1))
+  expect_equal(flatten(list("a")), list("a"))
+})
+
+test_that("NULLs are silently dropped", {
+  expect_equal(flatten(list(NULL, NULL)), list())
+  expect_equal(flatten(list(NULL, 1)), list(1))
+  expect_equal(flatten(list(1, NULL)), list(1))
+})
+
+test_that("names are preserved", {
+  expect_equal(flatten(list(list(x = 1), list(y = 1))), list(x = 1, y = 1))
+  expect_equal(flatten(list(list(a = 1, b = 2), 3)), list(a = 1, b = 2, 3))
+})
+
+test_that("names of 'scalar' elements are preserved", {
+  out <- flatten(list(a = list(1), b = list(2)))
+  expect_equal(out, list(a = 1, b = 2))
+
+  out <- flatten(list(a = list(1), b = 2:3))
+  expect_equal(out, list(a = 1, 2, 3))
+
+  out <- flatten(list(list(a = 1, b = 2), c = 3))
+  expect_equal(out, list(a = 1, b = 2, c = 3))
+})
+
+test_that("child names beat parent names", {
+  out <- flatten(list(a = list(x = 1), b = list(y = 2)))
+  expect_equal(out, list(x = 1, y = 2))
+})
+
+
+# atomic flatten ----------------------------------------------------------
+
+test_that("must be a list", {
+  expect_error(flatten_lgl(1), "must be a list")
+})
+
+test_that("can flatten all atomic vectors", {
+  expect_equal(flatten_lgl(list(F)), F)
+  expect_equal(flatten_int(list(1L)), 1L)
+  expect_equal(flatten_dbl(list(1)), 1)
+  expect_equal(flatten_chr(list("a")), "a")
+})
+
+test_that("preserves inner names", {
+  expect_equal(
+    flatten_dbl(list(c(a = 1), c(b = 2))),
+    c(a = 1, b = 2)
+  )
+})
+
+
+# data frame flatten ------------------------------------------------------
+
+test_that("can flatten to a data frame with named lists", {
+  expect_is(flatten_dfr(list(c(a = 1), c(b = 2))), "data.frame")
+  expect_error(flatten_dfc(list(1)))
+})
diff --git a/tests/testthat/test-head-tail.R b/tests/testthat/test-head-tail.R
new file mode 100644
index 0000000..89eb95f
--- /dev/null
+++ b/tests/testthat/test-head-tail.R
@@ -0,0 +1,16 @@
+context("head-tail")
+
+y <- 1:100
+
+test_that("head_while works", {
+  expect_length(head_while(y, function(x) x <= 15), 15)
+})
+
+test_that("tail_while works", {
+  expect_length(tail_while(y, function(x) x >= 86), 15)
+})
+
+test_that("original vector returned if predicate satisfied by all elements", {
+  expect_identical(head_while(y, function(x) x <= 100), y)
+  expect_identical(tail_while(y, function(x) x >= 0), y)
+})
diff --git a/tests/testthat/test-imap.R b/tests/testthat/test-imap.R
new file mode 100644
index 0000000..05b18b9
--- /dev/null
+++ b/tests/testthat/test-imap.R
@@ -0,0 +1,26 @@
+context("imap")
+
+x <- 1:3 %>% set_names()
+
+test_that("imap is special case of map2", {
+  expect_identical(imap(x, paste), map2(x, names(x), paste))
+})
+
+test_that("imap always returns a list", {
+  expect_is(imap(x, paste), "list")
+})
+
+test_that("atomic vector imap works", {
+  expect_true(all(imap_lgl(x, `==`)))
+  expect_length(imap_chr(x, paste), 3)
+  expect_equal(imap_int(x, ~ .x + as.integer(.y)), x * 2)
+  expect_equal(imap_dbl(x, ~ .x + as.numeric(.y)), x * 2)
+})
+
+test_that("data frame imap works", {
+  expect_identical(imap_dfc(x, paste), imap_dfr(x, paste))
+})
+
+test_that("iwalk returns invisibly", {
+  expect_output(iwalk(mtcars, ~ cat(.y, ": ", median(.x), "\n", sep = "")))
+})
diff --git a/tests/testthat/test-invoke.R b/tests/testthat/test-invoke.R
new file mode 100644
index 0000000..23787f0
--- /dev/null
+++ b/tests/testthat/test-invoke.R
@@ -0,0 +1,52 @@
+context("invoke")
+
+
+# invoke ------------------------------------------------------------------
+
+test_that("invoke() evaluates expressions in the right environment", {
+  x <- letters
+  f <- toupper
+  expect_equal(invoke("f", quote(x)), toupper(letters))
+})
+
+test_that("invoke() follows promises to find the evaluation env", {
+  x <- letters
+  f <- toupper
+  f1 <- function(y) {
+    f2 <- function(z) purrr::invoke(z, quote(x))
+    f2(y)
+  }
+  expect_equal(f1("f"), toupper(letters))
+})
+
+# invoke_map --------------------------------------------------------------
+
+test_that("invoke_map() works with bare function", {
+  data <- list(1:2, 3:4)
+
+  expected <- list("1 2", "3 4")
+  expect_equal(invoke_map(paste, data), expected)
+  expect_equal(invoke_map("paste", data), expected)
+  expect_equal(invoke_map_chr(paste, data), unlist(expected))
+
+  expect_identical(invoke_map_dbl(`+`, data), c(3, 7))
+  expect_identical(invoke_map_int(`+`, data), c(3L, 7L))
+  expect_identical(invoke_map_lgl(`&&`, data), c(TRUE, TRUE))
+
+  ops <- set_names(c(`+`, `-`), c("a", "b"))
+  expect_identical(invoke_map_dfr(ops, data), invoke_map_dfc(ops, data))
+})
+
+test_that("invoke_map() evaluates expressions in the right environment", {
+  shadowed_object <- letters
+  shadowed_fun <- toupper
+  expect_equal(
+    invoke_map("shadowed_fun", list(quote(shadowed_object))),
+    list(toupper(letters))
+  )
+})
+
+test_that("invoke_maps doesn't rely on c() returning list", {
+  day <- as.Date("2016-09-01")
+  expect_equal(invoke_map(identity, list(day)), list(day))
+})
diff --git a/tests/testthat/test-list-modify-update.R b/tests/testthat/test-list-modify-update.R
new file mode 100644
index 0000000..4b331a7
--- /dev/null
+++ b/tests/testthat/test-list-modify-update.R
@@ -0,0 +1,77 @@
+context("list_modify")
+
+# list_modify -------------------------------------------------------------
+
+test_that("named lists have values replaced by name", {
+  expect_equal(list_modify(list(a = 1), b = 2), list(a = 1, b = 2))
+  expect_equal(list_modify(list(a = 1), a = 2), list(a = 2))
+  expect_equal(list_modify(list(a = 1, b = 2), b = NULL), list(a = 1))
+})
+
+test_that("unnamed lists are replaced by position", {
+  expect_equal(list_modify(list(3), 1, 2), list(1, 2))
+  expect_equal(list_modify(list(1, 2, 3), 4), list(4, 2, 3))
+  expect_equal(list_modify(list(1, 2, 3), NULL, NULL), list(3))
+})
+
+test_that("error if one named and the other is not", {
+  expect_error(
+    list_modify(list(a = 1), 2),
+    "must be either both named or both unnamed"
+  )
+})
+
+test_that("lists are replaced recursively", {
+  expect_equal(
+    list_modify(
+      list(a = list(x = 1)),
+      a = list(x = 2)
+    ),
+    list(a = list(x = 2))
+  )
+
+  expect_equal(
+    list_modify(
+      list(a = list(x = 1)),
+      a = list(y = 2)
+    ),
+    list(a = list(x = 1, y = 2))
+  )
+})
+
+
+# list_merge --------------------------------------------------------------
+
+test_that("list_merge concatenates values from two lists", {
+  l1 <- list(x = 1:10, y = 4, z = list(a = 1, b = 2))
+  l2 <- list(x = 11, z = list(a = 2:5, c = 3))
+  l <- list_merge(l1, !!! l2)
+  expect_equal(l$x, c(l1$x, l2$x))
+  expect_equal(l$y, c(l1$y, l2$y))
+  expect_equal(l$z$a, c(l1$z$a, l2$z$a))
+  expect_equal(l$z$b, c(l1$z$b, l2$z$b))
+  expect_equal(l$z$c, c(l1$z$c, l2$z$c))
+})
+
+test_that("list_merge concatenates without needing names", {
+  l1 <- list(1:10, 4, list(1, 2))
+  l2 <- list(11, 5, list(2:5, 3))
+  expect_length(list_merge(l1, !!! l2), 3)
+})
+
+test_that("list_merge returns the non-empty list", {
+  expect_equal(list_merge(list(3)), list(3))
+  expect_equal(list_merge(list(), 2), set_names(list(2), ""))
+})
+
+
+# update_list ------------------------------------------------------------
+
+test_that("can modify element called x", {
+  expect_equal(update_list(list(), x = 1), list(x = 1))
+})
+
+test_that("quosures and formulas are evaluated", {
+  expect_identical(update_list(list(x = 1), y = quo(x + 1)), list(x = 1, y = 2))
+  expect_identical(update_list(list(x = 1), y = ~x + 1), list(x = 1, y = 2))
+})
diff --git a/tests/testthat/test-lmap.R b/tests/testthat/test-lmap.R
new file mode 100644
index 0000000..55848cc
--- /dev/null
+++ b/tests/testthat/test-lmap.R
@@ -0,0 +1,9 @@
+context("lmap")
+
+test_that("lmap output is list if input is list", {
+  expect_is(lmap(as.list(mtcars), as.list), "list")
+})
+
+test_that("lmap output is tibble if input is data frame", {
+  expect_is(lmap(mtcars, as.list), "tbl_df")
+})
diff --git a/tests/testthat/test-map.R b/tests/testthat/test-map.R
new file mode 100644
index 0000000..852a215
--- /dev/null
+++ b/tests/testthat/test-map.R
@@ -0,0 +1,73 @@
+context("map")
+
+test_that("preserves names", {
+  out <- map(list(x = 1, y = 2), identity)
+  expect_equal(names(out), c("x", "y"))
+})
+
+test_that("creates simple call", {
+  out <- map(1, function(x) sys.call())[[1]]
+  expect_identical(out, quote(.f(.x[[i]], ...)))
+})
+
+test_that("fails on non-vectors", {
+  expect_error(map(environment(), identity), "not a vector")
+  expect_error(map(quote(a), identity), "not a vector")
+})
+
+test_that("0 length input gives 0 length output", {
+  out1 <- map(list(), identity)
+  expect_equal(out1, list())
+
+  out2 <- map(NULL, identity)
+  expect_equal(out2, list())
+})
+
+test_that("map() always returns a list", {
+  expect_is(map(mtcars, mean), "list")
+})
+
+test_that("types automatically coerced upwards", {
+  expect_identical(map_int(c(FALSE, TRUE), identity), c(0L, 1L))
+
+  expect_identical(map_dbl(c(FALSE, TRUE), identity), c(0, 1))
+  expect_identical(map_dbl(c(1L, 2L), identity), c(1, 2))
+
+  expect_identical(map_chr(c(FALSE, TRUE), identity), c("FALSE", "TRUE"))
+  expect_identical(map_chr(c(1L, 2L), identity), c("1", "2"))
+  expect_identical(map_chr(c(1.5, 2.5), identity), c("1.500000", "2.500000"))
+})
+
+test_that("logical and integer NA become correct double NA", {
+  expect_identical(
+    map_dbl(list(NA, NA_integer_), identity),
+    c(NA_real_, NA_real_)
+  )
+})
+
+test_that("map forces arguments in same way as base R", {
+  f_map <- map(1:2, function(i) function(x) x + i)
+  f_base <- lapply(1:2, function(i) function(x) x + i)
+
+  expect_equal(f_map[[1]](0), f_base[[1]](0))
+  expect_equal(f_map[[2]](0), f_base[[2]](0))
+})
+
+test_that("row and column binding work", {
+  mtcar_mod <- mtcars %>%
+    split(.$cyl) %>%
+    map(~ lm(mpg ~ wt, data = .x))
+  f_coef <- function(x) as.data.frame(t(as.matrix(coef(x))))
+  expect_length(mtcar_mod %>% map_dfr(f_coef), 2)
+  expect_length(mtcar_mod %>% map_dfc(f_coef), 6)
+})
+
+test_that("walk is used for side-effects", {
+  expect_output(walk(1:3, str))
+})
+
+test_that("map_if() and map_at() always return a list", {
+  df <- tibble::tibble(x = 1, y = "a")
+  expect_identical(map_if(df, is.character, ~"out"), list(x = 1, y = "out"))
+  expect_identical(map_at(df, 1, ~"out"), list(x = "out", y = "a"))
+})
diff --git a/tests/testthat/test-map2.R b/tests/testthat/test-map2.R
new file mode 100644
index 0000000..ec2e16c
--- /dev/null
+++ b/tests/testthat/test-map2.R
@@ -0,0 +1,44 @@
+context("map2")
+
+test_that("map2 inputs must be same length", {
+  expect_error(map2(1:3, 2:3, function(...) NULL), "different lengths")
+})
+
+test_that("map2 can't simplify if elements longer than length 1", {
+  expect_error(
+    map2_int(1:4, 5:8, range),
+    "Result 1 is not a length 1 atomic vector"
+  )
+})
+
+test_that("fails on non-vectors", {
+  expect_error(map2(environment(), "a", identity), "not a vector")
+  expect_error(map2("a", environment(), identity), "not a vector")
+})
+
+test_that("map2 vectorised inputs of length 1", {
+  expect_equal(map2(1:2, 1, `+`), list(2, 3))
+  expect_equal(map2(1, 1:2, `+`), list(2, 3))
+})
+
+test_that("any 0 length input gives 0 length output", {
+  expect_equal(map2(list(), list(), ~ 1), list())
+  expect_equal(map2(1:10, list(), ~ 1), list())
+  expect_equal(map2(list(), 1:10, ~ 1), list())
+
+  expect_equal(map2(NULL, NULL, ~ 1), list())
+  expect_equal(map2(1:10, NULL, ~ 1), list())
+  expect_equal(map2(NULL, 1:10, ~ 1), list())
+})
+
+test_that("map2 takes only names from x", {
+  x1 <- 1:3
+  x2 <- set_names(x1)
+
+  expect_equal(names(map2(x1, x2, `+`)), NULL)
+  expect_equal(names(map2(x2, x1, `+`)), names(x2))
+})
+
+test_that("map2 always returns a list", {
+  expect_is(map2(mtcars, 0, ~mtcars), "list")
+})
diff --git a/tests/testthat/test-map_n.R b/tests/testthat/test-map_n.R
new file mode 100644
index 0000000..a7fcb60
--- /dev/null
+++ b/tests/testthat/test-map_n.R
@@ -0,0 +1,60 @@
+context("pmap")
+
+test_that("input must be a list of vectors", {
+  expect_error(pmap(environment(), identity), "not a list")
+  expect_error(pmap(list(environment()), identity), "not a vector")
+})
+
+test_that("elements must be same length", {
+  expect_error(pmap(list(1:2, 1:3), identity), "has length 2")
+})
+
+test_that("handles any length 0 input", {
+  expect_equal(pmap(list(list(), list(), list()), ~ 1), list())
+  expect_equal(pmap(list(NULL, NULL, NULL), ~ 1), list())
+
+  expect_equal(pmap(list(list(), list(), 1:10), ~ 1), list())
+  expect_equal(pmap(list(NULL, NULL, 1:10), ~ 1), list())
+})
+
+test_that("length 1 elemetns are recycled", {
+  out <- pmap(list(1:2, 1), c)
+  expect_equal(out, list(c(1, 1), c(2, 1)))
+})
+
+test_that(".f called with named arguments", {
+  out <- pmap(list(x = 1, 2, y = 3), list)[[1]]
+  expect_equal(names(out), c("x", "", "y"))
+})
+
+test_that("names are preserved", {
+  out <- pmap(list(c(x = 1, y = 2), 3:4), list)
+  expect_equal(names(out), c("x", "y"))
+})
+
+test_that("... are passed on", {
+  out <- pmap(list(x = 1:2), list, n = 1)
+  expect_equal(out, list(
+    list(x = 1, n = 1),
+    list(x = 2, n = 1)
+  ))
+})
+
+test_that("outputs are suffixes have correct type", {
+  x <- 1:3
+  expect_is(pmap_lgl(list(x), is.numeric), "logical")
+  expect_is(pmap_int(list(x), length), "integer")
+  expect_is(pmap_dbl(list(x), mean), "numeric")
+  expect_is(pmap_chr(list(x), paste), "character")
+  expect_is(pmap_dfr(list(x), as.data.frame), "data.frame")
+  expect_is(pmap_dfc(list(x), as.data.frame), "data.frame")
+})
+
+test_that("pmap on data frames performs rowwise operations", {
+  mtcars2 <- mtcars[c("mpg", "cyl")]
+  expect_length(pmap(mtcars2, paste), nrow(mtcars))
+  expect_is(pmap_lgl(mtcars2, function(mpg, cyl) mpg > cyl), "logical")
+  expect_is(pmap_int(mtcars2, function(mpg, cyl) as.integer(cyl)), "integer")
+  expect_is(pmap_dbl(mtcars2, function(mpg, cyl) mpg + cyl), "numeric")
+  expect_is(pmap_chr(mtcars2, paste), "character")
+})
diff --git a/tests/testthat/test-modify.R b/tests/testthat/test-modify.R
new file mode 100644
index 0000000..07899a0
--- /dev/null
+++ b/tests/testthat/test-modify.R
@@ -0,0 +1,56 @@
+context("modify")
+
+test_that("modify returns same type as input", {
+  df1 <- data.frame(x = 1:3, y = 4:6)
+  expect_equal(modify(df1, length), data.frame(x = rep(3, 3), y = rep(3, 3)))
+})
+
+test_that("modify_if/modify_at return same type as input", {
+  df1 <- data.frame(x = "a", y = 2, stringsAsFactors = FALSE)
+  exp <- data.frame(x = "A", y = 2, stringsAsFactors = FALSE)
+
+  df2a <- modify_if(df1, is.character, toupper)
+  expect_equal(df2a, exp)
+
+  df2b <- modify_at(df1, "x", toupper)
+  expect_equal(df2b, exp)
+})
+
+test_that("modify_at requires a named object", {
+  df1 <- data.frame(x = "a", y = 2, stringsAsFactors = FALSE)
+  expect_error(modify_at(unname(df1), "x", toupper))
+})
+
+test_that("modify_at operates on character and numeric indexing", {
+  df1 <- data.frame(x = "a", y = 2, stringsAsFactors = FALSE)
+  expect_error(modify_at(df1, TRUE, toupper))
+})
+
+
+# modify_depth ------------------------------------------------------------
+
+test_that("modify_depth modifies values at specified depth", {
+  x1 <- list(list(list(1)))
+
+  expect_equal(modify_depth(x1, 0, length), list(1))
+  expect_equal(modify_depth(x1, 1, length), list(1))
+  expect_equal(modify_depth(x1, 2, length), list(list(1)))
+  expect_equal(modify_depth(x1, 3, length), list(list(list(1))))
+  expect_equal(modify_depth(x1, -1, length), list(list(list(1))))
+  expect_error(modify_depth(x1, 4, length), "List not deep enough")
+  expect_error(modify_depth(x1, -5, length), "Invalid `depth`")
+})
+
+test_that(".ragged = TRUE operates on leaves", {
+  x1 <- list(
+    list(1),
+    list(list(2))
+  )
+  x2 <- list(
+    list(2),
+    list(list(3))
+  )
+
+  expect_equal(modify_depth(x1, 3, ~ . + 1, .ragged = TRUE), x2)
+  expect_equal(modify_depth(x1, -1, ~ . + 1, .ragged = TRUE), x2)
+})
diff --git a/tests/testthat/test-negate.R b/tests/testthat/test-negate.R
new file mode 100644
index 0000000..54ed4af
--- /dev/null
+++ b/tests/testthat/test-negate.R
@@ -0,0 +1,10 @@
+context("negate")
+
+test_that("negate works with both functions and vectors", {
+  true <- function(...) TRUE
+  expect_equal(negate(true)(), FALSE)
+  expect_equal(negate("x")(list(x = TRUE)), FALSE)
+
+  expect_equal(negate(is.null)(TRUE), TRUE)
+  expect_equal(negate(is.null)(NULL), FALSE)
+})
diff --git a/tests/testthat/test-output.R b/tests/testthat/test-output.R
new file mode 100644
index 0000000..0e65e1e
--- /dev/null
+++ b/tests/testthat/test-output.R
@@ -0,0 +1,43 @@
+context("output")
+
+test_that("safely has NULL error when successful", {
+  out <- safely(log10)(10)
+  expect_equal(out, list(result = 1, error = NULL))
+})
+
+test_that("safely has NULL result on failure", {
+  out <- safely(log10)("a")
+  expect_equal(out$result, NULL)
+  expect_equal(out$error$message,
+    "non-numeric argument to mathematical function")
+})
+
+
+test_that("quietly captures output", {
+  f <- function() {
+    cat(1)
+    message(2, appendLF = FALSE)
+    warning(3)
+    4
+  }
+  expect_output(quietly(f)(), NA)
+  expect_message(quietly(f)(), NA)
+  expect_warning(quietly(f)(), NA)
+
+  out <- quietly(f)()
+  expect_equal(out, list(
+    result = 4,
+    output = "1",
+    warnings = "3",
+    messages = "2"
+  ))
+})
+
+test_that("possibly returns default value on failure", {
+  expect_identical(possibly(log, NA_real_)("a"), NA_real_)
+})
+
+test_that("auto_browse() not intended for primitive functions", {
+  expect_error(auto_browse(log)(NULL), "primitive functions")
+  expect_error(auto_browse(identity)(NULL), NA)
+})
diff --git a/tests/testthat/test-partial.R b/tests/testthat/test-partial.R
new file mode 100644
index 0000000..c2d45f7
--- /dev/null
+++ b/tests/testthat/test-partial.R
@@ -0,0 +1,28 @@
+context("partial")
+
+test_that("dots are correctly placed in the signature", {
+  dots_last_actual <- call("runif", n = call("rpois", 1, 5), quote(...))
+  dots_last_alleged <- partial(runif, n = rpois(1, 5)) %>% body()
+  expect_identical(dots_last_actual, dots_last_alleged)
+
+  # Also tests that argument names are not eaten when .dots_first = TRUE
+  dots_first_actual <- call("runif", quote(...), n = call("rpois", 1, 5))
+  dots_first_alleged <- partial(runif, n = rpois(1, 5), .first = FALSE) %>%
+    body()
+  expect_identical(dots_first_actual, dots_first_alleged)
+})
+
+test_that("partial() works with no partialised arguments", {
+  actual <- call("runif", quote(...))
+  alleged1 <- partial(runif, .first = TRUE) %>% body()
+  alleged2 <- partial(runif, .first = FALSE) %>% body()
+  expect_identical(actual, alleged1)
+  expect_identical(actual, alleged2)
+})
+
+test_that("lazy evaluation means arguments aren't repeatedly evaluated", {
+  f <- partial(runif, n = rpois(1, 5), .lazy = FALSE)
+  .n <- 100
+  v <- map_int(rerun(.n, f()), length)
+  expect_true(table(v) == .n)
+})
diff --git a/tests/testthat/test-pluck.R b/tests/testthat/test-pluck.R
new file mode 100644
index 0000000..047daa3
--- /dev/null
+++ b/tests/testthat/test-pluck.R
@@ -0,0 +1,156 @@
+context("pluck")
+
+test_that("contents must be a vector", {
+  expect_error(pluck(quote(x), list(1)), "Don't know how to pluck")
+})
+
+# pluck vector --------------------------------------------------------------
+
+test_that("can pluck by position", {
+  x <- list("a", 1, c(TRUE, FALSE))
+
+  # double
+  expect_identical(pluck(x, list(1)), x[[1]])
+  expect_identical(pluck(x, list(2)), x[[2]])
+  expect_identical(pluck(x, list(3)), x[[3]])
+
+  # integer
+  expect_identical(pluck(x, list(1L)), x[[1]])
+  expect_identical(pluck(x, list(2L)), x[[2]])
+  expect_identical(pluck(x, list(3L)), x[[3]])
+})
+
+test_that("can pluck by name", {
+  x <- list(a = "a", b = 1, c = c(TRUE, FALSE))
+
+  expect_identical(pluck(x, list("a")), x[["a"]])
+  expect_identical(pluck(x, list("b")), x[["b"]])
+  expect_identical(pluck(x, list("c")), x[["c"]])
+})
+
+test_that("can pluck from atomic vectors", {
+  expect_identical(pluck(TRUE, list(1)), TRUE)
+  expect_identical(pluck(1L, list(1)), 1L)
+  expect_identical(pluck(1, list(1)), 1)
+  expect_identical(pluck("a", list(1)), "a")
+})
+
+test_that("can pluck by name and position", {
+  x <- list(a = list(list(b = 1)))
+  expect_equal(pluck(x, list("a", 1, "b")), 1)
+})
+
+
+test_that("require length 1 vectors", {
+  expect_error(pluck(1, list(letters)), "must have length 1")
+  expect_error(pluck(1, list(TRUE)), "must be a character or numeric")
+})
+
+test_that("special indexes never match", {
+  x <- list(a = 1, b = 2, c = 3)
+
+  expect_null(pluck(x, list(NA_character_)))
+  expect_null(pluck(x, list("")))
+
+  expect_null(pluck(x, list(NA_integer_)))
+
+  expect_null(pluck(x, list(NA_real_)))
+  expect_null(pluck(x, list(NaN)))
+  expect_null(pluck(x, list(Inf)))
+  expect_null(pluck(x, list(-Inf)))
+})
+
+test_that("special values return NULL", {
+  # unnamed input
+  expect_null(pluck(list(1, 2), list("a")))
+
+  # zero length input
+  expect_null(pluck(integer(), list(1)))
+
+  # past end
+  expect_null(pluck(1:4, list(10)))
+  expect_null(pluck(1:4, list(10L)))
+})
+
+test_that("handles weird names", {
+  x <- list(1, 2, 3, 4, 5)
+  names(x) <- c("a", "a", NA, "", "b")
+
+  expect_equal(pluck(x, list("a")), 1)
+  expect_equal(pluck(x, list("b")), 5)
+
+  expect_null(pluck(x, list("")))
+  expect_null(pluck(x, list(NA_character_)))
+})
+
+
+# closures ----------------------------------------------------------------
+
+test_that("can pluck attributes", {
+  x <- structure(
+    list(
+      structure(
+        list(),
+        x = 1
+      )
+    ),
+    y = 2
+  )
+
+  expect_equal(pluck(x, list(attr_getter("y"))), 2)
+  expect_equal(pluck(x, list(1, attr_getter("x"))), 1)
+})
+
+test_that("attr_getter() evaluates eagerly", {
+  getters <- list_len(2)
+  attrs <- c("foo", "bar")
+  for (i in seq_along(attrs)) {
+    getters[[i]] <- attr_getter(attrs[[i]])
+  }
+
+  x <- set_attrs(list(), foo = "foo", bar = "bar")
+  expect_identical(getters[[1]](x), "foo")
+})
+
+test_that("delegate error handling to Rf_eval()", {
+  expect_error(pluck(letters, list(function() NULL)), "unused argument")
+  expect_error(pluck(letters, list(function(x, y) y)), "missing, with no default")
+})
+
+
+# environments ------------------------------------------------------------
+
+test_that("pluck errors with invalid indices", {
+  expect_error(pluck(environment(), list(1)), "not a string")
+  expect_error(pluck(environment(), list(letters)), "not a string")
+})
+
+test_that("pluck returns missing with missing index", {
+  expect_equal(pluck(environment(), list(NA_character_)), NULL)
+})
+
+test_that("plucks by name", {
+  env <- new.env(parent = emptyenv())
+  env$x <- 10
+
+  expect_equal(pluck(env, list("x")), 10)
+})
+
+
+# S4 ----------------------------------------------------------------------
+
+newA <- methods::setClass("A", list(a = "numeric", b = "numeric"))
+A <- newA(a = 1, b = 10)
+
+test_that("pluck errors with invalid indices", {
+  expect_error(pluck(A, list(1)), "not a string")
+  expect_error(pluck(A, list(letters)), "not a string")
+})
+
+test_that("pluck returns missing with missing index", {
+  expect_equal(pluck(A, list(NA_character_)), NULL)
+})
+
+test_that("plucks by name", {
+  expect_equal(pluck(A, list("a")), 1)
+})
diff --git a/tests/testthat/test-predicates.R b/tests/testthat/test-predicates.R
new file mode 100644
index 0000000..1acf922
--- /dev/null
+++ b/tests/testthat/test-predicates.R
@@ -0,0 +1,14 @@
+context("predicates")
+
+test_that("predicate-based functionals work with logical vectors", {
+  expect_equal(keep(as.list(1:3), c(TRUE, FALSE, TRUE)), list(1, 3))
+  expect_equal(discard(as.list(1:3), c(TRUE, FALSE, TRUE)), list(2))
+  expect_equal(
+    modify_if(as.list(1:3), c(TRUE, FALSE, TRUE), as.character),
+    list("1", 2, "3")
+  )
+  expect_equal(
+    lmap_if(as.list(1:3), c(TRUE, FALSE, TRUE), ~list(as.character(.x[[1]]))),
+    list("1", 2, "3")
+  )
+})
diff --git a/tests/testthat/test-prepend.R b/tests/testthat/test-prepend.R
new file mode 100644
index 0000000..7fade0e
--- /dev/null
+++ b/tests/testthat/test-prepend.R
@@ -0,0 +1,15 @@
+context("prepend")
+
+test_that("prepend is clearer version of merging with c()", {
+  x <- 1:3
+  expect_identical(
+    x %>% prepend(4),
+    x %>% c(4, .)
+  )
+  expect_identical(
+    x %>% prepend(4, before = 3),
+    x %>% {
+      c(.[1:2], 4, .[3])
+    }
+  )
+})
diff --git a/tests/testthat/test-recycle_args.R b/tests/testthat/test-recycle_args.R
new file mode 100644
index 0000000..e08b7b2
--- /dev/null
+++ b/tests/testthat/test-recycle_args.R
@@ -0,0 +1,25 @@
+context("recycle_args")
+
+test_that("rejects uneven lengths", {
+  args <- list(1, c(1:2), NULL)
+  expect_error(purrr:::recycle_args(args), "lengths == 1L \\| lengths == n")
+})
+
+
+test_that("recycles single values and preserves longer ones", {
+  args <- list(1, 1:12,  month.name, "a")
+  recycled <- purrr:::recycle_args(args)
+
+  expect_equal(recycled[[1]], rep(1, 12))
+  expect_equal(recycled[[2]], 1:12)
+  expect_equal(recycled[[3]], month.name)
+  expect_equal(recycled[[4]], rep("a", 12))
+})
+
+test_that("will not recycle non-vectors", {
+  args <- list(1:12,  identity)
+  expect_error(
+    purrr:::recycle_args(args),
+    "replicate an object of type 'closure'"
+  )
+})
diff --git a/tests/testthat/test-reduce.R b/tests/testthat/test-reduce.R
new file mode 100644
index 0000000..38409f4
--- /dev/null
+++ b/tests/testthat/test-reduce.R
@@ -0,0 +1,57 @@
+context("reduce")
+
+test_that("empty input returns init or error", {
+  expect_error(reduce(list()), "no `.init` supplied")
+  expect_equal(reduce(list(), `+`, .init = 0), 0)
+})
+
+test_that("first/value value used as first value", {
+  x <- c(1, 1)
+
+  expect_equal(reduce(x, `+`), 2)
+  expect_equal(reduce(x, `+`, .init = 1), 3)
+  expect_equal(reduce_right(x, `+`), 2)
+  expect_equal(reduce_right(x, `+`, .init = 1), 3)
+})
+
+test_that("length 1 argument reduced with init", {
+  expect_equal(reduce(1, `+`, .init = 1), 2)
+  expect_equal(reduce_right(1, `+`, .init = 1), 2)
+})
+
+test_that("reduce_right equivalent to reversing input", {
+  x <- list(c(2, 1), c(4, 3), c(6, 5))
+  expect_equal(reduce_right(x, c), c(6, 5, 4, 3, 2, 1))
+  expect_equal(reduce_right(x, c, .init = 7), c(7, 6, 5, 4, 3, 2, 1))
+})
+
+# accumulate --------------------------------------------------------------
+
+test_that("accumulate passes arguments to function", {
+  tt <- c("a", "b", "c")
+  expect_equal(accumulate(tt, paste, sep = "."), c("a", "a.b", "a.b.c"))
+  expect_equal(accumulate_right(tt, paste, sep = "."), c("c.b.a", "c.b", "c"))
+})
+
+
+# reduce2 -----------------------------------------------------------------
+
+test_that("basic application works", {
+  paste2 <- function(x, y, sep) paste(x, y, sep = sep)
+
+  x <- c("a", "b", "c")
+  expect_equal(reduce2(x, c("-", "."), paste2), "a-b.c")
+  expect_equal(reduce2(x, c(".", "-", "."), paste2, .init = "x"), "x.a-b.c")
+})
+
+test_that("reduce2_right works if lengths match", {
+  x <- list(c(0, 1), c(2, 3), c(4, 5))
+  y <- list(c(6, 7), c(8, 9))
+  expect_equal(reduce2_right(x, paste, y), c("4 2 8 0 6", "5 3 9 1 7"))
+  expect_error(reduce2_right(y, paste, x))
+})
+
+test_that("reduce returns original input if it was length one", {
+  x <- list(c(0, 1), c(2, 3), c(4, 5))
+  expect_equal(reduce(x[1], paste), x[[1]])
+})
diff --git a/tests/testthat/test-rerun.R b/tests/testthat/test-rerun.R
new file mode 100644
index 0000000..a91f094
--- /dev/null
+++ b/tests/testthat/test-rerun.R
@@ -0,0 +1,22 @@
+context("rerun")
+
+test_that("single unnamed arg doesn't get extra list", {
+  expect_equal(rerun(2, 1), list(1, 1))
+})
+
+test_that("single named arg gets extra list", {
+  expect_equal(rerun(2, a = 1), list(list(a = 1), list(a = 1)))
+})
+
+test_that("every run is different", {
+  x <- rerun(2, runif(1))
+  expect_true(x[[1]] != x[[2]])
+})
+
+test_that("rerun uses scope of expression", {
+  f <- function(n) {
+    rerun(1, x = seq_len(n))
+  }
+
+  expect_equal(f(10)[[1]]$x, 1:10)
+})
diff --git a/tests/testthat/test-simplify.R b/tests/testthat/test-simplify.R
new file mode 100644
index 0000000..28e7ea6
--- /dev/null
+++ b/tests/testthat/test-simplify.R
@@ -0,0 +1,35 @@
+context("simplify")
+
+test_that("can_simplify() understands vector molds", {
+  x <- as.list(1:3)
+  x2 <- c(x, list(1:3))
+  expect_true(can_simplify(x, integer(1)))
+  expect_false(can_simplify(x, character(1)))
+  expect_false(can_simplify(x2, integer(1)))
+
+  x3 <- list(1:2, 3:4, 5:6)
+  expect_true(can_simplify(x3, integer(2)))
+  expect_false(can_simplify(x, integer(2)))
+})
+
+test_that("can_simplify() understands types as strings", {
+  x <- as.list(1:3)
+  expect_true(can_simplify(x, "integer"))
+  expect_false(can_simplify(x, "character"))
+})
+
+test_that("integer is coercible to double", {
+  x <- list(1L, 2L)
+  expect_true(can_simplify(x, "numeric"))
+  expect_true(can_simplify(x, numeric(1)))
+  expect_true(can_simplify(x, "double"))
+  expect_true(can_simplify(x, double(1)))
+})
+
+test_that("numeric is an alias for double", {
+  expect_true(can_simplify(list(1, 2), "numeric"))
+})
+
+test_that("double is not coercible to integer", {
+  expect_false(can_simplify(list(1, 2), "integer"))
+})
diff --git a/tests/testthat/test-splice.R b/tests/testthat/test-splice.R
new file mode 100644
index 0000000..3741356
--- /dev/null
+++ b/tests/testthat/test-splice.R
@@ -0,0 +1,18 @@
+context("splice")
+
+test_that("predicate controls which elements get spliced", {
+  x <- list(1, 2, list(3, 4))
+
+  expect_equal(splice_if(x, ~ FALSE), x)
+  expect_equal(splice_if(x, is.list), list(1, 2, 3, 4))
+})
+
+test_that("splice() produces correctly named lists", {
+  inputs <- list(arg1 = "a", arg2 = "b")
+
+  out1 <- splice(inputs, arg3 = c("c1", "c2"))
+  expect_named(out1, c("arg1", "arg2", "arg3"))
+
+  out2 <- splice(inputs, arg = list(arg3 = 1, arg4 = 2))
+  expect_named(out2, c("arg1", "arg2", "arg3", "arg4"))
+})
diff --git a/tests/testthat/test-transpose.R b/tests/testthat/test-transpose.R
new file mode 100644
index 0000000..76a852f
--- /dev/null
+++ b/tests/testthat/test-transpose.R
@@ -0,0 +1,111 @@
+context("transpose")
+
+test_that("input must be a list", {
+  expect_error(transpose(1:3), "is not a list")
+})
+
+test_that("elements of input must be vectors", {
+  expect_error(transpose(list(environment())), "is not a vector")
+  expect_error(transpose(list(list(), environment())), "is not a vector")
+})
+
+test_that("empty list returns empty list", {
+  expect_equal(transpose(list()), list())
+})
+
+test_that("transpose switches order of first & second idnex", {
+  x <- list(list(1, 3), list(2, 4))
+  expect_equal(transpose(x), list(list(1, 2), list(3, 4)))
+})
+
+test_that("inside names become outside names", {
+  x <- list(list(x = 1), list(x = 2))
+  expect_equal(transpose(x), list(x = list(1, 2)))
+})
+
+test_that("outside names become inside names", {
+  x <- list(x = list(1, 3), y = list(2, 4))
+  expect_equal(transpose(x), list(list(x = 1, y = 2), list(x = 3, y = 4)))
+})
+
+test_that("warns if element too short", {
+  x <- list(list(1, 2), list(1))
+  expect_warning(out <- transpose(x), "Element 2 has length 1")
+  expect_equal(out, list(list(1, 1), list(2, NULL)))
+})
+test_that("warns if element too long", {
+  x <- list(list(1, 2), list(1, 2, 3))
+  expect_warning(out <- transpose(x), "Element 2 has length 3")
+  expect_equal(out, list(list(1, 1), list(2, 2)))
+})
+
+test_that("can transpose list of lists of  atomic vectors", {
+  x <- list(list(TRUE, 1L, 1, "1"))
+  expect_equal(transpose(x), list(list(TRUE), list(1L), list(1), list("1")))
+})
+
+test_that("can transpose lists of atomic vectors", {
+  expect_equal(transpose(list(TRUE, FALSE)), list(list(TRUE, FALSE)))
+  expect_equal(transpose(list(1L, 2L)), list(list(1L, 2L)))
+  expect_equal(transpose(list(1, 2)), list(list(1, 2)))
+  expect_equal(transpose(list("a", "b")), list(list("a", "b")))
+})
+
+test_that("can't transpose expressions", {
+  expect_error(transpose(list(expression(a))), "Unsupported type")
+})
+
+# Named based matching ----------------------------------------------------
+
+test_that("can override default names", {
+  x <- list(
+    list(x = 1),
+    list(y = 2, x = 1)
+  )
+  tx <- transpose(x, c("x", "y"))
+
+  expect_equal(tx, list(
+    x = list(1, 1),
+    y = list(NULL, 2)
+  ))
+})
+
+test_that("if present, names are used", {
+  x <- list(
+    list(x = 1, y = 2),
+    list(y = 2, x = 1)
+  )
+  tx <- transpose(x)
+
+  expect_equal(tx$x, list(1, 1))
+  expect_equal(tx$y, list(2, 2))
+})
+
+test_that("if missing elements, filled with NULL", {
+  x <- list(
+    list(x = 1, y = 2),
+    list(x = 1)
+  )
+  tx <- transpose(x)
+  expect_equal(tx$y, list(2, NULL))
+})
+
+# Position based matching -------------------------------------------------
+
+test_that("warning if too short", {
+  x <- list(
+    list(1, 2),
+    list(1)
+  )
+  expect_warning(tx <- transpose(x), "has length 1 not 2")
+  expect_equal(tx, list(list(1, 1), list(2, NULL)))
+})
+
+test_that("warning if too long", {
+  x <- list(
+    list(1),
+    list(1, 2)
+  )
+  expect_warning(tx <- transpose(x), "has length 2 not 1")
+  expect_equal(tx, list(list(1, 1)))
+})
diff --git a/tests/testthat/test-utils.R b/tests/testthat/test-utils.R
new file mode 100644
index 0000000..86706c8
--- /dev/null
+++ b/tests/testthat/test-utils.R
@@ -0,0 +1,31 @@
+context("utils")
+
+test_that("%@% is an infix attribute accessor", {
+  expect_identical(mtcars %@% "names", attr(mtcars, "names"))
+})
+
+test_that("rbernoulli is a special case of rbinom", {
+  set.seed(1)
+  x <- rbernoulli(10)
+
+  set.seed(1)
+  y <- ifelse(rbinom(10, 1, 0.5) == 1, TRUE, FALSE)
+
+  expect_equal(x, y)
+})
+
+test_that("rdunif works", {
+  expect_length(rdunif(100, 10), 100)
+})
+
+test_that("rdunif fails if a and b are not unit length numbers", {
+  expect_error(rdunif(1000, 1, "a"))
+  expect_error(rdunif(1000, 1, c(0.5, 0.2)))
+  expect_error(rdunif(1000, FALSE, 2))
+  expect_error(rdunif(1000, c(2, 3), 2))
+})
+
+test_that("has_names returns vector of logicals", {
+  expect_equal(has_names(letters %>% set_names()), rep_along(letters, TRUE))
+  expect_equal(has_names(letters), rep_along(letters, FALSE))
+})
diff --git a/tests/testthat/test-when.R b/tests/testthat/test-when.R
new file mode 100644
index 0000000..81d41b6
--- /dev/null
+++ b/tests/testthat/test-when.R
@@ -0,0 +1,64 @@
+context("when")
+
+test_that("when chooses the correct action", {
+
+  x <-
+    1:5 %>%
+    when(
+      sum(.) <=  50 ~ sum(.),
+      sum(.) <= 100 ~ sum(.) / 2,
+      ~ 0
+    )
+
+  expect_equal(x, 15)
+
+  y <-
+    1:10 %>%
+    when(
+      sum(.) <=  50 ~ sum(.),
+      sum(.) <= 100 ~ sum(.) / 2,
+      ~ 0
+    )
+
+  expect_equal(y, sum(1:10) / 2)
+
+  z <-
+    1:100 %>%
+    when(
+      sum(.) <=  50 ~ sum(.),
+      sum(.) <= 100 ~ sum(.) / 2,
+      ~ 0
+    )
+
+  expect_equal(z, 0)
+})
+
+test_that("named arguments work with when", {
+  x <-
+    1:10 %>%
+    when(
+      sum(.) <=     x ~ sum(.) * x,
+      sum(.) <= 2 * x ~ sum(.) * x / 2,
+      ~ 0,
+      x = 60
+    )
+
+  expect_equal(x, sum(1:10) * 60)
+})
+
+test_that("default values work without a formula", {
+  x <-
+    iris %>%
+    subset(Sepal.Length > 10) %>%
+    when(
+      nrow(.) > 0 ~ .,
+      head(iris, 10)
+    )
+
+  expect_equivalent(x, head(iris, 10))
+
+})
+
+test_that("error when named arguments have no matching conditions", {
+  expect_error(1:5 %>% when(a = sum(.) < 5 ~ 3))
+})
diff --git a/vignettes/other-langs.Rmd b/vignettes/other-langs.Rmd
new file mode 100644
index 0000000..e029eff
--- /dev/null
+++ b/vignettes/other-langs.Rmd
@@ -0,0 +1,46 @@
+---
+title: "Functional programming in other languages"
+output: rmarkdown::html_vignette
+vignette: >
+  %\VignetteIndexEntry{Functional programming in other languages}
+  %\VignetteEngine{knitr::rmarkdown}
+  %\VignetteEncoding{UTF-8}
+---
+
+purrr draws inspiration from many related tools:
+
+* List operations defined in the Haskell [prelude][haskell]
+
+* Scala's [list methods][scala].
+
+* Functional programming librarys for javascript: 
+  [underscore.js](http://underscorejs.org), 
+  [lodash](https://lodash.com) and 
+  [lazy.js](http://danieltao.com/lazy.js/).
+
+* [rlist](http://renkun.me/rlist/), another R package to support working
+  with lists. Similar goals but somewhat different philosophy.
+
+However, the goal of purrr is not to try and simulate a purer functional programming language in R; we don't want to implement a second-class version of Haskell in R. The goal is to give you similar expressiveness to an FP language, while allowing you to write code that looks and works like R:
+
+* Instead of point free (tacit) style, we use the pipe, `%>%`, to write code 
+  that can be read from left to right.
+
+* Instead of currying, we use `...` to pass in extra arguments.
+
+* Anonymous functions are verbose in R, so we provide two convenient shorthands.
+  For unary functions, `~ .x + 1` is equivalent to `function(.x) .x + 1`.
+  For chains of transformations functions, `. %>% f() %>% g()` is
+  equivalent to `function(.) . %>% f() %>% g()` (this shortcut is provided
+  by magrittr).
+
+* R is weakly typed, so we need `map` variants that describe the output type 
+  (like `map_int()`, `map_dbl()`, etc) because don't the return type of `.f`.
+
+* R has named arguments, so instead of providing different functions for
+  minor variations (e.g. `detect()` and `detectLast()`) we use a named
+  argument, `.right`. Type-stable functions are easy to reason about so
+  additional arguments will never change the type of the output.
+
+[scala]:http://www.scala-lang.org/api/current/index.html#scala.collection.immutable.List
+[haskell]:http://hackage.haskell.org/package/base-4.7.0.1/docs/Prelude.html#g:11

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-med/r-cran-purrr.git



More information about the debian-med-commit mailing list