Live data from Hacker News

The type system is a programmer's best friend

dusted.codes

451–460 of 467 posts

Re: The type system is a programmer's best friend

#451

Earlier quoted context omitted.

> they can be represented by a term and therefore can have a type. It is informative, I see what you mean. Let me try again following your terms: An object is a type plus behaviour (mutable state).

I am saying that an object has a type, rather than is a type or some augmentation of it. An object is a term-level construction and therefore is not really comparable to a type. Types can be given to both state and behaviour. For example, a function type describes pure behaviour. Note that statically-typed OOP languages have a name for the nominal types of objects: "classes". One could say that a class is a type repr…

Fair enough, seems much more correct: A class is a type representing both state and behaviour.

Re: The type system is a programmer's best friend

#452

Earlier quoted context omitted.

Go does not have operator overloading, and numeric operators must have identical types. So if you have `var x int = 5` and `var t time.Duration = 2500 * time.Millisecond`, you have to `time.Duration(x) * t` or `time.Duration(x * int(t))`. It's slightly better than languages with no operator overloading nor newtypes at all (well, actually a lot better given other things you can use newtypes for) but without operator o…

Two options: 1. Don't type units like that. 2. Allow the * operator to multiply a time-unitful value with an untimed scalar and disallow using it with two time-unitful values. Presumably the requirement that * operands are the same is arbitrarily modifiable and go developers have control over what types operators take.

1. Uh, ok. Might as well throw out the whole thread then?

2. A "time unit" is not a special type of value. You can construct arbitrary types of integers, and it is common to do so. `*` has no clue what a time is, just that it's "not an int" (for example).

Re: The type system is a programmer's best friend

#453
post #434

Earlier quoted context omitted.

> The point of typing is to move errors to compile-time. You've got to understand: Whether or not a particular email address is verified or not isn't something that's generally known at compile time... That means compile time checks cannot actually guarantee if that email address is verified or not . The compiler cannot know something that isn't known. There must be runtime code somewhere doing the check. What people…

> Whether or not a particular email address is verified or not isn't something that's generally known at compile time Yes, obviously. What is knowable at compile time is the stuff that comes after: given that the input to this function has property X, does the output have property Y? Arguments, not premises. > But... (1) you can do the very same thing without types Sure, and if someone makes an SMT-solver-oriented la…

I like types just fine. They are great for expressing static constraints.

Re: The type system is a programmer's best friend

#454
post #282

Earlier quoted context omitted.

Casting is often necessary for parsing inbound data from certain mysql libraries or CSV or JSON depending on how it's written. I would guess that might be what the parent is talking about. That said, if you don't cast or parseFloat or whatever in JS you're going to have a lot of trouble. And if you're doing that, why not do it in Typescript where you'll know that the data you're accessing has been safely cast based o…

> Casting is often necessary for parsing inbound data from certain mysql libraries or CSV or JSON depending on how it's written. No, that's what sum types are for.

I don't see how they're mutually exclusive. I use union types prior to typeguards to end up with ultimately checked, cast values. Say I have a boolean fetched from JSON as "1" or "0". By the time I expose it to the rest of the code as part of a Record, I want to change it to an actual boolean. At first I'm going to treat the inbound value as a union type, e.g. (String | Number | null | undefined | Error). After I deal with the error cases, I'm going to cast it (or in TS, reinitialize it as a single type, boolean) so that any code looking at the imported value sees it as definitely only a boolean without needing to have lots of different pieces of code run their own checks on its type.

Re: The type system is a programmer's best friend

#455
post #282

Earlier quoted context omitted.

> Casting is often necessary for parsing inbound data from certain mysql libraries or CSV or JSON depending on how it's written. No, that's what sum types are for.

I don't see how they're mutually exclusive. I use union types prior to typeguards to end up with ultimately checked, cast values. Say I have a boolean fetched from JSON as "1" or "0". By the time I expose it to the rest of the code as part of a Record, I want to change it to an actual boolean. At first I'm going to treat the inbound value as a union type, e.g. (String | Number | null | undefined | Error). After I dea…

I wouldn't really call that casting, that's just narrowing based on control flow.

Re: The type system is a programmer's best friend

#456

Earlier quoted context omitted.

Code completion, search, cross referencing, and all sorts of other features in vim, emacs, and all kinds of other editors (including Visual Studio) predate LSP by decades. LSP is cool though, an advancement certainly, but it is not a completely new thing. https://en.m.wikipedia.org/wiki/Ctags

If you think ctags or rtags are even remotely comparable to what an actual IDE brings you you have absolutely never used more than 10% of what an IDE with semantic understanding of your code can do

The post I replied to brought up a very narrow set of features that had been available in vim for a long time.

Re: The type system is a programmer's best friend

#457

Earlier quoted context omitted.

If you think ctags or rtags are even remotely comparable to what an actual IDE brings you you have absolutely never used more than 10% of what an IDE with semantic understanding of your code can do

The post I replied to brought up a very narrow set of features that had been available in vim for a long time.

it brings up "Code completion, search, cross referencing, and all sorts of other feature" and again, those work much less well in ctags / rtags than with a proper IDE

Re: The type system is a programmer's best friend

#458

Earlier quoted context omitted.

> Personally I'm excited to see how C++ Concepts can evolve. So, C++ 20 Concepts is basically what Bjarne Stroustrup proposed for a future version of his C++ language in the early 2000s. Several people proposed and WG21 accepted, a far more capable feature set for Concepts, this is often referred to as C++ 0x Concepts, since it was accepted for C++ 0x, the standard that would eventually (after years of delays) become…

To be fair I've mostly used concepts in Nim, not C++ x0 concepts. However, your comparisons of C++ concepts to Rust traits seems to be lacking a lot of details or is plain inaccurate. It also misses the flexibility of C++ concepts. > C++ 0x Concepts was similar to Rust's Trait system in many ways. Particularly notable features of C++ 0x Concepts you might recognise in Rust's traits: Perhaps at the loosest level of co…

Unfortunately you don't seem to have understood much of what I wrote.

The most crucial thing to understand is that C++ 0x Concepts were a significantly more powerful feature than the C++ 20 Concepts you got. Even though I emphasised this, you seem to have muddled them together to produce what you're calling "C++ x0 concepts" in several places, which is not actually a thing.

> That's not possible AFAICT with Rust's traits.

It is of course possible to write a Rust trait for something as useless as "any numeric type regardless of what kind", that's what the Num crate's Num trait is. Because Rust's traits have semantics, useless traits reveal themselves - you can't do much with something whose only decisive property is that it's numeric in some way.

Num also defines some traits for numeric properties that are way more useful, like the additive and multiplicative identities (Zero and One). A type can be numeric without having zero (NonZeroU32 is a trivial Rust example from the standard library) so expressing that you mean specifically a type with additive identity is useful in a way that merely "numeric" largely is not.

The "HasPower" example is revealing though, lots of people's toy Concepts are like this. They just dictate a morsel of syntax. C++ 20 Concepts are indeed suitable for this, but so is nothing whatsoever, because of C++ template "magic".

Why C++ 20 Concepts at all then? Your C++ compiler's diagnostics with nothing whatsoever are terrible because Substitution Failure Is Not An Error. Bjarne's simple "Concepts" can hide this somewhat - the diagnostics you get for a Concept failure are more digestible.

> That's true for C++ concepts, but not entirely true for Rust traits.

No, it would be true for C++ 0x Concepts but those never existed beyond a draft document. It does work for Rust traits as you say but you can't do it for C++ 20 Concepts.

> That doesn't appear to match with C++ resources like: https://en.cppreference.com/w/cpp/language/constraints

Once again you're confused, that document is about C++ 20 Concepts, which exist, but I was describing C++ 0x Concepts, which are much closer to the capabilities of Rust's Traits and were never implemented.

> Everything in Rust traits requires them to be encoded into existing traits.

What you've written is a tautology. So I can only guess what insight you thought you had here.

Maybe you're imagining it's not possible to do obvious stuff like say that a type T must implement both trait A and trait B (a conjunction, signified in C++ Concepts with &&)? I assure you things do that all the time in Rust. foo(p1: T, p2: S) is a function which takes two parameters p1 and p2, the type of p1 must implement traits A, B and C, while the type of p2 must implement traits C and D. For such complicated trait bounds idiomatic Rust would use the where keyword, but it's not mandatory, just easier to read.

In Rust you can't write the disjunctive bounds C++ 20 Concepts can express as || because it's not yet clear (and might never become clear) how to do so in a sound way. C++ doesn't care, none of the rest of the language is sound anyway, so it's too late to worry.

Re: The type system is a programmer's best friend

#459

Earlier quoted context omitted.

To be fair I've mostly used concepts in Nim, not C++ x0 concepts. However, your comparisons of C++ concepts to Rust traits seems to be lacking a lot of details or is plain inaccurate. It also misses the flexibility of C++ concepts. > C++ 0x Concepts was similar to Rust's Trait system in many ways. Particularly notable features of C++ 0x Concepts you might recognise in Rust's traits: Perhaps at the loosest level of co…

Unfortunately you don't seem to have understood much of what I wrote. The most crucial thing to understand is that C++ 0x Concepts were a significantly more powerful feature than the C++ 20 Concepts you got. Even though I emphasised this, you seem to have muddled them together to produce what you're calling "C++ x0 concepts" in several places, which is not actually a thing. > That's not possible AFAICT with Rust's tr…

> The most crucial thing to understand is that C++ 0x Concepts were a significantly more powerful feature than the C++ 20 Concepts you got.

This makes a bit more sense than what you originally wrote.

Though regardless if C++ 0x concepts were more powerful, C++ 20 concepts as they exist today are strictly more powerful than Rust traits. You admit that yourself when you say that Rust traits cannot model disjunctive bounds. They certainly can't do arbitrary boolean predicates or negation.

That means that Rust's trait system cannot implement compile time type checking rules that C++ 20 concepts can today. You cannot encode entire sets of logic in the type system that you can in encode in C++ (or other) languages.

Features like disjunctive logic may not be able to be proved "sound" for things like borrow checking but that's not the point or intent. The point is that you can use arbitrary boolean predicates in inventive ways. Hence my original comment about "seeing where C++ concepts go".

> Even though I emphasised this, you seem to have muddled them together to produce what you're calling "C++ x0 concepts" in several places, which is not actually a thing.

True, my terminology got muddled. Trying to follow your terminology and (odd) backstory about C++ 0x concepts was confusing.

You are incorrect about how C++ 20 concepts work and that makes it more confusing.

There are lots of resources showing that C++ concepts are implicit and don't need to be explicitly instantiated. Take this Rust comparison: https://mcla.ug/blog/cpp20-concepts-are-not-like-rust-traits...

While Rust traits force a "closed" system (to be imprecise) that's easier to prove soundness on upfront, that doesn't make the type system more powerful. It may make it more useful in some people's view. That's a pretty big distinction.

> No, it would be true for C++ 0x Concepts but those never existed beyond a draft document. It does work for Rust traits as you say but you can't do it for C++ 20 Concepts.

Err, no that's incorrect as you can easily check in any of the references I gave. C++ concepts as they exist allow you to call concepts if they fulfill the concept.

In Rust you must own either the trait or the type in order to implement said trait for that type. This is a widely known and deliberate limitation of the Rust trait system. It has some benefits, but is also leads to significant "trait bloat".

The "orphan rule" doesn't exist in C++ 20 concepts. It's an intentional Rust design decision: https://rust-lang.github.io/chalk/book/clauses/coherence.htm...

> It is of course possible to write a Rust trait for something as useless as "any numeric type regardless of what kind", that's what the Num crate's Num trait is. Because Rust's traits have semantics, useless traits reveal themselves - you can't do much with something whose only decisive property is that it's numeric in some way.

I don't really follow what you're trying to say here.

In contrast, I do find it very useful to define default algorithms for any numeric type that matches. It's a core part of C++ numerical libraries.

However, I get that this would be fairly pointless in Rust because you can't do much useful with it without things like generics specializations being stable.

> The "HasPower" example is revealing though, lots of people's toy Concepts are like this. They just dictate a morsel of syntax. C++ 20 Concepts are indeed suitable for this, but so is nothing whatsoever, because of C++ template "magic".

This makes no sense. A "morsel of syntax" or alluding to "C++ template magic" make no sense.

Granted C++ template's are amazing powerful, and amazingly difficult to debug.

C++ 20 concepts provide a useful and flexible way to describe compile time type restrictions, while not limiting C++ templates to the purely adjunctive subset of type logic able to be described in Rust's trait system.

> > Everything in Rust traits requires them to be encoded into existing traits. > What you've written is a tautology. So I can only guess what insight you thought you had here.

It is, I was being lazy but the point I'm reaching for is that using Rust's trait system requires the traits you want to target to already exist and to be implemented for the types in question. Moreover, creating some type-based logic rules using the Rust trait system, requires that logic to effectively already be encoded into the traits (as some combination of adjunctive properties).

This is why you end up with incompatible HAL libraries for various STM32 models, among others. In my opinion it's a very limiting part of the ecosystem.

Re: The type system is a programmer's best friend

#460

Earlier quoted context omitted.

Unfortunately you don't seem to have understood much of what I wrote. The most crucial thing to understand is that C++ 0x Concepts were a significantly more powerful feature than the C++ 20 Concepts you got. Even though I emphasised this, you seem to have muddled them together to produce what you're calling "C++ x0 concepts" in several places, which is not actually a thing. > That's not possible AFAICT with Rust's tr…

> The most crucial thing to understand is that C++ 0x Concepts were a significantly more powerful feature than the C++ 20 Concepts you got. This makes a bit more sense than what you originally wrote. Though regardless if C++ 0x concepts were more powerful, C++ 20 concepts as they exist today are strictly more powerful than Rust traits. You admit that yourself when you say that Rust traits cannot model disjunctive bou…

> This makes a bit more sense than what you originally wrote.

It re-states what I originally wrote, I'm glad you find it clearer now although since this is a matter of history it was all there for you to read if you cared. I don't think I have time to say everything two or three times until you "get" it.

> Features like disjunctive logic may not be able to be proved "sound" for things like borrow checking but that's not the point or intent.

The soundness problem is, unfortunately, fundamental. It's not about the borrow checker. C++ doesn't care whether your program has any logical meaning at all, so long as it is syntactically OK in these cases - of course in such case its meaning is unknown, but the standard explicitly tells compilers not to worry about that, the important thing is that the gibberish compiled, a C++ programmer can congratulate themselves on another successful project.

Maybe I need to expand the abbreviation I used, IFNDR: Ill-Formed, No Diagnostic Required. This is what the standard says to wave away such problems, not only with concepts but throughout the language. "Ill-formed" means this isn't actually a C++ program and so the standard does not define what it means, but "No Diagnostic Required" means the compiler needn't give an error or warning, it just presses on anyway.

[ You might imagine surely they could give a diagnostic, but actually they can't because of Rice's theorem. For a sufficiently powerful programming language you have to pick: 1. Your compiler sometimes gets "stuck" forever trying to decide whether a program is valid. 2. Your compiler reports errors in some otherwise valid programs. 3. Your compiler reports no errors in some invalid programs. Rust chose (2) and C++ chose (3) ]

> C++ concepts as they exist allow you to call concepts if they fulfill the concept.

Once again you've got turned around. The question isn't whether you can call concepts but whether anybody else can implement the concepts, and you simply can't do that. C++ 0x Concepts had "concept maps" to fix this, in Rust obviously the traits are explicitly implemented, but C++ 20 Concepts doesn't have an equivalent.

> Granted C++ template's are amazing powerful, and amazingly difficult to debug.

They're copy-paste. A slight improvement on C pre-processor macros. I suppose it's in the name, "templates" like a mail merge system. It's childishly simple like the cups and balls trick. The resulting mess does indeed produce unintelligible error messages and is also unsound in both obvious and surprising ways.

> In contrast, I do find it very useful to define default algorithms for any numeric type that matches. It's a core part of C++ numerical libraries.

Useful here meaning only you get better error messages than from SFINAE?

> using Rust's trait system requires the traits you want to target to already exist and to be implemented for the types in question.

I think it obviously follows that you can't use things which don't exist.

Post reply on HN