Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Should magrittr handle monadic binding? #75

Open
lionel- opened this issue Mar 31, 2015 · 16 comments
Open

Should magrittr handle monadic binding? #75

lionel- opened this issue Mar 31, 2015 · 16 comments
Labels
feature a feature request or enhancement

Comments

@lionel-
Copy link
Member

lionel- commented Mar 31, 2015

e.g. this experiment with monadic error handling:
https://gist.github.com/lionel-/a9aee3edb45a60a6e393

@lionel-
Copy link
Member Author

lionel- commented Mar 31, 2015

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.

@smbache
Copy link
Member

smbache commented Mar 31, 2015

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 :)

@smbache
Copy link
Member

smbache commented Mar 31, 2015

And I'm quite curious to see how you thought about going about it. ;) but will most likely not have time before after Easter. :(

@lionel-
Copy link
Member Author

lionel- commented Mar 31, 2015

and I'm quite curious to see what you guys think about it because I'm not sure what I did makes perfect sense ;)

@hadley
Copy link
Member

hadley commented Mar 31, 2015

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.

@lionel-
Copy link
Member Author

lionel- commented Mar 31, 2015

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.

@smbache
Copy link
Member

smbache commented Apr 1, 2015

I can't run it; I get a stack overflow error...

Still not really sure what I'd want in general

@lionel-
Copy link
Member Author

lionel- commented Apr 1, 2015

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.

@smbache
Copy link
Member

smbache commented Apr 1, 2015

I noticed that one, loading purrrr got me past that..

@vapniks
Copy link

vapniks commented Apr 20, 2015

I think this is a very good idea. Anything that moves us in the Haskell direction is very welcome by me.

@tonyfischetti
Copy link

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.

@lionel-
Copy link
Member Author

lionel- commented Apr 20, 2015

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 hadley added the feature a feature request or enhancement label Jul 2, 2018
@jcheng5
Copy link

jcheng5 commented Jul 17, 2018

@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?

@lionel-
Copy link
Member Author

lionel- commented Jul 18, 2018

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.

@jcheng5
Copy link

jcheng5 commented Jul 18, 2018

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.

@jrosell
Copy link

jrosell commented Feb 13, 2024

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature a feature request or enhancement
Projects
None yet
Development

No branches or pull requests

7 participants