I'd call it verbose. In comparison Haskell syntax seems like a lot of meaning should be magically assumed by default. Sort of like mathematicians tend to do in their notation. Here it's spelled out.
Like in the example of
f = average . filter (> 0) . map normalize
vs
let f(lst) = lst.map(normalize)
.filter((x) => x > 0)
.average
In the second version I can clearly see that I'm creating a function that is supposed to take one argument. It even has a name that might indicate what I'm expecting.
Don't get me wrong. All the required information is there but some of it is "written between the lines".
Sort of like CoffeeScript which people tend to hate (and I love, just because I understand JS perfectly and in comparison to CoffeeScript it looks noisy for me).
Standard, base Haskell admits the following definition:
let f lst = lst
& map normalize
& filter (\x -> x > 0)
& average
Which is barely different from your second presentation. (EDIT: just in case you think I'm ass-pulling here, this is fairly idiomatic code that I'd certainly write in prod! See another comment I posted earlier this week...)
> you can add parens to function arguments if you want?
Of course. This "(x)" and this "x" are the same. You can put any expression inside parenthesis. Haskell also has tuples, that you write as "(a, b, c)" and can use as a function parameter.
There is even some functions in base for converting functions that receive a tuple into functions that receive many parameters and the other way around.
It seems kind of weird coming from other languages to not visually distinguish the function call from the arguments if you have the option.
Nobody in Haskell does this (to the point I had no idea it could be done until I tried it) so clearly it's something you get used to, but boy does it look real alien to me.
Space is the symbol that denotes a function call in Haskell.
It is indeed different from other languages, that require an argument list in parenthesis. It can be different because values and functions are not ambiguous on Haskell; there isn't such thing as a function with no argument.
I personally prefer Haskell's syntax. I find myself using JavaScript transpilers like BuckleScript to take me into a typesafe land with ML syntax rather than using a language like TypeScript with Java like syntax.
That said, I really appreciate seeing work like this. Haskell is super powerful and it's great to see transpilers like this that are lowering the bar of entry to Haskell to those more familiar with the traditional languages. Nice job!
To me the biggest advantage of this that it's not whitespace sensitive, which makes it way easier to refactor and automatically indent/format code. Although I'd have preferred something more ML-like.
> What are the advantages of white-space insensitive code for refactoring and formatting?
For refactoring, it's less error prone when you're using an editor without refactoring support for it, would be my guess.
For indent/format, your guess is as good as mine, AFAIK programs don't care whether block separators are called INDENT or {. Though the parsing of the latter would be somewhat trivially simpler as you don't need to store a stack of indent levels.
With white-space insensitive code I can easily cut/paste code from one function to another, without having to potentially manually re-indent it afterwards. This makes refactoring easier.
For formatting, it means I never need to manually add any spaces or tabs: the formatter always knows exactly how to indent it. With whitespace-sensitive code, I have to manually pick the indentation, as different indentations could have different semantics. That's why if I hit tab in Emacs when editing a Haskell file, it will cycle through multiple possible indentions. If I hit it in a whitespace-insensitive language, it will just move it to the "correct" indentation straight away and I don't need to worry about it.
Using {} braces in Haskell seems less intuitive, at least to me. E.g. how would I use braces to avoid the need to manually indent a nested where statement?
I have to fix up the layout essentially manually every time I have to edit C in someone else's idea of correct layout when they haven't included C Mode rules for it. (That's assuming they want a consistent style for changes, of course.)
If simplicity of parsing, and cut-and-paste are important, we should be using Lisp, of course, but there are still different indentation styles.
I love seeing new Haskell projects, though I personally would rather just stick with Haskell's clean syntax:
data Maybe a = Nothing | Just a
...over this:
data Maybe<a> {
Nothing,
Just(value: a)
}
I understand that programmers coming from mainstream languages might feel uncomfortable without all those angle brackets, curly braces, and parentheses (why do C-like languages have so many ways to syntactically group things?), so if this project helps them ease their way into Haskell I totally support it. However, a small part of the joy of Haskell is freeing yourself from the arbitrary syntactic boilerplate that most languages have.
> why do C-like languages have so many ways to syntactically group things?)
TL;DR math notation
Because they are different things. Brackets are for types, curly braces are for "bodies" (struct definitions, initializers, statements, function bodies, control flow construct bodies), parentheses are for overriding precedence.
There are obviously exceptions that don't fit in this general scheme: e.g. function call syntax is based on parentheses, so it's not just about precedence.
All this is just a post-fact rationalization; this all comes from standard math notation.
Math uses parentheses for function application. Math uses commas for arguments of functions of multiple variables. Math uses curly braces to state of things are defined (sets, inductive rules) etc. Math uses parentheses to override precedence.
All the rest is that historical accident. Angle brackets were unused so they choose them for types. Square brackets for array indexing? No idea why
This is not an accurate description of notation as it is used in mathematics. All types of brackets (round, curly, square, angled, ...) are used in mathematics for grouping things. Typically parens for innermost small groups and other brackets for larger “outer” groups.
Curly brackets may be used for sets but they are also used for different cases of functions, or as a more succinct and precise way of expressing sentences about multiple things than writing “foo, respectively bar” all over the place, or for Stirling numbers of the second kind, or for whatever other things an author might want.
Other “structure making” notations like group presentations use angle brackets. Ideals are sometimes written with their generators in angle brackets, sometimes with round brackets. Similarly tuples or sequences may be written with either.
Matrices are sometimes written with square brackets and sometimes round, and never with commas. Vectors typically are written with round brackets but may be square. Row vectors sometimes have commas.
Function application is sometimes written with round brackets. Arguments are sometimes separated by commas, sometimes by semicolons, sometimes with one argument as a subscript or implicit. Application often omits brackets altogether. Sometimes the function goes on the left, sometimes it goes on the right.
Now consider the few features which I find are relatively common to different mathematics notations are context, alignment and structure in 2 dimensions, juxtaposition, implicitness, and terseness. I think I see basically none of these in the common C/Rust style syntax you claim to be so rooted in mathematical notation, and indeed I would guess that you would oppose them.
There is another ingredient I failed to make explicit: c syntax was a product of the 70s and it was a tradeoff between writing a parser easily and designing a syntax that would feel reasonably natural to people used to other notations.
Surely implicitness and tersness were wisely used in math notations then and now, but devising a formal syntax that would work for C at the time was likely deemed too much work for little gain.
there is a lot of random decisions that shaped the C syntax, as it's ultimately a random even that so many modern languages have chosen to continue that trend.
My point was more about why it's not completely irrational to use "many different grouping symbols" since math notation also uses many different grouping symbols (rather than arguing the exact reasons this or that symbol is used instead of something else)
> freeing yourself from the arbitrary syntactic boilerplate that most languages have.
And coming up against arbitrary three-four-character combinations people come up with for their operators.
Terser does not automatically mean better (or J and K would've won that argument a long time ago).
At least mainstream languages are usually consistent. A method call is a method call. A structure is a structure. What is this ungodly mess?
type API = "polls" :> Get '[JSON] [Poll]
:<|> "polls" :> Capture "question_id" Int :> Get '[JSON] Poll
:<|> "polls" :> Capture "question_id" Int :> "results" :> Get '[JSON] PollResults
> And coming up against arbitrary three-four-character combinations people come up with for their operators.
Nothing requires that though, it's more of a debatable community habit, similar to the overly abstract or cutesy names.
> At least mainstream languages are usually consistent. A method call is a method call. A structure is a structure. What is this ungodly mess?
Haskell is arguably way more consistent there: "mainstream languages" also have operators, often overloadable, you just can't declare your own operators in most of them.
Haskell simply allows sigils to be used as infix functions, aka binary operators, and furthermore allows those to be used as prefix functions, as well as prefix binary functions to be used infix.
You mean it makes it harder to read, because it's harder to look for the meaning of operators (although there is hoogle). If that's what you mean, I agree.
It does look weird to the foreign eyes, because it looks nothing like what you may encounter in your life when learning a natural language or math. As a consequence, this notation cannot leverage any intuition at all. You need to learn the syntax. Nothing is explicit and helps you guess if you haven't.
It is telling that this is "just a typed api with 3 routes". Somebody who haven't learned all the specific bits of Haskell (or, for that matter, specific bits of this program that may be overloading strange operators to define routes) won't even start to guess that it's defining routes. They can guess it is defining an API, thanks to the words used at the beginning of the snippet. And even though you told us that it's defining a route, I cannot figure out what it is really doing. It is just not clear.
So, yes, it looks like a mess for the foreign eye. I have no doubt it looks straightforward as soon as you learn Haskell and practice a bit.
Mainstream languages also look gibberish for people who haven't learned programming though. That's also something that we have to humbly keep in mind while, as foreigners who practice other programming languages, we discuss the syntax of Haskell.
> It does look weird to the foreign eyes, because it looks nothing like what you may encounter in your life when learning a natural language or math.
It also looks nothing like what programmer might encounter earlier when learning programming languages.
Actually I think Haskell seems to be a little bit similar to mathematical notations so it might be easier for mathematicians to learn than programmers. Unfortunately they don't really have a business case for knowing Haskell and they learn Python instead.
> And even though you told us that it's defining a route, I cannot figure out what it is really doing. It is just not clear.
it does nothing. :) that code sample is a description of the API. nothing more. when you're writing programs in a functional way, you describe your computation rather than actually computing it as opposed to imperative languages. imagine summation in math. it is a description rather than computation. you compute the sum according to the description. this API description is exactly the same.
the code in the comment is incomplete which makes it harder for you to figure out. where are the handlers? how do we return responses? i believe the rest of the code is actually mapping the API description to actual handlers.
server :: Server API
server = getPolls :<|> getPoll :<|> getResults
getPolls = queryDBForPolls `andThen` transFormToJson
getPoll pollId = (queryDBForPoll pollId) `andThen` transformToJson
getResults pollId = (queryDBForResults pollId) `andThen` transformToJson
i agree with the comments and sentiments which say mathematicians learn and write haskell easier than the rest of us but i will tell you a secret: i'm a high school graduate with no degree at all let alone a math / cs one. i'm using haskell happily but it took way much longer time than probably everybody else. it just takes a bit of focused time and will to learn it.
type API = "polls" :> Get '[JSON] [Poll]
:<|> "polls" :> Capture "question_id" Int :> Get '[JSON] Poll
:<|> "polls" :> Capture "question_id" Int :> "results" :> Get '[JSON] PollResults
Delightful :D
The API is the route "/polls" accessed via GET, returning a list of Polls in JSON format
or the route "/polls/question_id" where question_id is an Int, accessed via GET, returning a Poll in JSON format
or the route "/polls/question_id/results" where question_id is an Int, accessed via GET, returning PollResults in JSON format
[JSON] and '[JSON] have different meanings. Here, '[JSON] does not refer to a list of json files, but to a type-level list of the mimetypes that can be returned. You could see '[JSON, PNG] instead, which would make no sense in the context of a list type declaration.
The genius of Servant is that the DSL doesn't require you to know how '[] is implemented and why it's different from [], you just have to know the DSL.
Yep as soon as you make it look like natural language, compilers magically become intelligent and context aware readers of prose, not the old-school pedantic expectors of syntax rules.
An eDSL ? Much like every DSL, it requires to invest some time learning, and gives you something if you take that time. It's not really specific to Haskell.
Back when I was doing some Java, I had to learn the logic of maven xml files, and it was exactly the same kind of investment-for-future-reward.
Apart from looking like the usual math notation, this "boilerplate" is like punctuation in natural languages: it helps recognize and scan things fast. In many languages, you both start sentences with capital letters and end them with a dot. A bit redundant, right? But probably much easier and faster to parse.
Many people put non-necessary parentheses in their mathematical expressions to help readers (and themselves!) understand them more easily. Sure, they could be left out, but this is not an optimization for the reader.
In your example, your version looks cleaner, but I find more complicated expressions far more readable with parentheses, brackets and all that stuff present in the second version. This is between angle brackets? This is a type! I don't have to parse the whole expression to infer this.
Expressions written with this "redundant" syntax are seekable. You don't need to read the whole book to pick the one thing you need to know and get on with your life. Redundancy also helps remove doubts on whether something is an error or is intended in complicated cases (good things these languages are strongly typed though, it helps a lot).
At the end of the day, it's all a matter of taste and habits.
Would math be usually written in an Haskell-like syntax, probably almost everybody would find it easy to read and beautiful. It's not, and therefore this kind of syntax probably (almost) only pleases people who are into these languages (because they've got used to it) and represents yet another obstacle for outsiders. I'd actually bet that this plays a role in the fact these languages are not more widely adopted. They make them look unreadable at first impression and people move on. It probably even prevent people from contributing to projects written in these languages. This is bad. I've been able to fix things written in languages I didn't know, but Haskell would probably be hard just because of its syntax, which is unfortunate.
On the other hand, it's good to see and develop new / alternative ideas, including on syntax matters.
Mathematics is usually written in something like Haskell syntax (or typically more extreme on the C-Haskell axis). It would not be surprising to use some random symbol to represent a binary operation the author thinks is important. In Haskell, juxtaposition of non-keywords always means application (function application, or type constructor application) or alludes to it in a pattern match. In mathematics the following: f g h x might correspond to any of the following Haskell depending on context:
f (g (h x))
f . g . h . x
x . h . g . f
x . h . g $ f
(f <> g <> h <> x) -- potentially more a type-dependant operation than <>
(f <> g <> h) <%> x -- For some operation <%>
Furthermore Haskell is only indentation sensitive whereas mathematics notation depends on alignment and structure in two dimensions more generally.
Therefore I claim it is silly to talk about mathematics notation as if it were a single standard thing because the reality is that mathematics notation depends on context a lot and varies a huge amount by situation. It is not obvious that the kind of elementary algebraic notation one might encounter in high school is the best fit for programming which tends to deal with totally different things.
I actually think Haskell makes a pretty good language for a small group as it may be better moulded to the problem domain. I recall an example from a mailing list where someone defined:
(.) x f = f x -- for my poor OO brain
infixl 9 (.)
So they could write code like:
prodSize xs ys = xs.length * ys.length
Which seems silly except that the user has adapted the language to fit themselves. Some mathematicians also prefer function application/composition on the right. It makes values pass through functions from left to right the same way that their types are written.
I’m not sure I follow your point since at times it seems like you’re agreeing with the parent and other times disagreeing;L. In any case, I think we can acknowledge that math syntax isn’t one single thing or even that it is more ambiguous than Haskell and still acknowledge that many (if not all of) the math ‘dialects’ share a terseness that is similar to that of Haskell and which creates similar readability problems. I certainly feel comfortable saying that the terseness of math syntax causes me readability problems in much the same way as the terseness of Haskell syntax. Visual structure and indentation are useful for communicating with humans because we don’t parse text the same way that a parser program would. Admittedly Haskell is parser-program friendly, but it doesn’t make sense to optimize for parser programs at the expense of humans.
Well, I would not know how to parse the mathematical formula `f g h x` without context and I would not have naturally read it as a function application. Function applications in math are usually written with parentheses and commas, except when explicitly using the lambda calculus notation or defining a specific syntax, which you don't have to do when using the usual syntax which you refer to as the one somebody encounters in high school, and which is also used in computer science and mathematics papers.
You are right on the fact that it's usual to define arbitrary operators in maths. It's also usual in math to use single letters, including Greek letters, for function names and constants or parameters, and this is fine in mathematical formulas or texts, when there are not too many of them and when there are explained next to the formula, but that would be a nightmare in a code base. Ever read a program written by a physicist, who translated their formula into a program, without having the "source"?
Weird operators are a feature when writing maths, but could be a code smell in a program. Unlike an academic paper, we have space, and we have auto-completion. Give me a proper, readable, easy to understand name, unless your operator is really omnipresent (it's a great thing we don't have to spell out +, ×, or - of course).
> It is not obvious that the kind of elementary algebraic notation one might encounter in high school is the best fit for programming which tends to deal with totally different things.
Indeed. What I am saying is that if you don't use this notation, it will probably feel less intuitive to many people because what they are used to is this algebraic notation we learn in high school. The Haskell notation might be more apt (and I definitely see its strengths).
However, the opposite is also true, so this is not really an argument: I have not found any real drawback to this notation after years of using it in my programs. This syntax do not feel verbose to me so the Haskell notation does not solve any problem I have.
A good way to make something readable to the general public would be to not even write it in math. (But then again, that's not a very useful statement when we need things written out in math.)
Snarkiness aside, I do agree that a good number of mathematicians don't know how to write, but that does not mean that mathematical writing is intrinsically bad. We don't judge a random programming language as "bad," just because a random member of the public isn't able to parse it (since a random member of the public likely has not learned any programming, much less that language's syntax), so why would we apply similar statements to math in the same way?
Well the parent comment seemed to claim that C syntax is more based on mathematics than Haskell. I don’t want to comment on what is better. Mathematicians tend to care quite a lot about notation and finding good notations but that notation typically doesn’t have to last very long or travel very far.
People tend to take it for granted that having very variable situation specific notation is bad but I don’t think it’s obviously true.
I certainly don’t think it’s obvious that programming language syntax should be based on the kind of high school algebra notation people are familiar with because that notation is mostly useful for manipulating sums of products of variables raised to powers, and one-argument functions thereof. I don’t think programming fits that category or that addition and multiplication are particularly important operations in programming.
Given the response a lot of people have to Erlang's syntax (borrowed from Prolog), that has commas, periods, and yes, meaningful casing distinctions (atom vs variable), I'm going to go out on a limb that any benefit from scansion is offset by effort in writing.
Further, I'll go out on a limb and say that the benefit of reading has NOTHING to do with audience reception of the syntax (whether that audience is new to the language, or extremely experienced with it), and instead has everything to do with what seems familiar. (I.e., I think I'm in agreement with you in that it's what people's habits are as to whether it's appreciated or not; I'm just disagreeing that improved readability is a factor in whether people appreciate the extra boilerplate, or the possible implication that it's a net gain)
What math notation uses curly braces and angle brackets? All math notation is terse and focused on efficient expressions of ideas. Haskell syntax is much closer to all of math than C-families.
Parsing a language like Haskell that does not use any parentheses and curly braces is slightly harder than a typical C like language (or Lisp for that matter). Haskell syntax is indentation sensitive, which require the parser to keep track of the indentation when parsing.
It does well but I'm not happy when I move code around and have to manually fix the indentation level at destination and check if I inadvertently broke an if or some loop. My editor does it for me with other languages and I have one less source of bugs.
Yep, but no editor can decide if the code must align with the last line of a loop or be indented out of the loop. I got at least one bug in production because of that, and a test that didn't catch the error.
VS Code tries to do it, but it’s heuristic based and generally fallible. (Disclaimer, work on vscode, not on python extension)
I much prefer working in languages wherein the series of tokens absolutely defines the semantics (as opposed to Python where the series of tokens + the context define the semantics)
Using curly braces or parentheses to signify blocks of code is the trivial and natural way to implement a parser for a programming language. Doing anything else requires a conscious design decision and some effort and extra complexity. The C philosophy is to avoid complexity in the implementation in favor of a slightly more verbose language. The Haskell (ML) philosophy is the exact opposite. These design decisions have been cargo culted by later languages.
- make it easy to write and to read at the expense of having complexity in the parser / implementation. Programs (or documents) are written and read a lot of times, it's worth optimizing, as long as it does not make the implementation unmaintainable. I'm saying that as a writer of several manually crafted parsers.
- avoid ambiguities in the grammar at all cost. They just suck for every party involved.
As for indentation vs braces to delimit blocks, I practice both and don't really have any preference.
Copying and pasting python code is annoying (more so because it doesn't have macros or any metaprogramming really) and having to indent things in the python repl just feels dumb. I don't know anyone who thinks python does "well" in this area.
If you're doing combinator parsing, the offside combinator is a few lines. I have one in front of me as part of a demo Haskell-like syntax for Scheme as a command processor. You have to tag the lexemes with position information, but you presumably want that for error-reporting anyhow.
[As others have pointed out, you get the choice of layout in Haskell syntaxes and, as it happens, that Scheme syntax also allowed you to use sexp input.]
Quite the glass house of stone-throwing there.
Haskell has braces, parens, and brackets for grouping, plus just about everything on the keyboard can be used to define an operator, with a 9-level hierarchy of operator associativity.
I think there’s a good use case for such a thing: make it as compatible with current Rust syntax as possible, including keyword names. This way you’d make use of people’s familiarity with Rust, and give them “lookalike Lang with better types and a GC”.
Coming from a C-style language background, I've always found the syntax of languages such as Haskell and ML to be alien, to the point that I can't get my head into learning one of them, despite their obvious power and utility.
I've been looking for some kind of C-style dialect for one of these languages for a long time, so thank you very much for creating this.
At the risk of hijacking this thread; is anyone here aware of other projects for Haskell or other languages that achieve a similar goal?
ReasonML is similar. What do you find confusing about the syntax? Imo, Haskell/ML syntax is more readable and lightweight than traditional C-style syntax.
The thing that's always turned me off Haskell is language extensions and the associated fractal fracture of the language. Sorry, I don't want to learn a dozen different styles of a language.
I would have used the word "features" or "extensions" instead of "styles" but I agree with rattray's point, if I understand it correctly. It seems like every Haskell project makes use of a different subset of 10 to 20 language extensions, each of which requires some deep knowledge of type theory to understand how it affects the language.
This makes me feel that, totally apart from the syntax, Haskell/GHC is still more of a laboratory for doing programming language research. Which is great for what it is; I would say Haskell's original motto "avoid popularity at all costs" still holds. I also had hope for languages like PureScript that make an opinionated choice about a subset of those features for the sake of a consistent development framework.
> each of which requires some deep knowledge of type theory to understand how it affects the language
This is simply false. Quite a few of them are not much more than syntax sugars, e.g. with LambdaCase I can write
eitherToMaybe = \case
Left _ -> Nothing
Right x -> Just x
instead of
eitherToMaybe var = case var of
Left _ -> Nothing
Right x -> Just x
With OverloadedStrings I can write "foo" instead of (T.pack "foo") or whatever and it picks the right "pack" function based on the inferred type.
OverloadedLists does the same for listy things, e.g. now [1] can be a literal vector and you don't have to say V.fromList [1].
TupleSections lets you write (1,) when you want a function that tuples something with 1 (so (1,) applied to 2 becomes (1,2)).
NamedFieldPuns lets you write
foo x = let myField = x + 1 in MyType{myField}
instead of
foo x = let thing = x + 1 in MyType{myField = thing}
-----
And then there are a bunch of "Deriving…" extensions that don't require any knowledge either, you just enable the pragma, and it lets you say e.g.
data X … deriving (FromJSON)
instead of having to explicitly implement the FromJSON instance by hand. I have used Deriving a bunch and have no idea how they work :-)
Looking through my main project, the only other extension I use on a regular basis is ScopedTypeVariables. That one's a bit more difficult to explain since you have to have some idea what the words "scope" and "type variable" mean, but basically lets you refer to a type variable from the outer function signature in the type signature of a where-clause. Normally, with
foo :: Maybe a -> Int
… where helper :: Maybe a -> Int
the a's are seen as different types. With ScopedTypeVariables, you can say
foo :: forall a. Maybe a -> Int
… where helper :: Maybe a -> Int
and the a's are the same type. So that lets you have explicit type sigs on helper functions that are restricted to the type of the outer function. (Though I am not an expert on this so maybe I've got it all wrong. But that doesn't stop me from using it to make stuff with.)
From what I dimly remember last time I looked, there were a bunch of ways to deal with strings and lists/arrays/etc.
Googling "most popular haskell language extensions", I see this article: https://lexi-lambda.github.io/blog/2018/02/10/an-opinionated... which lists 34 recommended extensions and admits "a few of them are likely to be more controversial than others". Half of them don't have names which make their purpose immediately obvious to me.
Of course, each language extension changes the syntax and/or semantics of the language, so in order to "learn Haskell" I'd have to learn one or more flavors/styles of it – and there doesn't seem to be one standard set of language extensions that are commonly used.
You say "of course... I'd have to learn one or more flavors/styles of it", and that seems like a reasonable assumption, but it's not accurate in practice - the extensions largely just add features, so you just turn them on if you're using that feature; there's not much of a separate style for coding around not using one. It's like, if your Java project used a Singleton class, you might feel a need to document it, but if it doesn't, it doesn't mean you have a separate "Singleton-less" style, you just didn't feel the need to use one.
Which isn't to say they're not an issue - Haskell really needs a new language standard to clean up that mess, but they don't really fragment the ecosystem the way that, say, the Python 2 vs 3 split did.
To share more on where I'm coming from, one example: when I look at things like Yesod's "Hello world" section in "Getting Started", the first thing I see is 4 pragmas: https://www.yesodweb.com/book/basics and I just don't have much information on what those are, how commonly they're used[0], whether (and why) I need them to use Yesod, etc.
I have a kneejerk reaction to that since I want to be able to understand the code I'm seeing and I'm not sure what's going on behind the scenes. But kneejerk reactions are bad, so I'll give them the benefit of the doubt next time!
[0] Well, I definitely understand OverloadedStrings and TemplatHaskell to be very widely used, and think I get their gist.
TemplateHaskell and QuasiQuotes are very closely related. They're where that square bracket syntax comes from:
[whamlet|Hello World!|]
I would say that they're closer to the situation you're concerned about - they're very much like C++ templates, and equally controversial for exactly the same reasons. Still, I don't usually think of C++ templates, or Haskell ones, as being a different style per-se, just an advanced feature to use sparingly, mostly just when they'll save you from writing a bunch of boilerplate.
For completeness - Overloaded strings are the Haskell parallel to Python's b-string, f-string, u-string, etc. TypeFamilies are "let me write functions over types", which sounds like it would be dramatic enough to introduce a different coding style, but since it, like other extensions, needs to play nicely with the type inference, it doesn't conflict with other extensions, so things mostly just work as you'd expect. Since extensions generally work nicely together, most people just turn on "the usual" extensions for the whole project (all the more reason we need a new standard), and only add explicit pragmas in files to call out heavyweight things like TemplateHaskell. In tutorials you're more likely to see them all listed at the top, though, to be clear about what's being used.
> Of course, each language extension changes the syntax and/or semantics of the language, so in order to "learn Haskell" I'd have to learn one or more flavors/styles of it – and there doesn't seem to be one standard set of language extensions that are commonly used
> It seems like every Haskell project makes use of a different subset of 10 to 20 language extensions, each of which requires some deep knowledge of type theory to understand how it affects the language.
These comments are both pretty off the mark. It's unhelpful to think of language extensions (the most commonly-used ones at least) as extending the language. It's more that there is one overall Haskell language and the absence of a particular language extension disables a particular feature. The language is, by and large, more coherent with language extensions turned on, not less coherent.
Maybe it's more coherent, but the fact that, after learning the language, you discover that you have 20 more extensions to learn feels like a never-ending quest. I think that's what the previous commentators mean.
If the language is more coherent with the extensions, they should enable them by default.
Most haskell programmers I know don't know or use all extensions, and do just fine without them.
I personally don't know most of them, and I've been doing haskell professionally for years now. Once in a while, I will need to do a specific thing and will google up stuff, and the thing I'll write will remain contained in a couple files, and callers won't have to know about it.
It is absolutely not a prerequisite to know all extensions to earn one's living doing Haskell, just like one doesn't have to know the JVM or the dozens of design patterns inside and out to program in Java.
> It is absolutely not a prerequisite to know all extensions
Which ones are a prerequisite? OverloadedStrings? TemplateHaskell? How many others?
If the answers to that list were enabled by default in the language, and most Haskellers agreed on them, there really wouldn't be much complaint – you'd reach for these fancy macros when you need them, like you say, and that'd be that. A newcomer wouldn't know that some of them are required reading and others aren't and not know which is which.
Learn them when you come across them, like any other language feature. It's as simple as that. That they are enabled via an extension is really just a historical quirk of the language having been originally defined by a standard.
OverloadedStrings is one of the few extensions you'll see everywhere, but there's nothing to remember about it. It's really the kind of thing you'll understand in 10s and find obvious forever after.
> If the answers to that list were enabled by default in the language, and most Haskellers agreed on them, there really wouldn't be much complaint
Here is the thing: just moving all the extensions into the language and removing the pragma would basically mean there would still be the same amount of things in the language, but with no convenient way to know what's used in a specific file, and it would be hard to research that topic which you don't know.
Extensions are great in that they force devs to label files with searchable names for the concepts they use in the code.
> A newcomer wouldn't know that some of them are required reading and others aren't and not know which is which.
A newcomer, just like a seasoned haskeller, shouldn't bother learning new extensions if they're not about to use them. That's the way to go forward.
I like TemplateHaskell, but it's very much a "turn it on where you need it" thing.
On the other side, ScopedTypeVariables is the way the language should have always worked, and should probably just be enabled globally.
OverloadedStrings is a trivial syntactic convenience, where every use of a string literal is replaced by (fromString "the string literal"). This makes code substantially more readable when you are wanting literal values of (typically) Text. The downside is that the compiler can't always know what type fromString is expected to return, so you sometimes need to add more type annotations, which gets messy again. I don't care much which way it's set at a project level - I toggle it per file if the project default is too much of a pain locally.
> there is one overall Haskell language and the absence of a particular language extension disables a particular feature. The language is, by and large, more coherent with language extensions turned on
Right, that's exactly it! Almost nobody uses Haskell without un-disabling many features; in doing so, they discover "Real Haskell" and leave behind the less-coherent "Plain Haskell".
However, there is no central understanding (that I've ever seen) of what "Real Haskell" is. Everyone defines their own.
Beauty being in the eye of the beholder, it's thus no wonder that wizened Haskellers come back from the wilderness speaking of a thing of magnificence – for the Haskell they have seen is a Haskell they wrought. They found the extensions that suited them, and unlocked the "Real Haskell" of their dreams.
But for a new adventurer setting out, there is no one charted path to Nirvana; only swirling rumors, each as different as the next. Perhaps all travelers agree that a good bit of String is necessary to carry along, but it ends there.
This isn't to say that Haskell fails – there are many who love it, and its curiosities inspire much exploration in plainer realms – but.... well, you can see that it wouldn't be appealing to someone who wants to understand a system instead of playing a choose-your-own-adventure mystery game ;)
This is a tale that people like to weave. I can only imagine that they haven't used Haskell much, or at least my experience is completely different.
Sometimes I try to do something that seems sensible. The compiler says "you can't do this, you need to turn on -XExtension". I do so. The end. It's really not terribly mysterious or complicated.
Ehh... except the times it thinks I could do something with an extension, but it's not actually something I want to do.
Still, it's very nice to have the starting point, and I agree it's not a bad experience overall.
Edited to add an example:
Prelude> 1 2
<interactive>:1:1: error:
• Non type-variable argument in the constraint: Num (t1 -> t2)
(Use FlexibleContexts to permit this)
• When checking the inferred type
it :: forall t1 t2. (Num t1, Num (t1 -> t2)) => t2
Prelude> :set -XFlexibleContexts
Prelude> 1 2
<interactive>:3:1: error:
• Could not deduce (Num t0)
from the context: (Num t, Num (t -> t1))
bound by the inferred type for ‘it’:
forall t t1. (Num t, Num (t -> t1)) => t1
at <interactive>:3:1-3
The type variable ‘t0’ is ambiguous
• In the ambiguity check for the inferred type for ‘it’
To defer the ambiguity check to use sites, enable AllowAmbiguousTypes
When checking the inferred type
it :: forall t1 t2. (Num t1, Num (t1 -> t2)) => t2
Prelude> :set -XAllowAmbiguousTypes
Prelude> 1 2
<interactive>:5:1: error:
• No instance for (Num (Integer -> ())) arising from a use of ‘it’
(maybe you haven't applied a function to enough arguments?)
• In the first argument of ‘print’, namely ‘it’
In a stmt of an interactive GHCi command: print it
There's nothing wrong with enabling FlexibleContexts, but it's not going to solve your problem. You likely don't want AllowAmbiguousTypes on globally (though it's not as dangerous as it might sound). The final error message you get is the most useful, so going on this little journey isn't terrible, but it could have been short circuited if you took a beat to understand the extension it was asking for, and why it's being asked for in this context.
Edited to add:
The problem with the above code, of course, is that I'm trying to apply the function 1 to the value 2, but 1 doesn't represent a function. But it could! Because the way number literals in Haskell are handled is much the same way as OverloadedStrings works - 5 is treated as (fromInteger 5) - and they have type (Num a => a), so that the context gets to pick which numeric type they're interpreted as.
As far as the language is concerned, I could make integer literals represent functions that (say) add that number to an input:
instance Num (Integer -> Integer) where
fromInteger n = (+ n)
...
... but that's not actually what I wanted. Almost certainly, it was a typo.
Sure, you are an experienced Haskeller so you have seen the sharp end of this. If this were a Haskell forum I might be proclaiming we have too many language extensions and the error messages are terrible! As were are on a forum for generalists I think the fairest thing to do is to point out as strongly as possible that, no, extensions don't mean that Haskellers have to choose to learn one of several incompatible languages, they don't mean you have to learn anything more than just another language feature, they're really not an impediment to being successful with Haskell in any way, and the fact that they are extensions at all is a quirk of having a language defined by a standard.
Extensions are additions not changes except in minor but fix cases. C++ has a dozen different styles and they are all implicit not even labeled with a flag.
Haskell doesn't have any more language extensions than other languages of its age, the only difference is that they're explicitly called out.
When Python adds new string formatters or generators or async, they're just in the language. When Haskell does that they require you to explicitly turn the new feature on. It's just a different strategy of managing language change. Versus the classical approach you get a lot more eyes on your language additions and have a nice "beta" period where you can make improvements before there is wide adoption.
This is basically the same approach that Rust is taking but they've been better about removing the feature flag when the feature is stabilized.
Hmm, those are language _features_ not language _extensions_, right?
And yes, I think the Rust comparison is a great one – I'm not the sort of person who would want to use nightly Rust and turn on feature flags; I'd want to use Stable rust with no extra features.
Extensions are just per-module importable language features.
These features have tradeoffs around usability and power, so making them importable defers that tradeoff to the end programmer, who is best positioned to make those decisions. It's kind of the opposite of the paternalistic approach the Go designers take.
This is just complaining about naming at this point. An "extension" must necessarily be extending the language with something. That something is a feature. When the GHC devs add a new feature to the language, they put it behind a flag and call it an extension. This is the only way features are added to GHC Haskell.
> they've been better about removing the feature flag when the feature is stabilized.
The developers of rustc define the language, while the developers of GHC are trying to be first an implementation of a purportedly independent standard.
I don't raise this as defense or attack on either side, but I think it's an important bit of context in understanding the differences.
Huh, this is like saying "who needs parsers?" Well, they've been invented for a reason. So after the NoSQL movement, we now have NoParser. I fear we might end up back at assembly or even microcode. Sure it will make life simpler, but it's the wrong kind of simple.
It's more inspired by TypeScript I'd say and there are also features inspired by Rust.
I'd call it readable haskell not hinc.
Most popular languages created sort of universal visual language of what things mean when they look certain way and weird languages with very cool mechanics like Haskell, have much trouble with conveying this mechanic to the new user when they don't subscribe to this visual language (for historical or whatever reasons).
Mainstream languages probably look very verbose, clunky and ugly to many people who like Haskell-like syntaxes more. I feel this way when looking at a Pascal code.
But they have to keep in mind that this verbosity is a feature and they should not act surprised that many people like it. This verbosity makes many things explicit, and I'll not be the first to think that "Explicit is better than implicit".
Sometimes, the verbosity just makes the language verbose. A pythonista will have zero trouble telling you when a block ends, despite not having braces around it.
Verbosity is unrelated to a language's explicitness as this is a semantic decision. It is usually related to syntax, which has other effects.
As a Typescript developer, I really like this! Haskell has always been difficult for me to grok but this syntax makes bridging the gap really intuitive. Bravo!
As far as functional languages go, F# (and thus OCaml which is the parent language of F#) IMO has the best syntax. The code is actually readable even if you haven't ever written a functional program and everything feels super practical about it.
Its a shame that F# doesnt gets the attention it deserves.
I want to like Haskell, but I think lazy evaluation is fundamentally broken. Haskell's facility for optional strict evaluation doesn't really repair the fundamental defect, which leads to memory leaks and unpredictable performance. And certain magic functions are strict anyway, and you just have to know what those are. I just don't see making lazy sequences easy as being worth the cost of making lazy evaluation the syntactic default.
Given that Haskell is fundamentally and irreparably broken (IMHO) in this way, it seems like a waste to devote effort to it instead of better functional languages like ML.
&& is always strict in its first argument. Pattern matching is strict. It's hard to predict when some random function you call with force a thunk you've passed it. My position is still that lazy-by-default is a really bad evaluation policy and that consequently, Haskell is a dead end in language evolution.
Well, if you need to evaluate (&&) you will necessarily need to evaluate its first argument. Much like (||). This behaviour works similar in many other languages.
On the other hand, the first argument passed to (&&) will never be evaluated until needed, so it is still lazy as expected. A good way to test this behaviour is to try this in GHCI:
let a = (length [1..] == 3) && False
Simply declaring a won't enter in an endless loop, but asking to show a will indeed loop endlessly.
Likewise, it is hard to understand how you expect pattern matching to not look at an expression's value when deciding which branch of the code it should use next.
> My position is still that lazy-by-default is a really bad evaluation policy and that consequently, Haskell is a dead end in language evolution.
Yeah, I have understood that you have an opinion. Nevermind that people run it in production without issues.
People run Java in production without issues, but we know today that languages shouldn't have nullable-by-default pointers. Software is infinitely malleable: that you can make something work is no argument that it's the right thing.
I dont think a lack of curly braces is whats keeping haskell from taking off. It’s the “I need to go study category theory to be able to read the docs on any useful package” problem and the proliferation of language extensions. Taking some of the ivory tower out of the learning curve will do a lot more than adding some tokens.
Honestly the syntax of Haskell is unnecessarily weird and I think this project is proof that it doesn't have to be. Haskell's syntax has really put me off learning it. I'd even say some parts of the syntax are elitist - they are almost deliberately - and unnecessarily! - hard for beginners but great if you are a language nerd and understand currying, lambda calculus and so on.
This is way better, but to be honest I don't think I'll use it because the extra easiness of learning with a sane syntax is probably going to be not worth the extra hassle caused by not learning the syntax that everyone else uses.
Without OO as main paradigm, and without being shoehorned into the JVM, Haskell has no chance to be mainstream. Stop trying to make it mainstream, it will only remove from Haskell what makes Haskell great.
128 comments
[ 3.4 ms ] story [ 171 ms ] threadLike in the example of
vs In the second version I can clearly see that I'm creating a function that is supposed to take one argument. It even has a name that might indicate what I'm expecting.Don't get me wrong. All the required information is there but some of it is "written between the lines".
Sort of like CoffeeScript which people tend to hate (and I love, just because I understand JS perfectly and in comparison to CoffeeScript it looks noisy for me).
Nobody seems to add parens to separate arguments from function calls though in Haskell, never seen it but it works.
Of course. This "(x)" and this "x" are the same. You can put any expression inside parenthesis. Haskell also has tuples, that you write as "(a, b, c)" and can use as a function parameter.
There is even some functions in base for converting functions that receive a tuple into functions that receive many parameters and the other way around.
Nobody in Haskell does this (to the point I had no idea it could be done until I tried it) so clearly it's something you get used to, but boy does it look real alien to me.
It is indeed different from other languages, that require an argument list in parenthesis. It can be different because values and functions are not ambiguous on Haskell; there isn't such thing as a function with no argument.
That said, I really appreciate seeing work like this. Haskell is super powerful and it's great to see transpilers like this that are lowering the bar of entry to Haskell to those more familiar with the traditional languages. Nice job!
> easier to refactor and automatically indent/format code.
What are the advantages of white-space insensitive code for refactoring and formatting?
[1] https://en.wikipedia.org/wiki/Haskell_features#Layout
For refactoring, it's less error prone when you're using an editor without refactoring support for it, would be my guess.
For indent/format, your guess is as good as mine, AFAIK programs don't care whether block separators are called INDENT or {. Though the parsing of the latter would be somewhat trivially simpler as you don't need to store a stack of indent levels.
For formatting, it means I never need to manually add any spaces or tabs: the formatter always knows exactly how to indent it. With whitespace-sensitive code, I have to manually pick the indentation, as different indentations could have different semantics. That's why if I hit tab in Emacs when editing a Haskell file, it will cycle through multiple possible indentions. If I hit it in a whitespace-insensitive language, it will just move it to the "correct" indentation straight away and I don't need to worry about it.
Using {} braces in Haskell seems less intuitive, at least to me. E.g. how would I use braces to avoid the need to manually indent a nested where statement?
If simplicity of parsing, and cut-and-paste are important, we should be using Lisp, of course, but there are still different indentation styles.
Warning: untested code
TL;DR math notation
Because they are different things. Brackets are for types, curly braces are for "bodies" (struct definitions, initializers, statements, function bodies, control flow construct bodies), parentheses are for overriding precedence.
There are obviously exceptions that don't fit in this general scheme: e.g. function call syntax is based on parentheses, so it's not just about precedence.
All this is just a post-fact rationalization; this all comes from standard math notation.
Math uses parentheses for function application. Math uses commas for arguments of functions of multiple variables. Math uses curly braces to state of things are defined (sets, inductive rules) etc. Math uses parentheses to override precedence.
All the rest is that historical accident. Angle brackets were unused so they choose them for types. Square brackets for array indexing? No idea why
Curly brackets may be used for sets but they are also used for different cases of functions, or as a more succinct and precise way of expressing sentences about multiple things than writing “foo, respectively bar” all over the place, or for Stirling numbers of the second kind, or for whatever other things an author might want.
Other “structure making” notations like group presentations use angle brackets. Ideals are sometimes written with their generators in angle brackets, sometimes with round brackets. Similarly tuples or sequences may be written with either.
Matrices are sometimes written with square brackets and sometimes round, and never with commas. Vectors typically are written with round brackets but may be square. Row vectors sometimes have commas.
Function application is sometimes written with round brackets. Arguments are sometimes separated by commas, sometimes by semicolons, sometimes with one argument as a subscript or implicit. Application often omits brackets altogether. Sometimes the function goes on the left, sometimes it goes on the right.
Now consider the few features which I find are relatively common to different mathematics notations are context, alignment and structure in 2 dimensions, juxtaposition, implicitness, and terseness. I think I see basically none of these in the common C/Rust style syntax you claim to be so rooted in mathematical notation, and indeed I would guess that you would oppose them.
I don’t buy your arguments at all.
Surely implicitness and tersness were wisely used in math notations then and now, but devising a formal syntax that would work for C at the time was likely deemed too much work for little gain.
there is a lot of random decisions that shaped the C syntax, as it's ultimately a random even that so many modern languages have chosen to continue that trend.
My point was more about why it's not completely irrational to use "many different grouping symbols" since math notation also uses many different grouping symbols (rather than arguing the exact reasons this or that symbol is used instead of something else)
And coming up against arbitrary three-four-character combinations people come up with for their operators.
Terser does not automatically mean better (or J and K would've won that argument a long time ago).
At least mainstream languages are usually consistent. A method call is a method call. A structure is a structure. What is this ungodly mess?
Nothing requires that though, it's more of a debatable community habit, similar to the overly abstract or cutesy names.
> At least mainstream languages are usually consistent. A method call is a method call. A structure is a structure. What is this ungodly mess?
Haskell is arguably way more consistent there: "mainstream languages" also have operators, often overloadable, you just can't declare your own operators in most of them.
Haskell simply allows sigils to be used as infix functions, aka binary operators, and furthermore allows those to be used as prefix functions, as well as prefix binary functions to be used infix.
/polls returns an array of Poll. /polls/:question_id returns a Poll. /polls/:question_id/results returns a PollResults.
the thing you call a mess is the combination of type level operators which helps you describe your API which is the essence of functional programming.
It is telling that this is "just a typed api with 3 routes". Somebody who haven't learned all the specific bits of Haskell (or, for that matter, specific bits of this program that may be overloading strange operators to define routes) won't even start to guess that it's defining routes. They can guess it is defining an API, thanks to the words used at the beginning of the snippet. And even though you told us that it's defining a route, I cannot figure out what it is really doing. It is just not clear.
So, yes, it looks like a mess for the foreign eye. I have no doubt it looks straightforward as soon as you learn Haskell and practice a bit.
Mainstream languages also look gibberish for people who haven't learned programming though. That's also something that we have to humbly keep in mind while, as foreigners who practice other programming languages, we discuss the syntax of Haskell.
It also looks nothing like what programmer might encounter earlier when learning programming languages.
Actually I think Haskell seems to be a little bit similar to mathematical notations so it might be easier for mathematicians to learn than programmers. Unfortunately they don't really have a business case for knowing Haskell and they learn Python instead.
it does nothing. :) that code sample is a description of the API. nothing more. when you're writing programs in a functional way, you describe your computation rather than actually computing it as opposed to imperative languages. imagine summation in math. it is a description rather than computation. you compute the sum according to the description. this API description is exactly the same.
the code in the comment is incomplete which makes it harder for you to figure out. where are the handlers? how do we return responses? i believe the rest of the code is actually mapping the API description to actual handlers.
i agree with the comments and sentiments which say mathematicians learn and write haskell easier than the rest of us but i will tell you a secret: i'm a high school graduate with no degree at all let alone a math / cs one. i'm using haskell happily but it took way much longer time than probably everybody else. it just takes a bit of focused time and will to learn it.Could we leave out this tick given that the kind of this parameter is [*]?
[JSON] and '[JSON] have different meanings. Here, '[JSON] does not refer to a list of json files, but to a type-level list of the mimetypes that can be returned. You could see '[JSON, PNG] instead, which would make no sense in the context of a list type declaration.
The genius of Servant is that the DSL doesn't require you to know how '[] is implemented and why it's different from [], you just have to know the DSL.
An eDSL ? Much like every DSL, it requires to invest some time learning, and gives you something if you take that time. It's not really specific to Haskell.
Back when I was doing some Java, I had to learn the logic of maven xml files, and it was exactly the same kind of investment-for-future-reward.
Many people put non-necessary parentheses in their mathematical expressions to help readers (and themselves!) understand them more easily. Sure, they could be left out, but this is not an optimization for the reader.
In your example, your version looks cleaner, but I find more complicated expressions far more readable with parentheses, brackets and all that stuff present in the second version. This is between angle brackets? This is a type! I don't have to parse the whole expression to infer this.
Expressions written with this "redundant" syntax are seekable. You don't need to read the whole book to pick the one thing you need to know and get on with your life. Redundancy also helps remove doubts on whether something is an error or is intended in complicated cases (good things these languages are strongly typed though, it helps a lot).
At the end of the day, it's all a matter of taste and habits.
Would math be usually written in an Haskell-like syntax, probably almost everybody would find it easy to read and beautiful. It's not, and therefore this kind of syntax probably (almost) only pleases people who are into these languages (because they've got used to it) and represents yet another obstacle for outsiders. I'd actually bet that this plays a role in the fact these languages are not more widely adopted. They make them look unreadable at first impression and people move on. It probably even prevent people from contributing to projects written in these languages. This is bad. I've been able to fix things written in languages I didn't know, but Haskell would probably be hard just because of its syntax, which is unfortunate.
On the other hand, it's good to see and develop new / alternative ideas, including on syntax matters.
Therefore I claim it is silly to talk about mathematics notation as if it were a single standard thing because the reality is that mathematics notation depends on context a lot and varies a huge amount by situation. It is not obvious that the kind of elementary algebraic notation one might encounter in high school is the best fit for programming which tends to deal with totally different things.
I actually think Haskell makes a pretty good language for a small group as it may be better moulded to the problem domain. I recall an example from a mailing list where someone defined:
So they could write code like: Which seems silly except that the user has adapted the language to fit themselves. Some mathematicians also prefer function application/composition on the right. It makes values pass through functions from left to right the same way that their types are written.You are right on the fact that it's usual to define arbitrary operators in maths. It's also usual in math to use single letters, including Greek letters, for function names and constants or parameters, and this is fine in mathematical formulas or texts, when there are not too many of them and when there are explained next to the formula, but that would be a nightmare in a code base. Ever read a program written by a physicist, who translated their formula into a program, without having the "source"?
Weird operators are a feature when writing maths, but could be a code smell in a program. Unlike an academic paper, we have space, and we have auto-completion. Give me a proper, readable, easy to understand name, unless your operator is really omnipresent (it's a great thing we don't have to spell out +, ×, or - of course).
> It is not obvious that the kind of elementary algebraic notation one might encounter in high school is the best fit for programming which tends to deal with totally different things.
Indeed. What I am saying is that if you don't use this notation, it will probably feel less intuitive to many people because what they are used to is this algebraic notation we learn in high school. The Haskell notation might be more apt (and I definitely see its strengths).
However, the opposite is also true, so this is not really an argument: I have not found any real drawback to this notation after years of using it in my programs. This syntax do not feel verbose to me so the Haskell notation does not solve any problem I have.
a good way to make something readable to the general public is to make the opposite of what a mathematician would do
Snarkiness aside, I do agree that a good number of mathematicians don't know how to write, but that does not mean that mathematical writing is intrinsically bad. We don't judge a random programming language as "bad," just because a random member of the public isn't able to parse it (since a random member of the public likely has not learned any programming, much less that language's syntax), so why would we apply similar statements to math in the same way?
People tend to take it for granted that having very variable situation specific notation is bad but I don’t think it’s obviously true.
I certainly don’t think it’s obvious that programming language syntax should be based on the kind of high school algebra notation people are familiar with because that notation is mostly useful for manipulating sums of products of variables raised to powers, and one-argument functions thereof. I don’t think programming fits that category or that addition and multiplication are particularly important operations in programming.
Further, I'll go out on a limb and say that the benefit of reading has NOTHING to do with audience reception of the syntax (whether that audience is new to the language, or extremely experienced with it), and instead has everything to do with what seems familiar. (I.e., I think I'm in agreement with you in that it's what people's habits are as to whether it's appreciated or not; I'm just disagreeing that improved readability is a factor in whether people appreciate the extra boilerplate, or the possible implication that it's a net gain)
(I've disabled this feature in my main editor for a long time though, so I'm used to hit Tab/Shift+Tab as many times as needed when pasting)
I much prefer working in languages wherein the series of tokens absolutely defines the semantics (as opposed to Python where the series of tokens + the context define the semantics)
- make it easy to write and to read at the expense of having complexity in the parser / implementation. Programs (or documents) are written and read a lot of times, it's worth optimizing, as long as it does not make the implementation unmaintainable. I'm saying that as a writer of several manually crafted parsers.
- avoid ambiguities in the grammar at all cost. They just suck for every party involved.
As for indentation vs braces to delimit blocks, I practice both and don't really have any preference.
[As others have pointed out, you get the choice of layout in Haskell syntaxes and, as it happens, that Scheme syntax also allowed you to use sexp input.]
Coming from a C-style language background, I've always found the syntax of languages such as Haskell and ML to be alien, to the point that I can't get my head into learning one of them, despite their obvious power and utility.
I've been looking for some kind of C-style dialect for one of these languages for a long time, so thank you very much for creating this.
At the risk of hijacking this thread; is anyone here aware of other projects for Haskell or other languages that achieve a similar goal?
it's such a beautiful thing ..
PureScript always seemed a little more appealing from that perspective, but doesn't seem to have really caught on (eg; https://trends.google.com/trends/explore?date=today%205-y&ge...)
This makes me feel that, totally apart from the syntax, Haskell/GHC is still more of a laboratory for doing programming language research. Which is great for what it is; I would say Haskell's original motto "avoid popularity at all costs" still holds. I also had hope for languages like PureScript that make an opinionated choice about a subset of those features for the sake of a consistent development framework.
This is simply false. Quite a few of them are not much more than syntax sugars, e.g. with LambdaCase I can write
instead of With OverloadedStrings I can write "foo" instead of (T.pack "foo") or whatever and it picks the right "pack" function based on the inferred type.OverloadedLists does the same for listy things, e.g. now [1] can be a literal vector and you don't have to say V.fromList [1].
TupleSections lets you write (1,) when you want a function that tuples something with 1 (so (1,) applied to 2 becomes (1,2)).
NamedFieldPuns lets you write
instead of -----And then there are a bunch of "Deriving…" extensions that don't require any knowledge either, you just enable the pragma, and it lets you say e.g.
instead of having to explicitly implement the FromJSON instance by hand. I have used Deriving a bunch and have no idea how they work :-)Looking through my main project, the only other extension I use on a regular basis is ScopedTypeVariables. That one's a bit more difficult to explain since you have to have some idea what the words "scope" and "type variable" mean, but basically lets you refer to a type variable from the outer function signature in the type signature of a where-clause. Normally, with
the a's are seen as different types. With ScopedTypeVariables, you can say and the a's are the same type. So that lets you have explicit type sigs on helper functions that are restricted to the type of the outer function. (Though I am not an expert on this so maybe I've got it all wrong. But that doesn't stop me from using it to make stuff with.)Googling "most popular haskell language extensions", I see this article: https://lexi-lambda.github.io/blog/2018/02/10/an-opinionated... which lists 34 recommended extensions and admits "a few of them are likely to be more controversial than others". Half of them don't have names which make their purpose immediately obvious to me.
A more data-driven approach is turned up here: https://gist.github.com/atondwal/ee869b951b5cf9b6653f7deda0b... which concludes "only 10 extensions show up in more than 10% of the Haskell files on GitHub, and 20 show up in more than 5%!"
Its discussion on reddit (https://www.reddit.com/r/haskell/comments/916jj0/popularity_...) highlights some gaps in the analysis, but also the general notion that "everybody uses language extensions, but not everybody uses the same ones".
Of course, each language extension changes the syntax and/or semantics of the language, so in order to "learn Haskell" I'd have to learn one or more flavors/styles of it – and there doesn't seem to be one standard set of language extensions that are commonly used.
Which isn't to say they're not an issue - Haskell really needs a new language standard to clean up that mess, but they don't really fragment the ecosystem the way that, say, the Python 2 vs 3 split did.
To share more on where I'm coming from, one example: when I look at things like Yesod's "Hello world" section in "Getting Started", the first thing I see is 4 pragmas: https://www.yesodweb.com/book/basics and I just don't have much information on what those are, how commonly they're used[0], whether (and why) I need them to use Yesod, etc.
I have a kneejerk reaction to that since I want to be able to understand the code I'm seeing and I'm not sure what's going on behind the scenes. But kneejerk reactions are bad, so I'll give them the benefit of the doubt next time!
[0] Well, I definitely understand OverloadedStrings and TemplatHaskell to be very widely used, and think I get their gist.
For completeness - Overloaded strings are the Haskell parallel to Python's b-string, f-string, u-string, etc. TypeFamilies are "let me write functions over types", which sounds like it would be dramatic enough to introduce a different coding style, but since it, like other extensions, needs to play nicely with the type inference, it doesn't conflict with other extensions, so things mostly just work as you'd expect. Since extensions generally work nicely together, most people just turn on "the usual" extensions for the whole project (all the more reason we need a new standard), and only add explicit pragmas in files to call out heavyweight things like TemplateHaskell. In tutorials you're more likely to see them all listed at the top, though, to be clear about what's being used.
> Of course, each language extension changes the syntax and/or semantics of the language, so in order to "learn Haskell" I'd have to learn one or more flavors/styles of it – and there doesn't seem to be one standard set of language extensions that are commonly used
> It seems like every Haskell project makes use of a different subset of 10 to 20 language extensions, each of which requires some deep knowledge of type theory to understand how it affects the language.
These comments are both pretty off the mark. It's unhelpful to think of language extensions (the most commonly-used ones at least) as extending the language. It's more that there is one overall Haskell language and the absence of a particular language extension disables a particular feature. The language is, by and large, more coherent with language extensions turned on, not less coherent.
If the language is more coherent with the extensions, they should enable them by default.
I personally don't know most of them, and I've been doing haskell professionally for years now. Once in a while, I will need to do a specific thing and will google up stuff, and the thing I'll write will remain contained in a couple files, and callers won't have to know about it.
It is absolutely not a prerequisite to know all extensions to earn one's living doing Haskell, just like one doesn't have to know the JVM or the dozens of design patterns inside and out to program in Java.
Which ones are a prerequisite? OverloadedStrings? TemplateHaskell? How many others?
If the answers to that list were enabled by default in the language, and most Haskellers agreed on them, there really wouldn't be much complaint – you'd reach for these fancy macros when you need them, like you say, and that'd be that. A newcomer wouldn't know that some of them are required reading and others aren't and not know which is which.
OverloadedStrings is one of the few extensions you'll see everywhere, but there's nothing to remember about it. It's really the kind of thing you'll understand in 10s and find obvious forever after.
> If the answers to that list were enabled by default in the language, and most Haskellers agreed on them, there really wouldn't be much complaint
Here is the thing: just moving all the extensions into the language and removing the pragma would basically mean there would still be the same amount of things in the language, but with no convenient way to know what's used in a specific file, and it would be hard to research that topic which you don't know.
Extensions are great in that they force devs to label files with searchable names for the concepts they use in the code.
> A newcomer wouldn't know that some of them are required reading and others aren't and not know which is which.
A newcomer, just like a seasoned haskeller, shouldn't bother learning new extensions if they're not about to use them. That's the way to go forward.
On the other side, ScopedTypeVariables is the way the language should have always worked, and should probably just be enabled globally.
OverloadedStrings is a trivial syntactic convenience, where every use of a string literal is replaced by (fromString "the string literal"). This makes code substantially more readable when you are wanting literal values of (typically) Text. The downside is that the compiler can't always know what type fromString is expected to return, so you sometimes need to add more type annotations, which gets messy again. I don't care much which way it's set at a project level - I toggle it per file if the project default is too much of a pain locally.
The main reason that they are not enabled by default is that Haskell has a standard. It's like gcc having -std=c++11, except the opposite way around.
Right, that's exactly it! Almost nobody uses Haskell without un-disabling many features; in doing so, they discover "Real Haskell" and leave behind the less-coherent "Plain Haskell".
However, there is no central understanding (that I've ever seen) of what "Real Haskell" is. Everyone defines their own.
Beauty being in the eye of the beholder, it's thus no wonder that wizened Haskellers come back from the wilderness speaking of a thing of magnificence – for the Haskell they have seen is a Haskell they wrought. They found the extensions that suited them, and unlocked the "Real Haskell" of their dreams.
But for a new adventurer setting out, there is no one charted path to Nirvana; only swirling rumors, each as different as the next. Perhaps all travelers agree that a good bit of String is necessary to carry along, but it ends there.
This isn't to say that Haskell fails – there are many who love it, and its curiosities inspire much exploration in plainer realms – but.... well, you can see that it wouldn't be appealing to someone who wants to understand a system instead of playing a choose-your-own-adventure mystery game ;)
Sometimes I try to do something that seems sensible. The compiler says "you can't do this, you need to turn on -XExtension". I do so. The end. It's really not terribly mysterious or complicated.
Ehh... except the times it thinks I could do something with an extension, but it's not actually something I want to do.
Still, it's very nice to have the starting point, and I agree it's not a bad experience overall.
Edited to add an example:
There's nothing wrong with enabling FlexibleContexts, but it's not going to solve your problem. You likely don't want AllowAmbiguousTypes on globally (though it's not as dangerous as it might sound). The final error message you get is the most useful, so going on this little journey isn't terrible, but it could have been short circuited if you took a beat to understand the extension it was asking for, and why it's being asked for in this context.Edited to add:
The problem with the above code, of course, is that I'm trying to apply the function 1 to the value 2, but 1 doesn't represent a function. But it could! Because the way number literals in Haskell are handled is much the same way as OverloadedStrings works - 5 is treated as (fromInteger 5) - and they have type (Num a => a), so that the context gets to pick which numeric type they're interpreted as.
As far as the language is concerned, I could make integer literals represent functions that (say) add that number to an input:
... but that's not actually what I wanted. Almost certainly, it was a typo.When Python adds new string formatters or generators or async, they're just in the language. When Haskell does that they require you to explicitly turn the new feature on. It's just a different strategy of managing language change. Versus the classical approach you get a lot more eyes on your language additions and have a nice "beta" period where you can make improvements before there is wide adoption.
This is basically the same approach that Rust is taking but they've been better about removing the feature flag when the feature is stabilized.
And yes, I think the Rust comparison is a great one – I'm not the sort of person who would want to use nightly Rust and turn on feature flags; I'd want to use Stable rust with no extra features.
These features have tradeoffs around usability and power, so making them importable defers that tradeoff to the end programmer, who is best positioned to make those decisions. It's kind of the opposite of the paternalistic approach the Go designers take.
The developers of rustc define the language, while the developers of GHC are trying to be first an implementation of a purportedly independent standard.
I don't raise this as defense or attack on either side, but I think it's an important bit of context in understanding the differences.
I'd call it readable haskell not hinc.
Most popular languages created sort of universal visual language of what things mean when they look certain way and weird languages with very cool mechanics like Haskell, have much trouble with conveying this mechanic to the new user when they don't subscribe to this visual language (for historical or whatever reasons).
python kind of ignored this "universal visual language", and the language seems pretty popular with newcomers.
Ruby is weirder.
But they have to keep in mind that this verbosity is a feature and they should not act surprised that many people like it. This verbosity makes many things explicit, and I'll not be the first to think that "Explicit is better than implicit".
Conciseness is also a feature, though.
Sometimes, the verbosity just makes the language verbose. A pythonista will have zero trouble telling you when a block ends, despite not having braces around it.
Verbosity is unrelated to a language's explicitness as this is a semantic decision. It is usually related to syntax, which has other effects.
Its a shame that F# doesnt gets the attention it deserves.
Given that Haskell is fundamentally and irreparably broken (IMHO) in this way, it seems like a waste to devote effort to it instead of better functional languages like ML.
Well, if you need to evaluate (&&) you will necessarily need to evaluate its first argument. Much like (||). This behaviour works similar in many other languages.
On the other hand, the first argument passed to (&&) will never be evaluated until needed, so it is still lazy as expected. A good way to test this behaviour is to try this in GHCI:
Simply declaring a won't enter in an endless loop, but asking to show a will indeed loop endlessly.Likewise, it is hard to understand how you expect pattern matching to not look at an expression's value when deciding which branch of the code it should use next.
> My position is still that lazy-by-default is a really bad evaluation policy and that consequently, Haskell is a dead end in language evolution.
Yeah, I have understood that you have an opinion. Nevermind that people run it in production without issues.
Maybe the 'data' could be called 'enum'? I believe Rust/Swift ADT definition use keyword 'enum' and it might make it more familiar to the mainstream.
While the 'await' is pretty recognizable, the word itself seems to be too coupled with asynchronous operation too much.
> For example, where Haskellers would write:
> in hinc the idiomatic translation would be: These seems like very sane, pragmatic choices considering the massive segment of programmers used to C/Java/JavaScript derived syntax.Now, if the currently top comment about category theory could be addressed, I think we'd have something.
This is way better, but to be honest I don't think I'll use it because the extra easiness of learning with a sane syntax is probably going to be not worth the extra hassle caused by not learning the syntax that everyone else uses.
Still, great job! Now do the git CLI. :-)