Reflecting on Haskell in 2015
stephendiehl.com
stephendiehl.com
> As of yet, there is no rich polymorphism or higher-kinded types. As such a whole family of the usual functional constructions (monoids, functors, applicatives, monads) are inexpressible.
I completely understand that this make Elm quite limited compared to Haskell as the author uses it, but these limitations are precisely what makes Elm accessible to newcomers. I think it's a great way to get people into Haskell syntax like partial application and into the functional way of thinking in general.
I have learned Haskell very gradually over the past 6 years, and I still find advanced Haskell with all the language extensions very daunting to get into.
My brain just refuses to fully "grok" them. I still don't see why they're so awesome. I know I use them day-to-day - LINQ, the "List" abstraction (supposedly also a monad??) but I just don't see why it's important to understand them on this whole new fundamentally different level.
It's like - loops. I use loops every day. But if someone were to say - "Hey, did you know that loops are just the Andifuncsplursx abstraction applied on Crazofors?? This is why Crazofors in Haskell are so awesome" I still wouldn't "get" Crazofors or why I should care enough to attempt to "get" them.
I've just given up at this point.
A monad is just a type with 2 functions defined. Like an interface with two methods in OO langages. The 2 functions have to respect some laws but you can imagine whatever implementation for the 2 functions as long as the laws are observed (et type signature of course).
You could invent a total different implementation for the list monad, for the maybe monad, etc (if you respect the laws). There is no hidden ultra powerfull meaning which implies only one implementation.
The best paper on monads i ever read is an ascii art one, by Graham Hutton : http://www.cs.nott.ac.uk/~pszgmh/monads
Could you show such a different implementation for either list or maybe?
newtype HeadList a = HeadList { getHeadList :: [a] }
instance Monad HeadList where
return a = HeadList [a]
m >>= f = HeadList $ fmap (head . getHeadList . f) (getHeadList m)
This is a list instance that only keeps the head of function result, so it's basically just a map.But you could imagine putting any function that returns one result. min max avg normalize, etc
-- const [] for a HeadList
quux :: a -> HeadList a
quux = HeadList . (const [])
*Main> HeadList "hello... no wait" >>= quux
HeadList {getHeadList = "*** Exception: Prelude.head: empty list
Such breakage is verboten. This monad instance does not fly.(Yes, I know the history of the term.)
1. Why is it important that a List is a monad?
A. Its not particularly important. Its really just pointing out that monad is a very general abstraction - it wont tell you anything you don't already know about Lists.
On the other side, lists as kind of trivial examples of monads - they didnt really help me understand monads either.
Its like saying 1 is a real number - true, but it wont help you understand real numbers.
2. Why should i use monads?
A: I like to think of monads as things you can use in a for-comprehension (or the do notation in Haskell). If you can imagine writing something like
for {x <- thing; y<-thing} x + y
for "thing", then "thing" might be a monad (assuming all the math laws work out - sometimes they don't). In scala, for-comprehensions are literally de-sugared into maps/flatMaps/filters so for comprehension without filter <=> monad.3. But a monad is just a monoid in the category of endofunctors?
A: There is deep category theory and math behind this stuff. It can be useful to talk about it, but when you are starting out its overkill. Don't worry about "getting" the really abstract crap at first, just skim right over. Programming in monads is a lot easier than the theory, and the theory can be learned after getting your hands dirty. Using monads is mostly just for-comprehensions.
ps:
"monoid" = there is a zero, and an add operation. like integers with +, or integers with *).
"functor" = thing that has a map() operation.
"endo" = self
"category" = kind of like a set. its a container.
So a monad has a "zero" or default monad, a way to add monads, and a map operation that returns another monad.
e.g List -> zero = Nil, add = append, map = the "normal" map with f applied to each element
Future -> zero = empty Future, add = do future2 after future1, map = make a future with the f applied to value
let liftF = fmap
liftF negate (Just 1)
liftA negate (Just 1)
liftM negate (Just 1)
Realizing these all performed the same operation (if operating on a monadic value), and that `fmap` is just a `liftF` ("lift a regular function to work on a functor"), cleared some things up for me.This also helped: https://en.wikibooks.org/wiki/Haskell/Applicative_functors#A...
So all these abstractions really just let you do different things with functions that normally don't take functor arguments. They make the function basically allow functor arguments (kind of), to save writing a lot of boilerplate within your functions; typically boilerplate that would be put at the start of every function.
Once someone really gets the idea of what a functor is and why they can be useful, I think the rest is easy to understand once you read the definition of monads and applicatives.
addToState :: Int -> State Int ()
addToState number = do
currentState <- get
put $ number + currentState
How is this working exactly? Well who cares. I know that once this function is called, my state will have been incremented by the given amount.Precisely. Then once you've used Haskell in many projects and gotten a handle on using it, you can figure out the relationships/laws and use it to even greater effect.
Just forget the math, look at all the squares, triangles and circles. When you start to notice the sameness between ovals and circles and how they aren't squares you start to get the notion.
When it comes to monads, it's just the mechanics behind why some things, are intuitively composable. You don't need the mechanics if you just get a feeling for it.
In linq this is the feeling that you can stack a bunch of from x in xs from y in ys from ... together and get sensible results without too much thinking. As long as the xs and ys implement SelectMany as a monad it doesn't matter what it does, be it transforming collections, building sql queries or scheduling async tasks, the safe feeling will be there.
If you really want to do the math it might help to identify the level of abstraction we are talking about. For this I found it easier to reach for abstract algebra. Lookup the concept of a monoid and how it relates to you everyday arithmetic, it's roughly the same abstraction leap as the difference between your code and the concept of monads.
Now, monads really are just monoids for a particular kind of binary operation and values. The problem is that understanding monoids in the context of multiplication and addition is easy, you already have good grasp of both the arithmetic and the algebra there. But the compositions that monads describe (kleisly arrows) are probably not something you think about most of the time. Which kind of is like trying to understand abstract algebra without a good grasp on algebra.
It's essentially a hidden argument that's passed around between the environment (the stuff in "do ... ") and the actions (the stuff called in "do ...", e.g, "x <- foo", foo would be the action). The argument is always the same type, and the each action can create a new one based off the old one. So you have IO, which is essentially "all interactions with the outside non-pure world", and the versions of it are "the world before I did this action" and "the world after I did this action".
Stuff that doesn't use the hidden arg doesn't have the action type (e.g., IO ()), so it has to be lifted, which essentially passes the old world state verbatim to the next step.
Why do you want to "grok" them? Just use them. In fact I'd say there isn't much more to grokking them than just using them.
You remind me of my first ground school instructor. I'd asked her why it was necessary to use rudder in a turn, and she waved her hand dismissively. "Just step on the ball" she recited, referring to the turn-and-bank indicator. No thank you, I'd rather know what's keeping my plane stable, so I found the answer elsewhere: Langewiesche's awesome Stick and Rudder, still relevant 70 years after publication.
Much lesser than not understanding ruddering into a turn I'd guess, though I don't know what it is. What do you think?
While it's tempting to characterize some programmers as just coders, that sounds pejorative and wouldn't be very charitable of me. So instead I'll distinguish programmers as tool makers and tool users. I know which one of those I'd put my faith in too, all things equal.
No offense intended... at all! I don't grok monads either, but in my world (mainframe stuff) Haskell doesn't register. If I used monads though I'd surely be driven to understand what's going on under the hood.
Forgive me another tangential OT story, an anecdote I read in a magazine many years ago. A man was spending a Saturday afternoon puttering around in his back yard while the family's hound slept on the back porch. The man called the dog, who then roused and put his nose to the ground, retracing all the steps his master had taken that afternoon until he finally reached the man. "The dog didn't give a damn about coming to me" the man growled, "He just wanted to know how I got here."
Hee! That's me.
Given up trying to understand Monads, or given up Haskell? I'm still confused by many monads, after years of Haskell programming; it hasn't stopped me getting stuff done.
The nice thing about monads with respect to the haskell programming thing is that you don't have to know why they work to get their benefit. However, if you wanted to build your own you probably do need to grok them.
However, I'm not convinced that monads are the best way to proceed on the most part in programming advancements. The immutable/mutable tracking, affine types, and borrow checker in Rust seems like it would help prevent a lot of the same types of bugs that pure FP does, but without the cost of needing to have a monadic effect system (and Rust is a more familiar style of programming than the State monad with a host of monad transformers). I'm hoping for optional affine types and more immutable data structures to become the norm more than I am monad usage.
That means really understanding the concept is much like understanding other algebraic abstractions.
The most basic concept in classic 19th century abstract algebra is "group." Just like the monad concept, this concept involves a set of values that can be combined with a few carefully chosen operations.
Just like with the monad concept, the group concept doesn't lend itself to immediate grokking. So a lot of people get frustrated by abstract algebra. They feel like someone just isn't telling them what a group "actually is."
But group is an abstraction over concrete "implementations". It is a common base for many algebraic topics, like integer arithmetic, modular arithmetic, matrix arithmetic, polynomial arithmetic, and even more complex structures. If you are familiar with the theory that applies to groups in general, you have access to proofs and formulations that can be applied to many different topics. Sometimes that generality is useless, sometimes it is very productive and succinct.
What the groups have in common are a binary operator that's associative (like plus or times) an identity element (like zero or one) and a way of taking inverses. This is all codified as group axioms. If you just look at those axioms you might say "so what?" but the concept is born from actual mathematical practice and is significantly useful and interesting.
Monad is an abstraction over different computational topics: I/O computations, randomized computations, failing computations, and so on. It captures in an elegant and abstract way the operations and elements required to express these topics. General functions can be written polymorphically over all monads, just like theorems and computations can be written to work for all groups.
So for a monad, you need return, which lifts a base element into the monadic class of values. (This notion of having a base element and a lifted set, for example Int and Maybe Int, is itself a basic abstraction that monad builds on, namely the functor abstraction, whose only operation is fmap, an abstraction of list mapping.) And you need bind, which is some way of combining one monadic value with a function producing another monadic value.
Those operations need to work together in reasonable ways specified formally by the monad axioms or monad laws.
Again, you can look at all that definition stuff and say "So what?" But again, the concept makes sense, it is useful, and it is born from abstracting over concrete topics. (Moggi wrote the first paper about the usefulness of the monad concept in computer science; the concept originally came from category theory, which is kind of like abstract algebra.)
The do notation is a good example of the usefulness of having an abstract type class for monads. It gives you syntactic sugar that works in a well defined way across many many topics.
Abstract algebra doesn't make any sense if you don't know how to work with plus and minus. Monads don't make sense if you're not comfortable with implementing pure combinators for simple computational structures.
So you should find some way to practice some of those basics, and then the abstraction "monad" will have meaning and not just look like a random assemblage of made-up rules.
Look at an example of using do notation with Maybe types to express failure. It's not that amazing, but it's useful enough, and makes sense. Now learn to implement the same thing from scratch without the syntactic sugar and without the monad functions. You will first write the whole thing with explicit pattern matching, tediously. Then you will implement the crucial combinators that lets you "bind" one Maybe value to another computation returning a new Maybe value. Then you can look at the source code for the Maybe instance of Monad and see that it is just the combinator you have written.
Then you can study the State monad in a similar way.
And then you can notice how the general monad functions are useful for both of those topics, failure and statefulness.
The concept monad in the context of category theory is even more abstract, but there's no need to worry about that level of abstraction merely to learn Haskell programming.
The reason monads are a big deal in Haskell is that the computational structures they conveniently express happen to be those which are otherwise described with "imperative" language features: mutation, jumps, and side effects.
So if you're interested in expressing those computations in a pure way, you should be a bit curious about the monad concept. If you're not interested, that's fine, but it's close to Haskell's reason for existing, so that's a more basic question: is it interesting to write programs in a pure way? If you say no, you're right to give up on Haskell.
Also, i dont think you need to understand crazofors, ot even monads in full generality, unless youre a hardcore library implementor. What people blog about and whats req'd to write an app are very different things
You mention LINQ and the List abstraction. Yes IEnumerable<T> is a monad (LINQ isn't in itself - it [the grammar] is the equivalent of 'do' notation in Haskell). Monads are simply 'wrapper types' that follow a couple of rules:
1. You must be able to construct one from the un-wrapped value (return in Haskell, new List<T>(...) in C#)
2. It must implement the bind function.
If you understand 'map' (or Select in LINQ) then bind is very similar, except instead of returning a mapped version of the wrapped value, it returns a mapped version of the wrapped value, re-wrapped.
So in C# parlance (because I assume from your comment you use this daily):
IEnumerable<R> Map<T, R>(this IEnumerable<T> self, Func<T, R> mapper)
{
foreach(var x in self)
{
yield return mapper(x);
}
}
IEnumerable<R> Bind<T, R>(this IEnumerable<T> self, Func<T, IEnumerable<R>> binder)
{
foreach(var x in self)
{
foreach(var y in binder(x))
{
yield return y;
}
}
}
You may recognise that as SelectMany in LINQ. Select and SelectMany are special case function names in C# that allow the formation of LINQ expressions: var res = from x in list
select x + 1;
Equates to: var res = list.Select( x => x + 1);
And: var res = from x in list1
from y in list2
select x + y;
Equates to: var res = list1.SelectMany(x => list2.Select(y => x + y));
The result is a 'flattened' IEnumerable<T> - and that's why this is also known as flat-map.It's been a while since I've done any Haskell, so forgive me if I get some of the syntax wrong here. But the LINQ statement above translates very similarly:
do x <- list1
y <- list2
return x + y
(I know there's a list comprehension syntax in Haskell, but I assume this is still right, Haskellers?)The syntax is saying 'get the value out of its wrapper (the list) for me, so I can use it as-is (the add operation), then put the result back in a new wrapper'.
So why do we do all of this? It's so you can write the functions once that behave on the wrapped types, whether they're integers, strings or any type. The monad concept allows you to create a 'chain' of behaviour that from the outside is opaque, but internally it's operating on the raw wrapped values, as-is; and that makes all of the existing functions available. This is one of the key benefits.
A good analogy is to call them 'programmable semi-colons', because what makes each monad type what it is, is the behaviour of bind and return. So a list monad (as seen above) knows how to iterate the collection - so you don't have to write a for loop every time, an option monad knows when to not invoke the bind function if it's in a None state - so you don't have to test it yourself with an 'if' every time, the state monad knows how to propagate state changes - so you don't have to have extra context arguments on every function down the hierarchy, etc.
They remove boilerplate and capture common patterns. That is the other key benefit. At its core is an abstract idea, and I think that's why it's sometimes quite hard to grasp; but it's just a design pattern. Forget the category theory side of it, you don't need to know any of that to use use them or write new ones.
If you're more used to C# than Haskell, then you may want to check out my 'C# functional language extensions' library [2] that has C# implementations of the following monads:
Option
Either
State
Reader
Writer
And a few others, but that should be enough to get started. It may help you get your head around it in a language you're more familiar with?
[1] http://adit.io/posts/2013-04-17-functors,_applicatives,_and_...
I learned LINQ/Select(Many) and later all the map/filter/reduce functional goodness by playing with Clojure and never had a problem and never heard the word "monad" and was fine, totally fine.
Later watching a video on Rx (MS's reactive extensions) and hearing Erik Meijer talk about monads and Haskell and how Rx was inspired by that is what led me to try to learn Haskell. I just didn't see the "connection" between the functional and reactive patterns I was applying daily without any kind of mental problem and the Haskell stuff which was supposedly "the same", yet so....abstract?
I need to read your sample code and the links more carefully, but maybe this is the "key" I was lacking. Thanks !!!!
public class Option<T>
{
public readonly bool HasValue;
public readonly T Value;
internal Option(bool hasValue, T value)
{
HasValue = hasValue;
Value = value;
}
public Option<U> Select<U>(Func<T, U> map) =>
HasValue
? Option.Some<U>(map(Value))
: Option.None<U>();
public Option<V> SelectMany<U,V>(Func<T, Option<U>> bind, Func<T,U,V> project) =>
HasValue
? bind(Value).Select(u => project(Value,u))
: Option.None<V>();
}
public static class Option
{
public static Option<T> Some<T>(T value) =>
new Option<T>(true, value);
public static Option<T> None<T>() =>
new Option<T>(false, default(T));
}
The SelectMany implementation is slightly more complicated than I showed before. This is an optimisation that C# does to group the bind and map together. So it may look slightly scary as a function. But hopefully you can see that if the Option<T> has a value then it first invokes bind, then uses the result of the bind (an Option<U>) to project the final result. Here's a more imperative version of it: public Option<V> SelectMany<U, V>(Func<T, Option<U>> bind, Func<T, U, V> project)
{
if (HasValue)
{
var u = bind(Value);
if (u.HasValue)
{
return Option.Some(project(Value, u.Value));
}
else
{
return Option.None<V>();
}
}
else
{
return Option.None<V>();
}
}
You can see that with the IEnumerable<T> version of Select and SelectMany it encapsulates list iteration. With the Option monad it doesn't do that. It instead checks the HasValue field, and if it's false then it doesn't run the map or bind functions.The second static class: Option, contains the 'return' functions: Some or None. These wrap a value of type T in an Option<T>.
Now if we use Option<T> in a LINQ expression:
var option1 = Option.Some(10);
var option2 = Option.Some(10);
var none = Option.None<int>();
var res1 = from x in option1
from y in option2
select x + y;
// res1.HasValue == true res1.Value == 20
var res2 = from x in option1
from y in none
select x + y;
// res2.HasValue == false
var res3 = from x in none
from y in option2
select x + y;
// res3.HasValue == false
This is the same as using do notation in Haskell: do x <- option1
y <- option2
return (x + y)
If we were to do that imperatively it would look like this: var res = Option.None<int>();
if( option1.HasValue )
{
if( option2.HasValue )
{
res = Option.Some(option1.Value + option2.Value);
}
}
Clearly more cluttered and error prone and importantly, not composable. This is where the notion of 'programmable semi-colons' comes from. It appears that the monad is running behaviour 'between the lines', and it is.Hopefully that clears the fog. I'll keep an eye on this thread for a few days, so feel free to drop any questions in here or on my project page.
at that point, you might say "but this thing they're calling a monad in js is just an object.". well, these things we call monads in Haskell are just type classes.
now, a more fundamental question is "why do we care about types so much," and I don't have a pat answer for that one. suggestions to try out lisps for comparison makes sense, tho.
This is what helped me most.
DrRacket is a great environment to learn programming and play with various concepts, both for beginners and advanced users alike.
Deleted comment
Elm is quite different than most front-end UI and I think your last sentence does it a great disservice.
But when I try to dip my toe back into the Haskell community, a wave of despair washes over me. There's just too much. Haskell is changing too fast. There are too many language extensions, and more and more conceptually sophisticated features keep getting added to it. Every project uses a different set of GHC extensions.
I feel that without more standardization or stability, Haskell can never be more than a playground for PL researchers who can afford to spend all their time learning and implementing advanced language features.
At the company I work at, we use Haskell for HTTP services. The software stack we've grown is extremely opinionated about how to do things. We provide just one way to do things like report errors, run queries, do work asynchronously, and so forth.
We get people up and running with about the same amount of effort as we spend getting people going on our PHP, and I think a big part of the reason why is because we're able to constrain the education effort to the slice of Haskell that we want them to read and write.
For instance, we accept the fact that we have new teammates who haven't yet truly understood what "do" blocks are all about. These people are nevertheless able to write perfectly serviceable, production-ready code. They've learned that they can chain imperative actions together in a particular way, and that's enough for awhile. Deep understanding comes later.
In cases like this I feel like convention does a good job of keeping a codebase from spawling out into using every feature of the language.
Second - where do you work?
I intended to react to the comment "But when I try to dip my toe back into the Haskell community, a wave of despair washes over me. There's just too much. Haskell is changing too fast."
I think this is true. There are too many ways to do everything in Haskell and they're changing too often. If you don't get very specific guidance, you're going to be totally paralyzed with indecision. I have good evidence that you can do very well by confining the solution space.
Gotcha. It also doesn't help that many of the canonical learning materials (e.g. LYAH, or Real World Haskell) are slowly drifting out of date.
This. I feel exactly the same. I read various concepts like Free Monads, I go find papers and read them. But when I sit down to write code, my code is a blob doing IO. I can use State monads, StateT etc. But the result is not the elegant programs that I see on books and blog posts.
I am also very pragmatic. I want to build tools that implement an idea I have. I want to build as soon as possible to try these ideas out. But when programming in Haskell, I find myself code golfing and lost in the means rather than the ends.
Tooling is absolutely great, Atom plugins are very fine, really, it's very enjoyable and it all happened in a single year.
We do however see some of its features being slowly introduced to other languages which is nice, I guess.
On your second point, while it seems nice to have Haskell features slowly percolate into other languages, I urge caution in thinking that's a panacea for not learning more complex languages. Haskell is greater than the sum of all it's features. It's power comes from the combination of everything working together in a very well thought-out way.
So I don't necessarily disagree with your tone, just some additional commentary from someone who knows C++, Python, Java, and Haskell equally well (and perhaps equally bad ;) ).
I doubt that seriously. Could you write a complete Mars landing project in Haskell for instance? That would impress me. That's the area where Ada excels, or (less impressive) C and FramaC. But Haskell? No way.
AFAIK there is not one serious large project written in Haskell There is not even one sophisticated Haskell IDE for daily use written in Haskell (excuse me Leksah). Tell me if I am wrong, and if there actually are really big Haskell projects. Please don't listen compilers, they are not really complex.
The only advantage which Haskell actually has in comparison to other modern languages is its greatest disadvantage. The overall rock solid type safety of the whole application makes it very difficult to add additional unpredicted functionality which requires a significant change in many modules (where on the other hand the type system helps a lot). I am very impressed by the efforts and the implementations of Haskell but the simple fact that the Cabal hell -- for the simple job of a basic package manager -- lasted for years reveals that Haskell is not that practical after all.
I invested a lot of time to learn Haskell to use it seriously but after I switched to Nim (nim-lang.org) I realized how easy I can get real productivity.
Depending on what you mean by "in Haskell", probably. You can write hard-realtime software in an embedded DSL in Haskell that gives you explicit control over timings, scheduling, etc, while still giving you Haskell's higher order features for composing your programs.
https://hackage.haskell.org/package/atom
As I understand, the company that wrote it uses it in production in automotive systems.
What is an example of an extremely complex problem that you have solved that would be vastly more harder to implement in another language.
The most powerful thing that I have noticed is the type system. When you have a complex problem, you end up breaking it up by writing a lot of functions. In case of a language with a less powerful type system, it is hard for you to string them all together to build the complete program.
But with Haskell, if you have cared to actually annotate the types, this becomes a really trivial task. It also detects flaws in your reasoning (because the result of the previous step is incompatible with the input of the current step).
So, doing this would be vastly more difficult in a language like Python or even a language with less powerful type system like C.
As is the JVM (and most languages running on it). Not only that, but the JVM has actually a proven track record of being good and performant at it. And you can solve these problems with pretty much the language of your choice.
What makes you think Haskell is well suited for it? Immutability? This is available in a lot of languages (and for languages where it's not enforced, simple programmer discipline and code reviews address it).
In my university, Programming 101 (mandatory) was (and is) Haskell. For context, there is typically a mix of experience among the class - some will have programmed seriously before, but others will have only studied math.
I had already studied Java, but I found Haskell a delightful re-introduction (it was my first exposure to functional programming) and I see its value as an educational tool.
My own first language was QBasic and I'm very happy about it. If I'd been made to suffer through parsers instead of putting colorful circles on the screen, I would think of programming as hard work, and that probably wouldn't lead to a very good career.
Computers are to computing science what telescopes are to astronomy, or something. Haskell is a great language for expressing computations, the thing CS is about. It's not meant to flip bits and observe processor states and page faults, if that is your idea of fun. That's more computer engineering stuff.
But Haskell is absolutely capable of drawing pretty circles on the screen in just a couple of lines of code![1]
[1]: https://hackage.haskell.org/package/gloss-1.9.4.1/docs/Graph...
I think Haskell is good at expressing a narrow range of ideas that, honestly, aren't all that fruitful outside the FP field. There are three main reasons why it's not a great language for expressing arbitrary algorithms:
1) It uses the pointer model instead of the integer RAM model. That leads to extra logarithmic factors.
2) Immutability. That's hypothesized to also cause extra logarithmic factors, but AFAIK that's still an open problem.
3) Non-strict evaluation. That wreaks havoc with space complexity, and compositional analysis of performance in general.
Yes, you can add epicycles to remedy these drawbacks (arrays, ST, strictness annotations). But I'd rather use a C-like language in the first place. That's closer to the "core" of CS as I understand it, and that's how most algorithm research is done.
Speaking more pragmatically, outside of CS study, most of the programs I write do not require me to write high-performance algorithms – they tend to be very I/O bound, and call out to external libraries/services for the data crunching. When I need performance (because of course I do at times), there is an amazing library that lets me write C code in Haskell[1]. The fact that some sections of my program is performance critical doesn't mean I have to write the entire program in C. I can write just the performance critical parts in C.
Out of curiosity, what kind of CS do you mostly work with? If it falls under the umbrella of Curry-Howard, then sure, I agree with you. But CS has tons of other areas like graphics, crypto, AI... The impact of Curry-Howard related ideas on these areas has been disappointingly small, IMO.
I don't think it's a good idea because it might discourage a lot of students from the get go and they might simply drop out.
It's well established today that the language you start with has little impact on your mastery of the field years later (most of us started with BASIC I bet), so you might as well start with an easy language that will get people hooked.
If the students are curious and talented, they will move on to more complex things with time.
I think this is true at a lot of universities. I would have thought if you get a random CS grad they're much more likely to have Haskell experience than Go, Ruby, Clojure, etc.
That said, scheme never managed to feel like anything other than a toy language to me, and that we were being fed problems that happened to map neatly into the structure of the language. Write a RPN calculator! Write a Tree Parser! Stuff that really isn't that much harder (but admittedly more verbose) in imperative languages.
I couldn't help but to think "Sure, these problems are easy enough, but how would I blit pixels with this language? How would I process TCP/IP packets? What would a database interface look like? How am I supposed to do error handling?"
In the end I had no desire to integrate Scheme into my day to day programming.
I think its more too alien for your typical mid- to late-career developer that hasn't seen a CS classroom for a decade or more, or your typical successful non-CS-degree-holding autodidact developer that's done well with a narrow range of industrially-popular languages, than it is too alien for your typical CS grad.
Haskell will probably not take over the world of web programming. It currently is gaining widespread adoption in a variety of interesting places, however.
If you want to write a new libssl or libjpeg, you need to use c, c++, or rust (or a few others, of course).
https://code.facebook.com/posts/745068642270222/fighting-spa...
A common "best practice" with cryptographic material is to hold it as short a time as possible (um, but garbage collection makes that hard), and zero it before releasing it (but immutability gets in the way). That means that, in a language like Haskell, you're going to leak crypto material into the free memory pool unless you break the language paradigm to prevent it. Worse, you have to remember to break the language paradigm everywhere you need to.
In C++, you'd just wipe it with zeroes in the destructor, and use RAII to make sure it's freed when you don't need it any more. In Java, you have to explicitly overwrite it on your way out of the block (maybe with a "finally" clause). But in Haskell, you can't overwrite it (yeah, you actually can, but still...)
That's just a question of teaching it to more CS students then. I don't think it's outside of the reach of the average programmer.
And yes you probably wouldn't use it to build real-time applications. But you probably wouldn't use Python to write an operating system. No language fits all use-cases.
The aim isn't necessarily widespread adoption, so much as the ability for people who know it to use professionally. It's way past merely "production ready", but reputation is a lagging indicator and so most programmers are stuck doing Java...
Since the goal is to avoid {success at all costs}, getting 25% market share is probably not possible. 1 percent would be a start.
The problem, economically speaking, is that Haskell and Clojure engineers are massively underpaid, when you consider that an average Haskeller would be Principal+ at any Java shop. That's because the Java and C++ people can create bidding wars every year and spike their salaries, whereas using a better but more niche language makes that career strategy untenable.
Haskell's laziness isn't as much of an issue as people make it out to be. You can have strictness any time you need it, so it's more akin to an opt-out model than laziness being forced upon the programmer. It is correct to observe that high-performance applications frequently have a lot of strictness annotations, and the argument can be made that high-performance Haskell (while it can be close to C in performance) isn't "idiomatic"... although that claim is true of all high-level languages (even Java). High-performance Clojure ends up being full of '^' characters for type annotations, and high-performance Haskell ends up being full of '!' characters for strictness.
I haven't found the same to be true for other languages, but in my experience the Average Haskeller is so caught up in academic exercises, technical one-upmanship, and code golf shenanigans that I'm surprised they find any time to contribute any business value at all. Having a discussion about acceptable levels of technical debt with a Haskeller is like having a discussion about acceptable levels of meat products in your diet with a vegan in a world where there are no plant foods..."everything is technical debt, and none of it is acceptable, and we absolutely can't move on until we change all relevant functors to applicatives and all nested record accesses to lenses!"
And it is squarely a cultural issue, not a technical one...it is quite obvious that Haskell is a fantastically powerful language and capable of mowing over most enterprise-y languages with ease for a very wide variety of domains. Its just that Haskellers are so nitpicky about elegance and style that they don't know how to let shit be shit and get something done when it is needed.
This obviously isn't the only case for Haskellers. Looking at JGM's github feels like looking at Dumbledore's magic through the eyes of a Muggle. It just feels that way with the average Haskeller that I work with.
The upcoming GHC 8 provides the Strict extensions which makes everything strict by default on a per-module level. That should greatly reduce the number of »!« characters.
Cool, we have that in Haskell these days![1] Except, of course, it has all the good high-level stuff of Haskell, which reduces boilerplate and increases correctness.
[1]: https://hackage.haskell.org/package/turtle-1.2.3/docs/Turtle...
The way I see it, if you want to extend your language to be able to express solutions to your problem in terms that are close to your problem domain, then Haskell is probably a good choice.
If you would rather transform your problem into constructs that are easily understood in terms of the low-level operations performed by a computer, then you might be better off with a low-level imperative language.
To put it another way, if your requirements include things like deterministic real-time scheduling, strict performance or memory requirements, anything involving assembly language or cachelines, then you should probably use something like C or C++ or Rust.
On the other hand, if you don't care about any of that and you just want your program to compute the right answer in a reasonable amount of time and you want to have high confidence that it's right, then Haskell is a pretty good choice.
But pure haskell might be too much for a begginer. For me, as an hobbyist, it was. Then I discovered a real world haskell, Elm that (for now) is 'married' to JS. Later, if I feel the need, I can always 'upgrade'. IMHO elm is for haskell, what meteor is for javascript. Even without support from purists, they conquered their market share. This irritated a lot of people.
On the topic of evaluation methods there is interesting research opportunity into exploring an alternative evaluation methods for the lambda calculus under the so-called “Call-By-Push-Value” method. Surprisingly this technique has been around for 11 years but lacks an equivalent of SPJ’s seminal “stock hardware implementation” paper describing an end to end translation from CBPV to x86. This would be an interesting student project."
Any idea which SPJ paper he is referencing?
In truth, though, I think trying to incorporate Call-By-Push-Value into Haskell would radically change the language unless there's some research in type theory that allows for the implicit Call-By-Need nature of Haskell to be extended into a form that can accommodate CBPV, which itself explicates a kind duality between Call-By-Name and Call-By-Value.
If advancements have been made here, I'd love to hear about it.
[1] http://research.microsoft.com/apps/pubs/default.aspx?id=6708...
So Haskell is still on my radar but, for now, off my list of things I intend to learn.
distance point = sqrt(point.x^2 + point.y^2)
The function application of sqrt shouldn't have parens.
And the type of distance should be
distance :: forall r. { x :: Number, y :: Number | r} -> Number
Otherwise, it's not row-polymorphic and you wouldn't be able to apply distance to a record of type Point3D.
Uh?