Thread overview
Problem with using std.math: abs and std.complex: abs at the same time
Sep 18, 2019
berni
Sep 18, 2019
Andrea Fontana
Sep 18, 2019
Daniel Kozak
Sep 18, 2019
Simen Kjærås
Sep 18, 2019
berni
Sep 19, 2019
Simen Kjærås
Sep 19, 2019
berni
Sep 19, 2019
Simen Kjærås
Sep 19, 2019
berni
Sep 18, 2019
H. S. Teoh
September 18, 2019
The following code doesn't compile:

>import std.stdio;
>
>void main()
>{
>    import std.complex: abs, complex;
>    import std.math: abs;
>
>    auto a = complex(1.0,1.0);
>    auto b = 1.0;
>
>    writeln(abs(a));
>    writeln(abs(b));
>}

The error message depends on the order of the two import statements. Seems like the second import is actually ignored.

I hoped for a mechanism similar to overloading, which makes the compiler decide, which "abs" to use, depending on the type of the operand. Is there a way to do this? (As the code appears inside a template, something like std.math.abs() with static import doesn't work out well.)
September 18, 2019
On Wednesday, 18 September 2019 at 12:03:28 UTC, berni wrote:
> The following code doesn't compile:
>
>>import std.stdio;
>>
>>void main()
>>{
>>    import std.complex: abs, complex;
>>    import std.math: abs;
>>
>>    auto a = complex(1.0,1.0);
>>    auto b = 1.0;
>>
>>    writeln(abs(a));
>>    writeln(abs(b));
>>}

What about https://run.dlang.io/is/PGasQD ?

September 18, 2019
On Wed, Sep 18, 2019 at 2:05 PM berni via Digitalmars-d-learn <digitalmars-d-learn@puremagic.com> wrote:
>
> The following code doesn't compile:
>
> >import std.stdio;
> >
> >void main()
> >{
> >    import std.complex: abs, complex;
> >    import std.math: abs;
> >
> >    auto a = complex(1.0,1.0);
> >    auto b = 1.0;
> >
> >    writeln(abs(a));
> >    writeln(abs(b));
> >}
>
> The error message depends on the order of the two import statements. Seems like the second import is actually ignored.
>
> I hoped for a mechanism similar to overloading, which makes the compiler decide, which "abs" to use, depending on the type of the operand. Is there a way to do this? (As the code appears inside a template, something like std.math.abs() with static import doesn't work out well.)

import std.stdio;

void main()
{
    import std.complex: abs, complex;
    import std.math: mabs = abs;

    auto a = complex(1.0,1.0);
    auto b = 1.0;

    writeln(abs(a));
    writeln(mabs(b));
}
September 18, 2019
On Wednesday, 18 September 2019 at 12:03:28 UTC, berni wrote:
> The following code doesn't compile:
>
>>import std.stdio;
>>
>>void main()
>>{
>>    import std.complex: abs, complex;
>>    import std.math: abs;
>>
>>    auto a = complex(1.0,1.0);
>>    auto b = 1.0;
>>
>>    writeln(abs(a));
>>    writeln(abs(b));
>>}
>
> The error message depends on the order of the two import statements. Seems like the second import is actually ignored.

    import std.complex: abs, complex;

Is treated by the compiler as if you'd written something somewhat like this:

    static import std.complex;
    alias abs = std.complex.abs;
    alias complex = std.complex.complex;

When you add

    import std.math: abs;

It's treated like:

    static import std.math;
    alias abs = std.math.abs;

In other words, we have two aliases with the same name inside a function scope, and the compiler can't do that (try doing it explicitly, as in the 'treated like' sections above - you'll get a compilation error). This is related to why you can't have overloaded nested functions:

    unittest {
        void fun() {}
        void fun(int i) {} // declaration fun is already defined
    }

How to resolve this, though? The simplest solution is to not use selective imports:

    import std.math;
    import std.complex;

    writeln(abs(complex(1.0,1.0)));
    writeln(abs(1.0));

If you absolutely can't contaminate the scope with all the unused symbols in std.math and std.complex, you *can* do this:

import std.stdio;

unittest {
    import std.complex : complex;
    static import std.math;

    alias abs = MergeOverloads!(std.complex.abs, std.math.abs);

    auto a = complex(1.0,1.0);
    auto b = 1.0;

    writeln(abs(a));
    writeln(abs(b));
}

template MergeOverloads(T...) {
    alias MergeOverloads = T[0];
    static if (T.length > 1) {
        alias MergeOverloads = MergeOverloads!(T[1..$]);
    }
}

I would however label that a horrible hack.

FWIW, I've filed this issue: https://issues.dlang.org/show_bug.cgi?id=20226

--
  Simen
September 18, 2019
On Wednesday, 18 September 2019 at 12:37:28 UTC, Simen Kjærås wrote:
> How to resolve this, though? The simplest solution is to not use selective imports:
>
>     import std.math;
>     import std.complex;
>
>     writeln(abs(complex(1.0,1.0)));
>     writeln(abs(1.0));

That works. But: I'm trying to write some code for math.d and when I put this code inside math.d it doesn't work anymore. Also removing "import std.math" or moving it after the other import, did not help.

September 18, 2019
On Wed, Sep 18, 2019 at 12:37:28PM +0000, Simen Kjærås via Digitalmars-d-learn wrote: [...]
> template MergeOverloads(T...) {
>     alias MergeOverloads = T[0];
>     static if (T.length > 1) {
>         alias MergeOverloads = MergeOverloads!(T[1..$]);
>     }
> }
> 
> I would however label that a horrible hack.
> 
> FWIW, I've filed this issue: https://issues.dlang.org/show_bug.cgi?id=20226
[...]

Horrible or not, it's very clever. I wouldn't have thought of that!

But yeah, the way alias can't be overloaded inside a function body is kinda stupid, esp. seeing that aliasing an overload is exactly how you resolve an analogous problem inside class scope:

	class Base {
		int abs(int);
	}
	class Derived : Base {
		float abs(float); // causes ambiguity
		alias abs = Base.abs; // brings Base.abs into overload set
		void func() {
			// now abs(...) will correctly use overload sets
		}
	}

I would have expected you could do this in function scope as well, and was surprised the compiler rejected it.


T

-- 
Famous last words: I wonder what will happen if I do *this*...
September 19, 2019
On Wednesday, 18 September 2019 at 13:24:05 UTC, berni wrote:
> On Wednesday, 18 September 2019 at 12:37:28 UTC, Simen Kjærås wrote:
>> How to resolve this, though? The simplest solution is to not use selective imports:
>>
>>     import std.math;
>>     import std.complex;
>>
>>     writeln(abs(complex(1.0,1.0)));
>>     writeln(abs(1.0));
>
> That works. But: I'm trying to write some code for math.d and when I put this code inside math.d it doesn't work anymore. Also removing "import std.math" or moving it after the other import, did not help.

So what you have is basically this?

import std.stdio;

float abs(float f) {
    return f >= 0 ? f : -f;
}

unittest {
    import std.complex : complex, abs;

    auto a = complex(1.0,1.0);
    auto b = 1.0f;

    writeln(abs(a));
    writeln(abs(b));
}

That does indeed fail to compile, and there's no easy way to introduce the module-level abs() function to the scope. Again though, MergeOverloads to the rescue:

float abs(float f) {
    return f < 0 ? -f : f;
}

unittest {
    import std.complex : complex, cabs = abs;
    alias abs = MergeOverloads!(cabs, .abs);
    abs(1);
    abs(complex(1,1));
}

template MergeOverloads(T...) {
    static foreach (E; T)
        alias MergeOverloads = E;
}

--
  Simen
September 19, 2019
On Thursday, 19 September 2019 at 07:26:17 UTC, Simen Kjærås wrote:
> That does indeed fail to compile, and there's no easy way to introduce the module-level abs() function to the scope. Again though, MergeOverloads to the rescue:

I'm not sure, if MergeOverloads will be accepted into std/math.d. Meanwhile I've created a pull request (#7187), that does not handle complex numbers although the algorithm would be identical. Maybe handling complex numbers in math.d can be added later (or maybe it's better to put this in std/complex.d anyway, but then code duplication would be necessary).
September 19, 2019
On Thursday, 19 September 2019 at 10:25:01 UTC, berni wrote:
> On Thursday, 19 September 2019 at 07:26:17 UTC, Simen Kjærås wrote:
>> That does indeed fail to compile, and there's no easy way to introduce the module-level abs() function to the scope. Again though, MergeOverloads to the rescue:
>
> I'm not sure, if MergeOverloads will be accepted into std/math.d. Meanwhile I've created a pull request (#7187), that does not handle complex numbers although the algorithm would be identical. Maybe handling complex numbers in math.d can be added later (or maybe it's better to put this in std/complex.d anyway, but then code duplication would be necessary).

You could perfectly well place MergeOverloads inside whatever function you make, thus not polluting std.math:

float abs(float f) {
    return f < 0 ? -f : f;
}

unittest {
    import std.complex : complex, cabs = abs;
    template MergeOverloads(T...) {
        static foreach (E; T)
            alias MergeOverloads = E;
    }
    alias abs = MergeOverloads!(cabs, .abs);
    abs(1);
    abs(complex(1,1));
}

(you can also use this to create ridiculous overload sets, since MergeOverloads doesn't care if one function is called abs and the other dropBackExactly. Please don't do this)

If you want to introduce MergeOverloads to Phobos officially, std.math is definitely *not* the place though. :)

I'd probably say std.functional, but I'm not really sure where it'd belong. Let's put it in std.exception, since it's an exception from D's usual overload rules in functions. :p

I don't think anything in std.math explicitly deals with std.complex at this point (some things may handle it generically), so it seems std.complex would be the logical place for anything that does. Might I ask what specifically you're working on?

--
  Simen
September 19, 2019
On Thursday, 19 September 2019 at 11:16:12 UTC, Simen Kjærås wrote:
> Might I ask what specifically you're working on?

Of course. It's about issue 15881 (and 15763), namely approxEqual not always doing, what people think it should do. (As a side note: I stumbled over this, when I wanted to file a bug report, because I thought it's not doing, what it should do. In my case I was comparing the distance of geographic items in a town. approxEqual decided, that two items which where at opposite ends of the town, are at the same place. Seen from a global perspective, this might indeed be true, but here it was not, what I was looking for.)

Well, some people (including me) think, that approxEqual should be a replacement for == for floatingpoint numbers, to accept small errors, that inevitantly creep in. But approxEqual accepts rather large errors with it's default values. (Probably because it's first use was to write some unittests for other functions in phobos, where this is sufficent to make the unittests pass, but not optimal, as errors might stay undetected). As it's not easy to define "small", I searched in the internet, if others have investigated on this and came up with [1], which, in my eyes, is a very good summary.

Now I'm working on a replacement for approxEqual with the constraints given in [1]. My first try (PR #7173) was a little bit too fast and would have caused quite some trouble, so I closed it and replaced it with two new PRs (#7180 and #7187) as suggested by n8sh.

[1] https://www.python.org/dev/peps/pep-0485/