3 ms·
Here's a common example. Some systems provide blocking operations: e.g. read_line waits for a line of text and then returns it as a string. Others use promises:
by talex5 10y ago
Here's a common example. Some systems provide blocking operations: e.g. read_line waits for a line of text and then returns it as a string. Others use promises: read_line returns a promise of a string.
What if you want to write a library that works with either blocking calls or asynchronous promises?
The cohttp HTTP library is written that way. For example, the Transfer_io module[1] (supporting both chunking and non-chunking HTTP transfers) takes as an argument another module, IO[2], that provides a read_line function of type "ic -> string option t", where the type t is abstract and higher-kinded (and "ic" = input channel). You can instantiate the module with a blocking IO module (where a "string t" is just the same as a "string" and read_line blocks) or with a non-blocking one (where a "string t" is a promise for a "t" and read_line returns a promise).
[1] https://github.com/mirage/ocaml-cohttp/blob/master/lib/transfer_io.mli https://github.com/mirage/ocaml-cohttp/blob/master/lib/trans...
[2] https://github.com/mirage/ocaml-cohttp/blob/master/lib/s.mli https://github.com/mirage/ocaml-cohttp/blob/master/lib/s.mli
(also useful if your language has multiple competing promise libraries...)
- pierrebai 10y agoYou don't need higher-kinded types for that. Just always return a promise, except the blocking version always return a promise that is already fulfilled. This is just a variation of 'any problem can be solved by adding a level of indirection' + 'any protocol can have a dummy implementation'.
- yummyfajitas 10y agoThe problem is that by doing this, you have made an operation that should be instantaneous - readLineFuture: () => Future[Line] - into something that now has built in lag. I.e. you've pulled the side effects (blocking) from the future to the present.
- IpV8 10y agoThat built in lag is so incredibly miniscule in the grand scheme of programming that 99.9999% of times it will not matter, and if it does you are using C or Assembly anyways.
- yummyfajitas 10y agoNo, readLine will block until a line is available. Consider a command line IM app using pipes to communicate to the server (just to roll with the readLine example). User 1 has not typed anything, so their file is empty. User 2 has typed a bunch of stuff. user1line = readLine "user1file.pipe" user2line = readLine "user2file.pipe" getFirstAvailableFutureAndProcess [user1line, user2line] processorFunc What you want this to do is handle whichever user types something first. In reality it will only process user1line.
- teacup50 10y agoNo, it matters. Inefficiency adds up, and CPU cycles you waste on useless overhead aren't available to spend on something that actually matters -- like Garbage Collection.
- gpderetta 10y agoBoost.ASIO (and the corresponding C++ network TR proposal) uses HKTs to select the wait strategy (i.e. coroutines, futures, plain callbacks or whatever the user prefers) with minimal overhead.
- tunesmith 10y agoThat argument applies to the example of the abstraction, not the abstraction itself.
- colanderman 10y agoIt matters a lot if you are relying on asynchronous semantics. You risk deadlock if the request blocks.
- thinkpad20 10y agoOne can almost always respond to a particular use-case with "well you could just do this instead." But the point is that there is a broader problem which type classes solve, which is to identify similar behavior and encode it in abstraction, such that your code becomes more generic and reusable. It's a powerful mechanism which leads to beautiful, performant and expressive code. You clearly don't need higher kinded types in order to write programs, much like any number of other features one doesn't need, but they can provide a huge boost.
- bweitzman 10y agoIf you instantiate `t` as a promise then you have to always consider that it isn't there yet, even if you're saying that it's going to be. If you instantiate `t` as `Identity` [0] then you have a type level guarantee that your value is there. You have a guarantee that your program blocks when you say it will. And that's the point of a lot of these abstractions. It's not about being able to write something that you couldn't in another language. After all, we could create the same functionality in assembly. [0] https://hackage.haskell.org/package/transformers-0.2.2.1/docs/Data-Functor-Identity.html https://hackage.haskell.org/package/transformers-0.2.2.1/doc...
- AdieuToLogic 10y ago> What if you want to write a library that works with either blocking calls or asynchronous promises? You don't need higher-kinded types for that. Just always return a promise, except the blocking version always return a promise that is already fulfilled. While strictly speaking this approach will work, a benefit of employing higher-kinded types (HTK's) to express a solution is being able to optimize without having to alter the solution. For this example, the Scalaz Id[0] type provides this adaptation. Since it conforms to what is expected of a container, yet does not require the overhead of fabricating one just to satisfy expectations, it affords HKT-based logic to be useful while automatically selecting the optimal implementation. In short, working in an environment which supports HKT's often allows implementations to reduce needless overhead, simplify implementations (by only having to address "happy path" logic), and promotes stability in a code base due to formally expressing the expectations of collaborators. 0 - http://eed3si9n.com/learning-scalaz/Id.html http://eed3si9n.com/learning-scalaz/Id.html