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

Proposal: Declare new primitive types with constraints #1795

Closed
thargol1 opened this issue Aug 18, 2018 · 10 comments
Closed

Proposal: Declare new primitive types with constraints #1795

thargol1 opened this issue Aug 18, 2018 · 10 comments

Comments

@thargol1
Copy link

Allow the creation of compile time only types that are primitive types with constraints attached. This allows for better code with less typing and less errors.

Examples

Declare a new type with constraints:

type age = int where value >= 0 && value <= 150;
  • The 'where' looks a bit like generic constraints.
  • The 'value' is the same name as used by property setters.

Compile time error:

age studentAge = -1; // generates a compile time error

Compiler generated argument check code:

int doubleAge(age currentAge)
{
	return currentAge * 2;
}

Compiles to:

int doubleAge(int currentAge)
{
	if (!(currentAge >= 0 && currentAge <= 150)) throw new ArgumentException("currentAge", "Constraint 'value >= 0 && value <= 150' not met.");
	return currentAge * 2;
}

Conversions:

int x = 3;
age y = (age)x;

Compiles to:

int x = 3;
int _temp = x;
if (!(_temp >= 0 && _temp <= 150)) throw new Exception("Constraint 'value >= 0 && value <= 150' not met.");
int y = _temp;
@Joe4evr
Copy link
Contributor

Joe4evr commented Aug 18, 2018

So like Contracts? #105

@thargol1
Copy link
Author

Yes something like contracts, but the proposal #105 is for contracts on functions. This proposal is for constraints, guards, contracts, or whatever you call them on types, and thus enforced every time one uses the type. I think this proposal and the contracts proposal could enhance each other.

@iam3yal
Copy link
Contributor

iam3yal commented Aug 18, 2018

@thargol1 You're proposing few things here not just contracts.

  1. You're proposing that type will be an abstraction on top of structs and classes.

  2. You're proposing to add contracts to the language.

  3. You're proposing to create a proxy over the existing type in this case int and for the compiler to inject the contracts to the methods of the abstracted type.

The moment people will ask how this is going be lowered by the compiler is the moment you will understand it's exactly just like #105 but maybe more succinct because it's more declarative, meaning, the compiler will have to generate a bunch of stuff for this to work.

So my guess is that you need both something like #1711 and #105 as the building block for this proposal.

@bondsbw
Copy link

bondsbw commented Aug 19, 2018

This kind of proposal has come of a few times with various syntax and semantics (#410, #413, #1198, #1538 to name a few).

But it's not a duplicate of #105. Method contracts do not provide the guarantees you get when a type enforces its constraints everywhere.

@thargol1
Copy link
Author

@bondsbw you're right. My proposal is a duplicate of #410. I didn't use the correct search terms when looking for existing issues.

@iam3yal
Copy link
Contributor

iam3yal commented Aug 19, 2018

@bondsbw

Method contracts do not provide the guarantees you get when a type enforces its constraints everywhere.

It actually does, it provides preconditions, postconditions and invariants.

And in CodeContracts it was something like this:

[ContractInvariantMethod]
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Required for code contracts.")]
private void __ObjectInvariant()
{
	Contract.Invariant(Name != null);
	Contract.Invariant(EnclosedName != null);
	Contract.Invariant(_sectionRegex != null);
}

But it's not a duplicate of #105.

Not sure whether it's directed to my comment but I didn't say it's a duplicate, I meant that lowering the declarative code will end up with something you would have written yourself if you had #105 in place and the reason #105 is essential for similar proposals for them to even consider it.

@bondsbw
Copy link

bondsbw commented Aug 19, 2018

@eyalsk

@bondsbw

Method contracts do not provide the guarantees you get when a type enforces its constraints everywhere.
It actually does, it provides preconditions, postconditions and invariants.

How does it enforce the following?

age studentage = -1; // generates a compile time error

@iam3yal
Copy link
Contributor

iam3yal commented Aug 19, 2018

@bondsbw Pretty simple, here is an example:

class Age {
	public Age(int value) {
		Contract.Requires(value > 0);
		
		Value = value;
	}

	public int Value { get; }

	public static implicit operator Age(int age)
	{
		Contract.Requires(age > 0);
		
		return new Age(age);
	}

	[ContractInvariantMethod]
	private void __ObjectInvariant() => Contract.Invariant(Value > 0);
}

The static checker would have caught your invalid assignment at compile-time because the contract was violated.

p.s. These contracts exists in the full framework today but because they are meant for CodeContract they do nothing by themselves so don't expect them to throw.

Just for reference here is the same version of the code without Contract.*:

class Age {
	public Age(int value) => Value = value > 0 ? value : throw new ArgumentOutOfRangeException();

	public int Value { get; }

	public static implicit operator Age(int age) => age > 0 ? new Age(age) : throw new ArgumentOutOfRangeException();
}

@thargol1
Copy link
Author

I'm aware of contracts, but the amount of code you need is not to my liking.

Many recent C# changes over the year reduce the amount of code needed (arrow-functions, auto properties, tuples, var and the C#8 Record?.
So changes to the compiler that make the implementation of contracts, guards, constraints (whatever) easier are welcome.

So if the compiler translates something like this

type age = int where value >= 0 && value <= 150;

into a struct with contracts, implicit operators, equals operators etc etc it would make life a little bit easier.
And perhaps the syntax should be

public struct Age(int value) where value >= 0 && value <= 150;

To be more in sync with the proposed Record classes.

@iam3yal
Copy link
Contributor

iam3yal commented Aug 21, 2018

@thargol1

To be more in sync with the proposed Record classes.

We currently don't have records in the language and as far as I can tell I can't see it in C# 8.0 but in your case you need something like #1711 more than records.

I probably didn't say it enough or phrased it poorly so I'll try once more, you need the primitives of #105 to allow any proposal similar to yours to work.

Once you have the two proposals above making it into the language, proposal like yours would have legs and might be considered but unfortunately I can't see #105 happening anytime soon or far future.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants