Hacker News new | ask | show | jobs
by mjburgess 651 days ago
Kinda disingenuous, you don't reskin one language in another to make an argument about syntax -- you develop a clear syntax for a given semantics. That's what rust did not do -- it copied c++/java-ish, and that style did not support the weight.

When type signatures are so complex it makes vastly more sense to separate them out,

Consider,

  read :: AsRef(Path) -> IO.Result(Vec(U8))  

  pub fn read(path):
    inner :: &Path -> IO.Result(Vec(U8))

    fn inner(path):
      bytes := Vec.new()

      return? file := File.open(path) 
      return? file.read_to_end(&! bytes)
      return OK(bytes)
    
    inner(path.as_ref())
7 comments

Rust does allow you to split out generic specifications into a separate "declaration block" so things don't get too busy. Like you could write the original Rust code as:

    pub fn read<P>(path: P) -> io::Result<Vec<u8>>
    where
        P: AsRef<Path>,
    {
        // ...
    }
Personally I don't find your example with the type signature completely separate to be easier to read. Having to look in more than one place doesn't really make sense to me.

Funny, though, Rust's 'where' syntax sorta vaguely superficially reminds me of K&R pre-ANSI C:

    unsigned char *read(path)
        const char *path;
    {
        /* ... */
    }
I have a feeling that most of the clarity you find in your example comes from better use of whitespace. Consider:

    pub fn read<P>(path: P) -> io::Result<Vec<u8>>
    where
        P: AsRef<Path>,
    {
        fn inner(path: &Path) -> io::Result<Vec<u8>> {
            let mut bytes = Vec::new();
    
            let mut file = File::open(path)?;
            file.read_to_end(&mut bytes)?;
    
            Ok(bytes)
        }
    
        inner(path.as_ref())
    }
Plus, your example does not have the same semantics as the Rust code. You omitted generics entirely, so it would be ambiguous if you want monomorphization or dynamic dispatch. Your `bytes` and `file` variables aren't declared mutable. The `try` operator is suddenly a statement, which precludes things like `foo()?.bar()?.baz()?` (somewhat normal with `Option`/`Error`). And you weirdly turned a perfectly clear `&mut` into a cryptic `&!`.

Please don't assume that the syntax of Rust has been given no thought.

To me this example is not more clear than normal Rust
If you've programmed a lot in Rust, then that's a win -- since its "not more clear", and yet, you've no experience in this syntax.
He said "not more clear" and yet you're responding as if he'd said "not less clear" or "exactly as clear"? This seems strange?
I strongly support your point, but the example is still sand-in-the-eyes for me. I hold that one symbol should not alter the semantics of a program and there should never ever be sequences of one-symbol syntactic elements.

In an Ada-like language, it would be something like

  generic
      type Path_Type implements As_Path_Ref;
      type Reader implements IO.File_Reader;
  function Read(Path: Path_Type) return Reader.Result_Vector_Type|Reader.Error_Type is
      function Inner_Read(P: Path) return Read'Result_Type is
      begin
          File:  mutable auto := try IO.Open_File(P);
          Bytes: mutable auto := Reader.Result_Vector_Type.Create();
          try Reader.Read_To_End(File, in out Bytes);
          return Bytes;
      end;
  begin
      return Inner_Read(Path.As_Ref());
  end Read;
Huh. My brain says logically I should find that better, but the -lack- of punctuation is making it really tricky to skim read the way I do most languages.

I'm not arguing the example you found sand-in-the-eyes is necessarily good but my mental skim reading algorithm copes with it much better.

It is indeed harder to skim, and I find myself much more relying on syntax highlighting and file outline when working in Ada than in C++. Not due to the lack of punctuation, though, which is in place but serves the guiding role only (it is MLs that tend to abolish all the unnecessary punctuation), but because of the overall dense style.

But while it is harder to _skim_, it is easier to _read_, as you don't have to concentrate on and decipher the syntax, risking to miss some of the crucial elements (oh, how do I hate missing ampersands in C++!).

Rust target user is C/C++ developer. not using brace is out of options.
Do you think C++ programmers are incapable of learning a language without braces?
No, but many are unwilling.
People may disagree on specifics, but you're definitely right that being able to separate the function signature from its definition would be very helpful in complex cases.
Why? You can easily find parameter names in the signature if you just put them on separate lines. For me there's very little reason of putting them all together on separate line after the signature. And then when you look for a type of a parameter you know the name of, it gets difficult.
Now there are too many colons.
Original code contains more. (::). Personally I don't understand why there are `::` and `.` to access deeper namespace levels. It's a static language, compiler should know how to resolve attributes and `.` should be enough for all cases.
It's because then you could have name collisions between variables and modules. E.g. it's ambigious whether io.Error is in a module called io, or a field of a variable called io. Which means you can no longer use fully qualified names regardless of context.
> Which means you can no longer use fully qualified names regardless of context.

How about don't shadow module names with variables if you want to use it? It works amazingly well for a bunch of other languages.