├── .gitignore ├── .Rbuildignore ├── .travis.yml ├── README.md ├── R ├── functions.R ├── reactives.R ├── maybe.R ├── logger.R ├── bind-fmap-join.R ├── listOf.R └── infix.R ├── man ├── maybe.Rd ├── listOf.Rd ├── logger.Rd ├── infix.Rd └── monad-generics.Rd ├── monads.Rproj ├── DESCRIPTION └── NAMESPACE /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | -------------------------------------------------------------------------------- /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.*\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^\.travis\.yml$ 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Sample .travis.yml for R projects 2 | 3 | language: r 4 | warnings_are_errors: true 5 | 6 | cache: packages 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # monads 2 | 3 | [![Travis-CI Build Status](https://travis-ci.org/hadley/monads.svg?branch=master)](https://travis-ci.org/hadley/monads) 4 | -------------------------------------------------------------------------------- /R/functions.R: -------------------------------------------------------------------------------- 1 | #' @export 2 | fmap.function <- function(.m, .f, ...) { 3 | function(...) { 4 | .f(.m(...)) 5 | } 6 | } 7 | 8 | #' @export 9 | bind.function <- function(.m, .f, ...) { 10 | .f(.m(...)) 11 | } 12 | -------------------------------------------------------------------------------- /R/reactives.R: -------------------------------------------------------------------------------- 1 | #' @export 2 | fmap.reactive <- function(.m, .f, ...) { 3 | .f <- purrr::as_function(.f) 4 | shiny::reactive(.f(.m(), ...)) 5 | } 6 | 7 | #' @export 8 | bind.reactive <- function(.m, .f, ...) { 9 | join(fmap(.m, .f, ...)) 10 | } 11 | 12 | #' @export 13 | join.reactive <- function(.m) { 14 | shiny::reactive(.m()()) 15 | } 16 | -------------------------------------------------------------------------------- /man/maybe.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/maybe.R 3 | \name{maybe} 4 | \alias{maybe} 5 | \alias{nothing} 6 | \title{Maybe there is something here?} 7 | \usage{ 8 | maybe(x) 9 | 10 | nothing() 11 | } 12 | \arguments{ 13 | \item{x}{Is there something here? Maybe.} 14 | } 15 | \description{ 16 | Or maybe there's just a \code{NULL}. 17 | } 18 | \examples{ 19 | double <- function(x) x * 2 20 | maybe(10) \%>>\% double() 21 | nothing() \%>>\% double() 22 | } 23 | 24 | -------------------------------------------------------------------------------- /monads.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: No 4 | SaveWorkspace: No 5 | AlwaysSaveHistory: Default 6 | QuitChildProcessesOnExit: Default 7 | 8 | EnableCodeIndexing: Yes 9 | UseSpacesForTab: Yes 10 | NumSpacesForTab: 2 11 | Encoding: UTF-8 12 | 13 | RnwWeave: knitr 14 | LaTeX: pdfLaTeX 15 | 16 | AutoAppendNewline: Yes 17 | StripTrailingWhitespace: Yes 18 | 19 | BuildType: Package 20 | PackageUseDevtools: Yes 21 | PackageInstallArgs: --no-multiarch --with-keep.source 22 | PackageRoxygenize: rd,collate,namespace 23 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: monads 2 | Version: 0.0.0.9000 3 | Description: Tools for monads. 4 | Title: Work with Monads in R 5 | URL: https://github.com/hadley/monads 6 | BugReports: https://github.com/hadley/monads/issues 7 | Authors@R: c( 8 | person("Hadley", "Wickham", , "hadley@rstudio.com", role = c("aut", "cre")), 9 | person("Joe", "Cheng", , "joe@rstudio.com", role = "aut"), 10 | person("RStudio", role = "cph") 11 | ) 12 | License: GPL-3 13 | LazyData: true 14 | Imports: 15 | purrr 16 | Suggests: shiny 17 | RoxygenNote: 5.0.1 18 | -------------------------------------------------------------------------------- /man/listOf.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/listOf.R 3 | \name{listOf} 4 | \alias{listOf} 5 | \title{A list where all elements are the same class.} 6 | \usage{ 7 | listOf(...) 8 | } 9 | \arguments{ 10 | \item{...}{Elements to combine into a list. All elements must be the 11 | same type.} 12 | } 13 | \description{ 14 | A list where all elements are the same class. 15 | } 16 | \examples{ 17 | x <- listOf(1L, 2L, 4:10) 18 | double <- function(x) x * 2 19 | x \%>>\% double() 20 | 21 | x \%>>\% function(x) list(x, x) 22 | x \%>+\% function(x) list(x, x) 23 | } 24 | 25 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | S3method(bind,"function") 4 | S3method(bind,listOf) 5 | S3method(bind,logger) 6 | S3method(bind,maybe) 7 | S3method(bind,reactive) 8 | S3method(fmap,"function") 9 | S3method(fmap,listOf) 10 | S3method(fmap,logger) 11 | S3method(fmap,maybe) 12 | S3method(fmap,reactive) 13 | S3method(join,listOf) 14 | S3method(join,logger) 15 | S3method(join,reactive) 16 | S3method(print,listOf) 17 | S3method(print,logger) 18 | S3method(print,maybe) 19 | export("%>+%") 20 | export("%>>%") 21 | export(bind) 22 | export(fmap) 23 | export(join) 24 | export(listOf) 25 | export(logger) 26 | export(maybe) 27 | export(nothing) 28 | -------------------------------------------------------------------------------- /man/logger.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/logger.R 3 | \name{logger} 4 | \alias{logger} 5 | \title{A list where all elements are the same class.} 6 | \usage{ 7 | logger(value, log = character()) 8 | } 9 | \arguments{ 10 | \item{value}{Value with logging context} 11 | 12 | \item{log}{Character vector of logged values.} 13 | 14 | \item{...}{Elements to combine into a list. All elements must be the 15 | same type.} 16 | } 17 | \description{ 18 | A list where all elements are the same class. 19 | } 20 | \examples{ 21 | (l <- logger(5, "object created")) 22 | (l2 <- l \%>>\% `*`(2)) 23 | 24 | set_value <- function(x, y) { 25 | logger(y, paste(x, "->", y)) 26 | } 27 | 28 | l2 \%>+\% set_value(20) 29 | } 30 | 31 | -------------------------------------------------------------------------------- /man/infix.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/infix.R 3 | \name{\%>>\%} 4 | \alias{\%>+\%} 5 | \alias{\%>>\%} 6 | \title{Infix fmap and bind.} 7 | \usage{ 8 | lhs \%>>\% rhs 9 | 10 | lhs \%>+\% rhs 11 | } 12 | \arguments{ 13 | \item{lhs}{A monad} 14 | 15 | \item{rhs}{Call to invoke.} 16 | } 17 | \description{ 18 | This works like magrittr's \code{\%>\%}, but uses fmap to unwrap, apply, 19 | and then rewrap the object. 20 | } 21 | \examples{ 22 | if (require("shiny")) { 23 | options(shiny.suppressMissingContextError = TRUE) 24 | r <- reactive(iris) 25 | r2 <- r \%>>\% head(5) 26 | r2() 27 | 28 | double <- function(x) x * 2 29 | maybe(10) \%>>\% double 30 | maybe(10) \%>>\% double() 31 | maybe(10) \%>>\% function(x) x * 2 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /R/maybe.R: -------------------------------------------------------------------------------- 1 | #' Maybe there is something here? 2 | #' 3 | #' Or maybe there's just a \code{NULL}. 4 | #' 5 | #' @param x Is there something here? Maybe. 6 | #' @export 7 | #' @examples 8 | #' double <- function(x) x * 2 9 | #' maybe(10) %>>% double() 10 | #' nothing() %>>% double() 11 | maybe <- function(x) { 12 | structure(list(x), class = "maybe") 13 | } 14 | 15 | #' @export 16 | #' @rdname maybe 17 | nothing <- function() { 18 | maybe(NULL) 19 | } 20 | 21 | #' @export 22 | fmap.maybe <- function(.m, .f, ...) { 23 | if (is.null(.m[[1]])) { 24 | return(.m) 25 | } 26 | 27 | maybe(.f(.m[[1]], ...)) 28 | } 29 | 30 | #' @export 31 | bind.maybe <- function(.m, .f, ...) { 32 | if (is.null(.m[[1]])) { 33 | return(.m) 34 | } 35 | 36 | .f(.m[[1]], ...) 37 | } 38 | 39 | #' @export 40 | print.maybe <- function(x, ...) { 41 | if (is.null(x[[1]])) { 42 | cat("\n") 43 | } else { 44 | cat("\n") 45 | print(unclass(x[[1]])) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /R/logger.R: -------------------------------------------------------------------------------- 1 | #' A list where all elements are the same class. 2 | #' 3 | #' @param value Value with logging context 4 | #' @param log Character vector of logged values. 5 | #' @param ... Elements to combine into a list. All elements must be the 6 | #' same type. 7 | #' @export 8 | #' @examples 9 | #' (l <- logger(5, "object created")) 10 | #' (l2 <- l %>>% `*`(2)) 11 | #' 12 | #' set_value <- function(x, y) { 13 | #' logger(y, paste(x, "->", y)) 14 | #' } 15 | #' 16 | #' l2 %>+% set_value(20) 17 | logger <- function(value, log = character()) { 18 | structure(list(value = value, log = log), class = "logger") 19 | } 20 | 21 | #' @export 22 | fmap.logger <- function(.m, .f, ...) { 23 | out <- .f(.m$value, ...) 24 | logger(out, .m$log) 25 | } 26 | 27 | #' @export 28 | bind.logger <- function(.m, .f, ...) { 29 | out <- .f(.m$value, ...) 30 | logger(out$value, c(out$log, .m$log)) 31 | } 32 | 33 | #' @export 34 | join.logger <- function(.m) { 35 | logger(.m$value$value, c(.m$value$log, .m$log)) 36 | } 37 | 38 | #' @export 39 | print.logger <- function(x, ...) { 40 | print(x$value) 41 | 42 | cat("Log: \n") 43 | cat(paste("* ", x$log, "\n"), "\n", sep = "") 44 | } 45 | -------------------------------------------------------------------------------- /R/bind-fmap-join.R: -------------------------------------------------------------------------------- 1 | #' Monad generics. 2 | #' 3 | #' To make a class a monad, you need to define three generics: \code{fmap}, 4 | #' \code{bind}, and \code{join}. \code{fmap} takes a non-monad function, 5 | #' and applies it to a monad by unwrapping, applying, and rewrapping. 6 | #' \code{bind} apply a function that takes a regular value and returns a monad, 7 | #' by unwrapping and applying. \code{join} collapses a monad that's nested 8 | #' inside itself. 9 | #' 10 | #' Any object with a \code{fmap} method is called a \code{functor}. 11 | #' 12 | #' @param .m The monad 13 | #' @inheritParams purrr::as_function 14 | #' @param ... Additonal arguments passed on to \code{.f}. 15 | #' @examples 16 | #' # Functions are functors 17 | #' add1 <- function(x) x + 1 18 | #' add2 <- function(x) x + 2 19 | #' 20 | #' add3 <- add1 %>>% add2 21 | #' add3(10) 22 | #' @name monad-generics 23 | NULL 24 | 25 | #' @rdname monad-generics 26 | #' @export 27 | fmap <- function(.m, .f, ...) { 28 | UseMethod("fmap", .m) 29 | } 30 | 31 | #' @rdname monad-generics 32 | #' @export 33 | bind <- function(.m, .f, ...) { 34 | UseMethod("bind") 35 | } 36 | 37 | #' @rdname monad-generics 38 | #' @export 39 | join <- function(.m) { 40 | UseMethod("bind") 41 | } 42 | -------------------------------------------------------------------------------- /R/listOf.R: -------------------------------------------------------------------------------- 1 | #' A list where all elements are the same class. 2 | #' 3 | #' @param ... Elements to combine into a list. All elements must be the 4 | #' same type. 5 | #' @export 6 | #' @examples 7 | #' x <- listOf(1L, 2L, 4:10) 8 | #' double <- function(x) x * 2 9 | #' x %>>% double() 10 | #' 11 | #' x %>>% function(x) list(x, x) 12 | #' x %>+% function(x) list(x, x) 13 | listOf <- function(...) { 14 | x <- list(...) 15 | 16 | classes <- purrr::map(x, class) 17 | if (length(unique(classes)) > 1) { 18 | stop("All elements must have identical classes.", call. = FALSE) 19 | } 20 | 21 | structure(x, class = "listOf") 22 | } 23 | 24 | #' @export 25 | fmap.listOf <- function(.m, .f, ...) { 26 | structure(purrr::map(.m, .f, ...), class = "listOf") 27 | } 28 | 29 | #' @export 30 | bind.listOf <- function(.m, .f, ...) { 31 | out <- purrr::map(.m, .f, ...) 32 | stopifnot(is.list(out)) 33 | 34 | join.listOf(out) 35 | } 36 | 37 | #' @export 38 | join.listOf <- function(.m) { 39 | structure(purrr::flatten(.m), class = "listOf") 40 | } 41 | 42 | #' @export 43 | print.listOf <- function(x, ...) { 44 | class <- paste(class(x[[1]]), collapse = "/") 45 | cat("ListOf<", class, ">\n\n", sep = "") 46 | print(unclass(x)) 47 | } 48 | -------------------------------------------------------------------------------- /R/infix.R: -------------------------------------------------------------------------------- 1 | #' Infix fmap and bind. 2 | #' 3 | #' This works like magrittr's \code{\%>\%}, but uses fmap to unwrap, apply, 4 | #' and then rewrap the object. 5 | #' 6 | #' @param lhs A monad 7 | #' @param rhs Call to invoke. 8 | #' @rdname infix 9 | #' @export 10 | #' @examples 11 | #' if (require("shiny")) { 12 | #' options(shiny.suppressMissingContextError = TRUE) 13 | #' r <- reactive(iris) 14 | #' r2 <- r %>>% head(5) 15 | #' r2() 16 | #' 17 | #' double <- function(x) x * 2 18 | #' maybe(10) %>>% double 19 | #' maybe(10) %>>% double() 20 | #' maybe(10) %>>% function(x) x * 2 21 | #' } 22 | "%>>%" <- function(lhs, rhs) { 23 | call <- inline_call(quote(monads::fmap), substitute(lhs), substitute(rhs)) 24 | eval(call, parent.frame()) 25 | } 26 | 27 | #' @export 28 | #' @rdname infix 29 | "%>+%" <- function(lhs, rhs) { 30 | call <- inline_call(quote(monads::bind), substitute(lhs), substitute(rhs)) 31 | eval(call, parent.frame()) 32 | } 33 | 34 | inline_call <- function(f, lhs, rhs) { 35 | if (singular_form(rhs)) { 36 | call <- as.call(c(f, lhs, rhs)) 37 | } else { 38 | call <- as.call(c(f, lhs, rhs[[1]], as.list(rhs[-1]))) 39 | } 40 | call 41 | } 42 | 43 | singular_form <- function(x) { 44 | if (is.name(x)) 45 | return(TRUE) 46 | 47 | x <- x[[1]] 48 | if (identical(x, quote(`function`))) 49 | return(TRUE) 50 | 51 | if (identical(x, quote(`(`))) 52 | return(TRUE) 53 | 54 | if (identical(x, quote(`{`))) 55 | return(TRUE) 56 | 57 | FALSE 58 | } 59 | -------------------------------------------------------------------------------- /man/monad-generics.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/bind-fmap-join.R 3 | \name{monad-generics} 4 | \alias{bind} 5 | \alias{fmap} 6 | \alias{join} 7 | \alias{monad-generics} 8 | \title{Monad generics.} 9 | \usage{ 10 | fmap(.m, .f, ...) 11 | 12 | bind(.m, .f, ...) 13 | 14 | join(.m) 15 | } 16 | \arguments{ 17 | \item{.m}{The monad} 18 | 19 | \item{.f}{A function, formula, or atomic vector. 20 | 21 | If a \strong{function}, it is used as is. 22 | 23 | If a \strong{formula}, e.g. \code{~ .x + 2}, it is converted to a 24 | function with two arguments, \code{.x} or \code{.} and \code{.y}. This 25 | allows you to create very compact anonymous functions with up to 26 | two inputs. 27 | 28 | If \strong{character} or \strong{integer vector}, e.g. \code{"y"}, it 29 | is converted to an extractor function, \code{function(x) x[["y"]]}. To 30 | index deeply into a nested list, use multiple values; \code{c("x", "y")} 31 | is equivalent to \code{z[["x"]][["y"]]}. You can also set \code{.null} 32 | to set a default to use instead of \code{NULL} for absent components.} 33 | 34 | \item{...}{Additonal arguments passed on to \code{.f}.} 35 | } 36 | \description{ 37 | To make a class a monad, you need to define three generics: \code{fmap}, 38 | \code{bind}, and \code{join}. \code{fmap} takes a non-monad function, 39 | and applies it to a monad by unwrapping, applying, and rewrapping. 40 | \code{bind} apply a function that takes a regular value and returns a monad, 41 | by unwrapping and applying. \code{join} collapses a monad that's nested 42 | inside itself. 43 | } 44 | \details{ 45 | Any object with a \code{fmap} method is called a \code{functor}. 46 | } 47 | \examples{ 48 | # Functions are functors 49 | add1 <- function(x) x + 1 50 | add2 <- function(x) x + 2 51 | 52 | add3 <- add1 \%>>\% add2 53 | add3(10) 54 | } 55 | 56 | --------------------------------------------------------------------------------