7 ms·
Firstly, this doesn't mention the thing that bothered me the most in my few encounters; how baroque and fluid the tooling is. I have regularly run into code th
by aidenn0 2y ago
Firstly, this doesn't mention the thing that bothered me the most in my few encounters; how baroque and fluid the tooling is.
I have regularly run into code that can only be compiled by a rather narrow range of ghc versions; too old or too new and it just won't.
> Haskell probably has the most elegant syntax across all languages I’ve seen (maybe Idris is better because dependently typed code can become ugly in Haskell really quickly).
I have a general dislike for ML-type syntaxes, and Haskell is my least favorite of the ML-type syntaxes I have encountered. Presumably there is some underlying set of principles that the author would like maximized that I assuredly do not.
> There’s utter joy in expressing your ideas by typing as few characters as possible.
Disagree; if I did agree, then I would probably use something from the APL family or maybe Factor.
- bmitc 2y agoThat's interesting you dislike ML syntaxes when they're generally thought to be the cleanest syntaxes. What syntax families do you generally like? Also, Haskell isn't really ML-syntax. I love MLs but find Haskell syntax pretty ugly.
- The_Colonel 2y ago> when they're generally thought to be the cleanest syntaxes That might be true for academics. But most engineers don't consider ML syntax to be the cleanest, since most don't know any ML language.
- wyager 2y ago> most engineers don't consider ML syntax to be the cleanest, since most don't know any ML language. This is like saying "most uncontacted Amazonian tribes don't like Shakespeare, because they've never read it". Sure, but why would we care about their opinion on this topic?
- unscaled 2y agoThey might think Shakespeare's story are silly even if they did read it. In fact, there is at least one widely publicized instance where exactly the same thing happened with a culture that wasn't exposed to Shakespeare before: https://law.ubalt.edu/downloads/law_downloads/IRC_Shakespeare_in_the_Bush.pdf https://law.ubalt.edu/downloads/law_downloads/IRC_Shakespear... The same idea is probably true with programmers who have grown used to C-like syntax or even Python-like or Ruby-like syntax. Syntax is at least in great part a cultural thing and your "cultural background" can affect your judgement in many cases: 1. Are braces good? Some programmers find them noisy and distracting and prefer end keywords or significant whitespace, but other programmers like the regularity and simplicity of marking all code blocks with braces. 2. Should the syntax strive for terseness or verbosity? Or perhaps try to keep a middle ground? At one end of the spectrum, Java is extremely verbose, but a lot of Java engineers (who have generally been exposed to at least one less-verbose language) are perfectly OK with it. The trick is that the main way that code gets typed in Java used to be copy-paste or IDE code generation (and nowadays with LLMs typing verbose code is even easier) and reading and navigating the code is done with the help of an IDE, so a lot of the effects of having a verbose language are mitigated. Diffs are harder to review, but in the Enterprise app world, which is Java's bread and butter, code reviews are more of a rubber stamp (if they are done at all). 3. Lisp-like S-expression syntax is also highly controversial. Many people who are introduced with it hate it with passion, mostly because the most important syntax feature (the parentheses) is repeated so often that it can be hard to read, but advocates extol the amazing expressive power, where the same syntax can be use to express code and data and "a DSL" is basically just normal code.
- johnisgood 2y agoI think Ada's verbosity is one of its strengths. Cannot say the same for Java, however.
- naasking 2y agoThere's "verbosity as descriptive clarity", and there's verbosity as in "I already said this, why do I have to repeat myself?" Ada unfortunately still has some of the latter, otherwise I would agree.
- bmitc 2y agoMLs aren't really academic languages aside from Standard ML. F# and OCaml are very pragmatic languages. The cleaner parts of Rust came from ML. What syntaxes do engineers find clean? I don't understand the distinction you're making.
- t-writescode 2y agoI assume they mean: * Python, Ruby, C#, Java, Go-style languages? I imagine most developers operate in neither ML languages nor Lisp-style languages. The most advanced "FP" trick that I imagine most developers use is Fluent-style programming with a whole bunch of their language's equivalent of: variable .map do ... end .map do ... end .flatten Addendum: or maybe Linq in C# Addendum 2: And even the fluent-style trick in those languages tends to follow a similar pattern. Using Kotlin and Ruby as examples, since those are my (edit: main) languages, variable .map do |i| something_with(i) end .map { |i| something_else(i) } .flatten shows familiar tricks. The dot operator implies that the thing previous to it has an action being done; and curly braces or the "do" operation both imply a block of some sort, and so a quick glance is easy to follow. In Kotlin, this gets a little bit more confusing (yes, really) because it's common to see: variable .map { somethingWith(it) } .map { somethingElse(it) } .flatten() And now there's this magic "it" variable, but that's easy enough to guess from, especially with syntax highlighting. Anything more advanced than that and the cognitive load for these language starts to rise for people that aren't deep in them. When you're starting working with a new language, that does increase difficulty and may even be so much of a barrier that developers may not want to hop over.
- skydhash 2y agoWhen you start working in a new language, the best thing to do is to get a book and familiarize yourself with any constructs or patterns you are not familiar with. Expecting things to be similar to what you’re used to (which lower the learning curve) is a fool’s errand.
- t-writescode 2y ago
- aidenn0 2y agoI love most entries in the Lisp family. I find Python to be quite readable as well.
- bmitc 2y agoIf you like Python and Lisps, then I'm surprised you don't like F#. It is much more concise than Python, even for pure OOP programming.
- IshKebab 2y agoI also dislike them. And I wouldn't disagree about them being "the cleanest", but cleanliness of syntax is only one concern. I also need to be able to mentally parse it, and on that from OCaml is very bad in my experience (haven't used Haskell much so I can't comment on that). I've said this before but if you remove all punctuation and capitalisation from English it will make it look "cleaner", but it will also make it awful to read. Look how "clean" text is if you get rid of all the spaces! https://en.wikipedia.org/wiki/Scriptio_continua#/media/File%3AVergilius_Augusteus%2C_Georgica_141.jpg https://en.wikipedia.org/wiki/Scriptio_continua#/media/File%... Clearly brilliant.
- bmitc 2y agoThe argument you make about literature is one I have made before, but I don't think it applies here. To me, languages with clean syntax aren't just removing things arbitrarily. They remove things that aren't needed or they otherwise have large amounts of consistency. Let's look at three implementations of a doubling function in modern languages where at least two of them are considered "clean" syntaxes. F#: let double x = 2 * x Python: def double(x: int) -> int: return 2 * x Rust: fn double(x: i32) -> i32 { return 2 * x; } Tying back to my original point, are the brackets, annotations, semicolons, and return really needed? From my perspective, the answer is no. These are the simplest functions you can have as well. The differences get multiplied over a large codebase. Are people here really making the argument that F# and MLs don't have clean syntax? All three of these functions have the same IDE behavior in terms of types being showcased in the IDE and also in the type-checking processes, with F# and Rust's type-checking happening at the compiler level and Python's happening with MyPy. People might argue that I left off type annotations for F#, and that's true, but I did so because they aren't required for F# to compile and have the same IDE features like the other languages. Even if I did, it would look like: F# with type annotations: let double (x: int) : int = 2 * x which is still cleaner.
- steveklabnik 2y agoIdiomatic Rust would look like this: fn double(x: i32) -> i32 { 2 * x } The reason Rust requires types here is because global inference is computationally expensive and causes spooky action at a distance. The only real difference between your F# and this is the braces, which I do personally think makes code easier to read. Of course there’s more overhead in smaller functions. I’d never write this code in Rust. I’d either inline it, or use a closure, which does allow for inference, and dropping the braces given that this is a one liner: |x| 2 * x Different needs for different things.
- yoyohello13 2y agoThat’s funny. I actually love Haskell syntax, it’s by far my favorite language to look at.
- gooosle 2y agoPeople often say that before they realize how complex and messy real-world Haskell looks like. The indentation-based rules aren't even defined anywhere except via imperative code in the implementation.
- brabel 2y agoIf that's true, that's such a disappointment. I always thought of Haskell as the pinnacle of mathematical discipline in programming (though too hard for me to wrap my head around!).
- mjan22640 2y agoSuch a pinnacle would be Agda. Haskell has mathematical holes.
- johnnyjeans 2y agoHaskell is 30 years old at this point. It was pretty cutting edge in the 90s, but the pinnacle has climbed ever higher.
- worksonmymach 2y agoOh sir we call that "operational semantics"
- yazzku 2y agoYou have earned your Martini.
- _flux 2y agoHmm, isn't this the definition? 10.3 of https://www.haskell.org/onlinereport/haskell2010/haskellch10.html https://www.haskell.org/onlinereport/haskell2010/haskellch10... > The effect of layout is specified in this section by describing how to add braces and semicolons to a laid-out program. I wasn't going to actually read that in detail to see if it makes any sense, though, but it looks very detailed :).
- jes5199 2y agoI wrote Haskell for a couple years and I _never_ figured out the indentation rules. The haskell people seemed confused that it was even possible to find it confusing and then there’s “do” notation. I has three people tell me “it’s just a simple mechanical transformation from lambdas” oh yeah? how? they couldn’t explain that either
- Tainnor 2y agoI'll agree about the weird Haskell indentation rules. But do notation really is just syntactic sugar for a chain of >> and >>= applications (plus "fail" for invalid pattern matches). It's not usually pretty or understandable to write it that way, but it's a very simple translation. If the people you talked to couldn't explain it to you, I think they maybe didn't understand it well themselves.
- deleted 2y ago[deleted]
- lmm 2y ago> and then there’s “do” notation. I has three people tell me “it’s just a simple mechanical transformation from lambdas” oh yeah? how? they couldn’t explain that either Scala's for/yield (which is pretty similar) you can literally do an IDE action in IntelliJ to desugar it. I forget the precise Haskell syntax, but do {a <- x; b <- y; c <- z; w } is sugar for something like x >>= $ \a y >>= $ \b z >>= $ \c w . Hopefully the pattern is clear. Were there cases where you actually couldn't see how to desugar something, or are you just nitpicking about them calling it a mechanical transformation without being to explain the precise rules?
- solomonb 2y agomySubroutine = do foo <- mkFoo bar <- mkbar foo mkBaz bar mySubroutineV2 = mkFoo >>= \foo -> mkBar foo >>= \bar -> mkBaz bar
- edflsafoiewq 2y agoNote that this is same desugaring for normal let let x = y in F <===> (\x.F)(y) just replacing function application with a different function (>>=).
- bbminner 2y agoI agree. I have limited experience with FPs, and I can get a hang of what is happening in OCaml examples relatively easily, but Haskell examples feel more like something akin to Forth - if I really needed, I could parse it with my brain, but cryptic one liners like that feel more like "puzzles" than an actual useful program.
- kqr 2y agoI feel like there's nuance here that a lot of people don't mention. There are the actual puzzles that have been minified beyond recognition, and one has to work backwards to figure out what clearer code the author started from. I don't think anyone likes this code, but it gets written by people who are more used to writing code than reading it. There is also the incredibly clear, straight-forward code that could have come from any other language. If you manage a sane production Haskell codebase, you'll have a lot of this. In between those two, there is clear code but which you wouldn't find in any other language. This is code that uses well-known Haskell idioms that anyone who works with a lot of Haskell code will recognise, but which look like puzzles to someone who has not worked with a lot of Haskell. These are things like guard (age person >= 18) $> beer to return Just beer to someone of age, but Nothing to someone underage; Uuid.fromWords <$> getRandom <*> getRandom <*> getRandom <*> getRandom to return a randomly generated UUID; maybe (compute_fresh seed) pure cached_value to use a cached value if it exists, computing it from scratch if it did not; or traverse_ $ \elem -> do error "stub" to create a function that loops through a collection and executes a side effect for each element. These look like nonsense to people not familiar with the idioms, but they appear frequently enough that they are no longer puzzles.
- serbuvlad 2y agoMaybe this is a bit off-topic but I have to say: I don't _get_ FP languages. I get the idea of having functions with no side-effects. I think that's a great thing. But this can be achieved in every language. Sure, the statements in the function will have side-effects on local variables, but as long as the function isn't too long, and you can comprehend all of it at a time, that's not a problem. Your examples of "clean code" aren't any cleaner than what can be found in any imperative language. Using C++ (with std::optional), for reasons of familiarity. (person.getAge() >= 18) ? "beer" : {}; UUID::fromWords(getRandom(), getRandom(), getRandom(), getRandom()); cache.has_value() ? cache : (cache = computeFresh(seed)); for (auto& elem : collection) { modify(elem); }
- mark_l_watson 2y agoCompiler version issues are a pain. For most programming languages I use, I like to constantly update toolchain and library versions. I fight that tendency with Haskell. “cabal freeze” can be your friend. I now find that using some combination of Claude, Gemini, and ChatGPT makes using Haskell more pleasant for processing compiler error and warning messages, suggesting changes to my cabal or stack configuration files, etc.
- johnnyjeans 2y agoFor me, my problem with Haskell's syntax is less rooted in the fact that it's in the ML-family. It's more to do with just how horrific complex type aliases can end up being thanks to currying. Imagine we have type aliases that are built up out of function signatures, made up of more aliases of function signatures, and this is a tower of some 20 layers deep. If you're working with types at different levels of this tower of garbage at the same time, you very quickly end up in an associativity hell and the type signatures you have to work with are beyond unhelpful. Combination of both the flaws of currying as a concept, as well as the complexity of Haskell's type system.