July 23, 2015
On 7/23/2015 7:49 AM, ixid wrote:
> If we had a clean sheet wouldn't it be better to have if return a value and
> ditch ternary?

Then we'd start seeing code like:

    x = 45 + if (y == 10) { while (i--) z += call(i); z; } else { switch (x) { case 6: foo(); y; } + tan(z);

I.e. the embedding of arbitrary statements within expressions. We already have some of this with embedded anonymous lambda support, and I've discovered one needs to be very careful in formatting it to not wind up with an awful unreadable mess.

So I'd be really reluctant to continue down that path.

Now, if you want to disallow { } within the embedded if statement, then the proposal becomes nothing more than:

    ? => if
    : => else

which is a potayto potahto thing.

I agree that trivial syntax issues actually do matter, but having used ?: a lot, I have a hard time seeing embeddable if-else as a real improvement, in fact I find it more than a little jarring to see.
July 23, 2015
On 7/23/2015 12:50 PM, H. S. Teoh via Digitalmars-d wrote:
> That assumes the template author is diligent (foolhardy?) enough to
> write unittests that cover all possible instantiations...

No, only each branch of the template code must be instantiated, not every possible instantiation. And we have a tool to help with that: -cov

Does anyone believe it is a good practice to ship template code that has never been instantiated?
July 23, 2015
On 7/23/2015 12:50 PM, Tobias Müller wrote:
> TBH I'm very surprised about that argument, because boolean conditions with
> version() were dimissed for exactly that reason.

I knew someone would bring that up :-)

No, I do not believe it is the same thing. For one thing, you cannot test the various versions on one system. On any one system, you have to take on faith that you didn't break the version blocks on other systems.

This is quite unlike D's template constraints, where all the combinations can be tested reliably with a unittest{} block.

July 23, 2015
On 7/23/2015 2:22 AM, Chris wrote:
> It's one thing to list "nice features", and it's another thing to use these
> features in production code. As a code base grows, the limitations become more
> and more obvious. Thus, I would be wary of jumping to conclusions or hailing new
> features as game changers, before having tested them thoroughly in the real
> world. Only time will tell, if something really scales. I've learned to wait and
> see what people with experience report after a year or two of using a given
> language.

It is very true that many features look good on paper, and only time and experience reveals the truth. There are a lot of programming features that fail the second clause - like implicit declaration of variables.
July 23, 2015
On Thu, Jul 23, 2015 at 8:41 AM, Jonathan M Davis via Digitalmars-d < digitalmars-d@puremagic.com> wrote:

> Maybe, but the ternary operator is a lot less verbose
>

The ternary operator becomes much harder to read the moment you have more than the simple if/else case. As it was mentioned elsewhere on this thread, you can do the following in Scala:

val x = if (condition_1) 1
           else if (condition_2) 2
           else if (condition_3) 3
           else 4

Having expressions be "built-in" extends beyond the simple if/else case, which can be emulated with the ternary operator as you said. You can assign the result of match expressions for instance, or the result of scoped blocks, .e.g.

val x = {
    val ys = foo()
    ys.map(...).filter(...).exists(...)
}

, and from some other comments in this thread, it sounds like the way they
> implemented it in Rust forces you to use braces for single line statements, which would be a _huge_ downside IMHO.
>

On the other hand, Rust does not require parenthesis around if conditions:

let x = if some_condition { 1 } else { 2 }


> I'm inclined to think that it would need a use case that's a lot more compelling than if-else chains to be worth it.
>
>
I provided examples above.



--
Ziad


July 23, 2015
On Thursday, 23 July 2015 at 20:40:17 UTC, Walter Bright wrote:
> On 7/23/2015 12:50 PM, H. S. Teoh via Digitalmars-d wrote:
>> That assumes the template author is diligent (foolhardy?) enough to
>> write unittests that cover all possible instantiations...
>
> No, only each branch of the template code must be instantiated, not every possible instantiation. And we have a tool to help with that: -cov
>
> Does anyone believe it is a good practice to ship template code that has never been instantiated?

I dunno about good practices but I have some use cases.

I write a bunch of zero-parameter template methods and then pass them into a Match template which attempts to instantiate each of them in turn, settling on the first one which does compile. So the methods basically form a list of "preferred implementation of functionality X". All but one winds up uninstantiated.

I also use a pattern where I mix in a zero-parameter template methods into a struct - they don't necessarily work for that struct, but they won't stop compilation unless they are instantiated. A complete interface is generated but only the subset which the context actually supports can be successfully instantiated - and anything the caller doesn't need, doesn't get compiled.

Again, not sure if this is a bad or good thing. But I have found these patterns useful.
July 23, 2015
On 7/23/2015 1:08 PM, Dicebot wrote:
> I am not sure how it applies.

D interfaces (defined with the 'interface' keyword) are simple dispatch types, they don't require an Object. Such interfaces can also have default implementations.


> My point was about the fact that `isInputRange`
> and `InputRangeObject` are the same entities in Rust, simply interpreted
> differently by compiler depending on usage context.

I understand.


> This is important because you normally want to design your application in terms
> of template constraints and structs to get most out of inlining and
> optimization. However, to define stable ABI for shared libraries, the very same
> interfaces need to be wrapped in runtime polymorphism.
>
> Closest thing in D would be to define traits as interfaces and use code like this:
>
> void foo(T)()
>      if (  (is(T == struct) || is(T == class))
>         && Matches!(T, Interface)
>      )
> { }
>
> where `Matches` is a template helper that statically iterates method list of
> interface and looks for matching methods in T.

I don't think the test for struct and class is necessary. It can be just:

    void foo(T)() if (Matches!(T, Interface)) { ... }

as opposed to:

    void foo(T : Interface)() { ... }


> However, making it built-in feels
> really convenient in Rust:
>
> - considerably less function declaration visual noise

It's less noise, sure, and perhaps we can do some syntactical sugar to improve that. But I don't think this is a fundamental shortcoming for D as it stands now (although nobody has written a Matches template, perhaps that should be a priority).


> - much better error messages: trying to use methods of T not defined by a trait
> will result in compile-time error even without instantiating the template

The error messages will occur at compile time and will be the same if you write a unit test to instantiate the template. As I wrote earlier, I don't really understand the need to ship template source code that has never been instantiated.

July 23, 2015
On Wed, Jul 22, 2015 at 4:25 PM, jmh530 via Digitalmars-d < digitalmars-d@puremagic.com> wrote:
>
>
> I feel like it's hard to separate borrowing from Rust's variety of pointers (& is borrowed pointer, ~ is for unique pointer, @ is for managed pointer).


Rust has ditched the ~ and @ syntax for pointers for a long time now. For unique pointers they use Box<T>, and for managed pointers it is either Rc<T>, Arc<T>, or Gc<T>, depending on the desired behavior. The last one is currently still under development I believe.


July 23, 2015
On Thursday, 23 July 2015 at 20:48:34 UTC, Ziad Hatahet wrote:
> The ternary operator becomes much harder to read the moment you have more than the simple if/else case.

I think it is actually kinda pretty:

auto x =   (condition_1) ? 1
         : (condition_2) ? 2
         : (condition_3) ? 3
         : 4;

> val x = {
>     val ys = foo()
>     ys.map(...).filter(...).exists(...)
> }

auto x = {
      auto ys = foo();
      return ys.map(...).filter(...).exists(...);
}();


Before you get too worried about the (), I'd point out that this is a very common pattern in Javascript (for like everything...) and while everybody hates JS, most every uses it too; this patten is good enough for usefulness.

(or for that you could prolly just write foo().map()... directly but i get your point)
July 23, 2015
On Thursday, 23 July 2015 at 20:52:46 UTC, Walter Bright wrote:
> > My point was about the fact that `isInputRange`
> > and `InputRangeObject` are the same entities in Rust, simply
> interpreted
> > differently by compiler depending on usage context.
>
> I understand.

Ok, sorry, it wasn't clear from the response context :)

> I don't think the test for struct and class is necessary. It can be just:
>
>     void foo(T)() if (Matches!(T, Interface)) { ... }
>
> as opposed to:
>
>     void foo(T : Interface)() { ... }

Correct indeed, though I don't feel it is much of a difference considering how common such code is (remember that you endorse ranges as The Way for designing APIs!)

> > - much better error messages: trying to use methods of T not
> defined by a trait
> > will result in compile-time error even without instantiating
> the template
>
> The error messages will occur at compile time and will be the same if you write a unit test to instantiate the template. As I wrote earlier, I don't really understand the need to ship template source code that has never been instantiated.

1)

It does not protect from errors in definition

void foo (R) (Range r)
    if (isInputRange!Range)
{ r.save(); }

unittest
{
    SomeForwardRange r;
    foo(r);
}

This will compile and show 100% test coverage. Yet when user will try using it with real input range, it will fail.

2)

There is quite a notable difference in clarity between error message coming from some arcane part of function body and referring to wrong usage (or even totally misleading because of UFCS) and simple and straightforward "Your type X does not implement method X necessary for trait Y"

3)

Coverage does not work with conditional compilation:

void foo (T) ()
{
    import std.stdio;
    static if (is(T == int))
        writeln("1");
    else
        writeln("2");
}

unittest
{
    foo!int();
}

$ dmd -cov=100 -unittest -main ./sample.d