by

Where communities thrive


  • Join over 1.5M+ people
  • Join over 100K+ communities
  • Free without limits
  • Create your own community
People
Repo info
Activity
  • Oct 09 2019 10:41
    iboss-ptk synchronize #460
  • Oct 09 2019 10:21
    iboss-ptk edited #460
  • Oct 09 2019 10:21
    iboss-ptk edited #460
  • Oct 09 2019 10:20
    iboss-ptk opened #460
  • Oct 09 2019 09:58

    truizlop on utilities

    (compare)

  • Oct 09 2019 09:58

    truizlop on master

    Miscellaneous utilities (#459) … (compare)

  • Oct 09 2019 09:58
    truizlop closed #459
  • Oct 09 2019 07:53
    truizlop review_requested #459
  • Oct 09 2019 07:53
    truizlop review_requested #459
  • Oct 09 2019 07:53
    truizlop opened #459
  • Oct 09 2019 07:53

    truizlop on utilities

    Add docs (compare)

  • Oct 09 2019 07:52

    truizlop on utilities

    Fix wrong documentation Add instances of Semigroup and … Add utility to Kleisli and 2 more (compare)

  • Oct 09 2019 07:03
    truizlop commented #448
  • Oct 09 2019 01:34
    iboss-ptk commented #448
  • Oct 09 2019 01:28
    iboss-ptk commented #448
  • Oct 08 2019 10:42

    miguelangel-dev on calvellido-patch-1

    (compare)

  • Oct 08 2019 10:42

    miguelangel-dev on master

    Fix bad space char at docs (#45… (compare)

  • Oct 08 2019 10:42
    miguelangel-dev closed #458
  • Oct 08 2019 10:42
    miguelangel-dev commented #458
  • Oct 08 2019 10:39
    calvellido edited #458
Tomás Ruiz-López
@truizlop
traverse will get you a single IO describing the effects of your array of IO, and collecting all results in an array. You can also use parTraverse if you'd like the execution of the effects to be run in parallel.
Miguel Ángel Díaz
@miguelangel-dev
Agree! On another hand, just for readability you can use the data type UIO<A>, it is a typealias to IO<Never, A>
Tomás Ruiz-López
@truizlop
With that, you don't need to wrap the body of send in a DispatchQueue.main.async, as the continueOn(.main) will do that for you.
so your send could be something like:
public func send(_ action: Action) {
        let effects = self.reducer(&self.value, action)
        let single: UIO<[Void]> = effects.traverse { effect in
            let action = UIO<Action>.var()
            return binding(
                continueOn(.global(qos: .userInitiated)),
                action <- effect,
                continueOn(.main),
                |<-UIO.invoke { self.send(action.get) },
                yield: ())
            }^
        single.unsafeRunAsync(on: .global(qos: .userInitiated)) { _ in }
    }
Sébastien Drode
@Dragna

oh great, I will try that, thank you for your help 🙂.
an other question on the API, wouldn't it be great to have this possibility :

effect.execute(on: .global(qos: .background)) // execute the IO on the background thread
            .unsafeRunAsync(on: .main,  // run the callback execution on the main thread
                { resultAction in
                    print(resultAction)
                    self.send(resultAction.rightValue) //Should never error out
                })

I find it hard to resort to Monad comprehension for a simple case like this. And the empty unsafeRunAsync is bothering me :). But there is maybe something like that already and I may have missed it.

Sébastien Drode
@Dragna
and so that I better understand what I did wrong, is the fact that I wasn't running the unsafeRunAsync closure on a background queue ?
Tomás Ruiz-López
@truizlop
Let me go one by one on your questions:
  • Once you call unsafeRunSync or unsafeRunAsync, you shouldn't make any other operation. We could add to the API a default empty closure, so that you don't have to pass it yourself.
  • You don't need to resort to monad comprehensions to switch the queue. IO also has a method continueOn that you can invoke to switch the queue of whatever comes next. In any case, you can still do what you are suggesting by calling attempt (your execute in the example above) and then the unsafeRun.
  • The problem was that you were wrapping everything on a DispatchQueue.main.async and then launching the unsafeRunAsync also in the main queue, and causing a deadlock.
Sébastien Drode
@Dragna

I added the DispatchQueue.main.async just before pushing the code, because it was working as I wanted to with it. I didn't have that when I was asking initially.
Thank you for your help on understanding that, it is really nice of you. So don't hesitate to tell me if it's bothering you, I don't want to take too much of your time. :)

so by doing something like that :

    public func send(_ action: Action) {
            print("send method called on \(DispatchQueue.currentLabel)")
            let effects = self.reducer(&self.value, action)
            print(effects)
            effects.forEach { effect in
                effect.attempt(on: .global(qos: .userInitiated))
                    .unsafeRunAsync(on: .main,
                                    { resultAction in
                                        print("Resulting action : \(resultAction) on \(DispatchQueue.currentLabel)")
                                        self.send(resultAction.rightValue) //Should never error out
                                    })
            }
    }

(I don't have the Traverse, but I want to understand with my initial code 🙂 )
I should have the same result as with your propose solution ? But it's not the case, I still have the deadlock . So what would be the right way to do it this way ?

Tomás Ruiz-López
@truizlop
Don't worry, we are here to help!
The issue there is that attempt is synchronous and you are invoking it from the main queue, which is later needed to run the async closure, and causes a deadlock
Sébastien Drode
@Dragna
oh yes ! you told me yesterday that attempt was synchronous
Sébastien Drode
@Dragna

Ok I think I put the finger on what is bothering me, the unsafeRunAsync run the whole computation of the IO, not just the ending callback on the thread that we pass, meaning that it will wait (because it wrap a unsafeRunSync call) for my IO computation to be executed before executing the callback on the given thread

Am I right with that ?

I assumed the IO would be executed asynchronously on any previously given Queue, then the callback would be called when the computation finished and this same callback would be called on the queue given through the .unsafeRunAsync method

Sébastien Drode
@Dragna
I think I don't make sense ^^ what I was assuming is that it worked like that :
extension IO {
    public func attemptAsync(on queue: DispatchQueue = .main) -> IO<E, A> {
        queue.shift().followedBy(self)^
    }

    public func myUnsafeRunAsync(on queue: DispatchQueue = .main,  _ callback: @escaping Callback<E, A>) {
        self.unsafeRunAsync(on: .global(qos: .background)) { (result) in   // I don't know on which queue it should run by default, it's only to show what I was assuming
            queue.async {
                callback(result)
            }
        }
    }
}

// using this extension I can now define the send method like that : 

    public func send(_ action: Action) {
            print("send method called on \(DispatchQueue.currentLabel)")
            let effects = self.reducer(&self.value, action)
            print(effects)
            effects.forEach { effect in
                effect
                    .attemptAsync(on: .global(qos: .userInitiated))
                    .myUnsafeRunAsync({ resultAction in
                                        print("Resulting action : \(resultAction) on \(DispatchQueue.currentLabel)")
                                        self.send(resultAction.rightValue) //Should never error out
                                    })

            }
    }
Sébastien Drode
@Dragna
I saw the .unsafeRunAsync as the finality of my IO, where I collect my data kind of like the .sinkin combine. but maybe it's not the right way of seeing things and I should see the final computation (getting the action back and feeding it to the .sendmethod) as part of my whole IO computation (the solution you gave earlier)
Georg Dresler
@ge-org

Hi,

I have a question regarding monad comprehension. Is it possible to run an IO using binding without waiting for the result of the execution of the IO?
My use case is, I want to trigger loading data from some source (API, DB,..). Once the data is loaded it will be made available using a Publisher. So I want to have a program that starts loading the data and then immediately returns without waiting for the loading to complete.

import Foundation
import Bow
import BowEffects
import Combine

func myProgram() -> IO<Never, ViewModel> {
    let publisher = CurrentValueSubject<Data, Never>(Data())
    return binding(
        // is it possible to run this IO without waiting for it to return?
        |<-loadData(publisher: publisher),
        yield: ViewModel(data: publisher.eraseToAnyPublisher())
    )^
}

func loadData(publisher: CurrentValueSubject<Data, Never>) -> UIO<Void> {
    UIO.invoke {
        // asume this takes a couple of seconds
        let data = Api.getMyData()
        publisher.send(data)
    }
}

// stubs
struct Data {}
struct Api {
    static func getMyData() -> Data {
        Data()
    }
}
struct ViewModel {
    let data: AnyPublisher<Data, Never>
}
Tomás Ruiz-López
@truizlop
Hi Georg. Let me clarify this a little bit. When you do a binding, IO is not run; it is just a way of composing values, like you'd do using flatMap. IO will be run when you invoke any of the unsafeRun methods, so depending on what you choose, that operation will be synchronous or asynchronous. That is, if you invoke myProgram().unsafeRunAsync(on: queue) { ... } and pass the appropriate queue, it will return immediately while doing the operation on the queue that you passed. Be aware that, if you don't send a queue, the main one will be used, and it could lead to a deadlock.
Georg Dresler
@ge-org
Thanks for the explanation @truizlop 🙏
I understand that binding() is syntactic sugar for a bunch of flatMap()s and the resulting IO needs to be executed using one of the unsafeRun functions.
So I guess, if I want to 'fire and forget' some side effect I just need to call unsafeRunAsync() on it. And if I want to do it within a binding I need to wrap it in an IO. E.g. UIO.invoke { loadData.unsafeRunAsync() }.
In Arrow there is syntactic sugar for this: !effect { }. That's why I was under the impression the same thing could be achieved in Bow using less code.
Robert Collins
@robbiemu
Robert Collins
@robbiemu
nevermind, I found the problem :) the project must be a playground project generated by nef
Devesh Shetty
@devesh-shetty
Hi team,
Congrats on the launch of Bow Arch 0.1.0! Is there any sample using Bow Arch?
Tomás Ruiz-López
@truizlop
Thanks Devesh! The docs include some samples, and here you can find a simple Tic Tac Toe https://github.com/truizlop/TicTacToeArch
We will be publishing more complete examples shortly, so much looking forward to showing what we’ve done!
Devesh Shetty
@devesh-shetty
Thanks @truizlop
Tomás Ruiz-López
@truizlop
If you give it a try and need help, just let us know!
Devesh Shetty
@devesh-shetty
Awesome, thanks!
Georg Dresler
@ge-org
Bow Arch looks really great. I've been playing around with it a bit and have a question
Is it possible to access the current state in the dispatcher? So I could perform an action depending on the current state. Or would I need to pass all required info using the input?
Tomás Ruiz-López
@truizlop
Hi Georg! Thanks for your kind words. When you are returning a State, you get access to the “current” state, but at that point you won’t be able to perform side effects. If you need to perform a side effect based on something from the current state, then yes, you need to pass that info as part of the data attached to an input.
Georg Dresler
@ge-org
Thanks for your answer Tomás
Tomás Ruiz-López
@truizlop
You’re welcome! Let me know if you have any additional questions, and keep us updated on your progress! I’m looking forward to seeing your proyect.
Georg Dresler
@ge-org
Back with another question 🙂 Is there a way to 'inject' an input into a dispatcher 'from the outside'? What I'm thinking about is having an observer (database, geo location,...) that needs to update the state of a component when new data becomes available.
Tomás Ruiz-López
@truizlop
This is an interesting question. Bow still does not have anything like streams, which will model something like this in a very nice way. We have bindings with Rx and it won't be hard to make an adaptation to make Combine work with Bow to do something like this. Just using what we have right now, you would have to start observing when the view appears, and use the handle: (Input) -> Void function to notify actions to Bow Arch whenever an event is triggered in the observer. If you have some sample code we can discuss around a concrete example.