Thread overview
Function Arguments with Multiple Types
Sep 06, 2019
Bob4
Sep 06, 2019
Ali Çehreli
Sep 06, 2019
Bob4
Sep 06, 2019
Ali Çehreli
Sep 07, 2019
Bert
September 06, 2019
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.
September 06, 2019
On 09/06/2019 01:02 PM, Bob4 wrote:

> I feel like it's wrong to rewrite identical functions over and over.

Enter templates. :)

auto clamp(T)(T value, T mini, T maxi) {
  if (value >= maxi) {
    return maxi;
  }
  if (value <= mini) {
    return mini;
  }
  return value;
}

unittest {
  assert(clamp("k", "d", "y") == "k");
  assert(clamp(42, 10, 20) == 20);
  assert(clamp(0.5, 1.5, 1_000) == 1.5);
}

void main() {
}

You can improve the function with template constraints.

Ali

September 06, 2019
On Friday, 6 September 2019 at 20:16:58 UTC, Ali Çehreli wrote:
> On 09/06/2019 01:02 PM, Bob4 wrote:
>
> > I feel like it's wrong to rewrite identical functions over
> and over.
>
> Enter templates. :)
>
> auto clamp(T)(T value, T mini, T maxi) {
>   if (value >= maxi) {
>     return maxi;
>   }
>   if (value <= mini) {
>     return mini;
>   }
>   return value;
> }
>
> unittest {
>   assert(clamp("k", "d", "y") == "k");
>   assert(clamp(42, 10, 20) == 20);
>   assert(clamp(0.5, 1.5, 1_000) == 1.5);
> }
>
> void main() {
> }
>
> You can improve the function with template constraints.
>
> Ali

Thanks; this works, but I'm not sure why. Where does `T` come from? I notice that I can change it to something else, like `ABC`, but I'm not sure why I need a `(T)` prepending the arguments.

What's the best way to type check the variables? I tried to use prepend `assert(cast(T)value !is null);` to the beginning of the function, but that didn't work.

I tried this, too, but it didn't work either (in fact it seemed to ignore it entirely when I replaced `T` with this):

```
template Number(T)
    if (is(T == float))
{}
```
September 06, 2019
On 09/06/2019 01:46 PM, Bob4 wrote:

> Thanks; this works, but I'm not sure why. Where does `T` come from?

Well... I assumed this would make you research templates. ;) Here is one resource: http://ddili.org/ders/d.en/templates.html

(T) means "this function is for any type; and I call that type T in the implementation."

> notice that I can change it to something else, like `ABC`, but I'm not
> sure why I need a `(T)` prepending the arguments.

Templates have two parameter lists; the compile-time parameter list comes first. You can call type parameters anything that makes sense; T is very common.

> What's the best way to type check the variables?

With template constraints: http://ddili.org/ders/d.en/templates_more.html#ix_templates_more.constraint,%20template

> I tried to use prepend
> `assert(cast(T)value !is null);` to the beginning of the function, but
> that didn't work.

That would bring an implicit requirement to your template: The type would have to be usable with the !is operator. (Template constraints make such requirements explicit.)

> I tried this, too, but it didn't work either (in fact it seemed to
> ignore it entirely when I replaced `T` with this):
>
> ```
> template Number(T)
>      if (is(T == float))
> {}
> ```

That should work, meaning "enable this template only if T is 'float'" (which would defeat the purpose of a template but I understand that it's a test.)

Ali

September 07, 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.