www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Idea : Expression Type

reply Xinok <xnknet gmail.com> writes:
I've held off this idea for a little while now, but I think it could really be
a nice addition to D.

Expressions are basically aliases which can accept arguments. Unlike aliases
though, an expression can accept non-const arguments.

expression mul(a, b) = a * b; // Syntax is different from aliases, for good
reason...

int main(){
	int a = 35, b = 62;
	a = mul(a, b);
}

'mul' isn't a constant expression. The expression is inlined into the code, it
doesn't call a function.

So basically, expressions are inline functions. BUT, because D lacks references
(which I'll never be able to understand), functions can't return l-values.
Expressions can be treated as so though.

One example of a recent post, the max function:
expression max(a, b) = a > b ? a : b;
Can handle any and all types, and you can use it as an l-value.


Expressions can also be used for simpler operations, such as dereferencing an
array or pointer, to make code easier to read and write:
int* ptr = new int;
expression exp = *ptr;
exp = 62;

int[] arr = [15, 30, 45, 60];
expression val = arr[2];
val = 30;


Expressions can also handle constants, so it could be used as an alias which
can take arguments.
(Maybe require const property?)
const expression ptr(T) = T*;


-- Expressions are NOT mixins! You can't use undeclared symbols in an
expression.
-- Expressions are NOT functions! The expression is inlined into the code.
Jan 31 2007
next sibling parent reply "Andrei Alexandrescu (See Website for Email)" <SeeWebsiteForEmail erdani.org> writes:
Xinok wrote:
 I've held off this idea for a little while now, but I think it could
 really be a nice addition to D.
 
 Expressions are basically aliases which can accept arguments. Unlike
 aliases though, an expression can accept non-const arguments.
 
 expression mul(a, b) = a * b; // Syntax is different from aliases,
 for good reason...
 
 int main(){ int a = 35, b = 62; a = mul(a, b); }
 
 'mul' isn't a constant expression. The expression is inlined into the
 code, it doesn't call a function.
 
 So basically, expressions are inline functions. BUT, because D lacks
 references (which I'll never be able to understand), functions can't
 return l-values. Expressions can be treated as so though.

Walter had the idea of allowing an expression to bind to an alias, and to allow the following: template mul(alias a, alias b) { alias (a * b) mul; } with the effect that you want. (The parens are there to keep alias happy, not to prevent breakage of order of operations.)
 One example of a recent post, the max function: expression max(a, b)
 = a > b ? a : b; Can handle any and all types, and you can use it as
 an l-value.

Nah, that won't work. It will doubly evaluate one of its arguments. But the feature will allow very simple implementation of my varargs_reduce and varargs_reduce_parallel primitives, e.g.: template varargs_reduce(alias fun) { template impl(alias args...) { static if (args.length == 2) alias fun(args) impl; else alias impl!(fun(args[0], args[1]), args[2..$]) impl; } } This has a similar structure to the varargs_reduce that I posted a while ago, except that it doesn't need to do any type inference - it just replaces the expression and lets the compiler take care of the rest. Andrei
Jan 31 2007
parent reply Kyle Furlong <kylefurlong gmail.com> writes:
Andrei Alexandrescu (See Website for Email) wrote:
 Xinok wrote:
 I've held off this idea for a little while now, but I think it could
 really be a nice addition to D.

 Expressions are basically aliases which can accept arguments. Unlike
 aliases though, an expression can accept non-const arguments.

 expression mul(a, b) = a * b; // Syntax is different from aliases,
 for good reason...

 int main(){ int a = 35, b = 62; a = mul(a, b); }

 'mul' isn't a constant expression. The expression is inlined into the
 code, it doesn't call a function.

 So basically, expressions are inline functions. BUT, because D lacks
 references (which I'll never be able to understand), functions can't
 return l-values. Expressions can be treated as so though.

Walter had the idea of allowing an expression to bind to an alias, and to allow the following: template mul(alias a, alias b) { alias (a * b) mul; } with the effect that you want. (The parens are there to keep alias happy, not to prevent breakage of order of operations.)
 One example of a recent post, the max function: expression max(a, b)
 = a > b ? a : b; Can handle any and all types, and you can use it as
 an l-value.

Nah, that won't work. It will doubly evaluate one of its arguments. But the feature will allow very simple implementation of my varargs_reduce and varargs_reduce_parallel primitives, e.g.: template varargs_reduce(alias fun) { template impl(alias args...) { static if (args.length == 2) alias fun(args) impl; else alias impl!(fun(args[0], args[1]), args[2..$]) impl; } } This has a similar structure to the varargs_reduce that I posted a while ago, except that it doesn't need to do any type inference - it just replaces the expression and lets the compiler take care of the rest. Andrei

This seems to me to be an obvious good extension to the template system. With the ability to pass aliases into the template, you gain arbitrary and easy expression composition.
Feb 01 2007
parent reply Don Clugston <dac nospam.com.au> writes:
Kyle Furlong wrote:
 Andrei Alexandrescu (See Website for Email) wrote:
 Xinok wrote:
 I've held off this idea for a little while now, but I think it could
 really be a nice addition to D.

 Expressions are basically aliases which can accept arguments. Unlike
 aliases though, an expression can accept non-const arguments.

 expression mul(a, b) = a * b; // Syntax is different from aliases,
 for good reason...

 int main(){ int a = 35, b = 62; a = mul(a, b); }

 'mul' isn't a constant expression. The expression is inlined into the
 code, it doesn't call a function.

 So basically, expressions are inline functions. BUT, because D lacks
 references (which I'll never be able to understand), functions can't
 return l-values. Expressions can be treated as so though.

Walter had the idea of allowing an expression to bind to an alias, and to allow the following: template mul(alias a, alias b) { alias (a * b) mul; } with the effect that you want. (The parens are there to keep alias happy, not to prevent breakage of order of operations.)
 One example of a recent post, the max function: expression max(a, b)
 = a > b ? a : b; Can handle any and all types, and you can use it as
 an l-value.

Nah, that won't work. It will doubly evaluate one of its arguments. But the feature will allow very simple implementation of my varargs_reduce and varargs_reduce_parallel primitives, e.g.: template varargs_reduce(alias fun) { template impl(alias args...) { static if (args.length == 2) alias fun(args) impl; else alias impl!(fun(args[0], args[1]), args[2..$]) impl; } } This has a similar structure to the varargs_reduce that I posted a while ago, except that it doesn't need to do any type inference - it just replaces the expression and lets the compiler take care of the rest. Andrei

This seems to me to be an obvious good extension to the template system. With the ability to pass aliases into the template, you gain arbitrary and easy expression composition.

The name mangling looks tricky. I think an expression alias would have to behave like a typedef: given alias (a*b) X; alias (a*b) Y; X and Y would not be the same. A name mangling scheme for an arbitrary expression would be a bit of a nightmare.
Feb 01 2007
parent Kyle Furlong <kylefurlong gmail.com> writes:
Don Clugston wrote:
 Kyle Furlong wrote:
 Andrei Alexandrescu (See Website for Email) wrote:
 Xinok wrote:
 I've held off this idea for a little while now, but I think it could
 really be a nice addition to D.

 Expressions are basically aliases which can accept arguments. Unlike
 aliases though, an expression can accept non-const arguments.

 expression mul(a, b) = a * b; // Syntax is different from aliases,
 for good reason...

 int main(){ int a = 35, b = 62; a = mul(a, b); }

 'mul' isn't a constant expression. The expression is inlined into the
 code, it doesn't call a function.

 So basically, expressions are inline functions. BUT, because D lacks
 references (which I'll never be able to understand), functions can't
 return l-values. Expressions can be treated as so though.

Walter had the idea of allowing an expression to bind to an alias, and to allow the following: template mul(alias a, alias b) { alias (a * b) mul; } with the effect that you want. (The parens are there to keep alias happy, not to prevent breakage of order of operations.)
 One example of a recent post, the max function: expression max(a, b)
 = a > b ? a : b; Can handle any and all types, and you can use it as
 an l-value.

Nah, that won't work. It will doubly evaluate one of its arguments. But the feature will allow very simple implementation of my varargs_reduce and varargs_reduce_parallel primitives, e.g.: template varargs_reduce(alias fun) { template impl(alias args...) { static if (args.length == 2) alias fun(args) impl; else alias impl!(fun(args[0], args[1]), args[2..$]) impl; } } This has a similar structure to the varargs_reduce that I posted a while ago, except that it doesn't need to do any type inference - it just replaces the expression and lets the compiler take care of the rest. Andrei

This seems to me to be an obvious good extension to the template system. With the ability to pass aliases into the template, you gain arbitrary and easy expression composition.

The name mangling looks tricky. I think an expression alias would have to behave like a typedef: given alias (a*b) X; alias (a*b) Y; X and Y would not be the same. A name mangling scheme for an arbitrary expression would be a bit of a nightmare.

I suppose I meant easy use, not easy implementation. <g>
Feb 01 2007
prev sibling next sibling parent reply Johan Granberg <lijat.meREM OVE.gmail.com> writes:
Xinok wrote:

 I've held off this idea for a little while now, but I think it could
 really be a nice addition to D.
 
 Expressions are basically aliases which can accept arguments. Unlike
 aliases though, an expression can accept non-const arguments.
 
 expression mul(a, b) = a * b; // Syntax is different from aliases, for
 good reason...
 
 int main(){
 int a = 35, b = 62;
 a = mul(a, b);
 }
 
 'mul' isn't a constant expression. The expression is inlined into the
 code, it doesn't call a function.
 
 So basically, expressions are inline functions. BUT, because D lacks
 references (which I'll never be able to understand), functions can't
 return l-values. Expressions can be treated as so though.
 
 One example of a recent post, the max function:
 expression max(a, b) = a > b ? a : b;
 Can handle any and all types, and you can use it as an l-value.
 
 
 Expressions can also be used for simpler operations, such as dereferencing
 an array or pointer, to make code easier to read and write: int* ptr = new
 int; expression exp = *ptr;
 exp = 62;
 
 int[] arr = [15, 30, 45, 60];
 expression val = arr[2];
 val = 30;
 
 
 Expressions can also handle constants, so it could be used as an alias
 which can take arguments. (Maybe require const property?)
 const expression ptr(T) = T*;
 
 
 -- Expressions are NOT mixins! You can't use undeclared symbols in an
 expression. -- Expressions are NOT functions! The expression is inlined
 into the code.

votes++ This is a good idea and could really complement mixins in a nice way. What should the limits of this syntax be? should this be allowed. //declared as expression loop(a,b) = foreach(i,s,a)b[i]=s; //used as char[] foo=new char[10]; char[] bar=new char[10]; loop(foo,bar); to be able to do this would be intresting as it could allow for some powerfull syntax abstractions. Another intresting case would be. //declared as this expression mix = AMixin!(bool)(5); //used like this mix; here mix would be a mixin doing some work rather than declaring variables and I would expect that the expression have there own scope so that no mixin variables is leaked into the surrounding namespace.
Jan 31 2007
parent reply Xinok <xnknet gmail.com> writes:
Johan Granberg Wrote:

 votes++
 
 This is a good idea and could really complement mixins in a nice way.
 What should the limits of this syntax be? should this be allowed.
 
 //declared as
 expression loop(a,b) = foreach(i,s,a)b[i]=s;
 
 //used as
 char[] foo=new char[10];
 char[] bar=new char[10];
 loop(foo,bar);
 
 to be able to do this would be intresting as it could allow for some
 powerfull syntax abstractions.
 
 Another intresting case would be.
 
 //declared as this
 expression mix = AMixin!(bool)(5);
 
 //used like this
 mix;
 
 here mix would be a mixin doing some work rather than declaring variables
 and I would expect that the expression have there own scope so that no
 mixin variables is leaked into the surrounding namespace.
 

I think loops would be best left to functions. Just my opinion anyways, I think expressions should have a return value. If it contains a loop, then it has no return value and is nothing more than a command, aka function. void loop(Ta, Tb)(Ta[] a, Tb[] b){ foreach(i,s; a)b[i]=s; } // In the case of dynamic arrays, it will point to the same block of data, so no pointer is necessary.
 Walter had the idea of allowing an expression to bind to an alias, and
 to allow the following:
 
 template mul(alias a, alias b)
 {
    alias (a * b) mul;
 }

The problem with aliases is that the end expression must be constant. Expressions are more like functions and can give non-constant values.
 One example of a recent post, the max function: expression max(a, b)
 = a > b ? a : b; Can handle any and all types, and you can use it as
 an l-value.

Nah, that won't work. It will doubly evaluate one of its arguments. But the feature will allow very simple implementation of my varargs_reduce and varargs_reduce_parallel primitives, e.g.:

This is something I've thought about myself. One possible solution though is lazy arguments. If you think about it, the expression given to a lazy argument is only evaluated once, and it remembers the value. The same could be possible for expressions. expression max(lazy a, lazy b) = a > b ? a : b; One topic I forgot to mention is using expressions as template arguments. This is the idea I had for it: template temp(expression a(a, b)){ } // Must have two arguments, a and b template temp(expression b){ } // Can accept any set of arguments expression mul(a, b) = a * b; temp!(mul); // Expression is given to template I think expressions should be like structs though; Even though the expression is exactly the same, it should be treated as a different type. expression mul1(a, b) = a * b; expression mul2(a, b) = a * b; is(mul1 == mul2) // False temp!(mul1) is different than temp!(mul2)
Feb 01 2007
parent "Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail erdani.org> writes:
Xinok wrote:
 Walter had the idea of allowing an expression to bind to an alias,
 and to allow the following:
 
 template mul(alias a, alias b) { alias (a * b) mul; }

The problem with aliases is that the end expression must be constant. Expressions are more like functions and can give non-constant values.

Nonono. As I said (in the paragraph you quoted above), expressions could bind to aliases, so mul accepts any two expressions and creates an expression.
 One example of a recent post, the max function: expression max(a,
 b) = a > b ? a : b; Can handle any and all types, and you can use
 it as an l-value.

But the feature will allow very simple implementation of my varargs_reduce and varargs_reduce_parallel primitives, e.g.:

This is something I've thought about myself. One possible solution though is lazy arguments. If you think about it, the expression given to a lazy argument is only evaluated once, and it remembers the value. The same could be possible for expressions. expression max(lazy a, lazy b) = a > b ? a : b;

But this semantics is opposite to lazy's current semantics. Overall, I think the alias-based macros are more in keep with the current language and allow more expressive constructs beyond expression manipulation.
 One topic I forgot to mention is using expressions as template
 arguments. This is the idea I had for it:
 
 template temp(expression a(a, b)){ } // Must have two arguments, a
 and b template temp(expression b){ } // Can accept any set of
 arguments
 
 expression mul(a, b) = a * b;
 
 temp!(mul); // Expression is given to template

Yes. This is exactly Walter's idea: allow an expression to bind to an alias. No need for a new keyword. Andrei
Feb 01 2007
prev sibling parent reply "Vladimir Panteleev" <thecybershadow gmail.com> writes:
On Thu, 01 Feb 2007 08:08:48 +0200, Xinok <xnknet gmail.com> wrote:

 I've held off this idea for a little while now, but I think it could r=

 Expressions are basically aliases which can accept arguments. Unlike a=

 expression mul(a, b) =3D a * b; // Syntax is different from aliases, f=

 int main(){
 	int a =3D 35, b =3D 62;
 	a =3D mul(a, b);
 }

votes++. However, why not go all the way and implement inline functions?= That is, functions which are inlined in the expression using them. These would have the following advantages: * using a simple syntax (an "inline" attribute for the function); * since no calls to the function are made, the compiler can decide whe= n to use lazy argument evaluation and when to cache the results; * since it's a function, it's possible to use complex instructions (bl= ocks, loops, etc.) * the code (arguments, return value) simply direct the data flow - the= re is no real stack frame, the return value is passed immediately and ev= aluated with the context, etc. Unless there is a strong barrier in the c= ompiler between compiling expressions and statements (so that compound s= tatements could be inlined in expressions), it shouldn't be hard to impl= ement either. In contrast to the above method, however, it won't be possible to use th= e same expression/inline function with different types. For example, usi= ng the syntax above, expression div(a, b) =3D a/b; int main(){ int i1 =3D 65, i2 =3D 32, i3; i3=3Ddiv(i1, i2); float f1 =3D 6.5, f2 =3D 3.2, f3; f3=3Ddiv(i1, i2); } won't be directly possible with inline functions - but is easily done by= enclosing the inline function in a template (not unlike regular functio= ns): template div(T) { inline T div(T a, T b) { return a/b; } } void main() { writefln(div!(int)(5,2)); writefln(div!(float)(5.5,2.1)); } What do you think? -- = Best regards, Vladimir mailto:thecybershadow gmail.com
Feb 02 2007
next sibling parent reply Don Clugston <dac nospam.com.au> writes:
Vladimir Panteleev wrote:
 votes++. However, why not go all the way and implement inline functions? That
is, functions which are inlined in the expression using them.
 
 These would have the following advantages:
 
   * using a simple syntax (an "inline" attribute for the function);
   * since no calls to the function are made, the compiler can decide when to
use lazy argument evaluation and when to cache the results;
   * since it's a function, it's possible to use complex instructions (blocks,
loops, etc.)
   * the code (arguments, return value) simply direct the data flow - there is
no real stack frame, the return value is passed immediately and evaluated with
the context, etc. Unless there is a strong barrier in the compiler between
compiling expressions and statements (so that compound statements could be
inlined in expressions), it shouldn't be hard to implement either.
 
 template div(T)
 {	inline T div(T a, T b)	{ return a/b; }
 }
 void main()
 {	writefln(div!(int)(5,2));
 	writefln(div!(float)(5.5,2.1));
 }
 
 What do you think?

??? This has been part of the compiler for a very long time. There's no need for the inline keyword, just use the -inline compiler switch and it happens automatically. Or did I miss something?
Feb 02 2007
parent reply "Vladimir Panteleev" <thecybershadow gmail.com> writes:
On Fri, 02 Feb 2007 12:57:06 +0200, Don Clugston <dac nospam.com.au> wrote:

 ???
 This has been part of the compiler for a very long time. There's no need
 for the inline keyword, just use the -inline compiler switch and it
 happens automatically.
 Or did I miss something?

AFAIK, even with -inline, the compiler doesn't automatically inline functions in certain circumstances - for example, if they contain loops. Sometimes it is critical for the functions to be inlined - out of performance or security (obfuscation) reasons. Either way, what are the disadvantages of such inline functions compared to the discussed expression types? -- Best regards, Vladimir mailto:thecybershadow gmail.com
Feb 02 2007
parent "Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail erdani.org> writes:
Vladimir Panteleev wrote:
 On Fri, 02 Feb 2007 12:57:06 +0200, Don Clugston <dac nospam.com.au>
 wrote:
 
 ??? This has been part of the compiler for a very long time.
 There's no need for the inline keyword, just use the -inline
 compiler switch and it happens automatically. Or did I miss
 something?

AFAIK, even with -inline, the compiler doesn't automatically inline functions in certain circumstances - for example, if they contain loops. Sometimes it is critical for the functions to be inlined - out of performance or security (obfuscation) reasons. Either way, what are the disadvantages of such inline functions compared to the discussed expression types?

They are different beasts. Expression transformations specify syntactic processing exclusively. Andrei
Feb 02 2007
prev sibling parent reply Xinok <xnknet gmail.com> writes:
Functions aren't powerful enough to be used in place of expressions.

-- Functions can't return references, which prevents you from using them as
l-values. The best you can do is return a pointer, which you have to manually
dereference.
-- All of the types in an expression are automatic, including the return type.
Getting the parameter types for a function can be simple, but the return type
can be difficult.
-- Functions can't take full advantage of features like conditionals, because
conditionals can have two or more possible 'return types'. Functions can only
return a single type.
Feb 02 2007
parent reply "Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail erdani.org> writes:
Xinok wrote:
 Functions aren't powerful enough to be used in place of expressions.
 
 -- Functions can't return references, which prevents you from using them as
l-values. The best you can do is return a pointer, which you have to manually
dereference.
 -- All of the types in an expression are automatic, including the return type.
Getting the parameter types for a function can be simple, but the return type
can be difficult.
 -- Functions can't take full advantage of features like conditionals, because
conditionals can have two or more possible 'return types'. Functions can only
return a single type.

Conditionals always have one return type that can be detected as typeof(true? T : U). The advantage of expression aliases is that indeed there's no need to worry for their type. Also, expression aliases work when passing e.g. constant expressions to templates because they expand during compilation. The disadvantages are that (1) writing statements in the replacement expression is not possible, (2) separate compilation is not possible. Andrei
Feb 02 2007
parent reply Don Clugston <dac nospam.com.au> writes:
Andrei Alexandrescu (See Website For Email) wrote:
 Xinok wrote:
 Functions aren't powerful enough to be used in place of expressions.

 -- Functions can't return references, which prevents you from using 
 them as l-values. The best you can do is return a pointer, which you 
 have to manually dereference.
 -- All of the types in an expression are automatic, including the 
 return type. Getting the parameter types for a function can be simple, 
 but the return type can be difficult.
 -- Functions can't take full advantage of features like conditionals, 
 because conditionals can have two or more possible 'return types'. 
 Functions can only return a single type.

Conditionals always have one return type that can be detected as typeof(true? T : U). The advantage of expression aliases is that indeed there's no need to worry for their type.

That's already possible, albeit clumsy. You can duplicate the expression inside an anonymous delegate, and use D's automatic return type inference, together with an is( : return) expression. This works even when there are statements in the delegate, together with expressions. So I suspect something even more general might be possible. Also, expression aliases work when passing e.g.
 constant expressions to templates because they expand during 
 compilation. The disadvantages are that (1) writing statements in the 
 replacement expression is not possible, (2) separate compilation is not 
 possible.
 
 
 Andrei

Feb 02 2007
parent reply "Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail erdani.org> writes:
Don Clugston wrote:
 Andrei Alexandrescu (See Website For Email) wrote:
 Xinok wrote:
 Functions aren't powerful enough to be used in place of expressions.

 -- Functions can't return references, which prevents you from using 
 them as l-values. The best you can do is return a pointer, which you 
 have to manually dereference.
 -- All of the types in an expression are automatic, including the 
 return type. Getting the parameter types for a function can be 
 simple, but the return type can be difficult.
 -- Functions can't take full advantage of features like conditionals, 
 because conditionals can have two or more possible 'return types'. 
 Functions can only return a single type.

Conditionals always have one return type that can be detected as typeof(true? T : U). The advantage of expression aliases is that indeed there's no need to worry for their type.

That's already possible, albeit clumsy. You can duplicate the expression inside an anonymous delegate, and use D's automatic return type inference, together with an is( : return) expression. This works even when there are statements in the delegate, together with expressions. So I suspect something even more general might be possible.

Oh, yes, it's definitely possible. "Worrying about" does not mean "can't do anything about" :o). It's just annoying. For example, half of varargs_reduce deals with types, although it doesn't care about them at all. A more general thing I was thinking of was to allow statements to bind to aliases. Then D will have a pretty interesting macro mechanism. Andrei
Feb 02 2007
parent Don Clugston <dac nospam.com.au> writes:
Andrei Alexandrescu (See Website For Email) wrote:
 Don Clugston wrote:
 Andrei Alexandrescu (See Website For Email) wrote:
 Xinok wrote:
 Functions aren't powerful enough to be used in place of expressions.

 -- Functions can't return references, which prevents you from using 
 them as l-values. The best you can do is return a pointer, which you 
 have to manually dereference.
 -- All of the types in an expression are automatic, including the 
 return type. Getting the parameter types for a function can be 
 simple, but the return type can be difficult.
 -- Functions can't take full advantage of features like 
 conditionals, because conditionals can have two or more possible 
 'return types'. Functions can only return a single type.

Conditionals always have one return type that can be detected as typeof(true? T : U). The advantage of expression aliases is that indeed there's no need to worry for their type.

That's already possible, albeit clumsy. You can duplicate the expression inside an anonymous delegate, and use D's automatic return type inference, together with an is( : return) expression. This works even when there are statements in the delegate, together with expressions. So I suspect something even more general might be possible.

Oh, yes, it's definitely possible. "Worrying about" does not mean "can't do anything about" :o). It's just annoying. For example, half of varargs_reduce deals with types, although it doesn't care about them at all. A more general thing I was thinking of was to allow statements to bind to aliases. Then D will have a pretty interesting macro mechanism.

Agreed. I think mixins were supposed to be a replacement for macros, but they're just too limited.
 
 
 Andrei

Feb 02 2007