Function Arguments with Multiple Types

Bert Bert at gmail.com
Sat Sep 7 08:45:55 UTC 2019


On Friday, 6 September 2019 at 20:02:35 UTC, Bob4 wrote:
> Hi,
>
> I'm coming from a background in Python, without a lot of 
> experience in statically typed languages, and I'm struggling to 
> see how to make a certain function in D.
>
> This is what I have in Python:
>
> ```
> from typing import Union
>
> Number = Union[int, float]
>
> def clamp(value: Number, mini: Number, maxi: Number) -> Number:
>     if value >= maxi:
>         return maxi
>     if value <= mini:
>         return mini
>     return value
> ```
>
> In Python, I could supply this function with any type really; 
> it doesn't type check, and essentially only runs 
> `value.__ge__(maxi)` and `value.__le__(mini)`. How can I 
> simulate this in D? There are already implementations of 
> `clamp` out there, but that isn't the point.
>
> I hear that there is a "variant" type, that allows one to pass 
> any type; but I either want to restrict it to certain types, or 
> only to types that possess some kind of `__ge__` or `__le__` 
> method. The only way I found on how to do the former was 
> overloading, like:
>
> ```
> bool test(int a) {...}
> bool test(float a) {...}
> ```
>
> Unfortunately this doesn't seem to work well with the return 
> type, and I feel like it's wrong to rewrite identical functions 
> over and over.



Do you know algebra?

You know, x?

x has a type. That is, it is a class that represents all 
numbers(usually reals at least but sometimes it's integers or 
complex or vectors or whatever).

In statistically typed languages one must always specify the type

int foo(float x)


In a dynamically typed language one does not have to specify the 
type but then one can corrupt data by treating the value as the 
wrong type. So effectively one has to do static typing in their 
mind and keep track of everything. Dynamic typing simply allows 
one to avoid verbosity because they can reuse a variable and 
treat it differently... the cost is safety.


In meta programming, one can sorta do both:

A foo(T)(T x)

Here T stands for the type, but it is like x in algebra but 
instead of numbers it can be types.

So T can be any type and so it is sorta like dynamic typing... so 
what's the point then?

Well, T can't necessarily be anything and one can check the type 
using meta programming.

What happens is that the compiler can determine what T is suppose 
to be by how the programmer is using it and this then allows it 
to revert to static typing internally and get all the safety:

e.g.,

int x;
foo(x);
double y;
foo(y)

Now we know that T is being treated as an int in the first case 
and double in the second.

The type is transitive, meaning that the compiler can trace out 
the type from the call/usage.

Inside foo, though, T is sorta like being dynamic and we have to 
be more careful by telling the compiler what exactly we want T to 
do.

So, we have dynamic <-------meta--------> static

and meta sits in between. It gives us the best of both worlds 
because we can be dynamic and static all in the right places(it's 
not totally dynamic but as close as one can get and still be 
fully dynamic).

Dynamic is totally typeless static is fully typed and meta allows 
us to finagle between them to some degree. The cost is that we 
have to write more complex code to deal with the typing.





> Number = Union[int, float]
>
> def clamp(value: Number, mini: Number, maxi: Number) -> Number:
>     if value >= maxi:
>         return maxi
>     if value <= mini:
>         return mini
>     return value

Your number is a union of two types. Either int or float.

In D, you do the same thing a number of ways(there is the 
algebraic type which looks to be the same as Union).

auto clamp(A,B,C)(A a, B b, C c)
{
     static if (is(A == int)) { // evaluated only if A is an int)
}


So now one has deal with what A really is inside the function and 
so one need extra code...

clamp(3, 5.0, "asdf");

inside clamp A is int, B is real, and C is string FOR THAT CALL 
ONLY!

If we call clamp again:

clamp(5.0, "asdf", new C);

Now A is a real, B is a string, and C is a C class type.


The two functions may look the same but in fact the compiler 
essentially builds two different functions. We essentially have 
to combine all the code... or we could overload and ignore meta 
programming.

In any case, it is not very difficult, it just seems complicated. 
If you are used to programming in one language you will have to 
get used to another. The thing is, if you actually can program 
then it is really nothing new... you do all of it in your head or 
use the language to do the same thing.... all these languages are 
Turing complete and so nothing is new under the sun.








More information about the Digitalmars-d-learn mailing list