October 17, 2019
On Thursday, 17 October 2019 at 03:46:23 UTC, rikki cattermole wrote:
> On 17/10/2019 4:32 PM, Jesse Phillips wrote:
>> Now as you said, it doesn't improve the error message. But what if the language changed so it could?
>
> That would require flow analysis and it won't give good names to different aspects.
>
> We have done research into template constraints and to improve the error messages there. But aggregate checks like isInputRange and with that implements, the compiler can't understand it even with flow analysis sadly.
>
I still think the only change that is needed is to make the operators return an error value instead of a boolean on failure. The error value then just needs to propagate properly through && and ||, and you should get an unambiguous error. You could even add further information with a wrapper template, as long as the error object is inspectable and rewritable.

I don't think the right approach is to have the compiler automagically produce good error messages, but rather give the template developers tools to help the compiler inform the user what actually went wrong.
October 17, 2019
On Wednesday, 16 October 2019 at 22:34:00 UTC, rikki cattermole wrote:

> So these sorts of "functions" are aggregates of behavior and properties that require reading the source code to know fully what it entails.
>
> Yes, aggregates of behavior can be complex, and it does make sense to use "functions" to do it. But the key difference here is if you can describe what most of the behaviors and properties are to the compiler it can produce better error messages but also allow code to be more descriptive. I.e.
>
> /// Docs go here
> signature InputRange(@named ElementType) {
> 	@property {
> 		/// Docs go here
> 		ElementType front();
>
> 		/// Docs go here
> 		bool empty();
> 	}
>
> 	/// Docs go here
> 	void popFront();
> }
>
> void func(T:InputRange)(T myInputRange) {}
>
> The above uses DIP1020 just to make this conversation a little easier to understand (ElementType will be inferred automatically from the implementation).
>
> And because we have described what an input range is in terms of an interface, we can use it as a value typed vtable that yes can work in -betterC.
>
> Something like this should enable us to have better documentation using much simpler looking features without hiding away details in the source code.

Can't we just allow structs to implement interfaces and use template specialization? Something like this:

interface InputRange(ElementType)
{
    ElementType front();
    bool empty();
    void popFront();
}

struct Range(E) : InputRange!E
{
    E front() { return E.init; }
    bool empty() { return true; }
    void popFront() { }
}

void foo(T : InputRange!int)(T range) {}

void bar()
{
    InputRange!int a = Range!int.init; // will not compile
}

--
/Jacob Carlborg
October 18, 2019
On 18/10/2019 12:48 AM, Jacob Carlborg wrote:
> Can't we just allow structs to implement interfaces and use template specialization? Something like this:
> 
> interface InputRange(ElementType)
> {
>      ElementType front();
>      bool empty();
>      void popFront();
> }
> 
> struct Range(E) : InputRange!E
> {
>      E front() { return E.init; }
>      bool empty() { return true; }
>      void popFront() { }
> }
> 
> void foo(T : InputRange!int)(T range) {}
> 
> void bar()
> {
>      InputRange!int a = Range!int.init; // will not compile
> }
> 
> -- 
> /Jacob Carlborg

That is a solution yes.

The reason I do not believe it is one of the better ones is because the struct has to inherit from the interface (and fields are not supported). For reference Basile came up with this very solution over a year ago.

The way we use input ranges today and with that DbI in general, is that the interface inherits from the implementation as part of the declaration of its usage. This is a very different approach to how we think of classes and much more inline with signatures from ML[0].

[0] https://www.cs.cmu.edu/~rwh/introsml/modules/sigstruct.htm
October 17, 2019
On Wednesday, 16 October 2019 at 22:34:00 UTC, rikki cattermole wrote:
> On 17/10/2019 11:16 AM, H. S. Teoh wrote:
>> [...]
>
> So these sorts of "functions" are aggregates of behavior and properties that require reading the source code to know fully what it entails.
>
> [...]

https://github.com/atilaneves/concepts/blob/master/source/concepts/implements.d
October 17, 2019
On Thursday, 17 October 2019 at 16:21:18 UTC, Atila Neves wrote:
> On Wednesday, 16 October 2019 at 22:34:00 UTC, rikki cattermole wrote:
>> On 17/10/2019 11:16 AM, H. S. Teoh wrote:
>>> [...]
>>
>> So these sorts of "functions" are aggregates of behavior and properties that require reading the source code to know fully what it entails.
>>
>> [...]
>
> https://github.com/atilaneves/concepts/blob/master/source/concepts/implements.d

Correct me if I'm wrong, but would using this with std.range be as simple as:

import std.range;

@implements!(MyCoolRange, InputRange)
struct MyCoolRange(T)
{
    ....
}

long sum(R)(R range)
if (implements!(R, InputRange))
{
    ....
}
October 17, 2019
On Thursday, 17 October 2019 at 19:37:38 UTC, Meta wrote:
> [snip]
>
> Correct me if I'm wrong, but would using this with std.range be as simple as:
>
> import std.range;
>
> @implements!(MyCoolRange, InputRange)
> struct MyCoolRange(T)
> {
>     ....
> }
>
> long sum(R)(R range)
> if (implements!(R, InputRange))
> {
>     ....
> }

implements is for an interface. You would actually use models (with the isInputRange from the concepts library, not phobos), so change
@implements!(MyCoolRange, InputRange)
to
@models!(MyCoolRange, isInputRange)
and you don't need models for below, just change
if (implements!(R, InputRange))
to
if (isInputRange!R)
but make sure you are using the one from the concepts library.
October 17, 2019
On Thursday, 17 October 2019 at 20:50:54 UTC, jmh530 wrote:
> On Thursday, 17 October 2019 at 19:37:38 UTC, Meta wrote:
>> [snip]
>>
>> Correct me if I'm wrong, but would using this with std.range be as simple as:
>>
>> import std.range;
>>
>> @implements!(MyCoolRange, InputRange)
>> struct MyCoolRange(T)
>> {
>>     ....
>> }
>>
>> long sum(R)(R range)
>> if (implements!(R, InputRange))
>> {
>>     ....
>> }
>
> implements is for an interface. You would actually use models (with the isInputRange from the concepts library, not phobos), so change
> @implements!(MyCoolRange, InputRange)
> to
> @models!(MyCoolRange, isInputRange)
> and you don't need models for below, just change
> if (implements!(R, InputRange))
> to
> if (isInputRange!R)
> but make sure you are using the one from the concepts library.

I'm referring to the InputRange et al. interfaces from std.range.interfaces:
https://dlang.org/phobos/std_range_interfaces.html
October 18, 2019
On 18/10/2019 8:37 AM, Meta wrote:
> On Thursday, 17 October 2019 at 16:21:18 UTC, Atila Neves wrote:
>> On Wednesday, 16 October 2019 at 22:34:00 UTC, rikki cattermole wrote:
>>> On 17/10/2019 11:16 AM, H. S. Teoh wrote:
>>>> [...]
>>>
>>> So these sorts of "functions" are aggregates of behavior and properties that require reading the source code to know fully what it entails.
>>>
>>> [...]
>>
>> https://github.com/atilaneves/concepts/blob/master/source/concepts/implements.d 
>>
> 
> Correct me if I'm wrong, but would using this with std.range be as simple as:
> 
> import std.range;
> 
> @implements!(MyCoolRange, InputRange)
> struct MyCoolRange(T)
> {
>      ....
> }
> 
> long sum(R)(R range)
> if (implements!(R, InputRange))
> {
>      ....
> }

This unfortunately still ties the implementation itself to the interface. Which goes against DbI.
October 18, 2019
On Thursday, 17 October 2019 at 21:24:15 UTC, Meta wrote:
> [snip]
>
> I'm referring to the InputRange et al. interfaces from std.range.interfaces:
> https://dlang.org/phobos/std_range_interfaces.html

Oh sorry. I completely forgot that was in there.
October 18, 2019
On Friday, 18 October 2019 at 00:19:08 UTC, rikki cattermole wrote:
>> Correct me if I'm wrong, but would using this with std.range be as simple as:
>> 
>> import std.range;
>> 
>> @implements!(MyCoolRange, InputRange)
>> struct MyCoolRange(T)
>> {
>>      ....
>> }
>> 
>> long sum(R)(R range)
>> if (implements!(R, InputRange))
>> {
>>      ....
>> }
>
> This unfortunately still ties the implementation itself to the interface. Which goes against DbI.

Not necessarily. I haven't tried this myself, but I think you could get pretty far with UDAs and some template magic. What I'm thinking:

//Unfortunately this is necessary so that R is in scope for the UDAs.
//Maybe it can be avoided with std.traits.TemplateArgsOf / TemplateOf?
template MapResult(R)
{
    //MapResult is always at least an input range
    @implements!(MapResult, InputRange)

    //If the wrapped type is a forward range, MapResult
    //is also a forward range and will forward to its implementation
    @given!(implements!(R, ForwardRange),
            implements!(inheritImpl!(ForwardRange, MapResult, R), ForwardRange))

    //etc.
    struct MapResult
    {
        R store;

        mixin forwardTo!(ForwardRange, store);

        //Input range primitives defined down here
    }
}