[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