Skip to content

Commit

Permalink
Update based on sync discussion
Browse files Browse the repository at this point in the history
  • Loading branch information
SeanTAllen committed Jan 23, 2024
1 parent df274f0 commit 60dd66b
Showing 1 changed file with 24 additions and 40 deletions.
64 changes: 24 additions & 40 deletions text/0000-constrained-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ By providing an approved mechanism in the standard library, we can demonstrate t

# Detailed design

The entire standard library addition can be included in a single file in a new package.
The entire standard library addition can be included in a single file in a new package. The package will be called `constrained_types`.

```pony
type ValidationResult is (Validated | Error)
type ValidationResult is (ValidationSuccess | ValidationErrors)
primitive Validated
primitive ValidationSuccess
class val Error
class val ValidationErrors
let _errors: Array[String val] = _errors.create()
new create(e: (String val | None) = None) =>
Expand All @@ -35,14 +35,14 @@ class val Error
fun ref apply(e: String val) =>
_errors.push(e)
fun ref errors(): this->Array[String val] =>
fun errors(): this->Array[String val] =>
_errors
interface val Validator[T]
new val create()
fun apply(i: T): ValidationResult
class val Valid[T: Any val, F: Validator[T]]
class val Constrained[T: Any val, F: Validator[T]]
let _value: T val
new val _create(value: T val) =>
Expand All @@ -51,28 +51,28 @@ class val Valid[T: Any val, F: Validator[T]]
fun val apply(): T val =>
_value
primitive ValidConstructor[T: Any val, F: Validator[T] val]
fun apply(value: T): (Valid[T, F] | Error) =>
primitive MakeConstrained[T: Any val, F: Validator[T] val]
fun apply(value: T): (Constrained[T, F] | ValidationErrors) =>
match F(value)
| Validated => Valid[T, F]._create(value)
| let e: Error => e
| ValidationSuccess => Constrained[T, F]._create(value)
| let e: ValidationErrors => e
end
```

The library could be used thusly:

```pony
type LessThan10 is Valid[U64, LessThan10Validator]
type MakeLessThan10 is ValidConstructor[U64, LessThan10Validator]
type LessThan10 is Constrained[U64, LessThan10Validator]
type MakeLessThan10 is MakeConstrained[U64, LessThan10Validator]
primitive LessThan10Validator is Validator[U64]
fun apply(i: U64): ValidationResult =>
recover val
if i < 10 then
Validated
ValidationSuccess
else
let s: String val = i.string() + " isn't less than 10"
Error(s)
ValidationErrors(s)
end
end
Expand All @@ -81,7 +81,7 @@ actor Main
let prints = MakeLessThan10(U64(10))
match prints
| let p: LessThan10 => Foo(env.out, p).go()
| let e: Error =>
| let e: ValidationErrors =>
for s in e.errors().values() do
env.err.print(s)
end
Expand All @@ -107,13 +107,13 @@ In our example usage code, we are creating a constrained type `LessThan10` that

Some key points from the design:

## We can only validate immutable objects
## We can only constrain immutable objects

Validating a mutable item is pointless as it could change and go outside of our constraints after it has been validated. All validated items must be immutable.
Creating a constrained mutable object is pointless as it could change and go outside of our constraints after it has been validated. All constrained items must be immutable.

## `Error` is immutable
## `ValidationErrors` is immutable

We don't want to allow error mesages to be changed after validation is done. Because everything being validated is sendable, we can wrap an entire validator in a `recover` block and build up error messages on a `ref` Error before lifting to `val`.
We don't want to allow error mesages to be changed after validation is done. Because everything being validated is sendable, we can wrap an entire validator in a `recover` block and build up error messages on a `ref` `ValidationErrors` before lifting to `val`.

## Validators are not composable

Expand All @@ -139,32 +139,16 @@ Adding this to the standard library means that if we decide we want to change it

# Alternatives

## Where this lives:
## Where this lives

Make this a ponylang organization library. I personally want this functionality and will create the library and maintain under the organization if we decided to not include in the standard library.

## `Error` collection type
## `ValidationErrors` collection type

I think that `Array[String val]` is a good error representation that is easy to work with and gives people enough of what they would need without taking on a lot of generics fiddling that might make the library harder to use. That said, we could look at making `Error` generic over the error representation and use something like `Array[A]`.
I think that `Array[String val]` is a good error representation that is easy to work with and gives people enough of what they would need without taking on a lot of generics fiddling that might make the library harder to use. That said, we could look at making `ValidationErrors` generic over the error representation and use something like `Array[A]`.

Additionally, if we thought it would be useful to get back a collection of errors that can be updated by calling code without changing the collection within the `Error` type, we could use a persistent collection type like `persistent/Vec`. Using a persistent collection would allow for additional errors to be "added on" later while the collection in `Error` would itself remain unchanged.

## Names

I'm generally happy with the names, but I would entertain that `Validated` instead of `Valid` reads better. So:

```pony
type LessThan10 is Validated[U64, LessThan10Validator]
```

instead of:

```pony
type LessThan10 is Valid[U64, LessThan10Validator]
```

If we made that change then `ValidConstructor` should become `ValidatedConstructor`.
Additionally, if we thought it would be useful to get back a collection of errors that can be updated by calling code without changing the collection within the `ValidationErrors` type, we could use a persistent collection type like `persistent/Vec`. Using a persistent collection would allow for additional errors to be "added on" later while the collection in `ValidationErrors` would itself remain unchanged.

# Unresolved questions

The package needs a name. I'm thinking `constrained_types` or `constrained`.
None

1 comment on commit 60dd66b

@adrianboyko
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One small suggestion... Since the opposite of "success" is "failure", the counterpart of ValidationSuccess should be ValidationFailure and not ValidationErrors. The errors are an attribute of the failure, accessible through fun errors(). The errors explain the failure but they are not the failure itself.

Please sign in to comment.