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
Hi Sébastien, you can use mapLeft (which will be renamed to mapError in the next release) in order to change the error to Never.
Since it may not be "beautiful", you can create an extension method over IO that does handleError followed by mapError in order to have Never as the error type
Sébastien Drode
@Dragna
Thank you for your answer. that is what I tried, but do you know a way to return a Never ? 😛 because I found abort() but I don't know how it will react.
func getPeopleAPI(id: Int) -> IO<Never, SWAPIPeopleResponse?> {
    var components = URLComponents(string: "https://swapi.co/api/people/\(id)")!
    URLSession.shared.dataTaskPublisher(for: components.url(relativeTo: nil)!).replaceError(with: <#T##Output##Foundation.URLSession.DataTaskPublisher.Output#>)

    return  URLSession.shared
            .dataTaskIO(with: components.url(relativeTo: nil)!)
            .map { _, data in data }
            .map { data in
                try? JSONDecoder().decode(SWAPIPeopleResponse?.self, from: data)
            }
            .handleError({ _ in nil })^
            .mapLeft({ _ in abort() })
}
Miguel Ángel Díaz
@miguelangel-dev
Try with fatalError()
Usually is the way Apple uses to force a wrong path
but, it will break your app, but you are not using an EXIT (as abort is)
Tomás Ruiz-López
@truizlop
yes, we use fatalError() usually, although it is not recommended to be used in production code, that code should never be executed, it's just to make the compiler happy
(I see you are playing with the Star Wars API, cool!!!)
Miguel Ángel Díaz
@miguelangel-dev

on another hand, for me it is weird model your problem like an right-optional (maybe I am, and you want to wrap this network over IO)

you are working with: ERROR + (ERROR + VALUE) = ERROR + ERROR + VALUE, so you can go out with 2 possible errors (one from IO another for Optional - losing the type) - do you think about to change the API to: IO<ApiError, SWAPIPeopleResponse>

Sébastien Drode
@Dragna

@miguelangel-dev you are right, I shouldn't model it like that, for the network call, I should pass the error to the caller (that is what I will do at a later time) but I still need a way (that you 've given me) to go back to a Left Never, because in my redux implementation, I don't want the core implementation to know about errors, errors should be handled early (in the reducer part) to model it as a state published by the store.

I'll show you when I'm done with the whole implementation 🙂

I will change for a fatalError as suggested,

Thanks to you two for your help

Tomás Ruiz-López
@truizlop
you're welcome! keep us updated with your progress
Sébastien Drode
@Dragna
yes, will do
Miguel Ángel Díaz
@miguelangel-dev
:)
Marcelo Jasek
@marcjal
Hi 👋
Where I can find a project using this library?
Miguel Ángel Díaz
@miguelangel-dev

Hi Marcelo, it depends on kind the project you are looking for. For now, as OSS, we are working in a refactor in another library from bash to FP in pure Swift. It is nef: https://github.com/bow-swift/nef - and you can find the project using an architecture FP thanks to Bow: https://github.com/bow-swift/nef/tree/develop/project

It is nice because in the same project you can find an example how to make a functional-library (API) and how to use it (CLI) in pure Swift with Bow

if you open the nef tree-project you can find 4 modules: UI, Component, Core and Tests.
UI: examples Bow in CLI
Component: example of FP-architecture with Bow using Bow, BowEffects, BowOptics
Core: business logic and how to wrap into a FP architecture
Tomás Ruiz-López
@truizlop
Also, our project Bow OpenAPI is built using Bow and Bow Effects: https://github.com/bow-swift/bow-openapi
We are working towards providing more examples for macOS and iOS apps using FP and Bow. If you get something, let us know and we can link it as a reference in our documentation
Devesh Shetty
@devesh-shetty
Hi team,
Could I work on this? bow-swift/bow#286
Tomás Ruiz-López
@truizlop
Sure! I can assign it to you.
Devesh Shetty
@devesh-shetty
Awesome, thanks!
Sébastien Drode
@Dragna

hi everyone !

what would be the right way to execute an IO on background queue and get back to main queue to use the result ?

I tried

effect.attempt(on: .global(qos: .background)).unsafeRunAsync(on: .main,
                    { resultAction in
                        print(resultAction)
                        self.send(resultAction.rightValue) //Should never error out
                    })

but it seems to block the UIThread

Sébastien Drode
@Dragna
I tried with comprehensions too, but I think I'm missing something 🤔
            let action = IO<Never, Action>.var()
            let sendEffect = binding(
                continueOn(.global(qos: .userInitiated)),
                action <- effect,
                |<-ConsoleIO.print("action is \(action.get)"),
                continueOn(.main),
                |<-IO.invoke { self.send(action.get) },
                yield: ()
            )^
            try! sendEffect.unsafeRunSync()
Tomás Ruiz-López
@truizlop
The first code blocks because attempt is synchronous. Even though you are passing a different queue to run it, it will block the current queue until it finishes, and then do the unsafeRunAsync. The second one should work fine if you call sendEffect.unsafeRunAsync. It is the same reason: it blocks because it is synchronous.
Sébastien Drode
@Dragna
hmm even with unsafeRunAsync it's acting this way :/
public func send(_ action: Action) {
        let effects = self.reducer(&self.value, action)
        print(effects)
        effects.forEach { effect in
            let action = IO<Never, Action>.var()
            let sendEffect = binding(
                continueOn(.global(qos: .userInitiated)),
                action <- effect,
                |<-ConsoleIO.print("action is \(action.get)"),
                continueOn(.main),
                |<-IO.invoke { self.send(action.get) },
                yield: ()
            )^
            sendEffect.unsafeRunAsync({ _ in

            })
        }
    }
the send method is the method I use to send action to a redux Store, I want to execute the effects gotten from the reducer and execute the on background, get their result back and pass them to the .send method again
the problem is maybe elsewhere, I'll look
Sébastien Drode
@Dragna
if I use simply unsafeRunAsync(on:, callback: ) with a background queue, it works fine, but I get an alert from swiftui telling that I'm publishing changes from background ^^ which is true in this case
effect.unsafeRunAsync(on: .global(qos: .background),
            { resultAction in
                print(resultAction)
                self.send(resultAction.rightValue) //Should never error out
            })
Tomás Ruiz-López
@truizlop
If you can get a minimal project so that we can reproduce it, it'd be great
Sébastien Drode
@Dragna
yes I'll do that
Sébastien Drode
@Dragna
I gave something that works here with a DispatchQueue.main.async
but when I'm using .binding, the UI is blocked when I make the network call even though the log say that it's running on the right queue
(the network call is on the second tab)
Tomás Ruiz-López
@truizlop
Let me find some time to look at it, I'll come back to you
Sébastien Drode
@Dragna
No problème, thank you for your help, I’ll keep looking at it to find the problem
Tomás Ruiz-López
@truizlop
@Dragna I think what you may be looking for is something like this:
let single: IO<Never, [Void]> = effects.traverse { effect in
                let action = IO<Never, Action>.var()
                return binding(
                    continueOn(.global(qos: .userInitiated)),
                    action <- effect,
                    continueOn(.main),
                    |<-IO.invoke { self.send(action.get) },
                    yield: ())
            }^
            single.unsafeRunAsync(on: .global(qos: .userInitiated)) { _ in }
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