List of what Haskell doesn't have?

Is haskell that much productive, though? I don’t think it would be productive for juniors, at least.

Hobbyists don’t care about correctness. In most cases, if it works it’s good. Also, I don’t think haskell is going to be on productive side for many of them. Plus, could haskell be made easy enough to really be 2xPython… it seems to me that even 10xPython is a tall order.

That’s probably due to many Haskellers having had unhappy experiences with languages that do (or did) have that culture.


That choice is the result of another “non-pragmatic” choice - Haskell being non-strict; this may help to explain the context for it:

1. Conventional Programming Languages: Fat and Flabby

Programming languages appear to be in trouble. Each successive language incorporates, with a little cleaning up, all the features of its predecessors plus a few more. Some languages have manuals exceeding 500 pages; […]

John Backus.

As others have noted here and elsewhere, the ongoing addition of “syntactic sugar” has other costs, the primary one being extra complexity.


…and newer languages like Rust don’t? As for constantly importing your preferred modules, I’m sure some would also like to have everything from complex numbers to regular expressions be imported by default too - where does it stop? Maybe a new non-strict language really is the solution (no “legacy support” issues) - we’ll just have to make sure it’s more successful than the previous attempt at a new Haskell standard…


You know what would be really awesome? One standard format for formatted strings - these days, each one is practically a language of its own!

If we could only bring together all the formatted-strings fans together in the one place…


[…] life is too short, and bright minds too precious, to waste on ugly things.

Robert Harper.

…maybe you’ll be happier with Standard ML - I wonder if it has support for formatted strings…

1 Like

It would be great! However…

Well to be honest, for string situations, we got Text.Printf. What would be bad about it except for

  1. Lack of polymorphic support for other string variants. Last I checked, text library did not have a module for such printf.
  2. (Less important) Lack of support on types implementing Show - show for every argument takes quite a typing. ← Perhaps we should adopt more expansive approach than current Text.Printf, what is the point to have %d?

Tbh I recall the string situation being one of the gripes of haskell. We can, indeed, have more than 1 string types (String & Text). However, the integration / polymorphism of each one is rather lacking.

Hm I think an (old) speech called “Growing a language” covered how to deal with these matters.

1 Like

Imo:

  1. Focus on stability instead of features
  2. Try to keep advanced features out of Preludes and common libraries as much as possible
  3. Keep advanced language concepts opt-in (for DT that would mean either use liquidhaskell or write a new superset language)
  4. Start writing good tutorials that focus on practical examples and clean but simple design patterns instead of reaching for DataKinds on the second page

All of this is rather hard.

3 Likes

Assuming that is what you’re referring to…the author is one of the designers of Java - that language that started out being OO, but is now being “retrofitted” to support the functional paradigm as well. This quote seems relevant - from page 13 of 15:

But if you do not take care at the start, you may be stuck for years to come with a wart you did not plan.

…so I’m guessing the choice to start as an OO language is a “wart”.

Are extensions like FlexibleContexts, FlexibleInstances, GADTsyntax, DeriveFunctor, DeriveFoldable, DeriveTraversable, GeneralizedNewtypeDeriving, TypeOperators parts of the advanced features?

Btw I thought DataKinds were mostly for proxy implementations.

Could be a wart, but there is this thing called “Multiparadigm”. I think Java is trying to do that, not trying to move OO out.
The wart could rather be generics, value types, etc.

Btw, I am talking about other parts of the presentation, which describes how to decide features to put in the standard.

Could you elaborate a bit on what exactly it is about fstrings that is so much better than concatenating strings. I can certainly see why it’s a bit better but i don’t see why it’s so much better that it deserves its own (non-quasiquoter) syntax. Perhaps you could help me understand?

1 Like

…you mean like C++ ?

Also Scala, F#, Kotlin, Rust, the list goes on.

…and C++ too? At this rate we might finally be able to define UNCOL after all!


…yes I know: we would probably need something like “Survivor: Format Strings” to have any useful chance of success.

1 Like

I’m reluctant to, because this is more of a feeling, and any rationalization I give of that feeling may or may not do it justice. I also want to note that I did say this is a relatively mild inconvenience, but one of many that I find in Haskell. But I want to be a good faith interlocutor, so I’ll do my best. There are a few considerations here:

My use of Haskell is primarily as a teacher and learner. I don’t have a commercial Haskell job, so I don’t have a single codebase that I interact with in the long term. When you are working on a codebase long term, small start up costs like adding dependencies get amortized, because you pay them once and reap the benefits for a long time. When you are creating many small one off learning projects, you have to pay that set up cost every time, and what’s more you have to remember to pay it. When I’m tutoring, a decent chunk is in a context where students haven’t even been introduced to projects and dependencies. And in the course, they aren’t at any point introduced to language extensions, templatehaskell, and quasiquoters.

The most common reason I find I’m wanting to build complex strings is for print debugging or logging. And I don’t think in advance of creating a project “hmm, I’m going to want a convenient way to format strings”. What generally happens is something goes wrong, and I start trace-ing things to see what happened. I’m probably already frustrated by this point. I very likely don’t have the mental bandwidth to think about adding a quasiquoter library. If I were to remember, it would involve a context switch, and I’d have to wait for the library and its dependencies to build - probably losing track of what I was thinking about. For a one off learning project, it’s likely not even worth bothering.

Ok, so I stick with concatenation. The first issue with this is line length. Suppose I want to trace three things with reasonable sized identifiers

"x = " <> show x <> ", smallThing = " <> show smallThing <> ", mediumSizeIdentifier = " <> show mediumSizeIdentifier 

It’s already getting enormous and hard to read. It gets far worse once you’re accessing nested record fields - then you probably want to start creating intermediate bindings just for your print debug. There’s lots of visual noise which makes it difficult to focus on the essence of what I’m doing. The line has probably wrapped in my editor, which is always visually confusing. It’s becoming difficult to navigate in vim with W and B. If I want to add something, particularly in the middle, I have to figure out where to split the string up, and remember to add two new " <> " on either side of the splice (12 extra characters!!!) (Edit: This was incorrect, but I’m leaving it because that fact that I got it wrong is indicative of the problem!), rather than just a pair of {}. If I’m debugging, this is again the kind of cost that won’t be amortized - I’m just going to get rid of the trace once I’m done. I want to get it in the editor as quickly as possible so I can get back to thinking scientifically about the problem without losing focus.

Compare:

f"x = {x}, smallThing = {smallThing}, mediumSizedIdentifier = {mediumSizedIdentifier}"

It’s a vast improvement for both reading and editing. Adding something is less cognitive load. Python even allows you to do this:

f"{x=}, {smallThing=}, {mediumSizeIdentifier=}"

Which gives the same result. When you’re just trying to do one off debugging with as tight a feedback loop as possible, features like this are an absolute godsend. When you’re using a language in anger, they stop you from going crazy.

6 Likes

Because base is mostly a historical accident and the barrier of fixing it too high. So people realized: to get anything done in Haskell, you either need to be maintainer or start forking.

This does not agree with my experience. I fixed a bug or two in base or the built-in libraries last year, I didn’t had problems to get the fixes included. Setting up dev environment is a bit challenging, but that’s expected for a large project like GHC. Another example where things are moving forward is the review of documentation of base
.

2 Likes

Thanks so much for sharing your point of view, despite the reluctance! No need to be apologetic that it’s “more of a feeling”. Feelings are very important indicators of the quality of a design!

I am also reluctant to give my reply, because it might sound like I’m saying “you’re holding it wrong”. In fact my intention is more to present the counterpoint in the hope that we can find a path that provides close to the best of both worlds.

Firstly, I agree with you then when you’re stuck debugging a tricky issue the mental overhead of switching context to add a dependency feels like a lot, not to mention adding {-# LANGUAGE QuasiQuotes #-} at the top, and whatever other requirements I have forgotten.

I also agree that

"x = " <> show x <> ", smallThing = " <> show smallThing <> ", mediumSizeIdentifier = " <> show mediumSizeIdentifier 

is harder to read than

f"x = {x}, smallThing = {smallThing}, mediumSizedIdentifier = {mediumSizedIdentifier}"

but don’t think it’s the length that is significant. It’s not that much longer.

On the other hand, I Kagied for “haskell string interpolation” and the first hit was string-interpolation. Its equivalent is

 [i|x = #{x}, smallThing = #{smallThing}, mediumSizedIdentifier = #{mediumSizedIdentifier}|]

and I think it’s hard to say that’s worse than your version. It comes with a few paper-cuts of its own:

  • Just like with OverloadedStrings, you may have to disambiguate the type with a type signature.
  • You have to {-# LANGUAGE QuasiQuotes #-} (the Haddock page doesn’t seem to explain this)
  • You have to depend upon string-interpolation in your cabal file (or equivalent)
  • You have to import Data.String.Interpolate in every file you want to use it in.
  • The quasi-quoter is called i so is likely to conflict with variables you want to define yourself!

Suppose someose addressed these problems as follows:

She adds the quasi-quoter under a less common (but still short) name to a custom prelude, and makes sure that QuasiQuotes is always enabled in her cabal file, before she starts any project.

I think this brings the quasi-quoter option close to par with, say, Python’s. It has a few downsides:

  • It’s still a bit longer, in terms of number of characters
  • It’s more awkward to type [i| ... |] (or whatever) than f"..."
  • The error messages are probably worse (but might actually be better)
  • It’s non-standard, so you won’t find thousands of Stack Overflow answers and blog posts that describe using it.

On the other hand it has a few upsides:

  • It’s not tied to the compiler so can be implemented independently,
  • Error messages can be made custom

And the Python version has some downsides:

  • There’s only one choice
  • It can’t be improved without depending on the language implementation team to accept it and an interpreter author to implement it

It think that before changing the language we have to be really, really, really, really, really, really, really, really, really, really, really, really, really, really, really, really, really, really, really, really, really sure that there isn’t a library level solution that can be made almost as nice as a language level solution (and nicer in some ways). In this case I don’t think we’ve exhausted the design space of format strings via quasi-quoters.

5 Likes

These are some fair points. It’s worth noting that I rarely use alternative preludes, again due to unamortized costs for one off projects (“how do I set this up again? Something about NoImplicitPrelude? Or Mixins? I think when I last tried this there wasn’t proper documentation for adding mixins to package.yaml as opposed to cabal files… But I feel like I remember stack repl breaks if you use mixins or something?”) Usually the hassle isn’t worth the cost.

I think the design space is reasonably well explored by other languages’ implementations. We can look at those to see what works. As a compromise, I could live with a QQ solution if it lived in base. That would eliminate most of my issues, even if it wasn’t quite as pretty as a language level solution. I’m not sure whether there’s any precedent for TH stuff in base or whether others might find it objectionable to require enabling extensions to use base features.

2 Likes

yeah, lambdacase works, but why all the language extensions?

also, is there a built-in Haskell keyword that specifies the name of the initial function, like how “it” works in ghci?

I’m not sure what you mean.

is there a built-in Haskell keyword that specifies the name of the initial function, like how “it” works in ghci?

Again, not sure what you mean. Perhaps the following?

import Data.Function

appendTuple = fix $ \it -> \case
  [] -> []
  x:xs -> (length x, x) : it xs
1 Like

I think what we probably both agree on is that there’s a lot of scope for Haskell to satisfy common needs more easily, without having to go through a huge amount of ceremony, importing this, depending upon that. Personally I’m optimistic this can be done by libraries in many cases, certainly in many more cases than in any other language I’m familiar with.

4 Likes

Another option is a good tool like summoner that can setup new projects for you according to a custom template.

5 Likes