Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

User-Defined Constant Functions #2222

Open
Jetz72 opened this issue Apr 29, 2022 · 2 comments
Open

User-Defined Constant Functions #2222

Jetz72 opened this issue Apr 29, 2022 · 2 comments
Labels
feature Proposed language feature that solves one or more problems

Comments

@Jetz72
Copy link

Jetz72 commented Apr 29, 2022

Seeing as this idea didn't reveal any glaring flaws when I asked at #1296 (comment), I figured I'd make it into a full feature request and see what comes of it.

The suggestion is for users to be able to define their own constant getters, operators, and functions with a restricted set of language features, which can be run at compile time. Naturally, interacting with non-constant/final members of classes wouldn't be possible. Recursion and conditional iteration probably should be prevented too if you don't want to risk the compiler never halting.

It might look something like:

class Foo
{
	final int a;
	final int b;
	
	const Foo(this.a, this.b);
	
	const int withC(int c) => (c > 0) ? (a + c) : (b + c);
	
	const int get aPlusB => a + b;
	
	//Operators in particular would be nice, in order to help bridge the gap between system types and others.
	const Foo operator+ (Foo other) => Foo(a + other.a, b + other.b); 
	
	//Can use string interpolation on primitives if toString is off the table.
	static const int _computeB(int x) => "${(x * 180 + 374)}".length;
	
	//Being able to use helper methods in constructors is a good way to split complicated things out from initializer lists, so it'd be cool to be able to do it with constant objects.
	const Foo.fromX(int x) : this(x, _computeB(x)); 
}

class Bar
{
	const Foo data = Foo.fromX(23);
	const Foo otherData = data + Foo.fromX(3);
	
	static void processData() {
		const info = data.aPlusB + otherData.withC(-4); //All computed at compile time.
		print(info.toString())
	}
}

It'd be cool to have full code block bodies with local variables, if and switch statements, maaaaaybe for-each loops, whatever else that can be done without side effects... But I'm not sure how much complexity that'd add, and expressions alone would be make for a helpful addition to the language.

Could make for a partial solution to #2219 - if some of those members could be defined as constant then they wouldn't have to be directly included in the language spec. I imagine some of the more fundamental ones like List.operator[] will still need to be, though.

I'm sorta realizing the ramifications and potential complications of a feature like this as I type it up and I'm sure I don't have the complete picture, so I'd be glad to hear others' thoughts on it.

@Jetz72 Jetz72 added the feature Proposed language feature that solves one or more problems label Apr 29, 2022
@lrhn
Copy link
Member

lrhn commented Apr 30, 2022

I think we'd have to require the const accessible fields to be declared as const as well.

So, here's my attempt of a definition:

  • Instance members except setters (variables, getters and methods) can be declared const. This const-ness is only in the implementation, not the interface signature.
  • A class declaring a const member must have a const initializing (non-redirecting and generative) constructor.
  • A const instance variable introduces a const getter. The variable counts as final as well. If it has no initializer expression, it must be initialized by all initializing (aka. non-redirecting generative) constructors. If it has an initializer, it must be a potentially constant expression.
  • The body of a const declared method or getter must be an => expression, the expression must be a potentially constant expression, and the member cannot be async (or async* or sync*, but those don't allow returns anyway). This potentially constant expression can use parameters of the method as potentially constant expressions, and it can use type parameters of the method and surrounding class as potentially constant types (usable in is and as operations as as type literals).
  • Accessing a const declared member of a constant object is itself a potentially constant expression.
  • It's a compile-time error if constant evaluation of a const declared member recursively invokes iteself.

This definition allows you to access const members in other potentially const members.
However, it does not make this statically checkable. If you do const int lengthOf(List l) => l.length;, then it's potentially valid. If invoked with a list which has a const length property, which the platform constant lists will have, then it works.

However, we do not want to require every implementation of List to have a constant length, so we won't make it part of the interface. (Not even every implementation of List which has a constant constructor).
That makes this functionality very error-prone. There is no static checking that your function is safe, and there is no way, looking a type, to know whether the object has const members. (The type of a constant list is just List, same as a mutable list which definitely does not have const members).
That's my biggest worry. No static checking and no discoverability of whether an object will work at runtime.

A solution might be to make the const part of the interface and require that all non-abstract subclasses that have a const constructor also have const implementations of all the members that were const in any superinterface.
Or, stated alternatively:

  • If a member is declared const in a superclass, it's also implicitly const in every subclass.
  • It's a compile-time error if a class is non-abstract, has a const constructor, and its implementation of a const instance member is not => potentiallyConstantExpression;.
  • It's a compile-time error if a member invocation in a potentially constant expression does not target a const member.

Then all non-constant sub-classes can ignore the problem, but constant subclasses are required to be consistent with the superclass.
Then we introduce ConstantList which implements List and has some constant members, probably including operator[].
(I'd make throw a potentially constant expression too, even with a non-constant operand, and then it's a compile-time error if constant evaluation reaches a throw. )

@Wdestroier
Copy link

Aren't you looking for a constexpr macro instead?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature Proposed language feature that solves one or more problems
Projects
None yet
Development

No branches or pull requests

3 participants