Thread overview
Can this be done? Defining type as in this Scala sample code
Feb 26, 2018
Bienlein
Feb 26, 2018
jmh530
Feb 26, 2018
drug
Feb 28, 2018
Bienlein
Feb 26, 2018
Simen Kjærås
Feb 28, 2018
Bienlein
February 26, 2018
Hello,

just curious whether this is a Scala speciality or whether it could also be done in D. Here is some Scala code:

object Scratch extends App {

  // compiles:

  val list = List(1, 2.4, 5)
  val sum = list.sum
  println(sum)


  // does not compile:

  val list2 = List(1, 2.4, 5, "123")
  val sum2 = list2.sum
  println(sum2)

}

In the code above list.sum compiles, because list only contains values that are of type Numeric. The sum method looks like this:

def sum[B >: A](implicit num: Numeric[B]): B = foldLeft(num.zero)(num.plus)

So sum compiles if all values can be converted to Numeric which the compiler checks at compile time.

For list2 this is not the case as "123" is a string and therefore not of type Numeric. Calling functions on list2 ist fine as long as no function is called that requires a conversion to Numeric for each element in the list such as in the case of sum.

My question is now whether that kind of logic can also be defined in D. My knowledge of D is too limited to find out in reasonable time myself. Reason for my curiosity is that the Scala solution relies on implicit conversion at compile time which has its drawbacks (compilation times, colliding implicit conversions the compiler cannot detect, etc.). So I just wanted to see whether D does this in a clean way and I know that D allows for some type parameter constraints to be set.

Thansk for any answers,
Bienlein
February 26, 2018
On Monday, 26 February 2018 at 15:43:54 UTC, Bienlein wrote:
> Hello,
>
> just curious whether this is a Scala speciality or whether it could also be done in D. Here is some Scala code:
>
> object Scratch extends App {
>
>   // compiles:
>
>   val list = List(1, 2.4, 5)
>   val sum = list.sum
>   println(sum)
>
>
>   // does not compile:
>
>   val list2 = List(1, 2.4, 5, "123")
>   val sum2 = list2.sum
>   println(sum2)
>
> }
>
> In the code above list.sum compiles, because list only contains values that are of type Numeric. The sum method looks like this:
>
> def sum[B >: A](implicit num: Numeric[B]): B = foldLeft(num.zero)(num.plus)
>
> So sum compiles if all values can be converted to Numeric which the compiler checks at compile time.
>
> For list2 this is not the case as "123" is a string and therefore not of type Numeric. Calling functions on list2 ist fine as long as no function is called that requires a conversion to Numeric for each element in the list such as in the case of sum.
>
> My question is now whether that kind of logic can also be defined in D. My knowledge of D is too limited to find out in reasonable time myself. Reason for my curiosity is that the Scala solution relies on implicit conversion at compile time which has its drawbacks (compilation times, colliding implicit conversions the compiler cannot detect, etc.). So I just wanted to see whether D does this in a clean way and I know that D allows for some type parameter constraints to be set.
>
> Thansk for any answers,
> Bienlein

Probably more appropriate in the Learn forum.

I don't really know much Scala, but it seems like
>   val list2 = List(1, 2.4, 5, "123")
would fail to compile because List requires the values to be of a common type and "123" is a string. By contrast, a tuple could hold all those elements, but then it might fail when calculating the sum. Even in D, it's a little tricky to take the sum of a Tuple. You may as well just do a for loop.

So I suppose the question is can D do
>   val list = List(1, 2.4, 5)
>   val sum = list.sum
Phobos has list containers in std.container. However, usually you would use an array for something like this unless you have a reason to use a list. The 1 and 5 ints are implicitly casted to doubles in both versions.

//array version

import std.algorithm : sum;
import std.stdio : writeln;

void main()
{
    auto x = [1, 2.4, 5];
    writeln(x.sum);
}

//list version

import std.algorithm : sum;
import std.stdio : writeln;
import std.container : SList;

void main()
{
    auto x = SList!double(1, 2.4, 5);
    writeln(x[].sum);
}
February 26, 2018
you can do something like this (https://run.dlang.io/is/RYR5Dm):
```
import std.algorithm : sum;
import std.range : only;
import std.stdio : writeln;
import std.typecons : tuple;

void main()
{
    {
        auto list = tuple(1, 2.4, 5);
        auto sum = list.expand.only.sum;
        writeln(sum);
    }

    {
        // do not compile
        /*
        auto list = tuple(1, 2.4, 5, "123");
        auto sum = list.expand.only.sum;
        writeln(sum);
        */
    }
}
```
February 26, 2018
On Monday, 26 February 2018 at 15:43:54 UTC, Bienlein wrote:
> object Scratch extends App {
>
>   // compiles:
>
>   val list = List(1, 2.4, 5)
>   val sum = list.sum
>   println(sum)
>
>
>   // does not compile:
>
>   val list2 = List(1, 2.4, 5, "123")
>   val sum2 = list2.sum
>   println(sum2)
>
> }

There's nothing in the language or standard library that supports this. However, it's perfectly possible to make something with those semantics:

import std.variant;
import std.stdio;

struct List(T) {
    T[] values;

    alias values this;
}

auto list(T...)(T args)
{
    import std.traits : CommonType;

    static if (is(CommonType!T == void))
        List!Variant result;
    else
        List!(CommonType!T) result;

    result.length = T.length;
    foreach (i, e; args) {
        result[i] = e;
    }
    return result;
}

auto sum(T)(List!T lst)
if (is(typeof(lst[0] + lst[0])) && !is(T == Variant))
{
    T result = 0;
    foreach (e; lst) {
        result += e;
    }
    return result;
}

unittest {
    auto list1 = list(1, 2.4, 5);
    auto sum1 = list1.sum;
    writeln(sum1);

    auto list2 = list(1, 2.4, 5, "123");
    auto sum2 = list2.sum;
    writeln(sum2);
}


Since std.variant.Variant does operator overloads, we have to explicitly check if T == Variant in the sum function. For Variant, that's probably the correct choice. We could use Algebraic instead, but it also does operator overloads, even when no type in its arguments support them. Again, we could create our own - Algebraic and Variant are library types, after all.

--
  Simen
February 28, 2018
On Monday, 26 February 2018 at 16:53:39 UTC, drug wrote:
> you can do something like this (https://run.dlang.io/is/RYR5Dm):
> ```
> import std.algorithm : sum;
> import std.range : only;
> import std.stdio : writeln;
> import std.typecons : tuple;
>
> void main()
> {
>     {
>         auto list = tuple(1, 2.4, 5);
>         auto sum = list.expand.only.sum;
>         writeln(sum);
>     }
>
>     {
>         // do not compile
>         /*
>         auto list = tuple(1, 2.4, 5, "123");
>         auto sum = list.expand.only.sum;
>         writeln(sum);
>         */
>     }
> }
> ```

This looks good. It's not completely transparent, because of this "expand.only" thing. But I guess it can be done to hide it. Thanks for this one.
February 28, 2018
On Monday, 26 February 2018 at 19:36:33 UTC, Simen Kjærås wrote:
> On Monday, 26 February 2018 at 15:43:54 UTC, Bienlein wrote:
>> object Scratch extends App {
>>
>>   // compiles:
>>
>>   val list = List(1, 2.4, 5)
>>   val sum = list.sum
>>   println(sum)
>>
>>
>>   // does not compile:
>>
>>   val list2 = List(1, 2.4, 5, "123")
>>   val sum2 = list2.sum
>>   println(sum2)
>>
>> }
>
> There's nothing in the language or standard library that supports this. However, it's perfectly possible to make something with those semantics:
>
> import std.variant;
> import std.stdio;
>
> struct List(T) {
>     T[] values;
>
>     alias values this;
> }
>
> auto list(T...)(T args)
> {
>     import std.traits : CommonType;
>
>     static if (is(CommonType!T == void))
>         List!Variant result;
>     else
>         List!(CommonType!T) result;
>
>     result.length = T.length;
>     foreach (i, e; args) {
>         result[i] = e;
>     }
>     return result;
> }
>
> auto sum(T)(List!T lst)
> if (is(typeof(lst[0] + lst[0])) && !is(T == Variant))
> {
>     T result = 0;
>     foreach (e; lst) {
>         result += e;
>     }
>     return result;
> }
>
> unittest {
>     auto list1 = list(1, 2.4, 5);
>     auto sum1 = list1.sum;
>     writeln(sum1);
>
>     auto list2 = list(1, 2.4, 5, "123");
>     auto sum2 = list2.sum;
>     writeln(sum2);
> }
>
>
> Since std.variant.Variant does operator overloads, we have to explicitly check if T == Variant in the sum function. For Variant, that's probably the correct choice. We could use Algebraic instead, but it also does operator overloads, even when no type in its arguments support them. Again, we could create our own - Algebraic and Variant are library types, after all.
>
> --
>   Simen

Didn't have time so far to look into this. But thanks anyway.