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

Let's play with monads #426

Open
askucher opened this issue Nov 24, 2013 · 17 comments
Open

Let's play with monads #426

askucher opened this issue Nov 24, 2013 · 17 comments

Comments

@askucher
Copy link

const unit = (value)->
  bind: (func) -> func (value)

const one = unit 1 

one.bind (-> unit (it+2)).bind(alert)

//should be

one >>= -> unit (it+2) >>= alert

In other words operator >>= should be a syntax sugar of calling bind method of current object

We can consider monads like piping, but

request 
      >>= promise ( -> data: it )
      >>= maybe (-> it.username) 
      >>= alert 

It is cool to don't care about scheduling of tasks, defining listeners and checking bad responses. We can define specific monads which can do all such work in background

@robotlolita
Copy link
Contributor

See Fantasy-Land

Also, you can sort of have Haskell's "do notation":

stuff = do # User
  data <- get(url).chain # Promise String
  [user, passwd] <- validate-login(JSON.parse data).chain # Validation Error [String, String]
  user-data <- do-login(user, passwd).chain # Validation Error, User
  return user

It would be cool to be able to define arbitrary binary operators though, the following could work:

operator a >>= f = a.chain f
operator a <|> b = a.get-or-else b

Instead of defining the precedence of things by infixr/infixl, they would be inferred based on the first characters of the operator (similar to Scala). Sadly, you wouldn't be able to overwrite the existing operators because there would be no way of resolving those at compile time without types :(

@askucher
Copy link
Author

We can use fay for it https://github.com/faylang/fay/wiki

@alexkalderimis
Copy link

Do syntax for promises would be fantastic.
On 24 Nov 2013 13:52, "Quildreen Motta" notifications@github.com wrote:

See Fantasy-Land https://github.com/fantasyland/fantasy-land

Also, you can sort of have Haskell's "do notation":

stuff = do # User
data <- get(url).chain # Promise String
[user, passwd] <- validate-login(JSON.parse data).chain # Validation Error [String, String]
user-data <- do-login(user, passwd).chain # Validation Error, User
return user

It would be cool to be able to define arbitrary binary operators though,
the following could work:

operator a >>= f = a.chain foperator a <|> b = a.get-or-else b

Instead of defining the precedence of things by infixr/infixl, they would
be inferred based on the first characters of the operator (similar to
Scala). Sadly, you wouldn't be able to overwrite the existing operators
because there would be no way of resolving those at compile time without
types :(


Reply to this email directly or view it on GitHubhttps://github.com//issues/426#issuecomment-29155718
.

@elclanrs
Copy link

+1. This would be great! I've been playing with monads in LiveScript recently. With backcalls the code looks obviously much nicer than in vanilla JS. Even with the piping operators you get nice composition. But for the "do" notation, it would be great to have an operator to replace x <- bind ma with just x <- ma, or maybe extend comprehensions with a syntax to handle monadic computations. Roy has a syntax for monads that compiles to clean JS. This is the closest I could get with LiveScript:

add = (x,y) --> x+y

List = do ->
  unit = (a) -> [a]
  bind = flip concat-map
  lift = (f, ma) --> bind ma, (a) -> unit f a
  {unit, bind, lift}

# "do"
let {unit, bind} = List
  x <- bind [1]
  y <- bind [2 3]
  z <- bind [4 5 6]
  unit x+y+z #=> [7 8 9 8 9 10]

# piping
let {unit, lift} = List
  unit 1 |> lift add 2 |> lift add 3 #=> [6]

@alexkalderimis
Copy link

That is a pretty excellent workaround. Perhaps it might be bit much, but
there could be space for a <= operator, that reads the bind function
from the current scope, leaving the <- operator as it is. A sugary
do-with Monad would be even better, but let {unit, bind} = Monad is
perfectly acceptable.

On 26 November 2013 06:33, elclanrs notifications@github.com wrote:

This would be great! I've been playing with monads in LiveScript recently.
With backcalls the code looks obviously much nicer than in vanilla JS. Even
with the piping operators you get nice composition. But for the "do"
notation, it would be great to have an operator to replace <- bind x with
just <- x. Roy http://roy.brianmckenna.org/ has a syntax for monads
that compiles to clean JS. This is the closest I could get with LiveScript:

add = (x,y) --> x+y
List = do ->
unit = (a) -> [a]
bind = (ma, f) --> concat (map f) ma
lift = (f, ma) --> bind ma, (a) -> unit f a
{unit, bind, lift}

"do"let {unit, bind} = List

x <- bind [1]
y <- bind [2 3]
z <- bind [4 5 6]
unit x+y+z #=> [7 8 9 8 9 10]

pipinglet {unit, lift} = List

unit 1 |> lift add 2 |> lift add 3 |> lift add1 #=> [7]


Reply to this email directly or view it on GitHubhttps://github.com//issues/426#issuecomment-29270436
.

@michaelficarra
Copy link
Contributor

@alexkalderimis: Unfortunately, <= is already an operator in LiveScript.

@vendethiel
Copy link
Contributor

Why unfortunately ? We need our lower-or-equal than :p. We could see =< or something else, tho, depending on how the monadic operators exist in other languages (<<=, >>=, ?). (I think ATM they'd be a bloat, especially considering the current little-use of monads)

@alexkalderimis
Copy link

/facepalm/ of course it is.

I agree that Monads are a bit of sledge-hammer to crack a nut for lists
(the most common Monad), but for promises (which I use heavily) they would
really clean things up. I'm just a sucker for clean syntax I guess.

On 26 November 2013 14:06, Nami-Doc notifications@github.com wrote:

Why unfortunately ? We need our lower-or-equal than :p. We could see =<or something else, tho, depending on how the monadic operators exist in
other languages. (I think they'd be a bloat, especially considering the
current little-use of monads, but the topic remains open)


Reply to this email directly or view it on GitHubhttps://github.com//issues/426#issuecomment-29294383
.

@robotlolita
Copy link
Contributor

@alexkalderimis do you even lift? :P

@askucher
Copy link
Author

Monads will be used for defining an context.

For instance we can define a chain of functions

const func = getUrl >>=
                   getContentByUrl >>=
                   sendContentToServer >>=
                   getResponseFromServer >>=
                   showResult

func \google.com.ua      //Server has processed the content
successfully

Each of these functions returns future result or immediate. Actually it is
business logic. And I shouldn't think about it here. I need to have
possibility to hide other stuff which makes my code work.

I cannot do it using piping. It just reverted composition.

I have solved my task by using simple array of functions

const actions =
    * getUrl
    * getContentByUrl
    * sendContentToServer
    * getResponseFromServer
    * showResult


const go = (arr, cur-val)--> ....//each function returns Promise and I add
observer of result here

go actions, null

But looks like it is common issue. And we should have special tool for that

@elclanrs
Copy link

After experimenting much with LiveScript and monads I came up with another maybe better solution, where the monadic value is this. Here's a gist with a few monads if anybody is interested. And here's the maybe monad using this pattern:

fluent = (f) -> ->
  f ...
  @

Maybe =
  unit: fluent (@x) ->
  '>=': (f) ->
    | @isNothing => @nothing!
    | @isJust => f.call @, @x
  nothing: fluent (@x=null) ->
    @isNothing = true
    @toString = -> "Nothing"
  just: fluent (@x) ->
    @isJust = true
    @toString = -> "Just: #{@x}"
  valueOf: -> @x

add = (x, y) -->
  | x? and y? => Maybe.just x+y
  |_ => Maybe.nothing!

# Just
result = do
  x <- add 1,2 .\>=
  y <- add x,2 .\>=
  @unit x+y

console.log result.toString! #=> Just: 8

# Nothing
result = do
  x <- add 1,2 .\>=
  y <- add x,null .\>=
  @unit x + y

console.log result.toString! #=> Nothing

@vendethiel
Copy link
Contributor

We're really limited by the fact we have do and <- already (at least for LS1). We could see for >>= and maybe <<=, though.

Thoughts ? Ops ?

@robotlolita
Copy link
Contributor

Using an example from my parser combinator library:

between = (open, close, p) --> (state) ->
  do-monad Either
    [s1, _] <<= open state
    [s2, a] <<= (backtrack state) p s1
    [s3, _] <<= (backtrack state) close s2
    return [s3, a]

Would compile to:

between = (open, close, p) --> (state) ->
  open(state).chain([s1, _]) ->
    backtrack(state)(p s1).chain([s2, a]) ->
      backtrack(state)(p s2).chain([s3, _]) ->
        Either.of [s3, a]

Basically, all of the <<= operations are translated to .chain(...) method calls, and a return ... expression inside the do-monad construct returns Monad.of(value), where Monad is the monad defined for the do-monad construct.

Similarly,

Maybe 1 >>= (a) -> Maybe (a + 1)

Would translate to:

Maybe(1).chain (a) -> Maybe (a + 1)

Though I don't see as much use for >>= as there is for <<= inside a do-monad block.

Edit: The operations are defined in Fantasy Land, there was a Sweet.js macro for this by @puffnfresh (?), though I can't remember the link.

@puffnfresh
Copy link

@robotlolita monadic bind macro, right here: https://github.com/puffnfresh/sweet-fantasies

@elclanrs
Copy link

@puffnfresh that macro looks great. Will try it out.

@puffnfresh
Copy link

@elclanrs awesome, we have a few bugs which need to be sorted out: fantasyland/sweet-fantasies#5

@maxov
Copy link

maxov commented Dec 25, 2013

Isn't the current syntax enough? I see absolutely no reason for new language features when we can work around the syntax a little bit. I'm going to implement haskell IO as an example, sorry in advance for the long post ahead:

What this function('liveify') does is take a normal, Haskell-ish monad constructor function, and convert it so that it works in the livescript do <- chain syntax. It returns a function that takes the function's normal args along with a callback(possibly), and apply the callback to the function's result(or bind it, in this case).

liveify = (fn) ->
  | fn.length == 0 => # in the case of something like readLn
    (callback = ->) -> fn!bind callback
  | otherwise =>
    (...args) -> 
      | args.length == fn.length => fn ... # in the case of no callback
      | otherwise => let [...init, last] = args 
        # the magic of monads is preserved: the value is passed along with 'bind'
        (fn ...init).bind last

We can write our monad simply:

class IO
  (@val) ~>
  # we 'liveify' unit so that it can be used nicely in the do <- notaiton
  @unit = liveify @@
  bind: (callback) -> callback @val

We must 'liveify' the monadic functions so they work with our syntax, otherwise they are super simple

putStrLn = liveify (msg) -> IO.unit alert msg
readLn = liveify (msg) -> IO.unit prompt msg

You can essentially drop this code right into Haskell. Feel like haskell yet?

main = do
  x <- readLn 'enter a number!'
  putStrLn "you entered: #x"
  y <- readLn 'enter another number!'
  putStrLn "you entered: #y"
  putStrLn "I added them up for you: #{parseInt(x) + parseInt(y)}"

Note that lines without a 'variable <- expression' statement don't continue the function nesting, and so aren't strictly proper, this can become a problem with promises and such. If needed, this can be fixed by prepending an empty '<-' to those lines:

main = do
  x <- readLn 'enter a number!'
  <- putStrLn "you entered: #x"
  ...

This isn't, strictly speaking, how Haskell would interpret this. It would be closer to make main a function, but that is really simple, plus, it returns the value! How convenient. Also, it hints that main is a monadic function with the extra arrow(the 'do' is not really needed anymore).

main = do ->
  x <- readLn 'enter a number!'
  putStrLn "you entered: #x"
  y <- readLn 'enter another number!'
  putStrLn "you entered: #y"
  putStrLn "I added them up for you: #{parseInt(x) + parseInt(y)}"

tl;dr Given 'liveify', it is trivial to implement monads and make implementations such as Maybe, Promises, etc. 'Liveified' functions even work outside of the do statement, just like normal ones!
I've made this comment into a gist: io.ls

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

No branches or pull requests

8 participants