Checking function parameters in Phobos

Simen Kjærås simen.kjaras at gmail.com
Sun Nov 24 09:35:23 PST 2013


On 22.11.2013 02:55, Simen Kjærås wrote:
> On 22.11.2013 00:50, Meta wrote:
>> On Thursday, 21 November 2013 at 22:51:43 UTC, inout wrote:
>>> What if you have more that just one validation, e.g. Positive and
>>> LessThan42?
>>> Is Positive!LessThan42!int the same type as LessThan42!Positive!int?
>>> Implicitly convertible?
>>
>> Allow multiple validation functions. Then a Validated type is only valid
>> if validationFunction1(val) && validationFunction2(val) &&...
>>
>> Validated!(isPositive, lessThan42, int) validatedInt =
>> validate!(isPositive, lessThan42)(34);
>> //Do stuff with validatedInt
>
> 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!

-- 
   Simen
-------------- next part --------------
module biotronic.utils;

import std.typetuple : TypeTuple, NoDuplicates, staticIndexOf;
import std.traits : Unqual, ParameterTypeTuple;

void staticEnforce(bool criteria, string msg)() {
    static if (!criteria) {
        pragma(msg, msg);
        static assert(false);
    }
}

void staticEnforce(bool criteria, string msg, string file, int line)() {
    staticEnforce!(criteria, file ~ "(" ~ line.stringof ~ "): Error: " ~ msg);
}

auto sum( R )( R range ) if ( isInputRange!R ) {
    ElementType!R tmp = 0;
    return reduce!( (a,b)=>a+b )( tmp, range );
}

template arrayToTuple( alias name ) {
    static if ( name.length ) {
        alias arrayToTuple = TypeTuple!( name[0], arrayToTuple!( name[1..$] ) );
    } else {
        alias arrayToTuple = TypeTuple!( );
    }
}

template Repeat( size_t n, T... ) {
    static if ( n ) {
        alias Repeat = TypeTuple!( T, Repeat!( n-1, T ) );
    } else {
        alias Repeat = TypeTuple!();
    }
}

template hasFloatBehavior( T ) {
    static if ( __traits( compiles, { T t; t = 1; return (t/2)*2 == t; } ) ) {
        enum hasFloatBehavior = { T t; t = 1; return (t/2)*2 == t; }();
    } else {
        enum hasFloatBehavior = false;
    }
} unittest {
    assert( hasFloatBehavior!float );
    assert( hasFloatBehavior!double );
    assert( hasFloatBehavior!real );
    assert( !hasFloatBehavior!int );
    assert( !hasFloatBehavior!char );
    assert( !hasFloatBehavior!string );
}

template hasNumericBehavior( T ) {
    template hasNumericBehaviorImpl( U... ) {
        static if ( U.length ) {
            enum hasNumericBehaviorImpl = is( Unqual!T == U[0] ) || hasNumericBehaviorImpl!( U[1..$] );
        } else {
            enum hasNumericBehaviorImpl = false;
        }
    }
    
    enum hasNumericBehavior = hasNumericBehaviorImpl!( byte, short, int, long, ubyte, ushort, uint, ulong, float, double, real );
} unittest {
    foreach ( Type; TypeTuple!( byte, short, int, long, ubyte, ushort, uint, ulong, float, double, real ) ) {
        assert( hasNumericBehavior!Type );
    }
    foreach ( Type; TypeTuple!( string, char, dchar, int[], void, void*) ) {
        assert( !hasNumericBehavior!Type );
    }
}

template StaticFilter(alias pred, T...) {
    static if (T.length == 0) {
        alias StaticFilter = TypeTuple!();
    } else static if (T.length == 1) {
        static if (pred!(T[0])) {
            alias StaticFilter = T;
        } else {
            alias StaticFilter = TypeTuple!();
        }
    } else {
        alias StaticFilter = TypeTuple!(
            StaticFilter!(pred, T[0..$/2]),
            StaticFilter!(pred, T[$/2..$]));
    }
}

struct CMP(T...){}

template sortPred(T...) if (T.length == 2) {
    static if ( TypeTuple!(T[0]).stringof < TypeTuple!(T[1]).stringof ) {
        enum sortPred = -1;
    } else static if ( TypeTuple!(T[0]).stringof > TypeTuple!(T[1]).stringof ) {
        enum sortPred = 1;
    } else {
        enum sortPred = 0;
    }
} unittest {
    assert( sortPred!(int, string) == -sortPred!( string, int ) );
}

template StaticSort(alias pred, T...) {
    static if (T.length == 0) {
        alias StaticSort = TypeTuple!();
    } else static if (T.length == 1) {
        alias StaticSort = T;
    } else {
        template lessPred(U...) {
            enum lessPred = pred!(T[0], U[0]) == 1;
        }
        template equalPred(U...) {
            enum equalPred = pred!(T[0], U[0]) == 0;
        }
        template morePred(U...) {
            enum morePred = pred!(T[0], U[0]) == -1;
        }
        
        
        alias eq = StaticFilter!(equalPred, T);
        alias less = StaticFilter!(lessPred, T);
        alias more = StaticFilter!(morePred, T);
        
        alias StaticSort = TypeTuple!(
            StaticSort!(pred, less),
            eq,
            StaticSort!(pred, more));
    }
} unittest {
    assert(is(StaticSort!(sortPred, int, string) == StaticSort!(sortPred, string, int)));
    assert(is(StaticSort!(sortPred, int, string) == StaticSort!(sortPred, string, int)));
    
    assert(is(CMP!(StaticSort!(sortPred, int, "waffles", string)) == CMP!(StaticSort!(sortPred, "waffles", string, int))));
}

template hasNoDuplicates( T... ) {
    enum hasNoDuplicates = is( CMP!T == CMP!(NoDuplicates!T) );
}

template isSorted( T... ) {
    enum isSorted = is( CMP!T == CMP!(StaticSort!( sortPred, T ) ) );
} unittest {
    assert( isSorted!() );
    assert( isSorted!int );
    assert( isSorted!(int, int) );
    assert( isSorted!(int, string) );
    assert( !isSorted!(string, int) );
}

template TypeEnum(T...) {
    template TypeEnumName(int n) {
        static if (n < T.length) {
            enum TypeEnumName = "_" ~ n.stringof ~ "," ~ TypeEnumName!(n+1);
        } else {
            enum TypeEnumName = "";
        }
    }
    mixin("enum TypeEnum {"~TypeEnumName!0~"}");
}
    
template ParameterTypeTupleOrVoid(T...) if (T.length == 1) {
    static if (is(ParameterTypeTuple!T)) {
        alias ParameterTypeTupleOrVoid = CMP!(ParameterTypeTuple!T);
    } else {
        alias ParameterTypeTupleOrVoid = CMP!void;
    }
}

template isType(T...) if (T.length == 1) {
    enum isType = is(T[0]);
}

template TypeSet(T...) {
    template superSetOf(U...) {
        static if (U.length == 0) {
            enum superSetOf = true;
        } else static if (U.length == 1) {
            enum superSetOf = staticIndexOf!(U, T) != -1;
        } else {
            enum superSetOf = superSetOf!(U[0..$/2]) && superSetOf!(U[$/2..$]);
        }
    }
    
    template strictSuperSetOf(U...) {
        enum strictSuperSetOf = superSetOf!U && !is(CMP!T == CMP!U);
    }
} unittest {
    assert(TypeSet!(int, string).superSetOf!(int));
    assert(TypeSet!(int, string).superSetOf!(int, string));
    assert(!TypeSet!(int, string).superSetOf!(float));
    assert(!TypeSet!(int, string).superSetOf!(float, int, string));
    assert(!TypeSet!(int, string).superSetOf!(float, int));
}
-------------- next part --------------
module biotronic.validation;

import std.conv : to;
import biotronic.utils;

version (unittest) {
    import std.exception : assertThrown, assertNotThrown;
}

version (D_Ddoc) {
/**
Encapsulates a validated value, the validation of which is enforced through $(LREF validated). $(BR)
The unadorned value is available through $(LREF value), and through alias this. $(BR)
The constraints can either throw on their own, or return a bool value of true if the constraint passed, false if it didn't. $(BR)

Example:
----
bool isPositive(int value) {
    return value >= 0;
}

void checkLessThan42(int value) {
    enforce(value < 42);
}

void foo(Validated!(int, isPositive) value) {
}

foo(13); // Refuses to compile.

Validated!(int, isPositive, checkLessThan42) x = validated!(isPositive, checkLessThan42)(14); // Would throw on invalid input
foo(x); // It works!
----

A validated value A whose constraints are a superset of those of another validated type B may be implicitly converted. The opposite is not possible.

Example:
----
alias A = Validated!(int, isPositive, checkLessThan42);
alias B = Validated!(int, isPostive);

A a = 13;
B b = a;

a = b; // Error
----

If the wrapped type is convertible, and the constraints match, a type conversion is performed.

Example:
----
Validated!(int, isPositive) a = validated!isPositive(4);

Validated!(long, isPositive) b = a;
----

**/
    struct Validated(T, Constraints) if (Constraints.length > 0 && hasNoDuplicates!Constraints) {
        /// The wrapped value.
        @property public T value() { return T.init; }
    }
}

template Validated(T, _Constraints...) if (_Constraints.length > 0 && !isSorted!_Constraints && hasNoDuplicates!_Constraints) {
    alias Validated!(T, StaticSort!(sortPred, _Constraints)) Validated;
}

struct Validated(T, _Constraints...) if (_Constraints.length > 0 && isSorted!_Constraints && hasNoDuplicates!_Constraints) {
    alias _Constraints constraints;
    
    private T _value;
    @property inout
    public T value() {
        return _value;
    }
    alias value this;
    
    @disable this();
    
    /+
    debug {
        this(int line = __LINE__, string file = __FILE__)(T other) {
            alias create = validated!constraints;
            this = create!(T, line, file)(other);
        }
    } else {
        this(T other) {
            this = validated!constraints(other);
        }
    }
    +/
    
    this(U)(U other) if (isValidated!U && TypeSet!(U.constraints).superSetOf!(constraints) ) {
        _value = other._value;
    }
    
    typeof(this) opAssign(U)(U other) if (isValidated!U && TypeSet!(U.constraints).superSetOf!(constraints) && is(typeof(_value = other._value))) {
        _value = other._value;
        return this;
    }
    
    inout(U) opCast(U)() inout if (isValidated!U && TypeSet!(constraints).superSetOf!(U.constraints) ) {
        U result = void;
        result._value = _value;
        return result;
    }
    
    inout(U) opCast(U)() inout if (is(T : U)) {
        return value;
    }
}

template isValidated(T...) if (T.length == 1) {
    static if (is(typeof(T))) {
        enum isValidated = isValidated!(typeof(T));
    } else {
        enum isValidated = is(T[0] == Validated!U, U...);
    }
} unittest {
    assert(isValidated!(Validated!(int, isPositive)));
    assert(isValidated!(validated!(isPositive)(4)));
    assert(!isValidated!string);
    assert(!isValidated!"foo");
}

/**
validated checks that the value passes all constraints, and returns a $(LREF Validated).

Example:
----
void foo(Validated!(int, isPositive) value) {
}

auto a = validated!isPositive(4);
foo(a);
----

Multiple constraints may be passed to validated.

Example:
----
auto b = validated!(isPositive, checkLessThan42)(54); // Will throw at runtime.
----
**/
template validated(Constraints...) if (Constraints.length > 0) {
    auto validatedImpl(string loc, T)(T value) {
        import std.exception : enforce;
        import std.typetuple : TypeTuple;
        
        foreach (fn; Constraints) {
            staticEnforce!(is(typeof(fn(value))), loc ~ "Invalid constraint " ~ TypeTuple!(fn).stringof[6..$-1] ~ " for value of type " ~ T.stringof);
            static if (is(typeof(fn(value)) == bool)) {
                enforce(fn(value), loc ~ "Validation failed for value (" ~ value.to!string ~ "). Constraint: " ~ TypeTuple!(fn).stringof[6..$-1]);
            }
            fn(value);
        }
        
        static if (isValidated!T) {
            Validated!(typeof(T._value), NoDuplicates!(Constraints, T.constraints)) result = void;
        } else {
            Validated!(T, Constraints) result = void;
        }
        result._value = value;
        return result;
    }
    debug {
        auto validated(T, int line = __LINE__, string file = __FILE__)(T value) {
            return validatedImpl!(file ~ "(" ~ line.to!string ~ "): ")(value);
        }
    } else {
        auto validated(T)(T value) {
            return validatedImpl!""(value);
        }
    }
} unittest {
    assertNotThrown(validated!(isPositive)(3));
    assertThrown(validated!(isPositive)(-4));
}

/**
assumeValidated does not run any checks on the passed value, and assumes that the programmer has done so himself. This is useful when checks may be prohibitively expensive or in inner loops where maximum speed is required.

Example:
----
auto a = assumeValidated!isPositive(-4);
----
**/
template assumeValidated(Constraints...) if (Constraints.length > 0) {
    auto assumeValidated(T)(T value) {
        Validated!(T, Constraints) result = void;
        result._value = value;
        return result;
    }
} unittest {
    assertNotThrown(assumeValidated!isPositive(-2));
}

version (unittest) {
    import std.exception : enforce;
    bool isPositive(int value) {
        return value >= 0;
    }
    void checkLessThan42(int value) {
        enforce(value < 42);
    }
    void checkString(string value) {
    }
}

unittest {
    void test1(int a) {}
    void test2(Validated!(int, isPositive)) {}
    void test3(Validated!(int, isPositive, checkLessThan42)) {}
    
    Validated!(int, isPositive, checkLessThan42) a = void;
    Validated!(int, isPositive) b = void;
    Validated!(long, isPositive) r = a;
    Validated!(int, checkString) s = validated!checkString(3);
    
    a = validated!(checkLessThan42, isPositive)(3);
    b = a;
    a = validated!(checkLessThan42)(b);
    
    test1(b);
    test1(a);
    test3(a);
    assert(!__traits(compiles, test2(3)));
    assert(!__traits(compiles, test3(b)));
}

void main() {
}


More information about the Digitalmars-d mailing list