Live data from Hacker News

Parse, Don't Validate and Type-Driven Design in Rust

harudagondi.space

21–30 of 89 posts

Re: Parse, Don't Validate and Type-Driven Design in Rust

#21

The alternative is one type, with many functions that can operate on that type. Like how clojure basically uses maps everywhere and the whole standard library allows you to manipulate them in various ways. The main problem with the many type approach is several same it worse similar types, all incompatible.

Yeah, there's something of a tension between the Perlis quote "It is better to have 100 functions operate on one data structure than 10 functions on 10 data structures" and Parse, don't validate. The way I've thought about it, though, is that it's possible to design a program well either by encoding your important invariants in your types or in your functions (especially simple functions). In dynamically typed langua…

There's probably a case for both. Core logic might benefit from hard types deep in the bowels of unchanging engine.

The real world often changes though, and more often than not the code has to adapt, regardless of how elegant are systems are designed.

Re: Parse, Don't Validate and Type-Driven Design in Rust

#22

The alternative is one type, with many functions that can operate on that type. Like how clojure basically uses maps everywhere and the whole standard library allows you to manipulate them in various ways. The main problem with the many type approach is several same it worse similar types, all incompatible.

This sounds like the "stringly typed language" mockery of some languages. How is it actually different?

Re: Parse, Don't Validate and Type-Driven Design in Rust

#23
Note that the division-by-zero example used in this article is not the best example to demonstrate "Parse, Don't Validate," because it relies on encapsulation. The principle of "Parse, Don't Validate" is best embodied by functions that transform untrusted data into some data type which is correct by construction.

Alexis King, the author of the original "Parse, Don't Validate" article, also published a follow-up, "Names are not type safety" [0] clarifying that the "newtype" pattern (such as hiding a nonzero integer in a wrapper type) provide weaker guarantees than correctness by construction. Her original "Parse, Don't Validate" article also includes the following caveat:

> Use abstract datatypes to make validators “look like” parsers. Sometimes, making an illegal state truly unrepresentable is just plain impractical given the tools Haskell provides, such as ensuring an integer is in a particular range. In that case, use an abstract newtype with a smart constructor to “fake” a parser from a validator.

So, an abstract data type that protects its inner data is really a "validator" that tries to resemble a "parser" in cases where the type system itself cannot encode the invariant.

The article's second example, the non-empty vec, is a better example, because it encodes within the type system the invariant that one element must exist. The crux of Alexis King's article is that programs should be structured so that functions return data types designed to be correct by construction, akin to a parser transforming less-structured data into more-structured data.

[0] https://lexi-lambda.github.io/blog/2020/11/01/names-are-not-...

Re: Parse, Don't Validate and Type-Driven Design in Rust

#25
post #14

btw the “quoth” crate makes it really really easy to implement scannerless parsing in rust for arbitrary syntax, use it on many of my projects

Interesting looking crate. You don't seem to have any examples at all though so I wouldn't say it makes it easy!

Re: Parse, Don't Validate and Type-Driven Design in Rust

#26

You can go even further with this in other languages, with things like dependent typing - which can assert (among other interesting properties) that, for example, something like get_elem_at_index(array, index) cannot ever have index outside the bounds of the array, but checked statically at compilation time - and this is the key, without knowing a priori what the length of array is. "In Idris, a length-indexed vector…

I wish dependent types were more common :(

Re: Parse, Don't Validate and Type-Driven Design in Rust

#27
post #24

The article quickly mentions implementing addition: ``` impl Add for NonZeroF32 { ... } impl Add for NonZeroF32 { ... } impl Add for f32 { ... } ``` What type would it return though?

Would have to be F32, no? I cannot think of any way to enforce "non-zero-ness" of the result without making it return an optional Result, and at that point we are basically back to square one...

Re: Parse, Don't Validate and Type-Driven Design in Rust

#28
post #22

The alternative is one type, with many functions that can operate on that type. Like how clojure basically uses maps everywhere and the whole standard library allows you to manipulate them in various ways. The main problem with the many type approach is several same it worse similar types, all incompatible.

This sounds like the "stringly typed language" mockery of some languages. How is it actually different?

[deleted]

Re: Parse, Don't Validate and Type-Driven Design in Rust

#29

You can go even further with this in other languages, with things like dependent typing - which can assert (among other interesting properties) that, for example, something like get_elem_at_index(array, index) cannot ever have index outside the bounds of the array, but checked statically at compilation time - and this is the key, without knowing a priori what the length of array is. "In Idris, a length-indexed vector…

Rust has some libraries that can do dependent typing too, based on macros. For example: https://youtube.com/watch?v=JtYyhXs4t6w

Which refers to https://docs.rs/anodized/latest/anodized/

Re: Parse, Don't Validate and Type-Driven Design in Rust

#30
post #24

The article quickly mentions implementing addition: ``` impl Add for NonZeroF32 { ... } impl Add for NonZeroF32 { ... } impl Add for f32 { ... } ``` What type would it return though?

Would have to be F32, no? I cannot think of any way to enforce "non-zero-ness" of the result without making it return an optional Result , and at that point we are basically back to square one...

> Would have to be F32, no?

Generally yes. `NonZeroU32::saturating_add(self, other: u32)` is able to return `NonZeroU32` though! ( https://doc.rust-lang.org/std/num/type.NonZeroU32.html#metho... )

> I cannot think of any way to enforce "non-zero-ness" of the result without making it return an optional Result, and at that point we are basically back to square one...

`NonZeroU32::checked_add(self, other: u32)` basically does this, although I'll note it returns an `Option` instead of a `Result` ( https://doc.rust-lang.org/std/num/type.NonZeroU32.html#metho... ), leaving you to `.map_err(...)` or otherwise handle the edge case to your heart's content. Niche, but occasionally what you want.

Post reply on HN