www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Partially instantiating templates?

reply Magnus Lie Hetland <magnus hetland.org> writes:
I'm building a function (or template or whatever, really) that is 
related to map and minPos in std.algorithm. Basically, it's the 
standard mathematical argmin, except that it also returns min. It looks 
something like this:

  auto minArg(alias fun, Range, T)(Range range, out T minVal) {
      ...
  }

Already there may be issues -- the return type should be 
ElementType(range) and T should be the return type of fun ... but it 
works. (Suggestions on these issues are welcome, but that's not really 
the main point here.)

The thing is, because I'm also returning the actual value, I'd rather 
not use the strategy of std.algorithm.minPos, which asks you to use an 
inverted function to get maxPos; instead, I'd like an explicit maxArg 
function. My idea was to have a common, more general optArg, which took 
an operator ("<" or ">") as a compile-time argument. Then I could do 
something like

  alias optArg!"<" minArg;
  alias optArg!">" maxArg;

Then, at some *later* time, I might want to do something like:

  alias maxArg!((v) {return dist(u,v);}) farthest;

(By the way: For some reason, I'm not allowed to use curry(dist,u) 
instead of the lambda here, it seems. Any insights on that? Would have 
been nice to use "d(u,a)" as well -- as I do use unaryFunc on the fun 
argument. That doesn't work either, though...)

I've been able to make either one of these two pieces of functionality 
work with some twiddling and nesting (i.e., *either* instantiating 
optArg into minArg/maxArg, *or* instantiating explicitly defined 
minArg/maxArg into specialized functions) but combining them has so far 
eluded me (unless I start fiddling with strinc constants and mixin(), 
which seems excessively hacky for such a simple thing).

Any ideas/suggestions? I'm sure I'm missing something obvious ... 
(Perhaps even existing functionality for minArg/maxArg -- although the 
general question still stands.)

-- 
Magnus Lie Hetland
http://hetland.org
Jan 31 2011
next sibling parent reply "Simen kjaeraas" <simen.kjaras gmail.com> writes:
Magnus Lie Hetland <magnus hetland.org> wrote:

 I'm building a function (or template or whatever, really) that is  
 related to map and minPos in std.algorithm. Basically, it's the standard  
 mathematical argmin, except that it also returns min. It looks something  
 like this:

   auto minArg(alias fun, Range, T)(Range range, out T minVal) {
       ...
   }

 Already there may be issues -- the return type should be  
 ElementType(range) and T should be the return type of fun ... but it  
 works. (Suggestions on these issues are welcome, but that's not really  
 the main point here.)

ElementType!Range minArg( alias fun, Range )( Range range, out ReturnType!fun ) { ... } Might I also ask why you use an out parameter instead of a tuple return?
 The thing is, because I'm also returning the actual value, I'd rather  
 not use the strategy of std.algorithm.minPos, which asks you to use an  
 inverted function to get maxPos; instead, I'd like an explicit maxArg  
 function. My idea was to have a common, more general optArg, which took  
 an operator ("<" or ">") as a compile-time argument. Then I could do  
 something like

   alias optArg!"<" minArg;
   alias optArg!">" maxArg;

 Then, at some *later* time, I might want to do something like:

   alias maxArg!((v) {return dist(u,v);}) farthest;

D currently does not support template currying to any good degree. However, there is at least one library out there that does that for you: http://www.dsource.org/projects/dranges In the file templates.d, there is the template CurryTemplate, which rewrites a template to a set of nested templates. This would allow you to partially instantiate a template, and add more parameters as you go.
 I've been able to make either one of these two pieces of functionality  
 work with some twiddling and nesting (i.e., *either* instantiating  
 optArg into minArg/maxArg, *or* instantiating explicitly defined  
 minArg/maxArg into specialized functions) but combining them has so far  
 eluded me (unless I start fiddling with strinc constants and mixin(),  
 which seems excessively hacky for such a simple thing).

dranges' templates.CurryTemplate should take care of some of your problems. Not sure if it will fix them all. -- Simen
Jan 31 2011
parent reply Magnus Lie Hetland <magnus hetland.org> writes:
On 2011-01-31 12:55:07 +0100, Simen kjaeraas said:

 ElementType!Range minArg( alias fun, Range )( Range range, out 
 ReturnType!fun ) {
      ...
 }

Aaaah. I guess I tried ElementType(Range), forgetting to make it a compile-time parameter. Thanks. (Hadn't seen ReturnType; makes sense :)
 Might I also ask why you use an out parameter instead of a tuple return?

Well... I had a tuple return at first, but one of the advantages of returning multiple values that I'm accustomed to is the ability to assign to multiple variables, such as arg, val = minArg(...) (Yeah, I'm a Python guy... ;) As far as I can see, you can't do that here? Using result[0] and result[1] or the like, or assigning separately to two variables just seemed more cumbersome. Then again, I could use a tuple with named members, I guess. In your opinion, what would be the prettiest (in D terms) way of doing this? [snip]
 D currently does not support template currying to any good degree.

OK. Well, I guess I don't really need it. Still trying to get a feel for what's "normal" D :)
 However, there is at least one library out there that does that for you:
 
 http://www.dsource.org/projects/dranges
 
 In the file templates.d, there is the template CurryTemplate, which
 rewrites a template to a set of nested templates. This would allow you
 to partially instantiate a template, and add more parameters as you go.

I see. I actually don't mind writing nested templates myself -- but for some reason I couldn't get them to work properly. (D kept complaining about declarations vs instances, and the like; I guess I'll have a look at how dranges does it.)
 I've been able to make either one of these two pieces of functionality 
 work with some twiddling and nesting (i.e., *either* instantiating 
 optArg into minArg/maxArg, *or* instantiating explicitly defined 
 minArg/maxArg into specialized functions) but combining them has so far 
 eluded me (unless I start fiddling with strinc constants and mixin(), 
 which seems excessively hacky for such a simple thing).

dranges' templates.CurryTemplate should take care of some of your problems. Not sure if it will fix them all.

OK, thanks. By the way, if you have suggestions for other more "D-like" ways of encapsulating this functionality (basically a linear scan for an element that yields a max/min value for a given expression), I'd be interested to hear that too. The best way to solve a problem is often to rephrase it :) -- Magnus Lie Hetland http://hetland.org
Jan 31 2011
next sibling parent Magnus Lie Hetland <magnus hetland.org> writes:
On 2011-01-31 15:50:41 +0100, Simen kjaeraas said:

 You might want to try more from dranges - the reftuple:
 
 _(arg,val) = minArg(...);

 This is also a possible implementation (coded in about 5 minutes, gives
 no nice error messages, but it seems to work :p ):

Thanks :)
 Yeah. D has the nice Eponymous Template Trick, but it sadly only works
 for one level. :(

Right :-/
 So you have to test for every single element of the range?

Yup.
 If so, I think this works:

Thanks. Hm. The nesting does seem similar to how I did it, but I guess there must be some crucial difference ;-) At the moment, I'm using a mixing to create the min and max templates (as rather large strings). Probably not ideal. Thanks! -- Magnus Lie Hetland http://hetland.org
Jan 31 2011
prev sibling next sibling parent reply Magnus Lie Hetland <magnus hetland.org> writes:
Hm. Using code quite similar to you, supplying a lambda in the second 
aliasing, I get this error:

something.d(93): Error: template instance cannot use local 
'__dgliteral2(__T3)' as parameter to non-global template optArg(alias 
fun)

It seems it's explicitly objecting to what I want it to do...

Using optArg!"a", for example, works just fine -- but the whole point 
was to include some local state. Using local functions worked (I 
think...?) when I had a global template.

It seems D's compile-time computation system is less straightforward 
than I thought :)

-- 
Magnus Lie Hetland
http://hetland.org
Jan 31 2011
parent reply Magnus Lie Hetland <magnus hetland.org> writes:
On 2011-01-31 19:46:53 +0100, Simen kjaeraas said:

 Magnus Lie Hetland <magnus hetland.org> wrote:
 
 Hm. Using code quite similar to you, supplying a lambda in the second 
 aliasing, I get this error:
 
 something.d(93): Error: template instance cannot use local 
 '__dgliteral2(__T3)' as parameter to non-global template optArg(alias 
 fun)


 
 This is a bug. Please report it.

Ah -- OK. Will do. -- Magnus Lie Hetland http://hetland.org
Feb 01 2011
parent reply Magnus Lie Hetland <magnus hetland.org> writes:
On 2011-02-01 10:12:44 +0100, Magnus Lie Hetland said:

 On 2011-01-31 19:46:53 +0100, Simen kjaeraas said:
 
 Magnus Lie Hetland <magnus hetland.org> wrote:
 
 Hm. Using code quite similar to you, supplying a lambda in the second 
 aliasing, I get this error:
 
 something.d(93): Error: template instance cannot use local 
 '__dgliteral2(__T3)' as parameter to non-global template optArg(alias 
 fun)


 
 This is a bug. Please report it.

Ah -- OK. Will do.

Hm. Just to make sure this *is* a bug, and I'm not just being a dumbass ... this is a tiny program that illustrates the problem (i.e., gives the error above). Perhaps the use of a local function here really is prohibited...? template A(int op) { template A(alias fun) { auto A(T)(T x) { return 0; } } } alias A!0 B; int gun() { return 0; } void main() { int fun() {return 0;} // alias B!((){return 0;}) C; // Won't compile // alias B!(fun) C; // Won't compile alias B!(gun) C; // Works } -- Magnus Lie Hetland http://hetland.org
Feb 01 2011
parent reply Magnus Lie Hetland <magnus hetland.org> writes:
On 2011-02-01 12:37:05 +0100, Simen kjaeraas said:

 Magnus Lie Hetland <magnus hetland.org> wrote:
 
 Hm. Just to make sure this *is* a bug, and I'm not just being a dumbass 
 ... this is a tiny program that illustrates the problem (i.e., gives 
 the error above). Perhaps the use of a local function here really is 
 prohibited...?

Maybe it is. It really shouldn't be, though. If this is not a bug, then Walter has a bug for not accepting this as a bug. :p

Hehe :) Sort of related (though perhaps only remotely) is the following, which won't compile (Error: static assert "Bad unary function: f(a) for type int"): import std.functional, std.stdio; int f(int x) {return x;} void main() { alias unaryFun!("f(a)") g; writeln(g(3)); } It may not be related -- but I've been trying to use the string representation instead of lambda in some places, and I thought maybe a similar name lookup problem may be present in the unaryFun template? (The detauls of the implementation are a bit beyond me at the moment...) Maybe there's an unstated restriction against using functions in the unaryFun string parameter (at least I couldn't find it in the docs) -- but ... there is the following example in the docs for std.algorithms: sort!("hashFun(a) < hashFun(b)")(array); So it would seem like this *should* work? -- Magnus Lie Hetland http://hetland.org
Feb 01 2011
next sibling parent Magnus Lie Hetland <magnus hetland.org> writes:
On 2011-02-01 16:00:16 +0100, Magnus Lie Hetland said:

   import std.functional, std.stdio;
   int f(int x) {return x;}
   void main() {
       alias unaryFun!("f(a)") g;
       writeln(g(3));
   }

Just to be clear -- I realize I could just have used unaryFun!f here (or just f, for that matter). The usage case was actually currying. I used "f(x,a)" as a compile-time argument to the kind of template that we discussed earlier. And the reason I tried that was that this didn't work either: import std.functional, std.stdio; int f(int x, int y) {return x;} void main() { alias unaryFun!(curry(f, 2)) g; writeln(g(3)); } At that point, the only thing that worked was using a lambda. And, as I pointed out, with the nested templates, that didn't work either. Seems like the language (or the stdlib) is resisting my efforts at every turn here. Perhaps I should just write out those for-loops redundantly, rather than using templates ;) -- Magnus Lie Hetland http://hetland.org
Feb 01 2011
prev sibling parent reply Magnus Lie Hetland <magnus hetland.org> writes:
On 2011-02-01 16:09:22 +0100, Simen kjaeraas said:

 Magnus Lie Hetland <magnus hetland.org> wrote:
 
 Sort of related (though perhaps only remotely) is the following, which 
 won't compile (Error: static assert "Bad unary function: f(a) for type 
 int"):

Not related. unaryFun and binaryFun are simply glorified string mixins, and thus can only access functions that are available in the modules where they are mixed in. That would be std.functional. Because of that, local functions may not be used as string arguments for *naryFun.

That certainly makes sense. I just got thrown off by the example in std.algorithm: uint hashFun(string) { ... expensive computation ... } string[] array = ...; // Sort strings by hash, slow sort!("hashFun(a) < hashFun(b)")(array); The only way this could work would be if hashFun was available to the sort template, I guess...? -- Magnus Lie Hetland http://hetland.org
Feb 01 2011
parent Magnus Lie Hetland <magnus hetland.org> writes:
On 2011-02-01 16:41:33 +0100, Simen kjaeraas said:

 That certainly makes sense. I just got thrown off by the example in 
 std.algorithm:
 
 uint hashFun(string) { ... expensive computation ... }
 string[] array = ...;
 // Sort strings by hash, slow
 sort!("hashFun(a) < hashFun(b)")(array);
 
 The only way this could work would be if hashFun was available to the 
 sort template, I guess...?

Nope, still std.functional. That's where the string is mixin'ed.

Right. Given the example, there's no way to tell that sort is implemented using std.functional, so really meant whichever function is actually using the string ;) But, yeah, I understand how it works. Thanks.
 But thanks for noting that, I've filed it as issue #5513.

Good. -- Magnus Lie Hetland http://hetland.org
Feb 01 2011
prev sibling parent reply bearophile <bearophileHUGS lycos.com> writes:
Magnus Lie Hetland:

 Well... I had a tuple return at first, but one of the advantages of 
 returning multiple values that I'm accustomed to is the ability to 
 assign to multiple variables, such as
 
   arg, val = minArg(...)
 
 (Yeah, I'm a Python guy... ;)

I will eventually add a detailed enhancement request on this topic. Bye, bearophile
Jan 31 2011
parent reply Magnus Lie Hetland <magnus hetland.org> writes:
On 2011-01-31 22:21:40 +0100, bearophile said:

 Magnus Lie Hetland:

 I'm accustomed to is the ability to
 assign to multiple variables, such as
 
 arg, val = minArg(...)
 
 (Yeah, I'm a Python guy... ;)

I will eventually add a detailed enhancement request on this topic.

Great! I think this is really useful -- also for swapping things around (a, b = b, a). For multiple return values it makes a huge difference, IMO. -- Magnus Lie Hetland http://hetland.org
Feb 01 2011
parent reply Magnus Lie Hetland <magnus hetland.org> writes:
On 2011-02-01 10:11:53 +0100, Magnus Lie Hetland said:

 On 2011-01-31 22:21:40 +0100, bearophile said:
 
 Magnus Lie Hetland:

 I'm accustomed to is the ability to
 assign to multiple variables, such as
 
 arg, val = minArg(...)
 
 (Yeah, I'm a Python guy... ;)

I will eventually add a detailed enhancement request on this topic.

Great! I think this is really useful -- also for swapping things around (a, b = b, a). For multiple return values it makes a huge difference, IMO.

Saw your post on digitalmars.D now, about the currying of templates (i.e., the main topic here). I guess perhaps that was what you were talking about? That would certainly be great, too :) -- Magnus Lie Hetland http://hetland.org
Feb 01 2011
parent reply bearophile <bearophileHUGS lycos.com> writes:
Magnus Lie Hetland:

 Saw your post on digitalmars.D now, about the currying of templates 
 (i.e., the main topic here). I guess perhaps that was what you were 
 talking about?

Tuple unpacking syntax and template currying are two different things. Bye, bearophile
Feb 01 2011
parent reply Magnus Lie Hetland <magnus hetland.org> writes:
On 2011-02-01 10:49:23 +0100, bearophile said:

 Magnus Lie Hetland:
 
 Saw your post on digitalmars.D now, about the currying of templates
 (i.e., the main topic here). I guess perhaps that was what you were
 talking about?

Tuple unpacking syntax and template currying are two different things.

Yes, certainly. That was the point of this post -- that I misunderstood what you were talking about in the original post (where you said "this topic" right after my tuple unpacking paragraph) :) -- Magnus Lie Hetland http://hetland.org
Feb 01 2011
parent bearophile <bearophileHUGS lycos.com> writes:
Magnus Lie Hetland:

 Yes, certainly. That was the point of this post -- that I misunderstood 
 what you were talking about in the original post (where you said "this 
 topic" right after my tuple unpacking paragraph) :)

I will eventually add to bugzilla a request for tuple unpacking syntax, see here: http://www.digitalmars.com/webnews/newsgroups.php?art_group=digitalmars.D&article_id=118601 Bye, bearophile
Feb 01 2011
prev sibling next sibling parent "Simen kjaeraas" <simen.kjaras gmail.com> writes:
Magnus Lie Hetland <magnus hetland.org> wrote:

 Might I also ask why you use an out parameter instead of a tuple return?

Well... I had a tuple return at first, but one of the advantages of returning multiple values that I'm accustomed to is the ability to assign to multiple variables, such as arg, val = minArg(...) (Yeah, I'm a Python guy... ;) As far as I can see, you can't do that here? Using result[0] and result[1] or the like, or assigning separately to two variables just seemed more cumbersome. Then again, I could use a tuple with named members, I guess. In your opinion, what would be the prettiest (in D terms) way of doing this?

You might want to try more from dranges - the reftuple: _(arg,val) = minArg(...); It resides in dranges.reftuple, and still has some warts, but it usually works. This is also a possible implementation (coded in about 5 minutes, gives no nice error messages, but it seems to work :p ): import std.typetuple; import std.typecons; template TypeOf( alias T ) { alias typeof( T ) TypeOf; } property void _( T... )( Tuple!( staticMap!(TypeOf, T) ) args ) { foreach ( i, e; T ) { e = args[i]; } } void main( ) { int a, b; _!(a,b) = tuple(b,a+b); // fibonacci }
 I see. I actually don't mind writing nested templates myself -- but for  
 some reason I couldn't get them to work properly. (D kept complaining  
 about declarations vs instances, and the like; I guess I'll have a look  
 at how dranges does it.)

Yeah. D has the nice Eponymous Template Trick, but it sadly only works for one level. :(
 By the way, if you have suggestions for other more "D-like" ways of  
 encapsulating this functionality (basically a linear scan for an element  
 that yields a max/min value for a given expression), I'd be interested  
 to hear that too. The best way to solve a problem is often to rephrase  
 it :)

So you have to test for every single element of the range? If so, I think this works: module foo; import std.typecons; import std.functional; import std.array; template optArg( alias pred ) { template optArg( alias fn ) { auto optArg( Range )( Range r ) { alias binaryFun!pred predicate; alias unaryFun!fn func; auto result = tuple( r.front, func( r.front ) ); foreach ( e; r ) { auto tmp = func( e ); if ( predicate( e, result[1] ) ) { result = tuple( e, tmp ); } } return result; } } } void main( ) { alias optArg!"a<b" minArg; alias minArg!"a" foo; assert( foo( [5,2,1,3] ) == tuple(1,1) ); } -- Simen
Jan 31 2011
prev sibling next sibling parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
On 1/31/11, Simen kjaeraas <simen.kjaras gmail.com> wrote:
 module foo;

 import std.typecons;
 import std.functional;
 import std.array;

 template optArg( alias pred ) {
      template optArg( alias fn ) {
          auto optArg( Range )( Range r ) {
              alias binaryFun!pred predicate;
              alias unaryFun!fn func;

              auto result = tuple( r.front, func( r.front ) );
              foreach ( e; r ) {
                  auto tmp = func( e );
                  if ( predicate( e, result[1] ) ) {
                      result = tuple( e, tmp );
                  }
              }
              return result;
          }
      }
 }

 void main( ) {
      alias optArg!"a<b" minArg;
      alias minArg!"a" foo;
      assert( foo( [5,2,1,3] ) == tuple(1,1) );
 }

Damn! That's pretty nice, I didn't know we could nest with the eponymous trick. This could be quite useful, thanks.
Jan 31 2011
prev sibling next sibling parent "Simen kjaeraas" <simen.kjaras gmail.com> writes:
Andrej Mitrovic <andrej.mitrovich gmail.com> wrote:

 On 1/31/11, Simen kjaeraas <simen.kjaras gmail.com> wrote:
 module foo;

 import std.typecons;
 import std.functional;
 import std.array;

 template optArg( alias pred ) {
      template optArg( alias fn ) {
          auto optArg( Range )( Range r ) {
              alias binaryFun!pred predicate;
              alias unaryFun!fn func;

              auto result = tuple( r.front, func( r.front ) );
              foreach ( e; r ) {
                  auto tmp = func( e );
                  if ( predicate( e, result[1] ) ) {
                      result = tuple( e, tmp );
                  }
              }
              return result;
          }
      }
 }

 void main( ) {
      alias optArg!"a<b" minArg;
      alias minArg!"a" foo;
      assert( foo( [5,2,1,3] ) == tuple(1,1) );
 }

Damn! That's pretty nice, I didn't know we could nest with the eponymous trick. This could be quite useful, thanks.

You can only do that using aliases. -- Simen
Jan 31 2011
prev sibling next sibling parent "Simen kjaeraas" <simen.kjaras gmail.com> writes:
Magnus Lie Hetland <magnus hetland.org> wrote:

 Hm. Using code quite similar to you, supplying a lambda in the second  
 aliasing, I get this error:

 something.d(93): Error: template instance cannot use local  
 '__dgliteral2(__T3)' as parameter to non-global template optArg(alias  
 fun)

 It seems it's explicitly objecting to what I want it to do...

 Using optArg!"a", for example, works just fine -- but the whole point  
 was to include some local state. Using local functions worked (I  
 think...?) when I had a global template.

 It seems D's compile-time computation system is less straightforward  
 than I thought :)

This is a bug. Please report it. -- Simen
Jan 31 2011
prev sibling next sibling parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
On 1/31/11, Simen kjaeraas <simen.kjaras gmail.com> wrote:
 You can only do that using aliases.

Yeah. I was just experimenting for the last half hour. I was hoping to make it easier to make an alias to a nested template using the eponymous trick. But it doesn't work at all. All I could come up with is this trickery: template optArg( alias pred ) { static class optArg2(alias fn) { static auto optArg3( Range )( Range r ) { alias binaryFun!pred predicate; alias unaryFun!fn func; auto result = tuple( r.front, func( r.front ) ); foreach ( e; r ) { auto tmp = func( e ); if ( predicate( e, result[1] ) ) { result = tuple( e, tmp ); } } return result; } static auto opCall(Range)(Range r) { return optArg3!(Range)(r); } } alias optArg2 optArg; } void main( ) { alias optArg!("a<b").optArg!("a") foo; assert( foo( [5,2,1,3] ) == tuple(1,1) ); } Of course, this won't work anymore: alias optArg!"a<b" minArg; alias minArg!"a" foo; assert( foo( [5,2,1,3] ) == tuple(1,1) );
Jan 31 2011
prev sibling next sibling parent "Simen kjaeraas" <simen.kjaras gmail.com> writes:
Magnus Lie Hetland <magnus hetland.org> wrote:

 Hm. Just to make sure this *is* a bug, and I'm not just being a dumbass  
 ... this is a tiny program that illustrates the problem (i.e., gives the  
 error above). Perhaps the use of a local function here really is  
 prohibited...?

Maybe it is. It really shouldn't be, though. If this is not a bug, then Walter has a bug for not accepting this as a bug. :p -- Simen
Feb 01 2011
prev sibling next sibling parent "Simen kjaeraas" <simen.kjaras gmail.com> writes:
Magnus Lie Hetland <magnus hetland.org> wrote:

 Sort of related (though perhaps only remotely) is the following, which  
 won't compile (Error: static assert "Bad unary function: f(a) for type  
 int"):

Not related. unaryFun and binaryFun are simply glorified string mixins, and thus can only access functions that are available in the modules where they are mixed in. That would be std.functional. Because of that, local functions may not be used as string arguments for *naryFun. -- Simen
Feb 01 2011
prev sibling parent "Simen kjaeraas" <simen.kjaras gmail.com> writes:
Magnus Lie Hetland <magnus hetland.org> wrote:

 On 2011-02-01 16:09:22 +0100, Simen kjaeraas said:

 Magnus Lie Hetland <magnus hetland.org> wrote:

 Sort of related (though perhaps only remotely) is the following, which  
 won't compile (Error: static assert "Bad unary function: f(a) for type  
 int"):

and thus can only access functions that are available in the modules where they are mixed in. That would be std.functional. Because of that, local functions may not be used as string arguments for *naryFun.

That certainly makes sense. I just got thrown off by the example in std.algorithm: uint hashFun(string) { ... expensive computation ... } string[] array = ...; // Sort strings by hash, slow sort!("hashFun(a) < hashFun(b)")(array); The only way this could work would be if hashFun was available to the sort template, I guess...?

Nope, still std.functional. That's where the string is mixin'ed. But thanks for noting that, I've filed it as issue #5513. -- Simen
Feb 01 2011