How to grow the (commercial) Haskell user base?

Oh look at that! Good find! A Haskell by Example.

ghcid can be installed like so, when the gods are pleased:

cabal install ghcid

or

stack install ghcid

(Seems only pages 2 and 3 use it)

And how many pages of text is that? Again, much too slow when the idea is to turn mild curiosity into more serious interest. That’s what the Golang By Example format excels at.

But if you feel writing a complete textbook and making it free - Well, yes, that’s a praiseworthy to spend a year or so of effort. I’m not sure that it will really help, but good luck. I really doubt you can improve on Hutton though, and anyone who can’t afford a copy probably has more urgent priorities than learning Haskell. And they’re probably not going to be very influential in getting Haskell used more.

Again, I think the most useful thing to do is to provide a fast tour to get people interested enough to buy a book - and to counter the Haskell memes that are most of what people know about the language. That’s something a few people could get done in a couple of weeks.

And getting people interested should be easy. A lot of programmers will find guards, pattern matching, and point style intriguing enough to want to know more, and a fast paced tutorial could fit all of those into the first five minutes. If Haskell was a car, then there are already plenty of driving manuals available - what it needs is an good strong advertisement.

1 Like

Apparently FP isn’t the only thing Python is doing well:

Haskell already is lazy by default. So when will Haskell also be parallel by default (with the option of manual annotations)? Surely performance still matters: who doesn’t want their programs to work faster?

1 Like

Well, we already discovered that laziness is a poor strategy for reliable streaming. That’s why there are streaming libraries.

My guess is the same thing would apply for baked in parallelism… and indeed, libraries like streamly already claim to be a better replacement for async.

Maybe this (GitHub - HigherOrderCO/HVM3: HVM3) project would interest you? Though it seems to be in a very, very, experimental state, and the company behind it still needs to find a way to (financially) support themselves long-term afaik.


Regarding the main topic. As a beginner, I would’ve appreciated (as a student on a tight budget…) a community book in the spirit of the Rust Book, which basically teaches 80% of things you might ever need to use Rust effectively.

Although someone noted that in print it is around 500 pages in length, the Rust book still is much shorter than something like Haskell Programming from First Principles, which describes in painful detail all topics that it discusses, and besides that, there’s the cost of buying it… (still, a really good book!)

1 Like

ā€¦ā€œweā€ did? Hmm:

…apparently laziness is why ā€œweā€ have streaming to begin with :-D


Hello, @jazzlobster!

Could the rest of us have your opinion on this text for Haskell that appeared in my search results some time ago:

…?

Great topic and great discussion! I don’t think I have anything to say on the topic that hasn’t already been said but it would be remiss of me not to make a tangential point to share my experience when it comes to effect systems, specifically in reply to:

These two points are the complete opposite of my experience. Since starting to use Bluefin daily I have found it provides an absolutely essential benefit in structuring my programs, commensurate with the benefit I obtained from switching from Python to Haskell in the first place. Without an effect system I have to either sacrifice making invalid states unrepresentable, or sacrifice some degree of compasability. To my surprise, I have found streaming to be the effect that I use most: more than state, exceptions or I/O. In fact, in my most recent project I have effectful operations that yield to up to four different streams. It has been a transformative experience!

I do think we should have a streaming module in base and it should take the ā€œlowest common denominatorā€ approach of passing a callback to run on every yielded element, which is the naked-IO equivalent to Bluefin’s approach. For example, a function that ā€œyieldsā€ all lines from a Handle would have type Handle -> (String -> IO ()) -> IO ().

4 Likes

I already use some haskell in production. Reasons why I do not use it more places include:

  • slow compile times
  • high memory requirements when compiling
  • high disk space requirements for ghc etc
  • initial project setup (getting all deps set up) would annoy co-workers more than pip something does
  • .. as would the lacking documentation
  • missing/under-developed libraries for task at hand (though generally I’m quite happy with the libraries available)
  • legacy code in other language works ā€œfineā€ and it’s not worth rewriting
  • deployment/platform issues (where I’m comparing with scripting languages)

(This sounds very negative, but most languages are worse in other ways :wink: and some, like C++, are worse in these and other ways.)

I also agree that something like ā€œgo by exampleā€ would be great, with a focus on solving real-world problems from the start: Setting up ghc, main=putStrLn "hello world", import Data.Text as T, make a simple data type with multiple constructors to show off how nice it is that the compiler notes your missing case statement, eventually show how to read big files without lazy IO (Go by Example: Reading Files immediately moves from slurping to seeking), Debug.Trace, pick one popular ā€œsimpleā€ lib for some major use cases like async to show off how easily you can race and TVar, aeson for JSON, fast-logger for logging; of course mention how nice it is that you can where-bind stuff in whatever order you like, perhaps at some point show off some lazy infinite structures for the math-curious (but that should not be the main focus for such a tutorial).

7 Likes

I view an ideal purely functional program as a sequence of actions (side-effect-producing functions) strung together with some state ā€œglueā€. Each action should be properly reduced to only require arguments it needs and to only return data that didn’t exist before. ā€œGlueā€ is the links between actions, combining previous outputs to create new inputs. ā€œGlueā€ should be pure (and hopefully immutable).

This satisfies both expectations: the program will not have unnecessary (and potentially invalid) states in it, and adding or removing actions requires rewiring the minimum of inputs and outputs around them.

Effects weave mutability into the ā€œglueā€ to achieve particular goals. In some cases—like dynamic dispatch—this does not contradict the previously stated goals, but any time easier state management is promised things get ugly:

Effect Issues Alternative
Reader Incentive to extract arguments later inside an action Argument passing
Writer Action order defines output shape Combine outputs manually
State Reader + Writer + incentive to pass state blobs around
Except Action determines whether other actions execute Output sum type

(Coroutines look like a benign case where I can do the exact same thing purely, but I’m not sure)


Break it into two to make both halves reusable.

data Stream a m r = Yield a (Stream a m r)
                  | Effect (m (Stream a m r))
                  | Return r

streamHandle :: Handle -> Stream String IO ()

forM_ :: Monad m => (a -> m ()) -> Stream a m () -> m ()

yieldAllLines :: Handle -> (String -> IO ()) -> IO ()
yieldAllLines h f = forM_ f $ streamHandle h
2 Likes

[This is getting tangential, so maybe if we continue we should fork a thread, but …]

I’m not sure I fully understand your notion of ā€œglueā€, but I don’t see that your proposed alternatives are composable. Suppose I have a state S, and the creation of initial value of that state can fail with an exception of type E1. Suppose further that deeper down the stack I have an operation that can fail with an exception of type E1 or E2, and I want to handle the E2 in a way that depends on S. Then the operation that creates the state is something like

createState :: ... -> Either E1 S

and the deeper operation is something like

deeper :: ... -> S -> Either E2 (S, Either E1 R)

So I can write

case createState of
  Left e1 -> Left e1
  Right s ->
    let s1 = ... s  -- some stateful operation
    in ...
       case deeper ... s1 of
         Left e2 -> ... e2 ... s ...  -- handle e2
         Right (_, Left e1) -> Left e1
         Right (s2, Right r) -> ... -- continue with s2 and r

You surely can’t be suggesting programming like this, can you?

Ah, but it’s already in two halves, it’s just that one half is trivial:

forEach :: ((a -> IO b) -> IO r) -> (a -> IO b) -> IO r
forEach = id

yieldAllLines :: Handle -> (String -> IO ()) -> IO ()
yieldAllLines h f = forEach (streamHandle h) f

(By the way, your forM_ has its arguments in unconventional order. Conventionally, yours would be called something more like traverseM_. See, for example, streaming’s for.)

Then your objection might be ā€œbut your forEach requires IO and my forM_ works for general mā€. Indeed, but your forM_ sacrifices compasability, specifically composability of the sort I describe in my article Bluefin streams finalize promptly. Someone might say ā€œbut that’s not composability, that’s prompt finalizationā€. Well, OK, I would say that having a standard and reusable bracket is an element of composability, but if we want to label it as a separate desirable quality then that’s fine by me. Another objection might be ā€œbut your version is not composable with MTL-like libraries that work polymorphically over a monadā€. Sort of. It is compatible with such libraries in the sense that we can instantiate m at IO or Bluefin’s Eff. It’s not compatible with them in the sense that we can use IO or Bluefin to provide an interface that’s polymorphic in m. But I don’t see the point of that. Doing so is just a means to an end, and Bluefin achieves the end in a different way.

One point I will concede is that there is no way of doing LogicT like effects in type safe IO wrapper effect systems like Bluefin or effectful yet. But firstly I don’t think that’s impossible, it just requires work to determine how to fit delimited continuation primops into the type system, and secondly I don’t know of any compelling use cases of LogicT in any case.

It looks very nicely done for what it is, but it wouldn’t fit the niche I’m talking about. It seems aimed at (first year?) students learning compsci rather than working programmers. And it’s a fairly thorough tutorial instead of a rapid tour.

Eg

Programmers use various data structures for arranging elements in a sequence. The simplest ones are arrays and lists. If the elements in the sequence are to be kept in order, a binary search tree may also be a good choice. In this chapter, we will focus on lists and arrays. We will also discuss tuples as a means for grouping together a constant number of values of different types. The number of elements in a list or array can be arbitrary, but they must all be of the same type.

In Haskell, lists are used much more widely than arrays, while in imperative languages, the opposite is the case. We already defined the list type before. Before looking at the definition of the Array type and discussing standard operations we can perform on lists and arrays, let us try to figure out why arrays are more popular in imperative languages, while lists are the more natural choice to use in functional languages.

..No working programmer - especially the influential seniors and team leads - needs to be told that. They’ll take one look and decide this isn’t meant for them. Instead just:

Here are some examples with Haskell lists (which, unlike tuples, are heterogenous):

a = [1,2,3,4]
b = [1..4]
c = 1 :
d = 42 : [1,2,3]
e = [1,2] ++ [3,4]
sq = x * x
map sq [1..4]
filter odd [1..4]
[x*x | x ← [1..10], odd x]

…Then show pattern matching on a list. Fast, minimal text, don’t insult their intelligence, bore them, or waste their time. Show them cool stuff and do it fast - you have to be fast because you can only count on a few minutes of their time until you hook them, and probably not more than an hour for the whole thing. So don’t start by giving a tutorial on ghci - show an intriguing piece of code, then tell them to use :l and :r, the indentation rules, and to wrap negative numbers in parens. And probably hammer home a sales point at the bottom of the code - ā€œHaskell is as - or more - concise and expressive as Python and like Python has a REPL, but its advanced type checking system makes it more maintainable. And in compiled mode it executes at speeds comparable to Java or Go.ā€

It doesn’t have be thorough but it does have to be interesting - thorough will come if you grab their interest and get them to buy a book. It’s not a textbook, it’s a product demo. It’s ok if they don’t know how to write life in Haskell when they finish it as long as they are interested enough to want to find out more.

Haskell already has outstanding textbooks: textbooks are not the problem - it’s the stage before that.

3 Likes

All excellent points - as were your others. My own thoughts:

  • Haskell stands zero chance of widely replacing Go or Python
  • The aim should be to make people aware that it is a viable choice for special mission work/startup juice like Scala and Clojure - because that is doable and replacing Go isn’t
  • Next year’s hardware always will reduce compile time problems, etc
  • Excellent point about file reading and showing off those libraries
1 Like

I never got why is Mu being so often criticised. ā€œIt’s not an example of Haskell usageā€, or under the job ads ā€œthey use a dialect, it’s a red flagā€. Do we have to be so elitist?

Mu is a huge success story of many years of engineering and millions lines of code trying to fit a functional language over a legacy C++ codebase.

9 Likes

Two points:

  1. The notion of ā€œinitial stateā€ implies overwriting on successive states, which would lose track of where a particular piece of data came from. Consider instead

    createState :: … -> Either E1 (A, B, …)
    
    deeper :: … -> A -> B -> Either E2 (C, Either E1 R)
    
  2. deeper is poorly reduced, returning nested errors, so I would expect to see them separated:

    deeper1 :: … -> A -> Either E2 (C, D)
    
    deeper2 :: … -> B -> D -> Either E1 R
    

    There may be counterexamples of foreign IO functions that return nested errors together with half-altered states, but as far as I’m concerned these are exceptional cases.

Other than that, yes, with sufficiently complex functions (most of anything IO is in this zone) you get ā€œcases of cases of casesā€, though I’m yet to see an example where this is a crippling deficiency and not merely a reminder to organize code better.

This happens to be an area I know a bit about! It’s still an open problem, and it’s not clear to me that we have a good way of doing automatic parallelism in a pure (lazy or not) language yet. Unfortunately for us, pure languages are the easy case for automatic parallelism. (EDIT: What I meant by this is that there are still lots of non-pure parts of the code that we’d like to parallelize! So if even the easy case is hard…)

More on the original topic, I’m not convinced that things like this help w.r.t. wide adoption. Haskell already has one of the best concurrency and parallelism stories of the major languages, in my opinion. Python had a GIL until very recently, that didn’t hamper its adoption. People worked around Python’s limitations because ā€˜there is a library for everything’ (amongst other reasons).

10 Likes

OK, I’ll take your word for it! But since I’ve been using Bluefin I’ve found it to be an excellent tool for organising code, and the style you seem to be suggesting fills me with dread. (Maybe I’ve misunderstood your style though.)

Automatic parallelism is an easy selling point, though.

Haskell in practice sells itself as ā€œpurely functional programmingā€, but the man-on-the-street feels deceived because purely functional is a technical term, #1, #2, functional programming, in an impure form, is pretty normal and standard these days as much as OOP was.

I think Graninas back in the day rehashed ā€œwhat happened to Smalltalk could happen to Ruby tooā€ with Haskell and Rust, but the analogy is incorrect. Smalltalk was displaced by Java because Smalltalk was ridiculously slow and mandated both a full IDE and a full deployment environment, whereas Haskell is roughly Java speed and is "impure’ in practice.

If, somehow, people were able to make GHC automatically produce parallel code, it would fulfill one of the big promises of functional programming, as well as produce a trivial selling point; Verse is automatically parallel (IIRC), but is dedicated to Epic Games’ metaverse. Haskell could be automatically parallel on top of its excellent parallel / concurrent story, and it’d be a big selling point for commercial adopters through greatly increased ergonomics.


But end of the day, your main point is that adding autoparallelism to GHC is harder than it looks (maybe a custom monad)? Honestly, with effect systems becoming so popular, we really need an alternative to forcing the IO monadic type on everyone and allowing people to have main have other types than IO a and have it be supported by GHC), and I guess it’s a pipe dream as long as this is the case.

People have mentioned a ton of books and learning resources on this thread. A quick ask. If your favorite one is not represented on the Documentation page, please submit a pull request to add it!

9 Likes

Believe me, if tail call optimization had been just complicated in Mu, then I would have done it. What makes it very difficult is the interop with C++ (and, C#, Java, Excel, …) where you need to follow the object ownership semantics of the underlying C++ library.
It was simply not worth the complexity to do it.

I’ve implemented TCO many times, first time in 1981. :slightly_smiling_face:

10 Likes

Thanks for your answer, that makes way more sense.
TCO in C++ is already hard enough, because you have to know the source of the destructor which is (or isn’t) to be called. The difference between ā€œjust do nothingā€ and ā€œlet nasal demons flyā€.

That’s exactly what I’ve had thought: somebody who is able to implement Mu surely knows how to implement TCO.

1 Like