ethul / purescript-angular

AngularJS 1.2 bindings for PureScript (currently in the experimental stage)
MIT License
23 stars 3 forks source link

Running effects in Promise's bind #12

Closed wcbeard closed 10 years ago

wcbeard commented 10 years ago

I'm trying to run a function with an effect while calling >>= (well, actually then') on an http promise, and while I can get my program to typecheck, it doesn't seem to be running as I expect.

I wrap the result of my "effectful" (or whatever it's called) function doStuff in a promise and call it f:

doStuff :: forall b . Eff (trace :: Trace | b) Unit
doStuff = do
  trace "Hello world 2."
  trace "Hello world 3."

f :: forall a b c. a -> Promise b (Eff (trace :: Trace | c) Unit)
f _ = return doStuff

Then when I call it with then' on the http response promise, I don't see anything printed to console:

controller scope http = do
  resProm <- get "test" http
  let res = then' f resProm
  return Unit

When I cheat by writing it in JS (and lying about the type...), it works fine:

foreign import thenArg
  "function thenArg(anything) {\
  \  console.log('Hello world.');  \
  \ };"
  :: forall a b c. a -> Promise b c

controller scope http = do
  resProm <- get "test" http
  let res = then' thenArg resProm
  return Unit

Is this a bug in the Promise interface, or a bug in my thinking? Or is it intended that I write effectful Promise solutions in JS?

I'd ultimately like to try to get a working '$http request + bind result to scope' program working, and perhaps include it in /examples here if you think it'd be helpful.

ethul commented 10 years ago

I believe what is happening is that since there is an Eff inside of the promise, the result ends up being a function that would need to be run in order to print out the trace messages to the console.

I don't know the best approach yet, but one way to make it work could be to introduce a function

foreign import unsafeRunEff
  " function unsafeRunEff(f) {\
  \   return f();\
  \ } " :: forall e a. Eff e a -> a

doStuff :: forall b . Eff (trace :: Trace | b) Unit
doStuff = do
  trace "Hello world 2."
  trace "Hello world 3."

fff :: forall a b c. a -> Promise b (Eff (trace :: Trace | c) Unit)
fff _ = return doStuff

controller http = do
  promise <- get "http://localhost:9501/examples/Todomvc/main.html" http
  return $ do fa <- then' fff promise
              return $ unsafeRunEff fa

I will have to think about it some more though.

If we can come up with a good way to do this, having an example would be great! Thanks!

ethul commented 10 years ago

Another option is that we could include a function:

foreign import unsafeRunPromiseEff
  " function unsafeRunPromiseEff(p) {\
  \   return p.then(function(eff){return eff();}); \
  \ } "
  :: forall e a b. Promise a (Eff e b) -> Promise a b

Using it (as in the example above) like

controller http = do
  promise <- H.get "http://localhost:9501/examples/Todomvc/main.html" http
  return $ unsafeRunPromiseEff $ promise >>= fff
wcbeard commented 10 years ago

@ethul I may be running into a situation where your first proposed function could be useful (but I'm still getting my head around how effects work in PS…). If I want to pass a function to the scope which changes the scope, it has an effect return type, which does not run when I call the function in JavaScript.

If I define function

testController' scope a = do
    let x = trace "I can see this if x is returned"
    extendScope {todo: "have dinner"} scope
    let x' = trace "I can't see this, I guess because laziness"
    x

I can attach {testControllerS: testController' scope} in the controller. But when I call testControllerS with ng-click like “testControllerS(’s’)”, I see nothing. If I call it with testControllerS(’s’)(), I see the print statement and I see the scope modified accordingly.

It looks like the JavaScript creates a function that returns another function with the do body, which additionally need an FFI step for the final call. Am I thinking about it correctly, or is there something I'm missing? (it looks like your current modification here only deals with promise effects, not executing general effects, right?)

ethul commented 10 years ago

Right. Since testControllerS has the type Eff, once it is compiled to Javascript, you have to run the function wrapper, which is what you're doing with testControllerS(’s’)(). So I think you have the right idea there.

And the update to promises is specialized to effects within a promise.

wcbeard commented 10 years ago

I'll probably write something like what you proposed above in my file:

foreign import unsafeRunEff
  " function unsafeRunEff(f) {\
  \   return f();\
  \ } " :: forall e a. Eff e a -> a

but do you think it would be a common enough need to include it in the repo, or is it more of an edge case (or should testControllerS(’s’)() type JS calls be preferred in PS)?

ethul commented 10 years ago

I am thinking this might be something best left up to the developer.

For example, some might want to run the effects outside of purescript, like doing testControllerS(’s’)(). While others might want to run them in purescript with an unsafeRunEff function.

In the Todomvc example, I was running effects similar to testControllerS(’s’)().