4 ms·
How does asynchronous futures end up being more complicated or in any way worse than the blocking threaded approach? for { user <- asyncGetUs
by runT1ME 12y ago
How does asynchronous futures end up being more complicated or in any way worse than the blocking threaded approach?
for {
user <- asyncGetUser(1)
company <- asyncGeCompany(user.companyid)
longresult <- asyncProcess(company.getSomething)
} yield longresult
- saryant 12y agoHis issue seems to be the potential for modifying or referencing mutable state where in the yield block (or map or flatMap or whatever). It can be a problem. In Akka actors, referencing sender() from a future will be unpredictable because sender() could've changed in the mean time. I think there are three strong solutions which address this problem: 1) Immutable state. Solves this problem completely but accidental capture remains an issue. 2) Hiding mutable state within actors. I hesitate to present this as a general solution though since it really requires going all-in with actors (I don't consider that to be a bad thing, necessarily). 3) Projects like Scala Spores [1] aim to tackle this at the compiler by capturing mutable references and executing async closures in an immutable environment and prohibiting accidental capturing. IOW, turning accidental capture into a compiler error. I'm excited about this one. [1] https://speakerdeck.com/heathermiller/spores-distributable-functions-in-scala https://speakerdeck.com/heathermiller/spores-distributable-f...
- runT1ME 12y agoYou can (and should) use Futures independently of Akka actors. You're correct, hidden mutable state with Actors can be a problem, as with mutable state in general.
- saryant 12y agoAbsolutely, but people make mistakes. It's also somewhat unavoidable when you're dealing IO, unless you stick to .pipeTo(self)
- pron 12y agoYep, that's very nice code, except: user = getUser(1) company = getCompany(user.companyId) longresult = process(company.getSomething) is a) simpler (because that's what your normal code looks like), b) performs exactly the same (as fibers basically do the same thing, only transparently), and c) retains context (like ThreadLocal variables). So if that was the only way, I'd say, fine. But once you have lightweight threads, they are always preferred (even Scala now has its own poor-man's lightweight threads in the form of async/await).
- runT1ME 12y ago> simpler (because that's what your normal code looks like) Actually, no. My code will always look like the for comprehension be it async or not, because my 'get' methods will always return an Either, Option or some other monadic context to represent the computation succeeding or not. > performs exactly the same (as fibers basically do the same thing, only transparently), Yes, I agree. > c) retains context (like ThreadLocal variables). Threadlocal variables are almost always code smell. If you find yourself using them, there is almost certainly a better way of accomplishing the same thing. I don't see how having a Fork/Join threadpool that 'powers' the futures would be any slower than lightweight threads either.
- pron 12y agoWhen I learned to program (back in the 80s), I learned that if you want to tell the computer to do operation X and when that's done, do operation Y, you just write both statements one after the other. If you're comfortable using for comprehensions to achieve that same goal -- that's great. To me it seems that your code simply replicates what a thread does. If you have a problem with your thread's implementation -- fix it. If you want the compiler's help -- use the compiler to write the for comprehensions, or monads for you. Personally, I don't think you should need monads for "do X then Y", and neither does Scala, by the way. Scala says that you only need them if you want to say "do X then Y, but don't use OS threads".
- miiike 12y ago