On Sunday, 24 November 2013 at 17:35:51 UTC, Simen Kjærås wrote:
I believe inout's point was this, though:
Validated!(isPositive, lessThan42, int) i = foo();
Validated!(isPositive, int) n = i; // Fails.
Validated!(lessThan42, isPositive, int) r = i; // Fails.
This is of course less than optimal.
If a type such as Validate is to be added to Phobos, these
problems need
to be fixed first.
Or just pass a function that validates that the int is both
positive and
less than 42, which would be much simpler.
I've created a version of Validated now that takes 1 or more
constraints, and where a type whose constraints are a superset
of
another's, is implicitly convertible to that. Sadly, because of
D's lack
of certain implicit conversions, there are limits.
Attached is source (validation.d), and some utility functions
that are
necessary for it to compile (utils.d).
Is this worth working more on? Should it be in Phobos? Other
critique?
Oh, sorry about those stupid questions, we have a term for that:
Detroy!
Awesome, I was messing around with something similar but you beat
me to the punch. A couple things:
- The function validated would probably be better named validate,
since it actually performs validation and returns a validated
type. The struct's name is fine.
- I think it'd be better to change "static if
(is(typeof(fn(value)) == bool))" to "static if
(is(typeof(fn(value)) : bool))", which rather than checking that
the return type is exactly bool, it only checks that it's
implicitly convertible to bool, AKA "truthy".
- It might be a good idea to have a version(AlwaysValidate) block
in assumeValidated for people who don't care about code speed and
want maximum safety, that would always run the validation
functions. Also, it might be a good idea to mark assumeValidated
@system, because it blatantly breaks the underlying assumptions
being made in the first place. Code that wants to be rock-solid
@safe will be restricted to using only validate. Or maybe that's
going too far.
- Validated doesn't work very well with reference types. The
following fails:
class CouldBeNull
{
}
bool notNull(T)(T t)
if (is(T == class))
{
return t !is null;
}
//Error: cannot implicitly convert expression (this._value) of
type inout(CouldBeNull) to f505.CouldBeNull
void takesNonNull(Validated!(CouldBeNull, notNull) validatedT)
{
}
- On the subject of reference types, I don't think Validated
handles them quite correctly. This is a problem I ran into, and
it's not an easy one. Assume for a second that there's a class
FourtyTwo that *does* work with Validated:
class FortyTwo
{
int i = 42;
}
bool containsFortyTwo(FortyTwo ft)
{
return ft.i == 42;
}
void mutateFortyTwo(Validated!(FortyTwo, containsFortyTwo)
fortyTwo)
{
fortyTwo.i = 43;
}
auto a = validated!containsFortyTwo(new FortyTwo());
auto b = a;
//Passes
assert(a.i == 42);
assert(b.i == 42);
mutateFortyTwo(a);
//Fails
assert(a.i == 43);
assert(b.i == 43);
This is an extremely contrived example, but it illustrates the
problem of using reference types with Validated. It gets even
hairier if i itself were a reference type, like a slice:
void mutateCopiedValue(Validated!(FortyTwo, containsFortyTwo)
fortyTwo)
{
//We're not out of the woods yet
int[] arr = fortyTwo.i;
arr[0] += 1;
}
//Continuing from previous example,
//except i is now an array
mutateCopiedValue(b);
assert(a.i[0] == 44);
assert(b.i[0] == 44);
Obviously in this case you could just .dup i, but what if i were
a class itself? It'd be extremely easy to accidentally invalidate
every Validated!(FortyTwo, ...) in the program in a single swipe.
It gets even worse if i were some class reference to which other,
non-validated references existed. Changing those naked references
would change i, and possibly invalidate it.