Jump to page: 1 2 3
Thread overview
Types: The Next Generation (Was: Why is phobos so wack?)
Jul 09, 2017
Meta
Jul 09, 2017
Nick Sabalausky
Jul 09, 2017
Meta
Jul 10, 2017
Nick Sabalausky
Jul 10, 2017
Jacob Carlborg
Jul 10, 2017
Jacob Carlborg
Jul 11, 2017
Jacob Carlborg
Jul 10, 2017
bpr
Jul 10, 2017
bpr
Jul 10, 2017
bpr
Jul 10, 2017
bpr
Jul 10, 2017
Martin Nowak
Jul 10, 2017
bpr
Jul 11, 2017
Marco Leise
Jul 15, 2017
Mark
July 09, 2017
On 07/09/2017 09:26 AM, Adam D. Ruppe wrote:
> On Sunday, 9 July 2017 at 12:56:55 UTC, FoxyBrown wrote:
>> return str.join(" ");
>> [...]
>> Error: template std.array.join cannot deduce function from argument
>> types !()(string, string)
>> [...]
>> simply trying to join a string[] with a separator.
>
> The error message sucks, but you clearly have a string when you meant
> string[].

Related to this, I've been giving some thought lately to a little bit of re-designing types themselves. Specifically, type creation tools that go beyond what structs and classes give us and allow better handling D-style generics.

It's all very incomplete right now, but basically here's the gist:

Awesome as D's generics, ranges, etc all are, they do make two things far more convoluted than when using basic straightforward types: Function declarations, and error messages when things don't match.

So, why not encapsulate much of that stuff we merely *describe* in signatures for generic functions into genuine honest-to-goodness types?

There would be user-defined symbols, such as "InputRange" or "SomeString", or "RandomAccessRange!SomeString", which the type system sees as actual types. And naturally there would be some mechanism for actually defining those types. Then, defining a function like this:

SomeString fizzbar(RandomAccessRange!SomeNumeric r) {...}

...would automatically imply to the compiler all (or at least a lot of) the pluming we usually clutter the function declaration with: Defining the templated types and calling the appropriate if(isBlahBlah) constraints. About the only things remaining would be additional constraints not already defined by the next-gen types, and restrictions on how the parameters relate to each other.

Even better, having all that encapsulated into genuine types should make it easier for the compiler to provide better error messages.

Thought could also be put into additional type-creation tools that cater specifically to common things (again, such as ranges) to help streamline the process of creating them (ranges are awesome, but let's face it, defining them can be a bother - there's gotta be some way to make it simpler. For example, input ranges especially would GREATLY benefit from being definable in the mannar of C# stackless coroutines).

And of course, Nemerle-like (ML/Haskell-inspired) algebraics would be a wonderful capability to encorporate as well.

Another area to explore would be merging the two sets of parameter lists (the compile-time list and the run-time list) into one list. Certain types would be known to imply "compile-time". A keyword/attr could be used to force compile-time or runtime. And for other params, a sufficiently-smart compiler could conceivably even choose "runtime" vs "compile-time" (or even, "it varies") based on optimization priorities. It would simplify the syntax for users, and help get around fear of templates.

Obviously this is all very incomplete, but it's an idea I think is rather interesting.
July 09, 2017
On Sunday, 9 July 2017 at 20:22:16 UTC, Nick Sabalausky (Abscissa) wrote:
> On 07/09/2017 09:26 AM, Adam D. Ruppe wrote:
> > On Sunday, 9 July 2017 at 12:56:55 UTC, FoxyBrown wrote:
> >> return str.join(" ");
> >> [...]
> >> Error: template std.array.join cannot deduce function from
> argument
> >> types !()(string, string)
> >> [...]
> >> simply trying to join a string[] with a separator.
> >
> > The error message sucks, but you clearly have a string when
> you meant
> > string[].
>
> Related to this, I've been giving some thought lately to a little bit of re-designing types themselves. Specifically, type creation tools that go beyond what structs and classes give us and allow better handling D-style generics.
>
> It's all very incomplete right now, but basically here's the gist:
>
> Awesome as D's generics, ranges, etc all are, they do make two things far more convoluted than when using basic straightforward types: Function declarations, and error messages when things don't match.
>
> So, why not encapsulate much of that stuff we merely *describe* in signatures for generic functions into genuine honest-to-goodness types?
>
> There would be user-defined symbols, such as "InputRange" or "SomeString", or "RandomAccessRange!SomeString", which the type system sees as actual types. And naturally there would be some mechanism for actually defining those types. Then, defining a function like this:
>
> SomeString fizzbar(RandomAccessRange!SomeNumeric r) {...}
>
> ...would automatically imply to the compiler all (or at least a lot of) the pluming we usually clutter the function declaration with: Defining the templated types and calling the appropriate if(isBlahBlah) constraints. About the only things remaining would be additional constraints not already defined by the next-gen types, and restrictions on how the parameters relate to each other.
>
> Even better, having all that encapsulated into genuine types should make it easier for the compiler to provide better error messages.

I'm sorry if I misunderstood what you're proposing, but isn't this exactly what C++ set out to do with concepts? If that's the case, I'd recommend you look up some of Andrei's refutation of concepts in favour of template guards and `static if`.


July 09, 2017
On Sunday, 9 July 2017 at 20:42:39 UTC, Meta wrote:
>
> I'm sorry if I misunderstood what you're proposing, but isn't this exactly what C++ set out to do with concepts? If that's the case, I'd recommend you look up some of Andrei's refutation of concepts in favour of template guards and `static if`.

Shit, I hope not :/ I gave up following C++ developments 15 years ago. Happen to have a link handy to that refutation?
July 09, 2017
On Sunday, 9 July 2017 at 21:59:04 UTC, Nick Sabalausky wrote:
> On Sunday, 9 July 2017 at 20:42:39 UTC, Meta wrote:
>>
>> I'm sorry if I misunderstood what you're proposing, but isn't this exactly what C++ set out to do with concepts? If that's the case, I'd recommend you look up some of Andrei's refutation of concepts in favour of template guards and `static if`.
>
> Shit, I hope not :/ I gave up following C++ developments 15 years ago. Happen to have a link handy to that refutation?

There's a couple posts he's made here but a lot of it's spread out across various talks, articles (I think) as well as newsgroup posts. Here's what I found with a quick google:

https://www.reddit.com/r/cpp/comments/4jqg5z/andrei_alexandrescu_on_c_concepts/
https://www.reddit.com/r/programming/comments/4jlkhv/accu_2016_keynote_by_andrei_alexandrescu/d391585/

And there's a ton of info on the internet about the C++ concepts proposal.
July 10, 2017
On Sunday, 9 July 2017 at 22:02:51 UTC, Meta wrote:
>
> There's a couple posts he's made here but a lot of it's spread out across various talks, articles (I think) as well as newsgroup posts. Here's what I found with a quick google:
>
> https://www.reddit.com/r/cpp/comments/4jqg5z/andrei_alexandrescu_on_c_concepts/
> https://www.reddit.com/r/programming/comments/4jlkhv/accu_2016_keynote_by_andrei_alexandrescu/d391585/
>
> And there's a ton of info on the internet about the C++ concepts proposal.

Ah, I guess it is very similar after all, except it'd be based on top of and coexist with all of D's design by introspection stuff (rather than exist without it as with C++), thus avoiding a lot of the downsides and getting best of both worlds.
July 10, 2017
 On 07/09/2017 09:21 PM, Nick Sabalausky wrote:
>
> Ah, I guess it is very similar after all, except it'd be based on top of
> and coexist with all of D's design by introspection stuff (rather than
> exist without it as with C++), thus avoiding a lot of the downsides and
> getting best of both worlds.

Ha ha, I still feel more than a little silly for pitching what amounts to contracts as "an out-there idea I've been mulling over", but indulge in a little (partially-baked) taste to show this at least has some merit anyway. Hell, call it "concepts++" or "concepts, the D way" (note: I'm NOT pitching this as a suggestion for something D should do. Not saying D should or should'nt, just speaking purely in the realm of "langauge design brainstorming" here...Just because it's been on my mind and the "Why is phobos so wack?" thread brought some relevence)

Behold! The power of combining constraints/design-by-introspection *with* concepts:

Current declaration of std.array.join:

---------------------------------------------------
ElementEncodingType!(ElementType!RoR)[] join(RoR, R)(RoR ror, scope R sep)
if (isInputRange!RoR && isInputRange!(Unqual!(ElementType!RoR)) && isInputRange!R && is(Unqual!(ElementType!(ElementType!RoR)) == Unqual!(ElementType!R)));

ElementEncodingType!(ElementType!RoR)[] join(RoR, E)(RoR ror, scope E sep)
if (isInputRange!RoR && isInputRange!(Unqual!(ElementType!RoR)) && is(E : ElementType!(ElementType!RoR)));

ElementEncodingType!(ElementType!RoR)[] join(RoR)(RoR ror)
if (isInputRange!RoR && isInputRange!(Unqual!(ElementType!RoR)));
---------------------------------------------------

(ie, completely incomprehensible at-a-glance)

Just one possibility of an entirely hypothetical D++ (assuming I'm even interpreting the D version correctly):

---------------------------------------------------
// Note: This assumes the "ElementEncodingType vs ElementType" distinction
// is nothing but a colossal mistake caused purely by the existance of
// auto-decoding, which was (as an assumption this psuedo-code is predicated
// upon) a complete clusterf*** of misdesign (Ie, *such* a huge stretch of
// the imagination ;)), which in this hypothetical ideal langauge has
// been killed dead with nuclear fire, and then beaten some more with
// spiked crowbars, just to be sure.

/++
  Params:
    ror: an input range (or better) of input ranges (or better) of
         any type (shorthand for 'InputRange!InputRange!Type')
    sep: a separator that can be any input range ('InputRange!Type').
  Returns: Shorthand for 'InputRange!Type': Ie, an input range (or better).
  Note: This is implicitly templated on typeof(ror) and typeof(sep).
+/
InputRange join(InputRange!InputRange ror, InputRange sep)
  // Relationship constraints:
  if(UnqualTypeMatch!(ror, sep, return))

/++
  Like above, but sep is 'Type' instead of 'InputRange!Type'.
  Since 'Type' is less specific than 'InputRange!Type', the prior overload
  is preferred.
+/
InputRange join(InputRange!InputRange ror, Type sep)
  // Relationship constraints:
  if(unqualTypeMatch!(ror, InputRange!sep, return))

// No separator
InputRange join(InputRange!InputRange ror)
  // Relationship constraints:
  if(unqualTypeMatch!(ror, return))

// Extra-special specialization:
// Why multiple if() allowed? Because it makes the formatting less
// of a mess, thus better readability. Good enough reason to me!
InputRange join(InputRange!InputRange ror, Type sep)
  if(unqualTypeMatch!(ror, sep, return))
  if(hasLength!typeof(sep))
  {  /+...take advantage of sep.length...+/ }

// Note: Those constraints have further room for syntactical improvement.
---------------------------------------------------

That example involves some additional things defined by the
stdlib (*extremely* hypothetical syntax):

---------------------------------------------------
concept InputRange(Element)
{
    this()
    {
        // Current body of isInputRange here
    }

    // Most of isInputRange can *optionally* be replaced with:
    Element front();
    void popFront();
    bool empty();
}

concept ForwardRange : InputRange // Builds upon InputRange
{
    // Author can opt to do this (more powerful):
    this()
    {
        super.this();
        typeof(this) x = this.save;
    }

    // Or this (more succinct):
    ForwardRange save();
}

// *Anything*: A concrete (instatiable) type, or a templated
// stand-in for a type (ie "T"), or an alias, or nothing at all.
// Types *themselves* are first-class types! But implemented as
// templates, rather than as runtime-OO.
algebraic Any : Bottom;

// An actual concrete type
algrbraic Type : Any
    if(isType!this);

// An actual concrete type?
bool isType(Any any) {/+...introspection magic lies here...+/}

// This is implicitly a template.
bool unqualTypeMatch(InputRange!Any args...)
{
    return args.map!(GetType).map!(Unqual).equal;
}

// If any is a static type, return any.
// If any is a value, return typeof(any)
// This is implicitly a template.
Type GetType(Any any)
{
    static if(typeof(any) == Type)
        return any;
    else
        return typeof(any);
}
---------------------------------------------------

Now, something like THAT is the language *I* would love to see.

Obviously leaves a TON of details TBD, but one can dream ;)

July 10, 2017
On 2017-07-10 07:54, Nick Sabalausky (Abscissa) wrote:
>   On 07/09/2017 09:21 PM, Nick Sabalausky wrote:
>  >
>  > Ah, I guess it is very similar after all, except it'd be based on top of
>  > and coexist with all of D's design by introspection stuff (rather than
>  > exist without it as with C++), thus avoiding a lot of the downsides and
>  > getting best of both worlds.
> 
> Ha ha, I still feel more than a little silly for pitching what amounts to contracts as "an out-there idea I've been mulling over", but indulge in a little (partially-baked) taste to show this at least has some merit anyway. Hell, call it "concepts++" or "concepts, the D way" (note: I'm NOT pitching this as a suggestion for something D should do. Not saying D should or should'nt, just speaking purely in the realm of "langauge design brainstorming" here...Just because it's been on my mind and the "Why is phobos so wack?" thread brought some relevence)
> 
> Behold! The power of combining constraints/design-by-introspection *with* concepts:
> 
> Current declaration of std.array.join:
> 
> ---------------------------------------------------
> ElementEncodingType!(ElementType!RoR)[] join(RoR, R)(RoR ror, scope R sep)
> if (isInputRange!RoR && isInputRange!(Unqual!(ElementType!RoR)) && isInputRange!R && is(Unqual!(ElementType!(ElementType!RoR)) == Unqual!(ElementType!R)));
> 
> ElementEncodingType!(ElementType!RoR)[] join(RoR, E)(RoR ror, scope E sep)
> if (isInputRange!RoR && isInputRange!(Unqual!(ElementType!RoR)) && is(E : ElementType!(ElementType!RoR)));
> 
> ElementEncodingType!(ElementType!RoR)[] join(RoR)(RoR ror)
> if (isInputRange!RoR && isInputRange!(Unqual!(ElementType!RoR)));
> ---------------------------------------------------
> 
> (ie, completely incomprehensible at-a-glance)
> 
> Just one possibility of an entirely hypothetical D++ (assuming I'm even interpreting the D version correctly):
> 
> ---------------------------------------------------
> // Note: This assumes the "ElementEncodingType vs ElementType" distinction
> // is nothing but a colossal mistake caused purely by the existance of
> // auto-decoding, which was (as an assumption this psuedo-code is predicated
> // upon) a complete clusterf*** of misdesign (Ie, *such* a huge stretch of
> // the imagination ;)), which in this hypothetical ideal langauge has
> // been killed dead with nuclear fire, and then beaten some more with
> // spiked crowbars, just to be sure.
> 
> /++
>    Params:
>      ror: an input range (or better) of input ranges (or better) of
>           any type (shorthand for 'InputRange!InputRange!Type')
>      sep: a separator that can be any input range ('InputRange!Type').
>    Returns: Shorthand for 'InputRange!Type': Ie, an input range (or better).
>    Note: This is implicitly templated on typeof(ror) and typeof(sep).
> +/
> InputRange join(InputRange!InputRange ror, InputRange sep)
>    // Relationship constraints:
>    if(UnqualTypeMatch!(ror, sep, return))
> 
> /++
>    Like above, but sep is 'Type' instead of 'InputRange!Type'.
>    Since 'Type' is less specific than 'InputRange!Type', the prior overload
>    is preferred.
> +/
> InputRange join(InputRange!InputRange ror, Type sep)
>    // Relationship constraints:
>    if(unqualTypeMatch!(ror, InputRange!sep, return))
> 
> // No separator
> InputRange join(InputRange!InputRange ror)
>    // Relationship constraints:
>    if(unqualTypeMatch!(ror, return))
> 
> // Extra-special specialization:
> // Why multiple if() allowed? Because it makes the formatting less
> // of a mess, thus better readability. Good enough reason to me!
> InputRange join(InputRange!InputRange ror, Type sep)
>    if(unqualTypeMatch!(ror, sep, return))
>    if(hasLength!typeof(sep))
>    {  /+...take advantage of sep.length...+/ }
> 
> // Note: Those constraints have further room for syntactical improvement.
> ---------------------------------------------------
> 
> That example involves some additional things defined by the
> stdlib (*extremely* hypothetical syntax):
> 
> ---------------------------------------------------
> concept InputRange(Element)
> {
>      this()
>      {
>          // Current body of isInputRange here
>      }
> 
>      // Most of isInputRange can *optionally* be replaced with:
>      Element front();
>      void popFront();
>      bool empty();
> }
> 
> concept ForwardRange : InputRange // Builds upon InputRange
> {
>      // Author can opt to do this (more powerful):
>      this()
>      {
>          super.this();
>          typeof(this) x = this.save;
>      }
> 
>      // Or this (more succinct):
>      ForwardRange save();
> }
> 
> // *Anything*: A concrete (instatiable) type, or a templated
> // stand-in for a type (ie "T"), or an alias, or nothing at all.
> // Types *themselves* are first-class types! But implemented as
> // templates, rather than as runtime-OO.
> algebraic Any : Bottom;
> 
> // An actual concrete type
> algrbraic Type : Any
>      if(isType!this);
> 
> // An actual concrete type?
> bool isType(Any any) {/+...introspection magic lies here...+/}
> 
> // This is implicitly a template.
> bool unqualTypeMatch(InputRange!Any args...)
> {
>      return args.map!(GetType).map!(Unqual).equal;
> }
> 
> // If any is a static type, return any.
> // If any is a value, return typeof(any)
> // This is implicitly a template.
> Type GetType(Any any)
> {
>      static if(typeof(any) == Type)
>          return any;
>      else
>          return typeof(any);
> }
> ---------------------------------------------------
> 
> Now, something like THAT is the language *I* would love to see.
> 
> Obviously leaves a TON of details TBD, but one can dream ;)

Something like this has been proposed several times before, but Andrei doesn't seem to like it. He think it's a failure that all the conditions need to have a name, or something like that. I prefer your approach.

-- 
/Jacob Carlborg
July 10, 2017
On Sunday, 9 July 2017 at 20:22:16 UTC, Nick Sabalausky (Abscissa) wrote:
> SomeString fizzbar(RandomAccessRange!SomeNumeric r) {...}

Looks like concepts.

We've settled on leveraging the already useful template constraints (at best in CNF [¹]) for better error messages. It's on the Agenda for later this year [²].

[¹]: https://github.com/dlang/phobos/pull/5461
[²]: https://wiki.dlang.org/Vision/2017H2_draft

July 10, 2017
On Sunday, 9 July 2017 at 20:22:16 UTC, Nick Sabalausky (Abscissa) wrote:
> Obviously this is all very incomplete, but it's an idea I think is rather interesting.

You've seen this, right?

https://wiki.dlang.org/User:9rnsr/DIP:_Template_Parameter_Constraint

A small step in one such direction, influenced by C++ concepts. That proto-DIP also raises a question I always had about why D doesn't allow chained template instantiation, but that's another DIP for another time.

July 10, 2017
On Monday, 10 July 2017 at 01:21:08 UTC, Nick Sabalausky wrote:
> Ah, I guess it is very similar after all, except it'd be based on top of and coexist with all of D's design by introspection stuff (rather than exist without it as with C++), thus avoiding a lot of the downsides and getting best of both worlds.

You've seen this, right?

https://wiki.dlang.org/User:9rnsr/DIP:_Template_Parameter_Constraint

A small step in one such direction, influenced by C++ concepts. That proto-DIP also raises a question I always had about why D doesn't allow chained template instantiation, but that's another DIP for another time.

« First   ‹ Prev
1 2 3