www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Function Arguments with Multiple Types

reply Bob4 <none gmail.com> writes:
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.
Sep 06 2019
next sibling parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
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
Sep 06 2019
parent reply Bob4 <none gmail.com> writes:
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)) {} ```
Sep 06 2019
parent =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
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
Sep 06 2019
prev sibling parent Bert <Bert gmail.com> writes:
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.
Sep 07 2019