"newtypes can provide a sort of safety, just a weaker one. The primary safety benefit of newtypes is derived from abstraction boundaries. If a newtype’s constructor is not exported, it becomes opaque to other modules. The module that defines the newtype—its “home module”—can take advantage of this to create a trust boundary where internal invariants are enforced by restricting clients to a safe API."
From an outside perspective, you seem to be arguing the same things the author has already stated in the article, which is why he's asking you to read the article more carefully.
For example, you may be interested in her recent talk on effect systems in Haskell at ZuriHac 2020,[1] based on the eff library she mainly contributes to.[2]
[1]: https://www.youtube.com/watch?v=0jI-AlWEwYI
[2]: https://github.com/hasura/eff
> he's asking you to read the article more carefully
she
The author appears to be claiming that this is somehow distinct from (and qualitatively weaker than) "type safety", without any justification for that claim.
"To some readers, these pitfalls may seem obvious, but safety holes of this sort are remarkably common in practice. ... Proper use of this technique demands caution and care:
* All invariants must be made clear to maintainers of the trusted module...
* Every change to the trusted module must be carefully audited to ensure it does not somehow weaken the desired invariants.
* Discipline is needed to resist the temptation to add unsafe trapdoors that allow compromising the invariants if used incorrectly.
* Periodic refactoring may be needed to ensure the trusted surface area remains small...
In contrast, datatypes that are correct by construction suffer none of these problems."
To get more specific:
* Safety holes of this sort are remarkably uncommon in practice, in my experience.
* Modules should be small and easily understood. If maintaining a module's invariants becomes too complex then you can, and should, recursively apply the same techniques within the module, breaking it up into smaller modules. This is good development practice anyway, as is periodic refactoring. So these cautions are a lot less costly than they sound; in fact the cost may well be zero.
* You need to resist the temptation to add unsafe trapdoors in any other approach as well; this simply isn't a disadvantage that's in any way specific to using newtypes.
In fact, in some cases, it's useful to retain (redundant or invalid) state that's discarded in "correct by construction" data structures. For example, saving application configuration as Option<int> is easier for the application to read correctly (int value, bool present). However when a user is editing an Option<T> through a GUI (checkbox, number) pair, then unchecking the checkbox will set the value to None and discard the last entered value, which is a poor user experience in my view.
I admit that I might be missing something fundamental to this discussion.
> the claim that newtype-based approaches are not type safety
To use your phrasing, the claim is that newtypes do not in and of themselves provide type safety, but that "newtype-based approaches" can and do provide a weaker form of safety than constructive modeling. Further, it's important to understand what the critical additional steps are in such approaches to achieving that safety and avoid cargo-culting "newtypes make things type-safe", and to understand the ways this safety can be violated.
The post implied that the newtype-based approach was somehow "not type safety" and offered significantly less safety in practice than the constructive model approach. The first of those is definitely false, and based on my own experiences I don't believe the second.
> Further, it's important to understand what the critical additional steps are in such approaches to achieving that safety and avoid cargo-culting "newtypes make things type-safe", and to understand the ways this safety can be violated.
Naively it sounds like that would be important, but I'm not convinced it actually is in practice. My experience is that even a cargo-culty use of newtypes delivers most (maybe even all) of the defect rate benefit, and that these vigorous warnings about newtypes are more likely to reduce real-world safety (because people faced with cases that they can't produce a constructive model for will be encouraged to use an alias, or no type at all, rather than a newtype) than improve it.
Your second point is certainly arguable and does represent a substantive disagreement with the post unlike your first comment. I think the post makes a solid case that gratuitous use of newtype absent any abstraction boundary is an anti-pattern that provides little benefit if conversions are done ad hoc. Sprinkling on some newtype can certainly help some cases, but the post encourages critical thinking about these issues and explores where this reasoning falls down. If your claim is that critical thinking gets in the way of cargo-culting approaches that lead to "nominal" type safety then you would be right. Your last parenthetical doesn't seem like a reasonable response of someone who has read and understood this post. The post specifically encourages the use of newtype with abstraction boundaries where practical and works through an example.
The author is still carefully avoiding describing any newtype-based approach as "type safety", to the point that they would mislead anyone who wasn't already familiar with the subject.
> Your last parenthetical doesn't seem like a reasonable response of someone who has read and understood this post. The post specifically encourages the use of newtype with abstraction boundaries where practical and works through an example.
It works through an example and follows that with a long list of (IMO exaggerated) weaknesses of that example, which seems more designed to dismiss it. It does not "specifically encourage" using newtypes at all (indeed it says "if you are fond of newtypes" as though one would only ever use newtypes for private emotional reasons). It concludes with "correctness by construction should be preferred whenever practical".
As for whether the example is being "dismissed" or not, I also think that's manifestly false. See: "[this tradeoff] is often a very good one!" while still diving into exactly what the tradeoff is.
Type safety is valuable in that it relatively easily helps achieve a goal of alerting the programmer that he/she is mixing together things that should have not been mixed. Ie. using something in a context where it should not be used.
That goal can be achieved in other ways and it is not even the best way to achieve that goal.
Type safety is valuable in that:
* does a lot of sense for a user of the programming language (ie. programmer) IF it is done sensibly (forget about template metaprogramming, that is not sensible). Thinking about things in terms of types is natural to our brain structure.
* warns early (ie. your program does not compile)
* enforces on everybody working on the application (you can ignore conventions but you can't ignore compilation error)
Now, what I don't like is that people forget that types and type safety is there to achieve the goal (making programs better, easier to read, easier to reason about, harder to make a mistake).
Thinking about type safety as if it was some kind of security mechanism to prevent any and all kinds of mistakes is IMO very misguided. I don't want to battle with my type system, I want to write working code and I want to use type safety mechanism that lets me get there reasonably without spending huge amount of extra effort.
So, I think, there is a sweet spot for type safety, where it is enough to help prevent most mistakes and make reading the code easier, but not enough to make you reduce your productivity.
Not only that, the sweet spot will depend on what task you are working on.
For example, languages with relaxed type safety are good for rapid development -- ie. "scripts". There sweet spot is shifted to less type safety because your program is much smaller and you don't need help remembering what are different things and there is probably less developers working on it.
On the other hand, for large projects with many people working on them, long build times, expensive testing, etc. you would very likely want a language with stronger typing (like Java that is very poor language but offers very good practical type system).
Now, Rust type safety is a special IMO compared to other type safety mechanism in that it is very hard for the user (ie. programmer) but I still am ok with this. The reason is because there the type safety is used to get so much more that would not otherwise be possible (at least not with our current knowledge).
This is unnecessarily rude. Maybe you should have written the article more carefully, or read my comment more carefully.
> newtypes are useful, but only when used in certain ways and in a weaker sense than constructive data modeling
Constructive data modelling can be an easier way to provide certain kinds of guarantees in some circumstances, perhaps. But the claim that newtype-based approaches are not type safety remains false.