Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

That'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.



I 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%...

Clearly brilliant.


The 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.


Idiomatic 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.


    #  Rust
    let double = |x| 2 * x;
    double(3);

    // F#
    let double = (*) 2
    double 3

    -- Haskell
    let double = (*2)       
    double 3

    #  Python
    double = lambda x: 2 * x
    double(3)


If I am understanding that |x| 2 * x in Rust is an anonymous function, another F# equivalent is fun x -> 2 * x.


It's a closure, not just an anonymous function, though it of course has an empty environment so it's not a significant difference in this case. Rust doesn't have syntax for anonymous functions, only closures.


The Haskell and F# examples above use partial application.

This also works:

  let zeroCount = numbers |> Seq.filter ((=) 0) |> Seq.length


I thought that the Rust playground was requiring me to use return, but I was mistaken. However, it actually lends to my original point though because I would wager that dropping the return was part of the influence of ML on the beginnings of Rust.


Expression orientation certainly comes from that general space, for sure.


> 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.

The answer is no for this trivial function. As your code goes beyond this it gets harder for humans to parse and so the syntax becomes more necessary.

My earlier analogy is pretty perfect here actually. Do you really need spaces in "thank you"? No clearly not. Does that mean you shouldn't use spaces at all?

> Are people here really making the argument that F# and MLs don't have clean syntax?

No. You are literally replying to a comment where I agreed that it is "clean".


I love most entries in the Lisp family.

I find Python to be quite readable as well.


If 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.


> 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.


MLs 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.


I 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.


When 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.


To learn a new language, you need a reason to learn that new language and you need sufficient drive to go through with it.

Having familiar constructs not only make it easier to code-switch between languages (people that work on multi-language projects know that pain pretty well), but also decreases the barrier to entry to the language in the first place.


> 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?


They 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_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.


I think Ada's verbosity is one of its strengths. Cannot say the same for Java, however.


There'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.


I mean, it is supposed to be verbose for descriptive clarity.


Java is really not that verbose if steer out of the 25+ years old conventions that were defined to justify expensive proprietary tooling. Leave all fields package scope or public final, so you don't need getters and setters. On JDK21+ use records and pattern matching. Go procedural + lambda, don't be afraid to use static methods. When OO is required, prefer composition over inheritance. Adopt a proper annotation processor library such as autovalue to eliminate remaining boilerplate.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: