digitalmars.D.learn - alias = compile-time variants?
- Jason Spencer (12/12) Jul 29 2010 In writing templates that make heavy use of alias parameters, does
- bearophile (7/10) Jul 29 2010 Normally D is a mostly statically typed language (objects have a dynamic...
- Philippe Sigaud (48/59) Jul 30 2010 I thought they could only be symbols. That is, an alias is a 'link', a s...
- Jason Spencer (30/48) Jul 31 2010 Whatever confidence you inspired by removing type from the list is
- Lutger (14/73) Jul 31 2010 Sure, but that would be quite a gotcha since you went from calling somet...
- Philippe Sigaud (66/141) Aug 01 2010 I discovered it by error, IIRC. That and the fact that template-like
In writing templates that make heavy use of alias parameters, does anyone else feel uneasy about whether the caller will pass a type, a value, or a schmoo? I'm having a hard time getting my head around how wide-open aliases are and trying to resist the urge to put in thousands of static asserts to check what should be invariants on the kind of thing that can be passed in that position. This all feels very strange when I typically look for templates to provide that nice, static, compile-time safety that keeps me from having to check everything that gets passed. Any words of wisdom on adjusting to this feature? Thanx, Jason
Jul 29 2010
Jason Spencer:In writing templates that make heavy use of alias parameters, does anyone else feel uneasy about whether the caller will pass a type, a value, or a schmoo?Normally D is a mostly statically typed language (objects have a dynamic type), but sometimes template juggling is purely dynamically typed. See also: http://d.puremagic.com/issues/show_bug.cgi?id=3881 Being able to avoid the 'alias' and pass structs, templates and arrays (curiously char/wchar/dchar arrays are allowed, but not other types of arrays) to templates can be positive to avoid some troubles caused by alias template arguments. Bye, bearophile
Jul 29 2010
On Thu, Jul 29, 2010 at 22:40, Jason Spencer <spencer8 sbcglobal.net> wrote:In writing templates that make heavy use of alias parameters, does anyone else feel uneasy about whether the caller will pass a type, a value, or a schmoo?I thought they could only be symbols. That is, an alias is a 'link', a sort of pointer to a symbol: a template name, a module name, a function name, etc. It does not seem to accept types: template TestAlias(alias a) { enum string TestAlias = __traits(identifier, a); } void main() { T foo(T)(T t) { return t;} writeln(TestAlias!foo); // works writeln(TestAlias!int); // do not compile } Though '3' is accepted by alias... Hmm.I'm having a hard time getting my head around how wide-open aliases are and trying to resist the urge to put in thousands of static asserts to check what should be invariants on the kind of thing that can be passed in that position. This all feels very strange when I typically look for templates to provide that nice, static, compile-time safety that keeps me from having to check everything that gets passed. Any words of wisdom on adjusting to this feature?Wisdom, I don't know, as I still feel like I'm exploring things. But template constraints are there to limit what you can instantiate. The C++ syntax is still there (T : U, etc), but it's quite limited and not so easy to parse, compared to D template constraints. Feel free to ignore what follows if that's well-treaded ground to you. Say I have a template that takes an alias, fun, and a type, T. fun is supposed to be a function, but in fact why limit it to that? What I need is for foo to be callable with a T. So let's test for that: auto doSomething(alias fun, T)(T t) if (is(typeof( fun(T.init) ))) { // now that I'm here, I can freely use fun as a callable on any T auto result = fun(t); // ... } So, T is a type. T.init is a value of type T, the default value for any T. .init is a easy way to generate a value from a type. Any type in D has a .init field (not typetuples, though). Note that 't' is not really accessible inside the constraint template, because t will have a runtime value, whereas here we are at compile-time. I think t can be used, but is still T.init at this stage. I call fun with T.init. If that compiles, then the resulting expression has a type: I use typeof() to get it, and then the is() expression to get a bool telling me if that's a valid type. If foo(T.init) doesn't compile, for whatever reason (fun as no opCall defined, or foo is callable, but not with a T...) then foo(T.init) has no type, so is(typeof( ... )) returns false and the template constraint forbids the template to be instantiated. inside doSomething(), I can freely use fun as something callable with one T. Note that my constraint is not complete: I did not test for fun(T.init) to return a value: it could be a void function(T) and the assignement would fail. I could also test for that, I guess. Philippe
Jul 30 2010
== Quote from Philippe Sigaud (philippe.sigaud gmail.com)'s article--0016e6d58a039d35e2048c9aa7e2 I thought they could only be symbols. That is, an alias is a 'link',a sortof pointer to a symbol: a template name, a module name, a function name, etc.Whatever confidence you inspired by removing type from the list is quickly lost and more when you add module name--I hadn't thought of that! :)Wisdom, I don't know, as I still feel like I'm exploring things. But template constraints are there to limit what you can instantiate. ... Say I have a template that takes an alias, fun, and a type, T. fun is supposed to be a function, but in fact why limit it to that? What I need is for foo to be callable with a T. So let's test for that: auto doSomething(alias fun, T)(T t) if (is(typeof( fun(T.init) ))) { // now that I'm here, I can freely use fun as a callable on any T auto result = fun(t); // ... }I understand this example, and (most of) the mechanics of constraints. What I'm not so sure about is the recommended practice around their use. I see lot's of code that doesn't check those things. Suppose you left off the constraint and did: class Foo(U){} doSomething!(Foo, int)(3) it seems like there's a good chance you could get: auto result = Foo!(int); // type inferred from 3 (since this doesn't actually work like I'm saying, please conveniently imagine a similar case that does. :) Even with your constraint, I'm not sure I feel any more comfortable. If it compiles in the body of doSomething, it will compile in the constraint--not sure I've added any value. So how do you sleep at night not knowing if there's some funky syntax on somebody's template-that-takes-a-template which, when combined with some inference, might look like your function call on a value param? My initial reaction is to specify the hell out of the constraints, but I couldn't beat the feeling I was going overboard. I suspect that most people rely on the fact that most improper calls won't compile. Maybe I'm still too new to the syntax to have a good feel for what will get caught, and what could interpreted by the compiler in multiple ways depending on the actual arguments. So, do folks write constraints to ensure that modules don't get passed to their templates? :) Jason
Jul 31 2010
Jason Spencer wrote:== Quote from Philippe Sigaud (philippe.sigaud gmail.com)'s articleSure, but that would be quite a gotcha since you went from calling something to merely instantiating a template. Perhaps there are such gotcha's, I am not aware of them.--0016e6d58a039d35e2048c9aa7e2 I thought they could only be symbols. That is, an alias is a 'link',a sortof pointer to a symbol: a template name, a module name, a function name, etc.Whatever confidence you inspired by removing type from the list is quickly lost and more when you add module name--I hadn't thought of that! :)Wisdom, I don't know, as I still feel like I'm exploring things. But template constraints are there to limit what you can instantiate. ... Say I have a template that takes an alias, fun, and a type, T. fun is supposed to be a function, but in fact why limit it to that? What I need is for foo to be callable with a T. So let's test for that: auto doSomething(alias fun, T)(T t) if (is(typeof( fun(T.init) ))) { // now that I'm here, I can freely use fun as a callable on any T auto result = fun(t); // ... }I understand this example, and (most of) the mechanics of constraints. What I'm not so sure about is the recommended practice around their use. I see lot's of code that doesn't check those things. Suppose you left off the constraint and did: class Foo(U){} doSomething!(Foo, int)(3) it seems like there's a good chance you could get: auto result = Foo!(int); // type inferred from 3 (since this doesn't actually work like I'm saying, please conveniently imagine a similar case that does. :)Even with your constraint, I'm not sure I feel any more comfortable. If it compiles in the body of doSomething, it will compile in the constraint--not sure I've added any value.Perhaps not in this case, but: - constraints add documentation, such as isInputRange!T or IsCallable!fun - you can add a constraint that may not be statically checked inside the body of the template. This way you can still reject template parameters considered to be invalid even if the template body *could* be instantiated with them. - a constraint is a nice place to add better compiler error messagesSo how do you sleep at night not knowing if there's some funky syntax on somebody's template-that-takes-a-template which, when combined with some inference, might look like your function call on a value param? My initial reaction is to specify the hell out of the constraints, but I couldn't beat the feeling I was going overboard. I suspect that most people rely on the fact that most improper calls won't compile. Maybe I'm still too new to the syntax to have a good feel for what will get caught, and what could interpreted by the compiler in multiple ways depending on the actual arguments. So, do folks write constraints to ensure that modules don't get passed to their templates? :) JasonWhy not? As long as your module does the right thing, it may be used to instantiate my template :)
Jul 31 2010
On Sat, Jul 31, 2010 at 21:17, Lutger <lutger.blijdestijn gmail.com> wrote:Jason Spencer wrote:I discovered it by error, IIRC. That and the fact that template-like statements like auto members = __traits(allMembers, std.stdio); work. Try it, print the result. Though I do not know what to do with it :)== Quote from Philippe Sigaud (philippe.sigaud gmail.com)'s article--0016e6d58a039d35e2048c9aa7e2 I thought they could only be symbols. That is, an alias is a 'link',a sortof pointer to a symbol: a template name, a module name, a function name, etc.Whatever confidence you inspired by removing type from the list is quickly lost and more when you add module name--I hadn't thought of that! :)You can test for the alias to be a function (is(typeof(a) == function)), a delegate, or you can test for the presence of an opCall operator (the '()' operator), with __traits(hasMember, alias, "opCall") I think there should be a isCallable trait in Phobos... But in the opCall case, the gotcha is the class templates are not classes. Only instantiated classes are really classes. That is, given class C(T) { T t; T opCall(T u) { return t;} } __traits(hasMember, C, "opCall") will answer 'false'. That's normal, since C is not a class, but a template, of type void. __traits(hasMember, C!int, "opCall") will answer 'true'.Sure, but that would be quite a gotcha since you went from calling something to merely instantiating a template. Perhaps there are such gotcha's, I am not aware of them.Wisdom, I don't know, as I still feel like I'm exploring things. But template constraints are there to limit what you can instantiate. ... Say I have a template that takes an alias, fun, and a type, T. fun is supposed to be a function, but in fact why limit it to that? What I need is for foo to be callable with a T. So let's test for that: auto doSomething(alias fun, T)(T t) if (is(typeof( fun(T.init) ))) { // now that I'm here, I can freely use fun as a callable on any T auto result = fun(t); // ... }I understand this example, and (most of) the mechanics of constraints. What I'm not so sure about is the recommended practice around their use. I see lot's of code that doesn't check those things. Suppose you left off the constraint and did: class Foo(U){} doSomething!(Foo, int)(3) it seems like there's a good chance you could get: auto result = Foo!(int); // type inferred from 3 (since this doesn't actually work like I'm saying, please conveniently imagine a similar case that does. :)I never thought of that.Even with your constraint, I'm not sure I feel any more comfortable. If it compiles in the body of doSomething, it will compile in the constraint--not sure I've added any value.Perhaps not in this case, but: - constraints add documentation, such as isInputRange!T or IsCallable!fun - you can add a constraint that may not be statically checked inside the body of the template. This way you can still reject template parameters considered to be invalid even if the template body *could* be instantiated with them.- a constraint is a nice place to add better compiler error messagesExcept the compiler just say it cannot instantiate the template NameWithConstraints.Yes.So how do you sleep at night not knowing if there's some funky syntax on somebody's template-that-takes-a-template which, when combined with some inference, might look like your function call on a value param? My initial reaction is to specify the hell out of the constraints, but I couldn't beat the feeling I was going overboard. I suspect that most people rely on the fact that most improper calls won't compile.I have nothing against long and well-documented constraints. I feel we are not using them as much as we could. With things like staticMap and such, you can do a lot!Maybe I'm still too new to the syntax to have a good feel for what will get caught, and what could interpreted by the compiler in multiple ways depending on the actual arguments.You can try to define an isTemplate template, and a isModule template :-) Look at this: T foo(T)(T t) { return t;} class C(T) { T t; T opCall(T u) { return t;} } template Describe(alias a) { enum string Name = a.stringof; enum string Ident = __traits(identifier, a); } void main() { writeln(Describe!(foo).Name); // foo(T) writeln(Describe!(foo).Ident); // foo writeln(Describe!(C).Name); // C(T) writeln(Describe!(C).Ident); // C writeln(Describe!(C!int).Name); // C (really, I thought that would be C!(int). I remember devising a way to extract the types from a template writeln(Describe!(C!int).Ident); // C writeln(Describe!(Describe).Name); // Calling it on itself! Describe(alias a) writeln(Describe!(Describe).Ident); // Describe writeln(Describe!(std.stdio).Name); // module stdio Strange, no std. in sight. writeln(Describe!(std.stdio).Ident); // stdio } So, inside a template a.stringof gives "module a" if a is a module, except it seems to cut anything before a dot. I do not like relying on .stringof, because it's undocumented and not always perfectly coherent. But a pragmatic implementation of isModule could be: template isModule(alias symbol) { enum bool isModule = (symbol.stringof[0..7] == "module "); } :-) PhilippeSo, do folks write constraints to ensure that modules don't get passed to their templates? :) JasonWhy not? As long as your module does the right thing, it may be used to instantiate my template :)
Aug 01 2010