truizlop on utilities
truizlop on master
Miscellaneous utilities (#459) … (compare)
truizlop on utilities
Add docs (compare)
truizlop on utilities
Fix wrong documentation Add instances of Semigroup and … Add utility to Kleisli and 2 more (compare)
miguelangel-dev on calvellido-patch-1
miguelangel-dev on master
Fix bad space char at docs (#45… (compare)
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.
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 }
}
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.
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.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
.DispatchQueue.main.async
and then launching the unsafeRunAsync
also in the main queue, and causing a deadlock.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 ?
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
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
})
}
}
.unsafeRunAsync
as the finality of my IO, where I collect my data kind of like the .sink
in 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 .send
method) as part of my whole IO computation (the solution you gave earlier)
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>
}
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.
binding()
is syntactic sugar for a bunch of flatMap()
s and the resulting IO
needs to be executed using one of the unsafeRun
functions.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() }
.!effect { }
. That's why I was under the impression the same thing could be achieved in Bow using less code.
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.
hey channel,
I recently started looking at Bow and it looks absolutely awesome. Kudos to the maintainers for putting it together. I have a question about pattern matching data types in Bow. I'd like to apologize in advance for it, as the question is very basic.
let's say I have a simple Option<String>
. How do I pattern match it? The following code:
let myObject: Option<String> = .some("42")
switch myObject {
case let .some(c):
// ...
}
produces the following compilation error Pattern variable binding cannot appear in an expression
Is there a way to use pattern matching with Options and other data types in Bow?
Hi Kyrill! Thanks for your nice words, it helps us be motivated to continue moving this project forward!
Regarding your question, it is not possible to use pattern matching on the types provided in Bow. The reason is the emulation of Higher Kinded Types we do; we need our types to be classes in order to be able to use them as HKTs. Only enums can be used in pattern matching. Nevertheless, all types provide a method fold
, where you can pass closures to handle each of the cases:
let myObject: Option<String> = .some("42")
myObject.fold(
{ /* Handle none case */ },
{ c in /* Handle some case */ }
)
This is possible with any other data type in Bow, like Either
:
let myObject: Either<Int, String> = .right("42")
myObject.fold(
{ left in /* Handle left case */ },
{ right in /* Handle right case */ }
)
You can read more about HKTs and how they are emulated in Bow here, and feel free to ask any questions you may have!
If you decide to use our recently released library Bow Lite, we got rid of the emulation of HKTs. With this, we lost a lot of abstraction power, but one of the things we got back is pattern matching. Therefore, if you use Bow Lite, you can still use fold
on all types, but you can also pattern match:
import BowLite
let myObject: Either<Int, String> = .right("42")
switch myObject {
case let .left(l): /* Handle left case */
case let .right(r): /* Handle right case */
}
I hope this clarifies your question!
Hi @dsabanin! Thanks for your kind words! We made Bow Lite in order to ease the learning process for newcomers to FP. It takes a while until you fully grasp HKTs, and since they are not native to the language (yet), we have observed many people have issues understanding and using the library. It is also a big commitment to add HKT emulation into your project, especially if you are working with others and they are not very familiar with FP.
Having said that, of course, Bow is much more versatile thanks to HKTs. You can see how much we could save being able to use more powerful abstractions by checking the implementations we had to make in Bow Lite, where there is plenty of duplication that cannot be abstracted out. We were even able to implement Bow Arch on top of the HKT emulation, which lets us replace a few types and get a whole new architecture with different properties. My recommendation is that, if you and your team are familiar with FP and HKTs, go ahead with Bow, and only choose Bow Lite if you want a lightweight library to practice FP without the heavy features.
Regarding benchmarking, I unfortunately haven't been able to do anything about it, but it is definitely something interesting to measure. I'd be interested in measuring which parts of the library are less performant and improving them. Hacktoberfest is coming, so if you are interested in doing this type of project, you are really welcome!