Open AKushWarrior opened 3 years ago
The notion of compile-time evaluation is supported in Dart by the constant expressions that you already mention. They allow for computations involving certain built-in types, plus invocation of const
constructors.
It is always a complex enhancement to allow constant expression evaluation to include some extra elements. In this particular case there's a need for detecting that actual arguments to a function invocation are constants, evaluating the asserts at the beginning of the function body. More complexity is implied if it should be supported that some actual arguments are constant expressions and others are not, and exactly the assertions that only evaluate potentially constant expressions (based on which actual arguments are constant) should be checked at compile-time. So this could easily grow to be a rather complex feature.
Hence, it's worthwhile to keep in mind how something similar could be expressed within the current feature set. Here's a workaround which can be used for full compile-time evaluation:
class Divide {
final double result;
const Divide(int dividend, int divisor)
: assert(divisor != 0),
result = dividend / divisor;
double call() => result;
}
void main(List<String> args) =>
print(const Divide(0, 0)()); // Compile-time error.
If the computation cannot be a constant expression then call
could contain whatever is needed:
class Divide {
final int dividend, divisor;
const Divide(this.dividend, this.divisor): assert(divisor != 0);
double call() {
// ... regular Dart code ...
return dividend / divisor;
}
}
The scenario where some arguments are constants and others are not isn't covered so easily. It would be possible to use more than one class and some currying to give compile-time constant arguments and run-time arguments separately, but it's probably far too verbose and difficult to read to be used in practice.
Hence, the extensions that would add the greatest amount of value are also the ones that add a lot of complexity, which might not be a surprise. ;-)
I thought about the class-based workaround, but, as you said, it tends to grow in verbosity really quickly as we advance to the cases where this feature might make the most difference. It's also not really idiomatic Dart code, and so the potential advantages of writing code like this is probably outweighed by the issues.
I have little experience in compiler design, so I'll take your word that this feature is likely to be high in implementation complexity given the way I've currently specified it.
My reasoning was primarily along the lines that, in its current iteration, Dart highly values compile-time safety to limit runtime errors. A feature like this should come in handy fairly regularly, since most practical programs have limitations on the range of values that are reasonable for a given parameter; this is especially relevant in graphical programs, where this range is often arbitrary and not especially clear from the method name alone.
The biggest issue I see with this idea is that it only work for statically resolved functions (constructors, static, top-level and extension methods). It cannot work for instance methods, which are still the vast majority of functions being called in an object oriented language like Dart, because you don't know which actual method is going to be called (at least not unless the receiver also happens to be a constant value). That alone reduces the potential usefulness and viability of the feature.
Nothing currently prevents the analyzer from recognizing constant arguments to known function, and checking whether any initial if (test(theVar)) throw SomeError
or known ArgumentError.checkSomething(theVar)
compile-time determinable checks in the function body will definitely throw for the provided value. It'd be perfectly fair to give warning for such cases.
(The title may be slightly misleading. When I speak about dependent types, I'm talking about the idea of, at compile time, allowing restrictions on arguments to a parameter beyond those imposed by the parameter type. This proposal isn't really for dependent types; it's more achieving one of the key advantages of dependent types, in allowing programmers to require a specific subset of a given type.)Fixed the title.A
const
value is known at compile-time. We can avoid a class of runtime errors, then, by allowing programmers to specify conditions under which a const argument is valid (as opposed to manually checking the parameter in a method or function, and throwing if the parameter is invalid). There's plenty of cases where an invalid constant could be passed to a method, but the caller would not recognize this until runtime:Such methods which are part of core libraries are simple enough to fix; you can simply use static analysis to provide a hint to the caller when they attempt to pass obviously invalid parameters. However, for third-party methods (say, in a package), these restrictions can't be hardcoded into static analysis.
To fix this, I propose a simple, optional addition to Dart function declarations: a second set of parentheses which declare a compile time constant boolean expression involving one or more parameters, where true is valid and false is invalid. The syntax might look like this:
Now, this example is best-case, where the user passes a const argument and the compiler is able to run the check at compile time. If the argument is not constant, the compiler can simply defer to a runtime check.
EDIT: I just thought of another case, where there is multiple arguments that need to be checked, and their checks are entirely separate logically. It wouldn't make sense to not check one argument because another argument is not compile-time constant. Instead, we could do something like this:
Some Notes:
The grammar for the check may prove difficult to implement as I've constructed it, but the placement of the check isn't all that important. The check could even be contained in an annotation above the method, which is less clean in my opinion but also is a less fundamental change to the language.
I think that all my claims about the current state of the Dart language are accurate. If not, please let me know.
I believe that this feature will help immensely in Flutter, which often has arbitrary restrictions on the range of values for a parameter that aren't immediately clear to the user.