Alternatives to nested angle brackets

I thought it would be good to discuss this and see if we can come up with a real alternative.

Consider HashMap<Vec<String>, Vec<Rc<Cell<int>>>>. It’s heavy, and it’s easy to mismatch the closing brackets.

One idea was to remove angle brackets entirely.

I find this a bit too extreme, but only removing nested angle brackets might be a good compromise. The question is then what to do with commas: they could be limited to the first level of nesting, or removed.

That would give us HashMap<Vec String, Vec Rc Cell int> or HashMap<Vec String Vec Rc Cell int>

Unfortunately that reduces readability in some cases. If the commas stay, the rules are a bit more complex as well.

Are there any other good alternatives?

1 Like

Commas need to stay. What if you want HashMap<String, HashMap<String, int>>, which is a very possibly type given JSON etc.

The problem with commas past the first level of nesting is that HashMap<String, HashMap<String, int>> would then become HashMap<String, HashMap String, int> which in my opinion is a bad middle ground between nothing at all and the current syntax. If we drop commas past the first level of nesting it would be HashMap<String, HashMap String int>. Of course, after two levels of nesting (of type parameter lists containing more than one type), it becomes messy (I wonder how common this actually is).

An alternative is that angle brackets are required wherever there are multiple type parameters. This means HashMap<String, HashMap<String, int>> remains the same but you still get the benefit of being able to say HashMap<Vec String, Vec Rc Cell int>

3 Likes

Ugly <<<<<>>>> nesting is one thing, but this suggestion is rather difficult to parse for the reader :frowning: I would rather the explicit ugliness.

If any of these collide with other notation, I didn’t know about it but the idea could possibly still work.

HashMap<Vec<String>, Vec<Rc<Cell<int>>>>
// wider braces doesn't help
// replace `<x>` with `<:x:>`
HashMap<:Vec<:String:>, Vec<:Rc<:Cell<:int:>:>:>:>

Would tagging help?

// numbers count depth
// replace `<x>` with `<#:x:#>`
HashMap<1:Vec<2:String:2>, Vec<2:Rc<3:Cell<4:int:4>:3>:2>:1>
// numbers count from left to right
HashMap<1:Vec<2:String:2>, Vec<3:Rc<4:Cell<5:int:5>:4>:3>:1>

Though, even if it works, I have no idea if you’d want this…

X/Y to mean X of Y (single arg only): HashMap<Vec/String, Vec/Rc/Cell/int>

3 Likes

Also, enforce parenthesis coloring. Excel is pretty good about this: http://tinypic.com/r/14jlvfl/8

Note: I wanted to inline the example but I don’t have the privileges to do so. This will probably disappear in a week or so.

Could the library just provide some common shortcuts.

OptBox<T> = Option<Box<T>>  // one type for a nullable pointer.
VecBox<T> = Vec<Box<T>>       // pointer-array
RcCell<T> = Rc<Cell<T>>           // shared object

Then you will have a lot of cases to cover manually. Seems like work for no benefit.

If we removed brackets for the simple cases like:

Option Box Vec Box Rc Cell

etc. you can still have more complex examples using the brackets for grouping, but the simple examples are easier even more complex examples like

HashMap<Vec<String>, Vec<Rc<Cell<int>>>> become HashMap<Vec String, Vec Rc Cell int>

2 Likes

On vim there's the g:lisp_rainbow option for coloring parenthesis on lisp code: http://i.imgur.com/0GIHout.png (discourse won't let me embed the image) Are you suggesting something along these lines?

That’s it exactly. It’s a bit easier to see on your black background also.

Since it hasn’t been mentioned: use <> for grouping types only when needed to put it into one parameter.

Option T
Option <Vec T>
HashMap String <Vec T>
Option <HashMap String int>

Typecheck would know how many type parameter each type constructor needs, and parsing should be fine(?)

Working(?) example:

fn find<'a, T>(key: &str, map: &'a HashMap String T) -> Option &'a T { .. }

cmr says commas need to stay for HashMap<String, HashMap<String, int>>, let’s try it:

HashMap String <HashMap String int>

Maybe this syntax will be very friendly for simple-ish types and when it’s complicated it gets very similar:

Foo<A<B<C,D>,  E<F>>>
 is
Foo <A <B C D> <E F>>
1 Like

[Only remove angle brackets for single-type-parameter types]

Option Box
Vec Box
Rc Cell

I really like this suggestion, because parsing does not rely on the arity of the type.

  • Single type parameter => can optionally(?) use juxtaposition-style
  • Multiple type parameters => always use angle brackets

Advantages

  1. Rules are easy to understand: syntactic sugar for one simple scenario Vec<int> <==> Vec int
  2. Unambiguous without symbols: compiler doesn't need to know number of type parameters to correctly parse type expression. If you forget one type in the remove-all-angle-brackets example, all other types shift to the "wrong" positions, potentially resulting in very confusing error messages

Disadvantages

  1. Only improves some expressions (but probably simplifies complex scenarios enough to make them acceptable)

I think the same arguments for using infix notation over postfix notation in mathematical expressions applies here. Yes, the latter is unambiguous as a whole (complete, correct expression with full arity information) but working on those expressions is not an ideal experience.

Personally, I would always allow angle brackets, even for single-type-parameter types. Makes transition from other languages easier and doesn't cost anything implementation-wise.

Putting no symbol between the elements makes it read wrong to me: Option Box isn’t an “option-box”, it is an “option of box”. I suggested X/Y for “X of Y”, but maybe some other symbol could be used.

Slash also suggests one being over the other, in the sense that the Option controls/encapsulates the Box (Option/Box), or the Cell controls/encapsulates the int (Cell/int).

Hm… I see what you mean.

Personally, I think I’ve been around Haskell etc. long enough for this to feel OK. There you read f x as “f of x” anyway.

I’m not too keen on the separating character. It seems to separate things (optically) that belong together (logically). The ‘dot form’ (A.B) doesn’t make sense in my mind. Those are not paths, they shouldn’t look like paths.

But what about doing it in the opposite order like in OCaml and F#: int Option. The original example would become:

HashMap<String Vec, int Cell Rc Vec>

Well, I don’t know Haskell. Since Haskell is more the Rust way, then probably they might favour the spaces approach then, so long as it is parseable.

(On parsing, does Rust depend on knowing definitely where the type stops? For example after as. Because many of these suggestions may introduce complications on that front.)

I agree. And <String Vec, Vec Rc Cell int>HashMap looks just wrong somehow.

On the topic of "paths". I'm not sure about the 'encapsulates' explanation. The one way in which I can imagine "path"-like syntax making sense is this:

Vec.int <=> From all possible Vec types, get me the Vec for ints. (or Vec/int or Vec\int ...)

Maybe use a character that looks more like "glue" as opposed to "navigation"?

  • Vec+int Might cause issues/confusion with generic parameter bounds
  • Vec-int, Vec-Rc-Cell-int
  • Vec@int, Vec@Rc@Cell@int (looks super busy, probably not)

Is having two different syntaxes for supplying type parameters really worth it? Sounds like an unnecessary hurdle for newcomers.

More variants:

HashMap<Vec<String>, Vec<Rc<Cell<int>>>>
// Use -> but confusable with `<>` ends
HashMap<Vec->String, Vec->Rc->Cell->int>
// a little better
HashMap[Vec->String, Vec->Rc->Cell->int]
// also => if available
HashMap[Vec=>String, Vec=>Rc=>Cell=>int]
// still . is nicer
HashMap<Vec.String, Vec.Rc.Cell.int>
// if : is made available (see Note)
HashMap<Vec:String, Vec:Rc:Cell:int>

// can extend : but not compact 
HashMap<Vec:-:String, Vec:-:Rc:-:Cell:-:int>
HashMap<Vec:=:String, Vec:=:Rc:=:Cell:=:int>
HashMap<Vec:_:String, Vec:_:Rc:_:Cell:_:int>

// not a fan of ~
HashMap<Vec~String, Vec~Rc~Cell~int>

Note: Would RFC 135 free up the colon?

[EDIT] Forgot that -> was return type. Was likening it to C++:

(*x).y equivalent to x->y

Use a font style like Hasklig so that << and >>map to a custom style. Then it is possible to type and make it look different. You could also use their other special symbols (-<, -<<, and =<< for example).