Open chouzar opened 3 years ago
Thanks for this write up, there's some really good information here and I've a lot to think about.
Do you envision that when a contract is violated that it returns an Error? Or that it crashes?
How much of this could be resolved at compile time? Currently we have tried to do as much statically as possible.
Do you envision that when a contract is violated that it returns an Error? Or that it crashes?
From what I have seen... libraries that try to integrate contract programming into the language just throw an error at runtime, but there is not hard rule of how the program should behave on failure aside from the "fail fast" mantra. I think both, crashes and types could be provided as failure mechanisms.
For example contracts with no explicit error type would just fail the assertion and crash:
ensure result.cuteness == (cat_a.cuteness * cat_b.uteness), "bad cat arithmetic"
pub fn multiply_cutenesss(cat_a: Cat, cat_b: Cat) -> Int {
cat_a.cuteness + cat_b.cuteness
}
While contracts may also be neatly integrated with the Result
type:
pub type CatArithmeticError{
BadArithmetic
}
ensure result.cuteness == (cat_a.cuteness * cat_b.uteness), BadArithmetic
pub fn multiply_cutenesss(cat_a: Cat, cat_b: Cat) -> Result(Int, CatArithmeticError) {
cat_a.cuteness + cat_b.cuteness
}
And then the result of the above implementation would be wrapped either as:
Ok(Int)
or Error(BadArithmetic)
NOTE: Here I'm unsure what is the convention in Gleam (or other typed languages) regarding errors... make heavy use of the default
Maybe
types? Create your ownCustomTypes
? Both? π€
Other possibilities would be to plugin user defined parametrized types or to define an entirely different category for contract types, for which the same principle as Result
could apply.
How much of this could be resolved at compile time? Currently we have tried to do as much statically as possible.
Yes! That would be ideal and languages/tools like Dafny seem to do very well on that regard, but it is a complete mystery to me how "totality" of contracts is achieved π³οΈ.
An interesting idea here is the approach that the Typeclass elixir library takes:
TypeClass meets this challenge halfway: property testing. definst will property test a small batch of examples on every data typed that the class is defined for at compile time. By default, it skips this check in production, runs a minimal set of cases in development, and runs a larger suite in the test environment. Property testing lets TypeClass check hundreds of specific examples very quickly, so while it doesnβt give you a guarantee that your instance is correct, it does give you a high level of confidence.
Something similar could be done but for contracts themselves, but for the checks to be effective we would need to specify the data generators somehow.
I found some very old Rust code, seems like they used to have preconditions: https://github.com/rust-lang/rust/blob/16e4369fe3b5f00aa3cdc584a4e41c51c0d3ca8a/src/libstd/list.rs#L131
Interesting! It is cool that the predicate is embedded directly on the signature, I wonder if the check is verified at compile time or just at runtime. Reminds me of guards.
It could be said that erlang and elixir guards are precondition constructs, but with current limitations they're more of a pattern matching mechanism.
Do you know what is the alt
construct doing? π€
I've not found much information on it yet, but it's possibly a good one to look at.
I think alt
might be the old name for match
.
Sorry for the long lull, nothing much to add aside from wanting to reference: https://github.com/xldenis/creusot.
An experimental contract library for rust that leans on provers to check its invariants, still on development.
This is meant as an exploratory topic πΊοΈ on how the the ideas behind contract programming could fit Gleam; below I try to make a case and summary of the pros and cons but there are not hard conclusions on any approach.
Would love anyone's thoughts π on the subject and feel free to close this suggestion if its simply not a fit for the language.
Brief intro to contracts π©βπ«
Contracts as in contract programming is a way of defining assertions about your code. It usually looks a bit like this:
Here the
require
andensure
clauses are ran at runtime; withrequire
being checked before our implementation starts andensure
after the implementation code is finished.The main value here is that contracts are a simple yet powerful technique to define properties around our program, in the lack of a more thorough compiler the runtime assertions can be very helpful for identifying parts of our code that may fail.
Making the case for gleam π
The main way in which Gleam helps designing programs is through types, taking an example from the guide we can define a custom type
Cat
like:Which later could be used in our code as:
But as we all know... it is impossible for a cat to have its cuteness factor below or equals to 0; sadly our program doesn't seem to account for this allowing us to do:
Thankfully we have ways in which we can avoid bad data!
Opaque types to the rescue π
Gleam allows us to define opaque types to hinder the type manipulation and have a stricter interface:
By making the
Cat
type opaque and offering thenew
interface we can now safely create cats in external modules by using:Using cats would now require some helper functions to unwrap our
Result
values, this can make it hard sometimes on deciding when to draw the line between using wrappers, custom types or just try to be less strict about data.So... contracts to the rescue? π
Assertions may be used in a limited fashion in Gleam to emulate contract pre or post conditions:
Special predicate syntax may make it easier to the eyes:
Not only functions, assertions could go directly on type definitions:
More interesting properties can be found with more complex functions:
Also other annotation trinkets could prove useful for performance or handling failures.
Thoughts π
Gleam's design really tries to push on using types to its full-extent for designing programs; by adding contract programming through assertion we could add an extra layer of data integrity to our codebase, at the expense of runtime evaluation. Being a language on the BEAM maybe the "fail fast" mindset is not completely out of the question.
It is a simple system that can be expanded to a bunch of appliances, for example contracts blend well with automated tests allowing to do pseudo property testing; however it could be argued that contracts can also be too generic of a solution and that more purposely defined tools would do just as well if not better without introducing extra constructs to the language.
Contracts may also introduce some noise when used to define properties that are not really relevant, like when the assertion equals an implementation:
Which feels odd and redundant, but in any case contracts would always be opt-in.
More on the topic π€―
I'm tying too many ideas here... but wanted to add some links that intersect with the design aspects of contract programming.
On the value of "failing fast" π€
Not that I know these topics extensively but here are some resources (that I want to review later) on the value of "failing fast".
Previous Gleam art π¨βπ¬
Again, libraries or extra constructs to the language may solve more on point issues and avoid feature bloat.
On contracts π€
Other languages and ecosystems that embrace contract programming.