January 17, 2019
On Wed, Jan 16, 2019 at 05:59:29PM -0800, Walter Bright via Digitalmars-d-announce wrote: [...]
> Bartosz Milewski is a C++ programmer and a Haskell fan. He once gave a presentation at NWCPP where he wrote a few lines of Haskell code. Then, he showed the same code written using C++ template metaprogramming.
> 
> The Haskell bits in the C++ code were highlighted in red. It was like a sea of grass with a shrubbery here and there. Interestingly, by comparing the red dots in the C++ code with the Haskell code, you could understand what the C++ was doing. Without the red highlighting, it was a hopeless wall of < > :-)
[...]

I don't know Haskell, but I've worked with Scheme (another Lisp dialect / derivative) a little, and sometimes I feel like the core of my logic is little bits of shrubbery lost in an ocean of parentheses. :-P


T

-- 
I don't trust computers, I've spent too long programming to think that they can get anything right. -- James Miller
January 17, 2019
On Sunday, 13 January 2019 at 04:04:14 UTC, Walter Bright wrote:

> One major takeaway is that the bugs/line are the same regardless of the language used. This means that languages that enable more expression in fewer lines of code result in fewer bugs for the same functionality.
>
Is the data to support this conclusion freely available on the web somewhere?

My impression is that Python is considered the easiest language to use. If it has no more bugs per line than a statically typed program that seems to suggest that non-speed-critical work should be done in Python.
January 17, 2019
On Thu, Jan 17, 2019 at 11:17:18AM +0000, Tony via Digitalmars-d-announce wrote:
> On Sunday, 13 January 2019 at 04:04:14 UTC, Walter Bright wrote:
> 
> > One major takeaway is that the bugs/line are the same regardless of the language used. This means that languages that enable more expression in fewer lines of code result in fewer bugs for the same functionality.
> > 
> Is the data to support this conclusion freely available on the web somewhere?
> 
> My impression is that Python is considered the easiest language to use. If it has no more bugs per line than a statically typed program that seems to suggest that non-speed-critical work should be done in Python.

No, if the number of bugs is truly proportional to the number of lines, then we should all ditch D and write APL instead.  :-P


T

-- 
Leather is waterproof.  Ever see a cow with an umbrella?
January 17, 2019
On Thursday, 17 January 2019 at 01:59:29 UTC, Walter Bright wrote:
> On 1/16/2019 4:19 PM, H. S. Teoh wrote:
>> On Wed, Jan 16, 2019 at 11:43:19PM +0000, John Carter via Digitalmars-d-announce wrote:
>>> [...]
>> 
>> Yes, that's one of the outstanding qualities of D, and one that I was
>> immensely impressed with when I perused the Phobos source code for the
>> first time.
> Bartosz Milewski is a C++ programmer and a Haskell fan. He once gave a presentation at NWCPP where he wrote a few lines of Haskell code. Then, he showed the same code written using C++ template metaprogramming.
>
> The Haskell bits in the C++ code were highlighted in red. It was like a sea of grass with a shrubbery here and there. Interestingly, by comparing the red dots in the C++ code with the Haskell code, you could understand what the C++ was doing. Without the red highlighting, it was a hopeless wall of < > :-)

Was that a pre C++11 version of C++, or a more modern one?

It would be instructive to see that example with C++17 or even 20 and D
next to each other.
January 17, 2019
On Thursday, 17 January 2019 at 16:06:39 UTC, bpr wrote:
> On Thursday, 17 January 2019 at 01:59:29 UTC, Walter Bright wrote:
>> Bartosz Milewski is a C++ programmer and a Haskell fan. He once gave a presentation at NWCPP where he wrote a few lines of Haskell code. Then, he showed the same code written using C++ template metaprogramming.
>>
>> The Haskell bits in the C++ code were highlighted in red. It was like a sea of grass with a shrubbery here and there. Interestingly, by comparing the red dots in the C++ code with the Haskell code, you could understand what the C++ was doing. Without the red highlighting, it was a hopeless wall of < > :-)
>
> Was that a pre C++11 version of C++, or a more modern one?
>
> It would be instructive to see that example with C++17 or even 20 and D
> next to each other.

The presentation was given at BoostCon 2011, and is (at least partially) available on youtube [1]. There is also a blog post from 2009, "What Does Haskell Have to Do with C++?" [2] that uses the same format, and presumably covers the same material. The examples in the blog post were tested with "the GNU C++ compiler v. 4.4.1 with the special switch -std=c++0x", which according to the GCC documentation [3] includes many (but not all) features from C++11.

[1] https://www.youtube.com/watch?v=GjhsSzRtTGY
[2] https://bartoszmilewski.com/2009/10/21/what-does-haskell-have-to-do-with-c/
[3] https://gcc.gnu.org/gcc-4.4/cxx0x_status.html
January 17, 2019
FYI, this whole subthread of B Revzin is NOT in my newsgroup reader. I only saw it because I was browsing the forum web page.

What happened?

-Steve
January 17, 2019
On Thu, Jan 17, 2019 at 06:03:07PM +0000, Paul Backus via Digitalmars-d-announce wrote: [...]
> [2] https://bartoszmilewski.com/2009/10/21/what-does-haskell-have-to-do-with-c/
[...]

Haha, seems D did better than C++ in this respect, but not quite at the level of Haskell.

The C++ example of a template that takes templates and arguments and declares another template is a perfect example of why C++ template syntax is utterly horrible for doing these sorts of things.

Coming back to the D example at the end, I totally agree with the sentiment that D templates, in spite of their significant improvements over C++ syntax, ultimately still follow the same recursive model. Yes, you can use CTFE to achieve the same thing at runtime, but it's not the same thing, and CTFE cannot manipulate template argument lists (aka AliasSeq aka whatever it is you call them).  This lack of symmetry percolates down the entire template system, leading to the necessity of the hack that Bartosz refers to.

Had template argument lists / AliasSeq been symmetric w.r.t. runtime list manipulation, we would've been able to write a foreach loop that manipulates the AliasSeq in the most readable way without needing to resort to hacks or recursive templates.

//

Lately, as I've been pondering over these fundamental language design issues, I've become more and more convinced that symmetry is the way to go.  And by symmetry, I mean the mathematical sense of being "the same under some given mapping (i.e., transformation or substitution)".

Why is C++ template syntax such a mess to work with?  Because it's a separate set of syntax and idioms grafted onto the original core language with little or no symmetry between them.  Where the core language uses < and > as comparison operators, template syntax uses < and > as delimiters. This asymmetry leads to all kinds of nastiness, like earlier versions of C++ being unable to parse `templateA<templateB<int>>` properly (the >> gets wrongly lexed as a bitshift operator). An intervening space is required to work around this asymmetry.  This is just one trivial example.

A more fundamental example, which also afflicts D, is that the template instantiation mechanism is inherently recursive rather than iterative, so when you need to write a loop, you have to paraphrase it as a recursive template. This is asymmetric with the runtime part of the language, where constructs like `foreach` are readily available to express the desired semantics.

On a different note, the same principle of symmetry applies to built-in types vs. user-defined types. In TDPL Andrei alludes to programmers disliking built-in types having "magic" behaviour that's different from user-defined types.  Why the dislike? Because of asymmetry. Built-in types have special behaviour that cannot be duplicated by user-defined types, so when you want the special behaviour but built-in types don't quite meet your needs, you find yourself without any recourse. It is frustrating because the reasoning goes "if built-in type X can have magic behaviour B, why can't user-defined type Y have behaviour B too?" The desire for behaviour B to be possible both for built-in types and user-defined types stems from the desire for symmetry.

Why is alias this so powerful?  Because it lets a new type Y behave as if it were an existing type X -- it's symmetry.  Similarly, the Liskov Substitution Principle is essentially a statement of symmetry in the universe of OO polymorphism.

Why is the Unix "everything is a file" abstraction so useful? Because of symmetry: whether it's a physical file, a network socket, or pipe, it exposes the same API. Code that works with the data don't have to care about what kind of object it is; it can simply use the API that is symmetric across different types of objects.

Similarly, why are D ranges so powerful? Because they make containers, data sources, data generators, etc., symmetric under the range API operations.  It allows code to be decoupled from the details of the concrete types, and focus directly on the problem domain.

Why does the GC simplify many programming tasks so much? Because it makes every memory-allocated object symmetric w.r.t. memory management: you stop worrying about whether something is stack-allocated or heap-allocated, whether it has cycles, or whether somebody else still holds a reference to it -- you focus on the problem domain and let the GC do its job.

At a higher level: in the old days, programming languages used to distinguish between functions and procedures (and maybe some languages still do, but they seem rare these days). But eventually this distinction was ditched in favor of things like returning `void` (C, C++, Java, D), or some other equivalent construct. Why? So that instead of having two similar but asymmetric units of code encapsulation, everything is just a "function" (it just so happens some functions don't return a meaningful value). IOW, introduce symmetry, get rid of the asymmetry.


On the flip side, when there is lack of symmetry, many problems arise. I already mentioned C++ template syntax and how it's asymmetric w.r.t. the imperative part of the language. The recursive nature of templates in both C++ and D, as opposed to iterative runtime constructs like foreach, is another example of asymmetry that leads to ugly / convoluted code where the corresponding runtime code has no such issue.

Auto-decoding is another prime example of asymmetry causing problems: all other arrays are ranges of their element type, but narrow strings are not.  While it still does support the range API (and is symmetric in that sense with other ranges), this internal asymmetry causes all sorts of trouble: performance troubles, tons of special-case code (just look at Phobos range algorithms and see how many special cases pertain to narrow strings -- and how many bugs were caused in the interim), introducing a difference between .count and .indexOf (it's the reason for the very existence of .indexOf where .count would have sufficed, had narrow strings been symmetric in range element type w.r.t. other ranges).

There's also APIs that are hard to use because they are asymmetric to other APIs that you happen to be using.  For example, if a hypothetical D library sported an API that used .next and .empty instead of the standard .empty, .front, .popFront, then the asymmetry with the range API common throughout D code would cause all sorts of impedance mismatch problems.  And one would naturally gravitate towards writing a wrapper around this incompatible API that replicated the range API.  IOW, you fix the problem by *introducing symmetry* where there was asymmetry.

A more minor point is the overloading of keywords to mean different things, like `void` meaning "does not return" in one context, yet in a different context `void*` means "can point to anything".  Or the various overloaded meanings of `static` in D, which is a rat's nest of strange exceptions and ad hoc semantics that have no real pattern, you just have to separately learn what it means in each context.

Also, the asymmetry of function attributes (there is `pure`, but no `impure`; there's `nothrow` but not `throwing`, you write @safe with a @ but pure doesn't have a @). A mostly minor syntactical point, but it seems to keep coming up every time somebody new encounters them, and the complaints seem disproportionate to the actual importance of the issue. Why?  Asymmetry.  And in some cases, it leads to not-so-trivial issues (which I won't repeat here). And the oft-proposed solution? Introduce symmetry.

And operator overloading. In spite of all the problems associated with operator overloading, it's still practically a necessity when you're implementing arithmetical types. A matrix library in C requires very ugly syntax to work with; in C++ and D, we naturally gravitate towards operator overloading.  Why? Because we desire symmetry with built-in arithmetic types. Sure I could write:

	result = prod(matrix1, sub(matrix2, matrix3));

but I would much rather write:

	result = matrix1 * (matrix2 - matrix3);


And why UFCS?  Because of symmetry: every subsequent operation on a range can be written as syntactically top-level function call, rather than the asymmetry of nesting every operation inside the previous one:

	myrange.map!(...)
		.filter!(...)
		.joiner
		.array;

vs.

	array(joiner(filter!(...)(map!(...)(myrange))));


I could go on and on.  But this principle of symmetry seems to me to underlie many fundamental language design issues.  There seems to be a general trend where the more symmetry there is, the smoother a language feature will be and the less problems will be caused; whereas the less symmetry there is, the more troubles, ugly workarounds, tendency towards bugs, or just general unhappiness there will be.  I don't know if it's possible for a programming language to be *completely* symmetrical -- maybe Lisp and Haskell come pretty close -- but the further you deviate from symmetry, the more troubles you can expect.

As a corollary, with every new language change, it would be worthwhile to evaluate how it affects the overall symmetry of the language. Does it add more symmetry, or does it introduce asymmetry (syntactic, semantic, etc.) w.r.t. existing features?  It seems to me that people rarely consider this aspect of language design, esp. when they are trying to fix what they perceive to be an important oversight in the language, but IMO the issue of symmetry is certainly worth careful consideration alongside the other usual factors.


T

-- 
English has the lovely word "defenestrate", meaning "to execute by throwing someone out a window", or more recently "to remove Windows from a computer and replace it with something useful". :-) -- John Cowan
January 17, 2019
On 1/17/19 2:31 PM, H. S. Teoh wrote:
> On Thu, Jan 17, 2019 at 06:03:07PM +0000, Paul Backus via Digitalmars-d-announce wrote:
> [...]
>> [2]
>> https://bartoszmilewski.com/2009/10/21/what-does-haskell-have-to-do-with-c/
> [...]
> 
> Haha, seems D did better than C++ in this respect, but not quite at the
> level of Haskell.
> 
> The C++ example of a template that takes templates and arguments and
> declares another template is a perfect example of why C++ template
> syntax is utterly horrible for doing these sorts of things.
> 
> Coming back to the D example at the end, I totally agree with the
> sentiment that D templates, in spite of their significant improvements
> over C++ syntax, ultimately still follow the same recursive model. Yes,
> you can use CTFE to achieve the same thing at runtime, but it's not the
> same thing, and CTFE cannot manipulate template argument lists (aka
> AliasSeq aka whatever it is you call them).  This lack of symmetry
> percolates down the entire template system, leading to the necessity of
> the hack that Bartosz refers to.
> 
> Had template argument lists / AliasSeq been symmetric w.r.t. runtime
> list manipulation, we would've been able to write a foreach loop that
> manipulates the AliasSeq in the most readable way without needing to
> resort to hacks or recursive templates.

well, there was no static foreach for that article (which I admit I didn't read, but I know what you mean).

But it's DEFINITELY not as easy as it could be:

import std.conv;

alias AliasSeq(P...) = P;

template staticMap(alias Transform, Params...)
{
    alias seq0 = Transform!(Params[0]);
    static foreach(i; 1 .. Params.length)
    {
       mixin("alias seq" ~ i.to!string ~ " = AliasSeq!(seq" ~ (i-1).to!string ~ ", Transform!(Params[" ~ i.to!string ~ "]));");
    }
    mixin("alias staticMap = seq" ~ (Params.length-1).to!string ~ ";");
}

alias Constify(T) = const(T);
void main()
{
    alias someTypes = AliasSeq!(int, char, bool);
    pragma(msg, staticMap!(Constify, someTypes)); // (const(int), const(char), const(bool))
}

Note, that this would be a LOT easier with string interpolation...

mixin("alias seq${i} = AliasSeq!(seq${i-1}, Transform!(Params[${i}]));".text);

-Steve
January 17, 2019
On Thursday, 17 January 2019 at 19:31:24 UTC, H. S. Teoh wrote:
> On Thu, Jan 17, 2019 at 06:03:07PM +0000, Paul Backus via Digitalmars-d-announce wrote: [...]
>> [2] https://bartoszmilewski.com/2009/10/21/what-does-haskell-have-to-do-with-c/
> [...]
>
> Coming back to the D example at the end, I totally agree with the sentiment that D templates, in spite of their significant improvements over C++ syntax, ultimately still follow the same recursive model. Yes, you can use CTFE to achieve the same thing at runtime, but it's not the same thing, and CTFE cannot manipulate template argument lists (aka AliasSeq aka whatever it is you call them).  This lack of symmetry percolates down the entire template system, leading to the necessity of the hack that Bartosz refers to.
>
> Had template argument lists / AliasSeq been symmetric w.r.t. runtime list manipulation, we would've been able to write a foreach loop that manipulates the AliasSeq in the most readable way without needing to resort to hacks or recursive templates.
>
For 2 years I have pondered this problem, and I did come up with a solution.
It's actually not that hard to have CTFE interact with type-tuples.
You can pass them as function parameters, or return them if you wish.
Of course a type-tuple returning ctfe function, is compile-time only.
This solved one more problem that ctfe has:
helper functions required for ctfe can only be omitted from the binary, if you use the trick of putting them into a module which is the import path but never explicitly given on the command line.
newCTFE has the facility to be extended for this, and implementing type-functions is at least on my personal roadmap.

At Dconf 2018 Andrei and Walter said, a DIP which is substantiated enough might make it.
However due to lack of time, (and productivity-reducing internal changes) it will take some time until I can get started on this.

Also I plan for newCTFE to be in shape before I add type-manipulation abilities.

Cheers,

Stefan

P.S. There is one caveat: because of how type-functions work they cannot, you cannot create a non-anonymous symbol inside a type-function, because there is no way to infer a mangle.
You can however create an anonymous symbol and alias it inside a template body, which gives it a mangle and it can behave like a regular symbol.


January 17, 2019
On Thu, Jan 17, 2019 at 10:20:24PM +0000, Stefan Koch via Digitalmars-d-announce wrote:
> On Thursday, 17 January 2019 at 19:31:24 UTC, H. S. Teoh wrote:
[...]
> > Coming back to the D example at the end, I totally agree with the sentiment that D templates, in spite of their significant improvements over C++ syntax, ultimately still follow the same recursive model. Yes, you can use CTFE to achieve the same thing at runtime, but it's not the same thing, and CTFE cannot manipulate template argument lists (aka AliasSeq aka whatever it is you call them).  This lack of symmetry percolates down the entire template system, leading to the necessity of the hack that Bartosz refers to.
> > 
> > Had template argument lists / AliasSeq been symmetric w.r.t. runtime list manipulation, we would've been able to write a foreach loop that manipulates the AliasSeq in the most readable way without needing to resort to hacks or recursive templates.
> > 
> For 2 years I have pondered this problem, and I did come up with a solution.  It's actually not that hard to have CTFE interact with type-tuples.  You can pass them as function parameters, or return them if you wish.  Of course a type-tuple returning ctfe function, is compile-time only.

YES!  This is the way it should be.  Type-tuples become first class citizens, and you can pass them around to functions and return them from functions, the only stipulation being that they can only exist at compile-time, so it's an error to use them at runtime.

In other words, they become symmetric to other built-in language types, and can be manipulated by conventional means, instead of being an oddball exception with special-case behaviour that requires special-case syntax dedicated to manipulating them.  Again, the root of the problem is asymmetry, and the solution is to make it symmetric.


> This solved one more problem that ctfe has:
> helper functions required for ctfe can only be omitted from the
> binary, if you use the trick of putting them into a module which is
> the import path but never explicitly given on the command line.

Exactly.  Yet another problem caused by the asymmetry of type-tuples w.r.t. other built-in types, and naturally solved by making them symmetric.


> newCTFE has the facility to be extended for this, and implementing type-functions is at least on my personal roadmap.

Awesome.


> At Dconf 2018 Andrei and Walter said, a DIP which is substantiated
> enough might make it.
> However due to lack of time, (and productivity-reducing internal
> changes) it will take some time until I can get started on this.
> 
> Also I plan for newCTFE to be in shape before I add type-manipulation abilities.

Yes, let's please get the Minimum Viable Product of newCTFE merged into master first, before we expand the scope (and delay the schedule :-P) yet again!


[...]
> P.S. There is one caveat: because of how type-functions work they cannot, you cannot create a non-anonymous symbol inside a type-function, because there is no way to infer a mangle.
>
> You can however create an anonymous symbol and alias it inside a template body, which gives it a mangle and it can behave like a regular symbol.

Interesting.  Is it possible to assign a "fake" mangle to type functions that never actually gets emitted into the object code, but just enough to make various internal compiler stuff that needs to know the mangle work properly?


T

-- 
Why do conspiracy theories always come from the same people??