[nem-en] Re: [nem-feedback] Generic's "where" constraint
improvement.
Kamil Skalski
kamil.skalski at gmail.com
Thu Mar 9 11:52:52 CET 2006
Recent conversation with Eryshov:
2006/3/9, Eryshov Ivan <I.Eryshov at cft.ru>:
>
> I'm sorry for delay within an answer.
>
> >> Secondly, I want to propose some generics relative improvement. Let's
> see >on
> >> C# example:
> >>
> >> class C1<T> where T : Enum {}
> >> class C2<T> where T : Delegate {}
> >>
> >> Both classes will not compile, because of "Constraint cannot be
> special
> >> class 'System.Enum' or 'System.Delegate'". I was very surprised when
> >> followed by code was successfully compiled in Nemerle:
> >>
> >> class C1['t] where 't : Enum {}
> >> class C2['t] where 't : Delegate {}
> >>
> >> This is very useful feature.
> >
> >I'm happy you like this, but I'm not sure how this is useful in
> >real-life development... You cannot use the fact that 't is
> >constrained to Enum / Delegate in any useful way. You only get some
> >additional compile-time checks.
>
> I think that additional compile-time checks never will be excess :)
> I can demonstrate you example when the enum constraint can be useful. I
> can write in Nemerle something like:
>
> enum LicenseAccessLevelType
> {
> | Growing
> | Supplemental
> }
>
> class LicenseManager['t]
> where 't : Enum
> {
> _current : 't;
> _licenseAccessLevelType : LicenseAccessLevelType
> = LicenseAccessLevelType.Growing;
>
> Validate(required : 't) : bool
> {
> def ci = NumberFormatInfo.CurrentInfo;
> def curInt =
> (_current:IConvertible).ToInt32(ci);
> def reqInt =
> (required:IConvertible).ToInt32(ci);
>
> match(_licenseAccessLevelType)
> {
> | Growing =>
> (curInt < reqInt);
> | Supplemental =>
> (curInt & reqInt) != curInt;
> | _ =>
> throw NotImplementedException();
> }
> }
>
> }
Ah :) Nice example, now I see a point. I agree this is a good feature
and probably allowing a special syntax for where 'a : enum is quite
natural. It is quite easy to implement, I will take a look at it in a
few days.
>
> [offtopic] I have one question about this code. This is for my education
> :) Why I must cast _current and required to IConvertible interface? I
This is a bug. If we have 'a : IFoo, then (x : 'a).Foo() and (x
: IFoo).Foo() should be the same. Could you file a bug report for this
in bugs.nemerle.org ?
> mean why this cast can not be inferred?
>
> Similar behavior in C# I can implement in such way:
>
> public abstract class LicenseManager<LicType>
> where LicType : struct, IConvertible
> {
> private LicType _current;
> private LicenseAccessLevelType _licenseAccessLevelType;
>
> static LicenseManager()
> {
> if (!typeof(LicType).IsEnum)
> {
> throw new LicenseRuntimeException(
> LRes.Exception.GenericParameterRestrict);
> }
> }
>
> bool Validate(LicType required)
> {
> IFormatProvider provider = NumberFormatInfo.CurrentInfo;
> int curIntRepr = _current.ToInt32(provider);
> int reqIntRepr = required.ToInt32(provider);
> switch (_licenseAccessLevelType)
> {
> case LicenseAccessLevelType.Growing:
> return (curIntRepr < reqIntRepr);
> case LicenseAccessLevelType.Supplemental:
> return ((curIntRepr & reqIntRepr) != curIntRepr);
> default:
> throw new NotImplementedException(
> LRes.Exception.NotImplemented);
> }
> }
> }
>
> As you can see I have additional check in static constructor, moreover I
> need add 2 where constraints struct and IConvertible.
> Of course this is just some sugar, but it can simplify some situations
> and prevent some errors.
>
>
> >> class C1['t] where 't : enum {}
> >> class C2['t] where 't : delegate {}
> >>
> >
> >Could you provide some examples, where you can benefit from
> >constraining 't to enum or delegate?
>
> I can not provide any example about delegate. So let's forget about it.
>
Right, System.Delegate doesn't even have a way to execute the delegate.
>
> >> Other improvement I want to offer I'll demonstrate with followed by
> code:
> >>
> >> class C1['t] where 't : struct, Foo(), operator+() // or may be
> >> op_Addition()
> >>
> >
> >Unfortunately this is not expressible in .NET design. It supports only
> >"class-level" contraints, so there is no way of specifying that class
> >used as 't will have some method.
>
> Yes, I know about it.
>
> >Compiler could of course track this dependency, but in order to
> >generate code for method calls in 't, it would need to use
> >dynamic-casting/reflection.
>
>
> Hmm, using reflection for such proposes isn't a good idea. What do you
> thing about followed by compile-time transformation, which can solve
> this problem.
>
> My suggestion is transform code:
>
> class T : IArithmetic[T]
> {
> ...
> public static T operator+(a : T, b : T)
> {
> ...
> }
> ...
> }
>
> ...
>
> class C1['t] where 't : struct, operator+()
> {
> Sum(arr : array['t]) : 't
> {
> mutable sum = DefaultValue('t);
> foreach (t in arr)
> sum += t;
> sum;
> }
> }
>
> ...
>
> def c = C1.[T];
>
> to code:
>
> interface IArithmetic['t]
> {
> Add(a : 't) : 't;
> }
>
> class T : IArithmetic[T]
> {
> ...
> public static T operator+(a : T, b : T)
> {
> ...
> }
> ...
> public Add(a : T) : T
> {
> this + a;
> }
> ...
> }
>
> class C1['t] where 't : struct, IArithmetic['t]
> {
> Sum(arr : array['t]) : 't
> {
> mutable sum = DefaultValue('t);
> foreach (t in arr)
> sum = sum.Add(t);
> sum;
> }
> }
>
> ...
>
> def c = C1.[T];
>
> like macro transformation.
> I believe that such improvements will simplify development process
> greatly.
>
The problem is still that for each primitive type we would need to
auto-generate an utility classr implementing the operations. But
maybe this is a good place to experiment with macros a little bit. For
example writing a macro, which would do:
sum(my_arr) --->
{ mutable sum = my_arr[0]; for (mutable i = 1; i < my_arr.Length;
i++) sum += my_arr[i]; sum }
is rather easy (though it is hard to initialize the first element
properly here...)
Also macros can generate those utility classes. We could include
utility classes in our stdlib and even auto-generate them for classes
having operators:
[GenerateOpUtility]
class Foo {
public static @+ (x : Foo, y : Foo) : Foo {
}
}
would generate
struct Foo_Adder : IAddition [Foo] {
public Add (x : Foo, y : Foo) : Foo {
x + y
}
}
and then it could be used as
class Summer ['a, 'b] where 'b : struct, IAddition ['a]
{
public Sum (x : array ['a]) : 'a {
def adder = 'b();
mutable sum = DefaultValue ('a);
foreach (e in x)
sum = adder.Add (sum, e);
sum
}
}
The problem here is that still we need to have this special 'b
generic parameter and (even worse) specify which class we want to use
when creating Summer object:
def summ = Summer () : Summer [int, AutoGenerater_int_Adder];
def sum = summ.Sum (array [1,2,3]);
I will need to think about it a little more. Maybe there is a way for
compiler to infer all information about the second parameter
automatically...
--
Kamil Skalski
http://nazgul.omega.pl
More information about the devel-en
mailing list