7
votes

I want to do this (supported by this), but I'm hitting a tiny issue (watered down for your less-headachy-non-displeasure).

Let's say I'm a library writer, and I have these functions in a D file:

module mod_a;
import std.stdio;
void run(T)(T v) { writeln("Jigglypuff!"); }
void runrun(T)(T v) { run(v); }

And I have client code in another module in which I attempt to overload run and call runrun:

import mod_a;
void run(T:double)(T v) { writeln("Wigglytuff!"); }
void main() { runrun(1.0); }

This code results in 'Jigglypuff!' being printed rather than 'Wigglytuff!', which makes sense, because the definition of runrun can only see the unevolved unspecialized form available to it in its module. I (and client code), however, would like to be seeing a 'Wigglytuff' rather than a 'Jigglypuff'.

In C++ I'd throw a namespace mod_a { ... } around the specialization of run to show that the client code's run should be examined alongside my library code when trying to determine what the definition of runrun calls, welcoming the can of worms that came along with such behavior.

Is there an idiomatic D-way to organize this such that the function run may be intentionally hijacked? Specifically, I'd like to mimic the way C++'s global functions behave with ad-hoc specializations.

3
Your second run(T) does not overload mod_a.run(T) at all, you're not in the same module. - Kris
@Kris Yes, you're right. I was abusing terminology. The intention is for the answer to show me how I'd shove it into the set of overloads the compiler checks for when examining runrun. - user
my comment was to meant to indicate why that doesn't (and shouldn't work, according to dlang.org/hijack.html). I don't think your use of terminology was wrong in any way. - Kris
@Kris I read that article before posting this question, and the problem is subtly different: there the overload sets are being checked in client code (all overloads known upon processing the file), here I want to change how the overloads are determined in generic/templated library code (not all overloads known upon processing the file). My Wigglytuff run call isn't even appearing in its own overload set at the runrun definition let alone being judged alongside other overload sets, and I want that situation to change. - user
I get the feeling we are trying to describe the same situation yet feeling like we're not getting through :) Anyhow, I'm loving this question because it's an edge case (that I hadn't thought of) and I'm very interested in finding those and finding ways to deal with them. Unfortunately, I haven't yet compiled anything that ends up working like you want it to, the way you'd like it to. - Kris

3 Answers

2
votes
//untested

module mod_a;
import std.stdio;
void run(T)(T v) if (!is(T : double)) { writeln("Jigglypuff!"); }
void runrun(T)(T v) { run(v); }

import mod_a;
void run(T)() if (is(T : double)) { writeln("Wigglytuff!"); }
void main() { runrun(1.0); }
1
votes

In this example you are the authoring the library mod_a so it would be relatively easy to modify it. But I can't help but think about the situation where you aren't the author of the library.

In which case the actual author of the library would probably either be happy that you cannot just do what you are trying to do... or actively wants to support what you are trying to do.

lets assume the library writer wants you to be able to "hijack" a function that he/she uses in his implementation. He or she would probably go about it differently; I would.

This is one area where I believe the encapsulation stories you linked and I just read describe exactly how to achieve the opposite situation of what you want here. This type of thing screams that it needs contract programming.

As a library author, I'd probably offer you an interface and possibly an abstract class, maybe even one or two concrete implementations, that you could use to do your thing. Someone else might give add a template or runtime parameter requiring a specific implementation as an argument. Yet someone else could add a lazy string delegate to the mix.

(my) conclusion: As a library author, there are options to make what you want possible. If it's not possible with your preferred library you'll probably end up filing a feature request.

0
votes

I found a weird way of doing it using mixins to move the namespace searched from library code to user code. From the perspective of the library-writer it's one extra function and one extra template type; from the perspective of the user it's one extra (somewhat annoying) line of code alongside one extra function call per instance of a type with global 'methods' outside of its module. Considering that this is a way of explicitly overriding a language feature, I think that's pretty good.

module wrappers;
mixin template wrapmix()
{
  struct Wrap(T)
  {
    T* val;
    auto opDispatch(string Name, A...) { return mixin("(*val)."~Name~"(a);"); }
  }
  auto wrap(T)(ref T val) { return Wrap!T(&val); }
}

This is then used to create a type that searches in whatever module it's been declared for the dispatch.

import mod_a;
import wrappers;

mixin wrapmix; ///< magic

void main() {
  double val = 1.0;
  runrun(wrap(val)); ///< note the 'wrap' call
}

While the above code wasn't explicitly tested, I tested the general approach on GDC and it appears to work.

Oh, and there's the shortcoming that as written this only handles lvalue references, but that can be fixed methinks with overloads of wrap in module wrappers.