Live data from Hacker News

Names are not type safety

lexi-lambda.github.io

81–90 of 130 posts

Re: Names are not type safety

#81
In Pascal you can easily create such a type OneToFive = 1..5

It should inherit all the normal arithmetic, but does not allow values outside the range

However, these kind of types are causing a lot of problems.

You do arithmetic on them, get a value outside of the range, and then it throws an exception. And an unhandled exception crashes the program, which is often worse than an invalid value.

And it is slow when it has to check if the value is in the range after every arithmetic operation, so you can disable the range checking to make it faster. But then you get values outside the range without an error

The language also assumes everything can be initialized to zero. It just calls "memset" on a allocated memory, so it can be initialized quickly. So you often get values that are zero whatever the range is

And you cannot even check for that, because the compiler knows what values are valid. So if you want to check if the variable has a valid value and write (value >= 1 and value <= 5), the compiler knows it is supposed to be always true, and optimizes it away.

Re: Names are not type safety

#82
I believe that Ada, or perhaps a specific subset with some extensions, achieves most of these goals. You can definitely restrict types to be a range or enumeration of e.g. ints, and they have some facility for contract based function interfaces.

Re: Names are not type safety

#83
I do not see the problem with newtype, but rather with fromOneToFive and toList. As soon as you use them, you are left with an Int and a List. So why complain safety is lost when it has been intentionally lost?

If the compiler does not provide safe ways of working of newtype's, the programmer indeed has to provide them in the form of methods, basically just as shown in the article.

Re: Names are not type safety

#84

In Pascal you can easily create such a type OneToFive = 1..5 It should inherit all the normal arithmetic, but does not allow values outside the range However, these kind of types are causing a lot of problems. You do arithmetic on them, get a value outside of the range, and then it throws an exception. And an unhandled exception crashes the program, which is often worse than an invalid value. And it is slow when it h…

> And an unhandled exception crashes the program, which is often worse than an invalid value.

Please elaborate. Why is working with garbage better than not working at all? Would it not also rather make it easier to find these out-of-range cases with fuzzing?

Re: Names are not type safety

#85

In Pascal you can easily create such a type OneToFive = 1..5 It should inherit all the normal arithmetic, but does not allow values outside the range However, these kind of types are causing a lot of problems. You do arithmetic on them, get a value outside of the range, and then it throws an exception. And an unhandled exception crashes the program, which is often worse than an invalid value. And it is slow when it h…

>You do arithmetic on them, get a value outside of the range, and then it throws an exception. And an unhandled exception crashes the program, which is often worse than an invalid value.

IMO an exception (failing fast) is always better than an invalid value.

Exceptions like these also excel at finding dangerous edge cases when paired with a decent test suite.

Re: Names are not type safety

#86

In Pascal you can easily create such a type OneToFive = 1..5 It should inherit all the normal arithmetic, but does not allow values outside the range However, these kind of types are causing a lot of problems. You do arithmetic on them, get a value outside of the range, and then it throws an exception. And an unhandled exception crashes the program, which is often worse than an invalid value. And it is slow when it h…

A problem that I see with these approaches (e.g. 1..5) is that the possible values are often not set in stone. This means that by using range constraints or similar, there is often an overspecification that can hurt down the road.

Furthermore, the possible values differ from location to location as the values travel through the program. In that sense, the possible values are often underspecified / underconstrained, even though the specifications are already not simple.

This is why I prefer to go with rather little constraints - in a compiled language, you have to specify the physical layout (8/16/32/64 bits?), and I try to specify not more than that in most cases.

Then, so much safety can be achieved by placing asserts in strategic locations.

Re: Names are not type safety

#87
post #77

Earlier quoted context omitted.

Again, you are conflating newtypes per se with approaches using newtype as part of an overall abstraction and interface design including constructor hiding. The post makes this quite clear and the author and others have pointed out this distinction to you multiple times here. Just because you made an inference and argued against it doesn't mean the article was making such an implication. Your second point is certainl…

> Again, you are conflating newtypes per se with approaches using newtype as part of an overall abstraction and interface design including constructor hiding. The post makes this quite clear and the author and others have pointed out this distinction to you multiple times here. Just because you made an inference and argued against it doesn't mean the article was making such an implication. The author is still careful…

If you'd carefully reread the post you'll see that your first assertion is false. It contains this sentence: "But it is a meaningfully distinct kind of type safety from the one I highlighted a year ago, one that is far weaker." The antecedent of "one" is "type safety", which is being provided, just, weaker, not "not type safety" as you claim.

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.

Re: Names are not type safety

#88
post #80

Earlier quoted context omitted.

Why would you use Data.List.head rather than just matching on lst?

I realized the example doesn't make sense for head , since head should always just return the first item of the tuple. I still think it would make sense to define e.g. last using Data.List.last : last :: NonEmpty a -> a last (NonEmpty (a, [])) = a last (NonEmpty (_, lst)) = Data.List.last lst which is safe even though Data.List.last contains error .

You can avoid the error of Data.List.last by 'inlining' it (just like you could in your first example)

   last :: NonEmpty a -> a
   last (NonEmpty (a, [])) = a
   last (NonEmpty (a, x:[])) = x
   last (NonEmpty (a, x:xs)) = last (NonEmpty (a, xs))

Re: Names are not type safety

#89

Earlier quoted context omitted.

You say that, on its own, a newtype is nothing more than a name and that names are not type safety, but then you give the example of using newtypes to prevent someone from adding a distance and a duration. I think it's a false distinction to say that the safety there is some kind of safety that isn't type safety.

Yes, on its own, a newtype is nothing more than a name. The safety comes from pairing a newtype with an encapsulation mechanism and a carefully-designed trust boundary. Without an encapsulation mechanism, I do not consider using newtypes to wrap real numbers with units of measure sufficient to be called “type safety”; in my experience it still requires significant discipline to use properly (because the points of wra…

So basically, "if you don't use newtype to implement an abstract data type, you don't get any type safety because the on-spot wrapping/unwrapping is still possible".

On one hand, yes. On another, no. I am working right now on a Go codebase with lots of newtypes (well, the Go's equivalent), and they're generally casted to/from underlying primitive types basically in two places: when they're serialized into JSON, and where they're deserialized from JSON. And in several places in the middle of the code where you need an explicit cast, well, the cast is explicit. You actually have to consider it.

Mind you, in Go it's almost impossible to hide the newtype's constructor unless you jump through some rather unintuitive hoops. Is Go fundamentally type-unsafe? I don't think it is, although I sometimes wish some structs were impossible to zero-initialize. Then again, that's generally amended by unexporting the struct and exporting its interface instead.

Re: Names are not type safety

#90
post #60

It should be noted that the -- to use the terminology of the article -- extrinsically safe example module Data.List.NonEmpty.Newtype can be converted into an intrinsically safe one by changing the definition of the NonEmpty type from newtype NonEmpty a = NonEmpty [a] to: newtype NonEmpty a = NonEmpty (a, [a]) (and changing the associated functions accordingly -- which can now be implemented without the use of error )…

That's the implementation used in previous posts, like 'parse, don't validate'. This post is talking about using newtypes instead (which seems like a worse choice in the case of NonEmpty, but may be more pragmatic when a correct-by-construction approach would be infeasable)
Post reply on HN