The idea of managing side effects as a type (monads) still hasn't seemed compelling to me in terms of development time. Here I agree with Liskov in saying that it's a bit over the top[0]. Most of the quality I see with Haskell has to do with strict types, not the handling of I/O errors as types themselves.
Not that I want to bash it.. Learning haskell has actively changed how I approach all my C/C++ development, and gotten me far far far into the weeds of now learning Agda as a prototyping language for designs/semantics[1].
The world may or may not need Haskell. However it's certainly a better place now that it has it.
[0]: She said this at a talk she gave at work.. It was similar or the same as her "The Power of Abstraction" talk, dunno if she makes the same comment in every presentation though.
No. IO is not the only monad encoding side effects.
Reminds me of the story of the old monk who emerges from the basement of the monestary holding an ancient parchment. Tears are streaming down his face. The student asks "What's wrong?" The old monk replies "All this time! We were supposed to be celebRate!"
It's always struck me as strange that value (as in a typical type system) and effects would be controlled through the same system. The type signature of a function and it's effects seem very much orthogonal to me.
If we want to be controlling side effects then we really ought to be using a separate effect system[1]. There's a scala plugin demonstrating this (although I've not tried it)[2].
With separate type and effect systems I should be able to define a pure function fib(n) and call it like this fib(getValueFromUser()) that is without having to use special operators to get at the value which can only be used in certain contexts a la Haskell.
x = fib(getValueFromUser())
x is not pure, but fib is. OK, the compiler can figure that out without requiring the programmer to write a special "bind" operator.How about this:
dofib(argumentProvider) = fib(argumentProvider())
dofib(lambda: 1) // pure
difib(getValueFromUser) // effectful
Is dofib pure or not? That depends on the value of argumentProvider cannot be determined statically.I don't have to resort to documentation or code reviews to ensure that my teammates write fast, reliable tests. This isn't possible in a language that doesn't restrict side effects.
The way haskell handles this reminds me of checked exceptions in Java. In java if you read from a file, your code won't compile unless you have a catch block that handles the possibility of an IO exception. This is called a checked exception because you have to check for the possibility or else your code won't compile.
I know many Java programmers handle checked exceptions by wrapping the checked exception in a unchecked exception so they don't have to deal with it. Don't Haskell developers end up doing the same thing with their Maybe concept?
Haskell avoids null by using a Maybe type/class (I always forget the terminology). A Maybe can evaluate to either a value or a type that represents the absence of a value. (This is an oversimplification for the consideration of people who know nothing about Haskell)
For example, you've got an associative map data type and you find an element in there. At the time of writing the code you "knew" that the element "has to be there". Haskell makes you deal with the possibility that it's not. Won't most developers just end up throwing an exception in that case so they don't have to deal with the impossible possibility? Then, x months from now when the code gets changed so that the map won't have the element there, all the sudden your code gets an error. How is this different from a null pointer exception in any other language?
(Part of me is ignorant and part of me is playing the devil's advocate.)
In practice, most of the time, you end up either coming up with a default or returning the Maybe. This is greatly helped by the fact that just propagating Maybe value is really easy because Maybe is a member of a bunch of standard typeclasses like Applicative, Alternative and Monad. Thanks to this, I've found most of my code follows a simple pattern: it pushes the maybe values through until it has a meaningful default. This is safe, simple and fairly elegant. At some point, I either pattern match against it or use a function like fromMaybe, which then allows me to plug everything back into normal code.
It would be really annoying if Java's Map type threw a checked exception when you called get() because try/catch is so verbose and difficult to refactor around. But if it weren't annoying, it would probably make the API better and save me from shooting myself in the foot.
if (foo==null) {return null} else {...}
This pattern is handled automaticly by maybe; If you try applying a function to Nothing (maybe's version of null), then the result of that function is also Nothing, even if the function itself was not designed to handle Maybes.
Additionally, as a matter of culture, Haskell programmers rarely throw exceptions.
The writers of unix and other base libraries would disagree. See for example `head`. Or most functions that calls into C where the C function fails with an error. In that case Haskell blows up into your face, and you have to wrap the function invocation in a catch.
"Rarely" is relative. Certainly far, far less than other languages, but many otherwise useful libraries are still unfortunately littered with them.
The real benefit is the ability to have things that aren't Maybe. I would say that in any language, most functions don't have meaningful answers for null inputs (especially when you count the 'this' object input in object oriented languages). Most functions also don't produce nullable results. Yet, in C# or Java, reference types could always be null. The cases where that nullability is a real possibility that you need to handle are difficult to identify.
Having nullability be opt-in with Maybe, rather than an inescapable part of all reference types, lets you leave worrying about handling the null case to the typically smaller amount of times where it can actually meaningfully occur. Hopefully that makes doing the handling correctly more likely.
However, in other situations the Haskell approach is much better. Say you have a Java class with five fields, three of them are nullable (i.e. have a sensible notion of being null) and two are not (have to be non-null unless there is a programming error). There are tons of places where you can mess up: assign null to an invalid variable, assign "x = f()" not noticing f can return null, call "y.f()" forgetting that y can be null etc. However, if your type system handles nullability, you will get a compile-time error on each of them. More: if you change nullability of a field, compiler errors will point out places that have to be changed.
By throwing an exception when you "know" an element in the hashmap has to be there, you resign from safety offered by the compiler. In Haskell this is a code smell; Haskellers are less content with such hacks and might try to redesign the structure so that the invariants are enforced by the type system.
If types aren't non-nullable by default then any function parameter or return value can potentially be null and a robust program practically needs to have null checks _everywhere_. Otherwise, when your program throws a NullPointerException you'll have no way to know where that null originated from.
In Haskell, the types guarantee that you never ever have to check for null (in fact, you can't even) unless the function's type explicitly allows for the possibility of having a null. In addition, you have several ways to compose function calls that might return null in such a way that you don't have to check for each null case separately.
Actually, it's the other way around: NullPointerExceptions are typically trivial to track down.
Finding out at what part of a monadic computation the return of a function put a None when there should have been a Some is much more tricky.
Without it you can easily hand null to anyone who doesn't expect it, and forget to think about null when receiving it.
a.) Haskell gives you tools to deal with it. Unlike checked exceptions which must be restated and dealt with in every function along the chain - Haskell provides powerful abstraction capabilities to manage this. (You can use maybe values while most of the functions know nothing about them)
b.) This is a tool that can help you if you don't shoot yourself in the foot. The compiler can provide guarantees that no one "just throws an exception" by throwing warnings/errors on partial functions and or the use of unsafePerformIO. These are the only two escape hatches that would allow you to do such a thing but they are both heavily frowned upon and detectable by the compiler.
I don't understand. You're saying that there is a mismatch between what the programmer knows and what the compiler can deduce? That you know, from the state the program must be in, that the map has to contain the value associated with the given key? I can't think of an example were that would be the case off the top of my head.
I guess a similar case is if you have to perform the head function (take first element) on a list, and you know that the list is non-empty. If you weren't sure, then if you don't check that the list is non-empty before taking head on the list, you might get an error at runtime from taking head on an empty list, which is undefined in Haskell, but in a dependently typed language is impossible to even do at runtime (much like null pointer exceptions are impossible to get in Haskell).
Why would you throw an exception in Java? If you're sure that a value will be returned (as opposed to a sentinel value... aka null pointer), then just happily dereference it.
You seem to be coming at this from a weird angle. The Maybe type let's you statically mark all things that might be "null", which is a big improvement from the Wild Wild West of everthing might be null. Now you seem to be coming at this from the "how does this make dealing with the semantics of absent values easier?", to which I guess, no, it doesn't. You still have to mull out what you should do if things are missing, or if they deviate from the normal. But you can throw exceptions, define things that you don't want to deal with or that truly are undefined, as simply "undefined", much like in most other languages.
Maybe you just put it there or it wasn't Maybe when you checked it?
I intend to learn some Haskell but I keep wondering if it might be more useful to spend some time with Clojure or F#
Hague defines puzzle languages by referencing the experience of realizing you're going down the wrong path and having to completely restructure your code; I have had this experience in Python and Java in the past, and conversely, I program in Haskell more or less daily and therefore almost never have that experience there any longer.
Programmers in my previous job would feel pleased with themselves if they'd solved the "puzzle" of an array processing loop which was a simple recursive function in disguise.
So do I, which is why I program in Haskell rather than the other languages which make me worry about a ton of irrelevant things like the global state of my program and don't let me enforce invariants of my code in the type system, guaranteeing I can't violate without making the type checker complain.
I like my languages straight forward, that's why I stick to English.
Even in Haskell you can cast out of your type system, or unsafePerformIO, no?
Also, if you need a language to dictate your coworker's behavior, that says something more about your coworker than the language. Why doesn't he just choose to write good code? (barring arguments regarding the activation energy of good versus bad code)
It still has nullable types, side-effects aren't tracked in the type-system (so any function can potentially do anything) and the presence of sub-typing has several unpleasant consequences, for example: lack of full type-inference, generally more complex type signatures than in Haskell and having to deal with covariance vs contravariance.
Anyway, while I generally agree, I'd wish that people would stop bringing up type-inference in these discussions. It always makes me question whether those persons have understood the topic at all.
It's Java-like language (no need to manage pointers), and it doesn't have null-pointer problem, but it's still high-level imperative OOP language.
While many people say that they're code in Haskell is easier to read, since it has this "side-effect-free" guarantees, for me it seemed as not true for some time recently. In Haskell, when your code gets complicated (and starts to have some patterns you want to omit in typing), you start writing Monads. And when you start writing monads, your code gets harder to read since you need not only consider the code, but also keep (>>=) operator (all of them, if you use multiple monads combined via transformers) in your head for every pair of lines. Your code can suddenly have something like global variables (dynamic scoping) hidden in monad (as with State monad), it's flow can be changed dramatically and different other surprises.
That said, I agree that Haskell code is typically dense, and suffers from readability problems.
https://gist.github.com/k-bx/594a415a06fdd0fc3841
could easily put 2 different values into `a` on each line. Of course, technically everything is still correct, and getA still returns same result for each call.
First of all - no static typing system, whatever sophisticated it is, could protect from incompetence, lack of knowledge of underlying principles and plain stupidity. The dream that idiots will write a decent code will never come true, no matter how clever tools could be, just because it is impossible to write any respectable code without understanding hows and whys.
But, for those who managed to understand the core ideas and concepts on which programming languages were based (immutability, passing by reference, properties of essential data-structures, such as lists and hash tables) it is possible to write sane and reasonable code even in C, leave alone CL or Erlang, and the type system will become a burden rather than advantage.
So, Haskell is really good to master Functional Programming (which is much better to learn using old classic Scheme courses), to understand the ideas it rests on. To realize what is function composition, environments, currying, partial evaluation, closures, why they and when they are useful and handy, and how clear and concise everything (syntax and semantics) could be if you just stop here - just skip the part about Monads - they are just over hyped fanciness to show off.
Learning Haskell after Scheme/CL really clarifies one's mind with realizations how the same foundation ideas work in a alien (static-typing) world, and how, everything is clean and concise, until you're starting messing everything up with "too advanced typing".
Again, it is much better to learn the underlying ideas (why it is good to separate and pay special attention to functions that performing IO, what is recursive data structures and why null-pointers do exist in the first place) instead of stupid memes like "monads are cool" or "Haskell prevents bugs".
The trick is that it is that dynamic languages with proper testing (writing tests before code) is not worse than this "static typing safety", and that the very word "safety" is just a meme.
It's much harder (in my experience) for very smart people to write decent code in C, C++, Python etc than in Haskell, simply because so much of their smartness is consumed by having to constantly think about what code might break their program.
Yes, there are trade-offs in terms of programmer convenience, which is why type-inference research exists.
However, getting bug reports from a static analyzer that runs before your code goes into production and proves strong, complex theorems about your code before allowing it into production will always beat having to manually write extensive test suites.
In fact, speaking of test suites, I recommend checking out Haskell's quickcheck library, and the Scala equivalent whose name I forget at the moment. They use the type system to help automatically generate better test suites, for those properties of your code you cannot yet prove correct.
Static analyzers exist for many languages. Clang toolset has a nice one.
As for testing, keeping functions small and doing just one thing will keep amount of tests small-enough.
Let's say that systematic approach of HtDP/Racket guys allows you write decent programs without necessity of proving any theorems about it.)
I like some of the Haskell concepts, and it seems to attract very smart folks that are heavy on math and CS jargon. This reflects on the materials and channels around the Haskell community and IMHO makes everything more intimidating than it has to be.
boost::optional<User> get_user_by_name(string);
boost::variant<string, User> get_user_by_name(string);
Optional is making its way into the standard library.If all pointers can be null, then every pointer type is an implied optional type. Haskell's advantage is that it allows us to define types which cannot be null.
All pointers can be null, but unlike (for example) Java, we don't have to deal with pointers to objects all the time. C++ has value types and it's perfectly possible to pass objects that cannot be null.
You can write tests with 100% coverage that don't use a single assertion. Test coverage is a completely useless metric, but it is an easy one to measure and understand, which is why it is so popular in pseudo-QA and the management tier.
If we're talking about statement coverage then I agree that achieving 100% statement coverage then calling it a day really isn't as helpful as it might sound.
[1] https://en.wikipedia.org/wiki/Code_coverage#Basic_coverage_c...
[2] https://en.wikipedia.org/wiki/Code_coverage#Modified_conditi...
double = (3*)