Design patterns for composability with traits (i.e. typeclasses)?

Sorry to be pedantic, and yes I was aware of that special rule and frankly I think that is silly that only don’t need to put a semicolon on the last block that is the result expression. I’d prefer never put semicolons and the result expression would also be the value of the last expression in the block, or () if the last statement in the block is not an expression, e.g. a while block.

I knew that return was optional on the last line, but I didn’t know if Rust was capable of recognizing if-else as an expression (and I actually thought of checking it since Scala can, but…). I don’t make such assumptions (and hadn’t bothered to try it because I like general, consistent rules so I don’t need to try it in order to not be surprised), because of the special case rules instead of a general one that everything is a expression (and not statement which I think one of the reasons Scala didn’t include while) or my generalized variant (to allow for while statements).

The 2 cents issue for me with lambdas is consistency. Since -> is used to indicate result type on functions, it would be more consistent IMO if it is also used to indicate the result expression for lambdas (anonymous functions). Here I must also criticize Scala for using : for function result types (consistent with function arguments yet…) and => for lambdas (which is non-standard from the -> of Haskel et al, and appears to be an attempt at consistency with = for function blocks that are expressions instead of brace-delimited blocks).

I am just arguing that lack of special cases, makes memorizing and learning a language less surprising.

Oh, I didn’t find it pedantic at all, and I’m glad you were aware. Your specific before and after examples left me guessing as to the precise points of confusion, so I tried to make sure I addressed the ones I guessed.

Regarding syntactical changes — you were comparing the lambda syntax to other languages; I only meant to provide you context for this choice (most likely Ruby), not argue for or against any specific syntax.

That being said, a few newcomers have previously argued for changes to syntax post rust stabilization and the invariable answer, understandably, from the development team has been that the time for that bikeshedding is long past. Rust has a lot of important things in its roadmap to develop toward and backward-incompatible syntactical changes on a stabilized language would be a tremendous burden to the community.

In any case, if you feel the need to argue for it further, perhaps a new thread would be more appropriate.

Thanks also.

Note I am just expressing (my “2 cents”) what I would do differently and what the motivations might be for me to maybe create my own programming language, rather than adopt Rust for my current targeted need:

Note also, that I am interested in identifying Rust’s target focus, so I might (and others do apparently) find a use case for Rust:

I believe we are in agreement?

Example, the following I see the pattern :: more than I see the names (which can be alleviated somewhat with syntax coloring and setting the :: to display in low contrast to the background):

I’d prefer to write that as follows where the . almost disappears:

  • Vec.iter() gives a Std.Slice.Iter
  • Vec.iter_mut() gives a Std.Slice.IterMut
  • Vec.into_iter() gives a Std.Vec.IntoIter

Two dots : is less (½ as?) distracting than four ::, but still IMO more (twice as?) distracting as one .

Perhaps different people visualize code differently. I seem to have a strong visual pattern matching which is difficult for me to turn off, thus the :: is akin to a lawn mower outside while I am trying to listen to music. I don’t seem to be autistic, but perhaps that is a slightly autistic type of skill/handicap although I wonder if others have the same distraction. Note it is not incredibly distracting and I am being perfectionist/pedantic with this comment. What happens is I need to make a conscious effort to read it versus just having it sort of instantly absorbed (wherein I am reading it but it is probably like driving where we do it unconsciously because of muscle memory).

But note I don’t prefer eliding punctuation which makes the code less readable, e.g. arguably Haskell’s elision of parenthesis around function arguments and spaces instead of comma-delimited list of arguments. Haskell is requiring me to lookup or memorize the function declarations so I know how many arguments each function consumes.

Edit: perhaps it is more distracting because combined with doubling a colon, a single colon has a normal meaning in English, math, and computing such that it shouldn’t be used as a delimiter, and especially given the delimited names can be as short as 2 or 3 characters in length. The valid use of a colon in English and computer languages (other than functional programming Cons) is to introduce a part of a sentence that is a logical continuation of the part before it. And for math, predominately to mean “such that”. Thus it doesn’t make sense to have colon-delimited list, since there should only be one part before and aft. Whereas, historically the single dot period has been a delimiter in computing (not just computer languages).

I agree with much of the logic against including the ternary operator, but only if if-else isn’t forcing that noisy brace-delimited syntax:

if !prev { cur } else { prev + cur }

I’d prefer:

if !prev: cur else prev + cur

Or even more clear:

if !prev then cur else prev + cur

Than:

!prev ? cur : prev + cur

But remove syntax coloring and assume we are using a plain text editor:

if !prev { cur } else { prev + cur } <— this style isn’t available in a Haskell/Python block indenting instead of brace-delimited

if !prev: cur else prev + cur

if !prev then cur else prev + cur

Then the English text variants render low contrast confusing the keywords and the adjacent juxtaposed fragments of code, thus we either need the brace-delimited or the operator delimited ternary:

!prev ? cur : prev + cur

Edit: I added a comment at the issue tracker linking to this post.

Edit#2: I’m thinking the best would be to force only ternary on single or double line if and if-else:

!prev ? cur
!prev ? cur : prev + cur

!prev ? cur
      : prev + cur

But I’m not sure if I prefer that compared to:

if !prev: cur
if !prev: cur else prev + cur
if !prev: cur
else prev + cur

Or:

if (!prev) cur
if (!prev) cur else prev + cur
if (!prev) cur
else prev + cur

Or:

if !prev then cur
if !prev then cur else prev + cur
if !prev then cur
else prev + cur

Then for multi-line blocks either brace-delimited or my preferred indented delimited with keywords:

if !prev {
   cur
}
else {
   prev + cur
}
if !prev
   cur
else
   prev + cur

It doesn’t meet the requirement I stated in the OP of the thread, which is if the API we can’t edit provides us a collection of trait Foo (not trait HasArea) and we want a collection of trait Shape then your proposal doesn’t suffice:

Which is why I said we must not discard the data types and thus can’t use a trait as the element type of the collection. So your idea is not even equivalent to my proposal in terms of solving the problem I laid out in the OP nor solves the use-case example I provided.

If Rust had first class intersection (conjunctions), then we could just declare the element type of the collection to be Shape /\ Color, but that still wouldn’t be a substitute for my proposal.

I had already explained upthread that your idea couldn’t work, but my conclusion was based on your prior confusing description of using scoping of the trait for the element type of the collection to model adding a union of new data types to collection and binding them to the unbounded varieties of trait bounds for any function that wants to input the collection.

Now you seem to be describing something different which doesn’t even accomplish the requirements I laid out in the OP, thus isn’t equivalent in expression and solving the Expression Problem compared to what I am proposing.

The ONLY way to solve the problem I am articulating is with my proposal.

I did not agree with your explanation. I think that this will work just as well as the union idea, and has precisely the same consequences on compilation.

Can you explain concisely what the problem is you are trying to solve? The solution I proposed solves the expression problem whilst still using traits. What problem are you trying to solve?

I have explained how I would implement the trait solution, and as the definition of soundness is that the type system only admit correct programs with respect to the implementation, then if the implementation works for all the type semantics it is by definition sound.

In this case we are starting with a sound type system, and extending it with a single property (trait extension), all we need do is prove we have an implementation for trait extension to prove soundness.

No it doesn’t. I explained in the prior post what it doesn’t solve. Why are you suggesting code and substitutes when you ostensibly don’t even understand yet the problem I am trying to solve. Wouldn’t it behove you to re-read the thread again first. Perhaps on second reading from start to finish, I won’t be required to repeat myself. If it is still unclear after re-reading, then I can endeavor to clarify what is not clear based on questions.

But irrelevant, because it doesn’t solve the Expression Problem as I have explained the problem that can’t be solved with any form of monkeying around with trait objects.

The suggestion I made allows new types to be added to a collection without touching code outside of the new module you are implementing, you simply ‘impl’ all the traits required to insert the type in the collection. It also allows you to add new traits to the collection by ‘extending’ the bounds of the collection trait, like “extend Shape with HasArea”, providing you provide ‘impl’ for each type that might be inserted into the collection. This provides extension in both directions, without having to modify any of the existing code whether in the same module or a different module.

I don’t see how this matters. We don’t need to know the types, in fact it is important we don’t (otherwise we don’t really have runtime polymorphism).

You are not understanding the Expression Problem. Your suggestion does nothing to solve the problem I stated upthread that the consuming function may need an entirely different trait than the one in the collection’s element type trait object. I repeated this so many times already. I just repeat this twice again in the past 2 minutes.

Right but then you can locally extend the containing trait with the one you want. We keep the set of trait bounds on the Boxed trait open.

My gosh, in the prior 2 minutes, I already explained why you can’t:

I really don’t understand what you are trying to say here. We can extend Foo with Shape at any time in any module using the new feature of trait extension (not implemented in Rust or Haskell), which I have proposed an implementation method for.

If the element type of a collection provided to you (which you can’t edit) is for example trait Foo trait object and your function wants a trait Shape trait object bounds, the compiler has erased the data types thus you have no way to know which data types are in that collection. There is nothing you can do with trait extensions to make the data types come back after they’ve been discarded from the compiler’s AST (or not even provided in the case of a DLL or separately combined linkable modules).

You have to prove you implement all the required traits when you insert the type into the runtime polymorphic container, so this is when the validation that you provide all the dictionaries takes place, before the types are erased. What we do is gather all the traits required of the elements in the collection from all the different modules (as pub traits are part of the module API this information is there already), and use that final trait list to check the types implement all the required traits (IE Foo and Shape).

We don’t care about DLLs because they only support ‘C’ types anyway so we have to import DLLs as foreign ‘C’ functions anyway. I would also be unsafe to add new requirements to a collection that already has elements in it.

I think you forgot the meaning the Halting problem.

There’s no problem with the halting problem here, why would you think there is?

Think again. :wink:

You will have to explain, because I think you must be missing something. I have already explained how the implementation would work… there’s no recursion because there are only two phases we need to care about, static polymorphism and runtime polymorphism, there is not an infinite regression of polymorphisms…