September 11, 2020
On Friday, 11 September 2020 at 02:24:07 UTC, H. S. Teoh wrote:
> [snip]
>
> Even better would be if the compiler enforced this: unless you tested for some operation in the sig constraints, that operation would be deemed illegal.  But in the past Walter & Andrei have shot down Concepts, which is very similar to this idea, so I don't know how likely this will ever make it into D.
>
> [snip]

Atila seems more convinced of the value of concepts.
September 11, 2020
On Friday, 11 September 2020 at 02:24:07 UTC, H. S. Teoh wrote:
> On Fri, Sep 11, 2020 at 01:07:55AM +0000, Paul Backus via Digitalmars-d wrote: [...]
>> I think the main difficulty of scaling code bases the rely heavily on templates (either D or C++), [...], is that templates themselves--not the code they generate when you instantiate them, but the actual *templates*--are essentially dynamically typed. In general, there's no way to catch errors in a template until you "run" it (that is, instantiate it) and see what it does.
> [...]
>
> This is why when I write template code, I try to write defensively in a way that makes as few assumptions as possible about the template arguments.  Ideally, every operation you'd do with that type should be tested in the sig constraints.
>
> Even better would be if the compiler enforced this: unless you tested for some operation in the sig constraints, that operation would be deemed illegal.  But in the past Walter & Andrei have shot down Concepts, which is very similar to this idea, so I don't know how likely this will ever make it into D.

Yeah, that's basically the traits/typeclasses approach: you commit to a particular set of constraints, and the compiler checks your generic code against them *prior* to instantiation with any particular type (or "monomorphization," as the Rustaceans call it).

The main downside is that you can't do design-by-introspection. Once you commit to a typeclass, its interface is all you get. If you want your algorithm to work differently for InputRange and RandomAccessRange, you have to write two implementations. And if you don't want to deal with the combinatorial blow-up, you just use the least-restrictive typeclass possible, and miss out on any opportunities for progressive enhancement.

(Hypothesis: one consequence of this is that an optimizing compiler backend has to work harder, on average, to get efficient code out of a Rust iterator than it does for the analogous range in D.)

Theoretically, if you had something like a type-level version of TypeScript's flow-based type analysis, you could combine the two approaches. But I don't know of any language that's actually implemented such a feature.
September 11, 2020
On Friday, 11 September 2020 at 11:54:20 UTC, Paul Backus wrote:
> On Friday, 11 September 2020 at 02:24:07 UTC, H. S. Teoh wrote:
>> On Fri, Sep 11, 2020 at 01:07:55AM +0000, Paul Backus via Digitalmars-d wrote: [...]
>>> I think the main difficulty of scaling code bases the rely heavily on templates (either D or C++), [...], is that templates themselves--not the code they generate when you instantiate them, but the actual *templates*--are essentially dynamically typed. In general, there's no way to catch errors in a template until you "run" it (that is, instantiate it) and see what it does.
>> [...]
>>
>> This is why when I write template code, I try to write defensively in a way that makes as few assumptions as possible about the template arguments.  Ideally, every operation you'd do with that type should be tested in the sig constraints.
>>
>> Even better would be if the compiler enforced this: unless you tested for some operation in the sig constraints, that operation would be deemed illegal.  But in the past Walter & Andrei have shot down Concepts, which is very similar to this idea, so I don't know how likely this will ever make it into D.
>
> Yeah, that's basically the traits/typeclasses approach: you commit to a particular set of constraints, and the compiler checks your generic code against them *prior* to instantiation with any particular type (or "monomorphization," as the Rustaceans call it).
>
> The main downside is that you can't do design-by-introspection. Once you commit to a typeclass, its interface is all you get. If you want your algorithm to work differently for InputRange and RandomAccessRange, you have to write two implementations. And if you don't want to deal with the combinatorial blow-up, you just use the least-restrictive typeclass possible, and miss out on any opportunities for progressive enhancement.
>
> (Hypothesis: one consequence of this is that an optimizing compiler backend has to work harder, on average, to get efficient code out of a Rust iterator than it does for the analogous range in D.)
>
> Theoretically, if you had something like a type-level version of TypeScript's flow-based type analysis, you could combine the two approaches. But I don't know of any language that's actually implemented such a feature.

There is a lot of static type language design terrain to be explored on the way to the fully dynamic type border.  Scouting ahead, as Paul and H.S. are doing, informs our near term decision regarding type functions.

These are my summary questions regarding type functions:
1) Are they worth another 1000 LoC in the compiler? and
2) Do they preclude envisionable advances in the future?

My answer to 1) is : Yes, the compounding benefits over time of simpler user meta code outweighs the, reportedly, small increase in compiler code today.

My answer to 2) is : No, type functions do not preclude future advances on the type front.  They are self contained.




September 11, 2020
On Fri, Sep 11, 2020 at 11:54:20AM +0000, Paul Backus via Digitalmars-d wrote: [...]
> Yeah, that's basically the traits/typeclasses approach: you commit to a particular set of constraints, and the compiler checks your generic code against them *prior* to instantiation with any particular type (or "monomorphization," as the Rustaceans call it).
> 
> The main downside is that you can't do design-by-introspection. Once you commit to a typeclass, its interface is all you get.
[...]

Not necessarily.  You can accept the most general type class in the sig constraints (or omit it altogether), and introspect in the function body.

To take it a step further: what if you can progressively refine the allowed operations on a template argument T within the template function body?  To use your example of InputRange vs. RandomAccessRange: the function declares it accepts InputRange because that's the most general class. Then within its function body, it tests for RandomAccessRange with a static if, the act of which adds random access range operations on T to the permitted operations within the static if block.  In a different static if block you might test for BidirectionalRange instead, and that would permit BidirectionalRange operations on T within that block (and prohibit RandomAccessRange operations).

This way, you get *both* DbI and static checking of valid operations on template arguments.


T

-- 
Don't modify spaghetti code unless you can eat the consequences.
September 11, 2020
On Friday, 11 September 2020 at 14:54:33 UTC, H. S. Teoh wrote:
> Not necessarily.  You can accept the most general type class in the sig constraints (or omit it altogether), and introspect in the function body.
>
> To take it a step further: what if you can progressively refine the allowed operations on a template argument T within the template function body?  To use your example of InputRange vs. RandomAccessRange: the function declares it accepts InputRange because that's the most general class. Then within its function body, it tests for RandomAccessRange with a static if, the act of which adds random access range operations on T to the permitted operations within the static if block.  In a different static if block you might test for BidirectionalRange instead, and that would permit BidirectionalRange operations on T within that block (and prohibit RandomAccessRange operations).
>
> This way, you get *both* DbI and static checking of valid operations on template arguments.
>
>
> T

I think that's exactly what he is talking about with "type-level" flow-based typing (kinding? ;-)). If types are just another value, though, you don't need separate mechanisms for value-level and type-level flow-based typing.
September 11, 2020
On Fri, Sep 11, 2020 at 03:17:52PM +0000, Meta via Digitalmars-d wrote:
> On Friday, 11 September 2020 at 14:54:33 UTC, H. S. Teoh wrote:
[....]
> > To take it a step further: what if you can progressively refine the allowed operations on a template argument T within the template function body?  To use your example of InputRange vs. RandomAccessRange: the function declares it accepts InputRange because that's the most general class. Then within its function body, it tests for RandomAccessRange with a static if, the act of which adds random access range operations on T to the permitted operations within the static if block.  In a different static if block you might test for BidirectionalRange instead, and that would permit BidirectionalRange operations on T within that block (and prohibit RandomAccessRange operations).
> > 
> > This way, you get *both* DbI and static checking of valid operations on template arguments.
[...]
> I think that's exactly what he is talking about with "type-level" flow-based typing (kinding? ;-)). If types are just another value, though, you don't need separate mechanisms for value-level and type-level flow-based typing.

That gives me an idea. What if we have compile-time pseudo-classes that represent classes? Something like this:

	typeclass InputRange(T) {
		bool empty();
		T front();
		void popFront();
	}

	typeclass BidirectionalRange(T) : InputRange!T {
		T back();
		void popBack();
	}

	auto myTemplateFunc(T, InputRange!T Ir)(Ir r)
	{
		// If successful, this makes `br` an alias of r but with
		// expanded allowed operations, analogous to downcasting
		// classes
		alias br = cast(BidirectionalRange!T) r;

		static if (is(typeof(br)))
		{
			// bidirectional range operations permitted on
			// br
			br.popBack();
		}
		else
		{
			assert(!is(typeof(br)));

			// only input range operations permitted on r
			r.popFront();
		}
	}


T

-- 
It's amazing how careful choice of punctuation can leave you hanging:
1 2 3
Next ›   Last »