For options it takes two options and return an option with a tuple of values if both are set, otherwise it return None. We could call it bothOrNone.
For Either... It's not unambiguous, but presumably it's similar to option, returning either a tuple of Rights, or the first Left of the arguments, following convention of using Left for errors. We could call it bothRightsOrALeft.
For State I'm just guessing. getBothWithStateOfLast?
These may all have interesting mathematical similarities, but the way you would use them in a program would be pretty different. I don't think you are making your program clearer by giving them all the same name.
Sometimes reading "overabstracted" code can feel similar to reading assembly code. Sure, add and mov are simple operations with a clear definition of input and output, just like flatMap and fold, but they say very little about the programmer's intent in using them. If you give me a chain of them without any comment, I'll need to work backwards to figure out what you're actually trying to accomplish.
By your logic, it's silly and confusing for Option, List, Either, and State to all have a function called map. But in my (and everyone else I work with)'s experience, map feels like map! And it doesn't take a mathematician to feel that way given our backgrounds...
[0] https://hackage.haskell.org/package/base-4.9.0.0/docs/Contro...
It adds complexity, so I'm not convinced I'd save any time.
Having a mathematical concept in common: is that important, or is it a coincidence? Seems like it depends on the domain and maybe on your point of view.
At the end of the day it's about how hard the code is to maintain. Sharing dependencies makes things better if you want to fix a bug in one place. But every dependency has a cost, too, since it's another thing that can break you if it changes.
Too many dependencies and you get something like the "fragile base class" problem where you can't easily change the code, even to fix a bug, because there are so many downstream usages that depend on it working the way it does today.
So, I would not be quick to depend on a common utility library for simple convenience, unless I know how stable and bug-free it is, and that it doesn't have too many dependencies of its own.
These may all have interesting mathematical
similarities, but the way you would use them
in a program would be pretty different.
Actually, by definition, how they are used are not different. This is a key concept. While the tuple example was just that, the idea of defining interaction contracts (often called "laws" in FP literature) is powerful. It can reduce conceptual load due to expectations being clearly defined. It also, perhaps more importantly, enables expressing a solution in a robust manner having clearly defined collaborator roles.I think your concern about communicating programmer intent is keen. The risk is there whether it is a chain of flatMap, LINQ, or fluent interfaces[0]. IMHO, this the same concern with poorly formulated imperative logic. Having to work backwards to figure things out is orthogonal to the approach taken and IME directly correlated with the attention paid to non-executable artifacts (such as comments, diagrams, discussions, etc.).
It is actively discussed in the Elm community (https://github.com/elm-lang/elm-compiler/issues/1039), but with a Wait-And-See-Approach. In my opinion, this is really laudable, as it signals readiness to add higher order types, and at the same time keeps developer friendliness of the resulting feature extension in mind.
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/trans...
[2] https://github.com/mirage/ocaml-cohttp/blob/master/lib/s.mli
(also useful if your language has multiple competing promise libraries...)
This is just a variation of 'any problem can be solved by adding a level of indirection' + 'any protocol can have a dummy implementation'.
> 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.
I.e. you've pulled the side effects (blocking) from the future to the present.
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/doc...
The LINQ abstraction is leaky, half-baked, and not as safe as it should be. Some of the various "LINQ to X" flavors are actually unable to support certain operations (cf. [2]), so your software can unexpectedly fail at runtime.
If C# and Visual Basic had support for higher-kinded types, then your LINQ-equipped thing wouldn't have to be punted back to an IEnumerable - it could retain its (static) type after applying LINQ methods.
[1] https://msdn.microsoft.com/en-us/library/mt693024.aspx
[2] https://msdn.microsoft.com/en-us/library/bb738550(v=vs.110)....
In practice this means that not all linq methods return IEnumerable<T>. x.Where(...) may return a statically typed IEnumerable<T> or an IQueryable<T> depending on whether x was IQueryable<T>.
Edit: I don't think linq would even actually improve by having this kind of fancy stuff. I don't want .Where() called on an array to return another array. I want a lazy IEnumerable<T> almost every time. I might want to consume only the first 10 elements from the filtered result. Allocating a whole 100000 array could be a tremendous waste of time.
Yes, good point. There is a lot of space to explore in collections API designs, and the LINQ design is just one of them that makes its own tradeoffs (e.g., only have to implement "GetEnumerator()" to get a watered-down monadic experience). Contrast that with the well-meaning but convoluted and buggy Scala collections API (very difficult for novices to create a custom collection due to CanBuildFrom and other tricky stuff).
Paul Phillips came up with a pretty neat collections API [1, 2] that provides a nice feature set, including high-performance and typesafe views when you don't really want to wastefully create a temporary collection just to interact with the first 10 elements.
[1] http://www.slideshare.net/extempore/a-scala-corrections-libr...
For an alternative example of limitations induced by lack of higher-kinded type abstraction, imagine implementing an Option/Maybe type [1] in C# and piggybacking on LINQ. Under the LINQ API, if you try to do anything with an instance of your Option/Maybe, its static type gets obliterated into an IEnumerable, which destroys the semantics. (The fact that the thing is an Option/Maybe and not, say, a list of things, is important for reasoning about, building, and abstracting over things.)
Incidentally, I think I might have just now understood what a monad even is. All those years of reading blog posts are finally starting to pay off!
This isn't true. It's only true if you derive your Option type from IEnumerable, but you can provide your own implementation of Select, SelectMany, and Where to maintain the Option type - this is my implementation [1], with SelectMany returning Option [2]. Granted, it doesn't make it a higher-kinded type, but you don't have to use IEnumerable to use LINQ.
[1] https://github.com/louthy/language-ext/blob/type-classes/Lan...
[2] https://github.com/louthy/language-ext/blob/type-classes/Lan...
Although it doesn't have higher-kinded types it can do ad-hoc polymorphism which will get you most of the way there. The primary problem is C#'s terrible type-inference, which means type annotations everywhere.
I am currently migrating the types in my language-ext project [1] (a functional framework for C#) to support this. Take a look at this [2] issue raised for a discussion on ad-hoc polymorphism in C#. There are lots of links to examples further down.
On my area of work, even alternative JVM and CLR languages are no go, so I really appreciate having at least the ability to use some FP concepts, even if the end solution isn't perfect.
Being focused on Ruby (or any dynamic language) makes the value harder to see, perhaps, since a lot of what HKT get you in a static language you don't need in a dynamic language -- its stuff you lose going from a dynamic language to a static language with an insufficiently powerful type system in exchange for typesafety. HKT let you have your cake and it eat it too (that is, lets you keep powerful abstractions that operate on classes of related types, while remaining typesafe.)
Also, my practical-oriented blog posts on the topic in .net (referenced in my own answer on that page):
http://www.sparxeng.com/blog/software/an-example-of-what-hig... http://www.sparxeng.com/blog/software/higher-kinded-fun-in-h...
let amplify x f = fmap (x *) f
amplify 2 [1,2,3]
>> [2,4,6]
cos pi
>> 1.0
let doublecos = amplify 2 cos
doublecos pi
>> 2.0
You can't do anything that operates so generically in a language without HKTs.
All that said, I don't find them all that useful in day-to-day work.
After a while using Haskell, I'm not sure about that anymore. I keep thinking that an "easier than rails" web framework is just one breakthrough by some random developer somewhere on the Internet away.
Even if I can't imagine the form such library would take, the features I see every day are so powerful that it just look possible.
Well, it does.
We detached this comment from https://news.ycombinator.com/item?id=12340160 and marked it off-topic.