Open lionel- opened 9 years ago
The changes to magrittr are in the following commits: https://github.com/lionel-/magrittr/commits/monads
It evaluates piped functions to check if they have class "monadic". If they do, the bind()
generic is called in the relevant parts of the fseq
sequence.
I'll look forward to looking at it—looks interesting. Not sure my initial enthusiasm for this is shared by my coauthor (maybe with good reason; I know little about the theory here).. In any case I'm sure we will be wiser soon :)
And I'm quite curious to see how you thought about going about it. ;) but will most likely not have time before after Easter. :(
and I'm quite curious to see what you guys think about it because I'm not sure what I did makes perfect sense ;)
I'd want to see an example with a monad not related to errors, because I think in R, it's better to deal with errors using the condition system.
The idea I think is not to replace R's condition system but rather to complement it. Ultimately, when it's time to process the errors, we'd use stop()
, warning()
, etc. The presentation that Stefan tweeted about was kind of convincing that there are advantages to propagate errors monadically. In particular, the argument of having all exceptions defined at the same place was compelling to me.
Other than that, I'm still struggling to figure out what monads are useful for! The Reader monad looks interesting and if I manage to use it in my work I'll add an example to the gist.
I can't run it; I get a stack overflow error...
Still not really sure what I'd want in general
weird, not sure why you would get such an error.
I noticed I used map()
instead of lapply()
, this is now fixed. Probably not the cause of your error though.
I noticed that one, loading purrrr got me past that..
I think this is a very good idea. Anything that moves us in the Haskell direction is very welcome by me.
That demo is super cool! I think that because of R's dynamic (and weak) type system, most non-error monads are of more limited use, though. For example, 'Either' is unnecessary because you can just return different types from an R function. Certainly the IO monad is necessary because any old function can perform side-effect IO. Writer monad-like-functionality might be pretty cool, but R could always mutate the global state of something that represents a running log.
I was also wondering about how useful monads really are without strong typing. But it seems they are used in Clojure so they should be useful in R too.
Ultimately, I think we would need someone experimented in monadic programming to implement a library demonstrating how to program with monads in R.
@hadley and I have had conversations about introducing a monadic bind operator for R. The main usages I had in mind were for reactives (from Shiny) and promises. Promises already have the %...>%
operator, my thinking was that a monadic operator would have similar semantics but be generic. I was also imagining a dedicated operator instead of overloading %>%
.
Reactive expression example w/o bind:
rand <- reactive({ invalidateLater(1000); runif(1, max=100) })
rand_int <- reactive({ rand() %>% round })
With bind (%$>%
):
rand <- reactive({ invalidateLater(1000); runif(1, max=100) })
rand_int <- rand %$>% round
Quite different than what you were discussing here I think. In fact I don't even think it's really monadic, just functor-y?
In fact I don't even think it's really monadic, just functor-y?
I think so. Are functors sufficient for promise chaining?
My feeling is that an operator would be too much syntax for fmap. Functor application is already provided by purrr::modify()
so that could be:
rand %>%
modify(round) %>%
modify(`*`, 10) %>%
modify(~ . + 42)
# Or equivalently
chain <- . %>% round() %>% { . * 10 + 42 }
rand %>% modify(chain)
This would have the advantage of reusing existing syntax.
Are functors sufficient for promise chaining?
I mean, technically, I don't think so. The steps in the promise chain need to be able to return objects that are either promises, or not promises, so promises need to join
in addition to fmap
. And I'm not sure what promises do is even a traditional join
, as they can flatten zero or more levels deep, where my understanding is that join
does exactly one level. It might be that promises::then
is just its own weird thing; would it be inappropriate for it to be used to implement modify.promise
?
OTOH I wasn't thinking that chains of reactive expressions would do any flattening at all, so just fmap
/modify
would do the right thing.
I'd want to see an example with a monad not related to errors, because I think in R, it's better to deal with errors using the condition system.
@hadley in Haskell there are some examples.
import Control.Monad (when)
import System.Directory (doesFileExist, removeFile)
removeIfExists :: FilePath -> IO ()
removeIfExists filePath =
doesFileExist filePath >>= \fileExists ->
when fileExists (removeFile filePath)
Now, one could write something like this in R:
remove_if_exists <- \(file_path){
f <- purrr::compose(
.dir = "forward",
\(file_path) file.exists(file_path),
\(file_exists) {
if(file_exists) return(file.remove(file_path))
return(file_exists)
}
)
f(file_path)
}
file.create("blank.txt")
remove_if_exists("blank.txt")
I think there are cases where what simply a pipable purrr::compose could help:
remove_if_exists <-
purrr::compose(\(x) {
list(name = x, return = file.exists(x))
}, .dir = "forward") %>%
purrr::compose(\(x) {
if(x$return) x$return <- file.remove(x$name)
x
}, .dir = "forward")
file.create("blank.txt")
remove_if_exists("blank.txt")
Another option, here:
when <- \(x, f) ifelse(x, f, x)
remove_if_exists <- \(file_path) {
file.exists(file_path) %>% (\(file_exists) when(file_exists, file.remove(file_path)))
}
file.create("blank.txt")
remove_if_exists("blank.txt")
Bonus: I think it's worth to mention https://github.com/b-rodrigues/chronicler by @b-rodrigues
e.g. this experiment with monadic error handling: https://gist.github.com/lionel-/a9aee3edb45a60a6e393