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)
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.
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?
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!)
ā¦ā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 ()
.
I already use some haskell in production. Reasons why I do not use it more places include:
pip something
does(This sounds very negative, but most languages are worse in other ways 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).
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
[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.
All excellent points - as were your others. My own thoughts:
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.
Two points:
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)
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).
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!
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.
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.