4 ms·
I completely agree. I used to be a big believer in monad based concurrency using Promise/Future/Async/etc. But the simplicity of using preemptive, user-space sc
by dgllghr 9y ago
I completely agree. I used to be a big believer in monad based concurrency using Promise/Future/Async/etc. But the simplicity of using preemptive, user-space scheduling has changed my mind. It makes concurrency, especially IO concurrency, cleaner. There are fewer moving parts in your code because the VM is doing a lot of the heavy lifting for you.
- randomstudent 9y agoWhat do Promises/Futures/Async/etc. have to do with monads? Are monads a good abstraction to model those things?
- dgllghr 9y agoMany of the actual implementations of these types are not actually monads by the monad laws, but they are monad-like in that they are containers for execution that can be composed with each other.
- arianvanp 9y agoContinuations / Promises / Futures form a Monad (https://www.stackage.org/haddock/lts-9.1/transformers-0.5.2.0/Control-Monad-Trans-Cont.html#t:ContT https://www.stackage.org/haddock/lts-9.1/transformers-0.5.2....) A Monad is any data structure with the follwing two methods: (>>=) :: m a -> ( a -> m b) -> m b pure :: a -> m a the monadic bind is exactly `andThen` in javascript: (>>=) :: m a -> (a -> m b) -> m b (>>=) :: Future a -> (a -> Future b) -> Future b Future.andThen :: Future a -> (a -> Future b) -> Future b and the monadic `pure` is exactly the `Future` constructor: pure :: a -> m a pure :: a -> Future a Future.always :: a -> Future a Every Monad is a Functor, so you also get this one for free: map :: (a -> b ) -> m a -> m b -- if I know how to convert a's to b's I can convert a future a to a future b Future.map :: (a -> b) -> Future a -> Future b So basically, saying "futures are a monad" gives you all the useful functions that you would expect from a future based API, which is nice. It means you can use all kinds of higher level functions not specific to futures to abstract behaviour: Like, given a list of element,s and a way to create a future for each element, create a future that returns a list of elements: forM :: (Monad m) => Array a -> (a -> m b) -> m (Array b Future.forEvery :: Array a -> (a -> Future b) -> Future (Array b)
- qualitytime 9y agoNoted down, thanks for that. Will be handy for future whiteboard interview questions.
- devjungle 9y agoHow do you begin to even read that notation stuff? Do you know of any resources for learning it?
- gamache 9y agoThat's the Haskell language. There are a lot of "big" concepts in the language itself, compared to just about any other language from the last 50 years that people get paid to write, but the notation isn't hard to straighten out after a few tries. There's a really good intro here: http://learnyouahaskell.com/ http://learnyouahaskell.com/
- jordwest 9y agoThe notation in the GP is Haskell-ish type notation, which is certainly different to the majority of languages out there today, but it's actually fairly simple (difficult to learn, but simple). Quick glossary: :: Defines a new function signature. The left hand side is the function name, and the right hand side is the signature. a -> b A function which converts something of type a to type b m a A generic m of type a. In Java/C# this might be written as m<a>. eg, `Array String` means an array of string elements, and might be written in C# as Array<String>. Types starting with a lowercase letter (m, a, b) generally mean the type is generic - as in the function will take any type. It might be helpful to define a function you already know: string_to_int :: String -> Int Or, for changing an Array of something to an Array of something else: map :: (a -> b) -> Array a -> Array b Here, we're defining a function that: 1. Takes a function that converts an 'a' to a 'b' (a -> b) 2. Takes an array of 'a' 3. Returns an array of 'b' Notice that 'a' and 'b' can be anything! Since the first parameter* is a function that handles the conversion from a to b, the map function actually doesn't need to know what type a and b are. Let's say we pass in the `string_to_int` as the first parameter, now the type checker will actually infer the following function type: map :: (String -> Int) -> Array String -> Array Int * Haskell functions actually only ever have one parameter, it uses Currying to accept multiple arguments: https://en.wikipedia.org/wiki/Currying https://en.wikipedia.org/wiki/Currying
- dmix 9y agoThey are just a way to wrap up the code/data somewhat similar to how callbacks are used in Javascript async code. And from using Haskell I believe they are an excellent abstraction when doing any async programming, once you wrap your head around them. Although in terms of concurrency I've found Elixir/Erlang actor-esque process based system with messaging much more intuitive when building complex programs and much simpler to reason about than the various solutions Haskell offered. But I'm also not an expert at Haskell so take that comparison with a grain of salt.
- dboreham 9y agoUm...you know this is basically threads right? (not disagreeing, but having watched the past few years as "threads bad, don't know why though..." it is amusing to see thread-style concurrency rediscovered with a new name).
- brightball 9y agoThis quote from Joe Armstrong explains the difference better than I can. There are other factors than just this in play of course. > [Erlang] is a concurrent language – by that I mean that threads are part of the programming language, they do not belong to the operating system. That's really what's wrong with programming languages like Java and C++. It's threads aren't in the programming language, threads are something in the operating system – and they inherit all the problems that they have in the operating system. One of the problems is granularity of the memory management system. The memory management in the operating system protects whole pages of memory, so the smallest size that a thread can be is the smallest size of a page. That's actually too big. > If you add more memory to your machine – you have the same number of bits that protects the memory so the granularity of the page tables goes up – you end up using say 64kB for a process you know running in a few hundred bytes.
- tim333 9y agoWhile it can be a little unclear what people mean, the Wikipedia take on threads is: "Multiple threads can exist within one process, executing concurrently and sharing resources such as memory" Whereas Erlang/Elixir processes don't share memory and have their own independent heaps and garbage collection. Shared memory can lead to complicated effects if two threads try to change the same bit. You don't have that with Erlang processes.
- anko 9y agoThat is like saying queues are basically the same as lists! size, scheduling and the lack of shared state add up to vast differences. Also the message parsing primitives are way more lightweight than the thread based equivalents.
- dgllghr 9y agoGreat point! However, I've found that the implementations of user space, preemptive threads (UPT) have friendlier APIs than the implementations of kernel space, preemptive threads (KPT). Also, UPT APIs generally come with really nice synchronization primitives (like STM in Haskell) or designs that remove the need for them (like message passing on BEAM). Also, I really like not worrying about creating too many threads and having to use a thread pool because the overhead of UPT is generally so much lower.