Draco-lang / Language-suggestions

Collecting ideas for a new .NET language that could replace C#
75 stars 5 forks source link

filter valid values in function declaration #69

Open Binto86 opened 2 years ago

Binto86 commented 2 years ago

You may want to throw an exception when value inputed into your function doesn't match what you want (value is null is the most common example). In c# you would problably need to manually check if value matches this rule using ifs and throwing exception in the function. It would be nice to be able to specify some structure that could throw on the method call without you needing to manually check the value. I think that this could be solved by new structure rule. I am thinking of this syntax:

rule notnull(value: string)
{
    if(value is null)
    {
        throw new ArgumentNullException("value");
    }
}

func method(x:string is notnull)
{
     //your code where you know x is not null
}

The rule structure would need to have at least one throw statement an would accept only one parameter. Question is how to specify that you want certain rule to be aplied to your parameter, i used is keyword, but thats probably not the best way.

LPeter1997 commented 2 years ago

This is called Design by Contract, and the previous proposal of C#s !! was essentially a special case for a not-null precondition for that. They are a perfect use-case for decorators I believe. It has the advantage that it requires no additional language elements. A made-up implementation of your example, partially based on the metaprogramming issue:

#[macro(decorator)]
func PreCondition(args: MacroArgs, target: SyntaxTree): SyntaxTree
{
    val target = target.cast[SyntaxTree.FuncDeclaration];
    val condition = parse_expr(args[0]);
    return quote!{
        #(target.signature)
        {
            if not (#(args.condition)) throw ArgumentException();
            return { #(target.body) };
        }
    };
}

An example usage could be:

#[PreCondition(n > 0)]
func log_positive(n: int32) = Console.WriteLine(n);

Which would be transformed by the macro to

func log_positive(n: int32)
{
    if not (n > 0) throw ArgumentException();
    return { Console.WriteLine(n) }; // Looks odd but it's OK, returns unit
}
Binto86 commented 2 years ago

That looks good to me

jl0pd commented 2 years ago

This can be achieved with active patterns. F# example

let (|NonNull|) x =
    if isNull x then
        nullArg "x"
    else
        x

let f (NonNull x) = // x is never null
    printfn "%A" x

It's a simple and very powerful tool, which can be used anywhere, where pattern matching is allowed. With addition for CallerArgumentExpression it's even can capture real argument name.

Research document: https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/p29-syme.pdf

LPeter1997 commented 2 years ago

While active patterns are cool and we have discussed them related to the pattern matching issue, they will probably not make it into the language, as we are planning to expose a pattern matching protocol that would be a more general tool. I believe this case is more neatly solved with decorators, but that's just my 2 cents.