On Thursday, 9 January 2014 at 20:40:33 UTC, H. S. Teoh wrote:
On Thu, Jan 09, 2014 at 06:25:33PM +0000, Brad Anderson wrote:
On Thursday, 9 January 2014 at 14:08:02 UTC, Manu wrote:
[...]
>On a side note, am I the only one that finds
>std.algorithm/std.range/etc for string processing really
>obtuse? I
>can rarely understand the error messages, so say it's better
>than STL
>is optimistic.
I absolutely hate the "does not match any template declaration"
error. It's extremely unhelpful for figuring out what you need
to do
and anytime I try to do something fun with ranges I can expect
to
see it a dozen times.
Yeah, that error drives me up the wall too. I often get
screenfuls of
errors, dumping 25 or so overloads of some obscure Phobos
internal
function (like toImpl) as though an end-user would understand
any of it.
You have to parse all the sig constraints (and boy some of them
are
obscure), *understand* what they mean (which requires
understanding how
Phobos works internally), and *then* try to figure out, by
elimination,
which is the one that you intended to match, and why your code
failed to
match it.
I'm almost tempted to say that using sig constraints to
differentiate
between template overloads is a bad idea. Instead, consider this
alternative implementation of toImpl:
template toImpl(S,T)
// N.B.: no sig constraints here
{
static if (... /* sig constraint conditions for overload #1
*/)
{
S toImpl(T t)
{
// implementation here
}
}
else static if (... /* sig constraint conditions for overload
#2 */)
{
S toImpl(T t)
{
// implementation here
}
}
...
else // N.B.: user-readable error message
{
static assert(0, "Unable to convert " ~
T.stringof ~ " to " ~ S.stringof);
}
}
By putting all overloads inside a single template, we can give
a useful
default message when no overloads match.
Interesting and there is a lot of flexibility there. It does make
the functions a lot more verbose though for something that is
really the compiler's job (clearly describing errors).
Alternatively, maybe sig constraints can have an additional
string
parameter that specifies a message that explains why that
particular
overload was rejected. These messages are not displayed if at
least one
overload matches; only if no overload matches, they will be
displayed
(so that the user can at least see why each of the overloads
didn't
match).
Each constraint would have a string? I think that would help for
some of the more obscure constraints that aren't wrapped up in an
eponymous template helper but I don't think it'd help with the
problem generally because the problem is identifying which exact
constraint failed.
Example:
void main()
{
import std.algorithm, std.range;
struct A { }
auto a = recurrence!"n"(0).take(5).find(A());
}
This is the error message you get:
---
/d14/f101.d(5): Error: template std.algorithm.find does not match
any function template declaration. Candidates are:
/opt/compilers/dmd2/include/std/algorithm.d(3650):
std.algorithm.find(alias pred = "a == b", R, E)(R haystack, E
needle) if (isInputRange!R &&
is(typeof(binaryFun!pred(haystack.front, needle)) : bool))
/opt/compilers/dmd2/include/std/algorithm.d(3713):
std.algorithm.find(alias pred = "a == b", R1, R2)(R1 haystack, R2
needle) if (isForwardRange!R1 && isForwardRange!R2 &&
is(typeof(binaryFun!pred(haystack.front, needle.front)) : bool)
&& !isRandomAccessRange!R1)
/opt/compilers/dmd2/include/std/algorithm.d(3749):
std.algorithm.find(alias pred = "a == b", R1, R2)(R1 haystack, R2
needle) if (isRandomAccessRange!R1 && isBidirectionalRange!R2 &&
is(typeof(binaryFun!pred(haystack.front, needle.front)) : bool))
/opt/compilers/dmd2/include/std/algorithm.d(3821):
std.algorithm.find(alias pred = "a == b", R1, R2)(R1 haystack, R2
needle) if (isRandomAccessRange!R1 && isForwardRange!R2 &&
!isBidirectionalRange!R2 &&
is(typeof(binaryFun!pred(haystack.front, needle.front)) : bool))
/opt/compilers/dmd2/include/std/algorithm.d(4053):
std.algorithm.find(alias pred = "a == b", Range, Ranges...)(Range
haystack, Ranges needles) if (Ranges.length > 1 &&
is(typeof(startsWith!pred(haystack, needles))))
---
Where do you even begin with that flood of information? To fix it
all you really want to see is which constraint you didn't
satisfy. An error message like this would help greatly:
---
/d539/f571.d(5): Error: template std.algorithm.find call fails
all constraints. Candidates are:
/opt/compilers/dmd2/include/std/algorithm.d:
(3650) find(alias pred = "a == b", R, E)(R haystack, E needle):
isInputRange!R
&& is(typeof(binaryFun!pred(haystack.front, needle)) :
bool) <- FAILS
(3713) find(alias pred = "a == b", R1, R2)(R1 haystack, R2
needle):
isForwardRange!R1
&& isForwardRange!R2 <- FAILS
&& is(typeof(binaryFun!pred(haystack.front,
needle.front)) : bool)
&& !isRandomAccessRange!R1
(3749) find(alias pred = "a == b", R1, R2)(R1 haystack, R2
needle):
isRandomAccessRange!R1 <- FAILS
&& isBidirectionalRange!R2
&& is(typeof(binaryFun!pred(haystack.front,
needle.front)) : bool)
(3821) find(alias pred = "a == b", R1, R2)(R1 haystack, R2
needle)
isRandomAccessRange!R1 <- FAILS
&& isForwardRange!R2
&& !isBidirectionalRange!R2
&& is(typeof(binaryFun!pred(haystack.front,
needle.front)) : bool)
(4053) find(alias pred = "a == b", Range, Ranges...)(Range
haystack, Ranges needles)
Ranges.length > 1 <-- FAILS
&& is(typeof(startsWith!pred(haystack, needles)))
---
The NG line limit will probably mangle that and I'm assuming
constraints are short-circuited. The exact appearance isn't as
important as just pointing out the failing constraints as
strongly as you can.
[...]
>I also find the names of the generic algorithms are often
>unrelated
>to the name of the string operation. My feeling is, everyone
>is
>always on about how cool D is at string, but other than
>'char[]', and
>the builtin slice operator, I feel really unproductive
>whenever I do
>any heavy string manipulation in D.
Really?? I find myself much more productive, because I only
have to
learn one set of generic algorithms, and I can use them not
just for
strings but for all sorts of other stuff that implement the
range API.
Whereas in languages like C, sure you get familiar with
string-specific
functions, but then when you need a similar-operating function
for an
array of ints, you have to name it something else, and then
basically
the same algorithm reimplemented for linked lists, called by
yet another
name, etc.. Added together, it's many times more mental load
than just
learning a single set of generic algorithms that work on
(almost)
everything.
The composability of generic algorithms also allow me to think
on a more
abstract level -- instead of thinking about manipulating
individual
chars, I can figure out OK, if I split the string by "," then I
can
filter for the strings I'm looking for, then join them back
again with
another delimiter. Since the same set of algorithms work with
other
ranges too, I can apply exactly the same thought process for
working
with arrays, linked lists, and other containers, without having
to
remember 5 different names of essentially the same algorithm
but applied
to 5 different types.
I actually feel a lot more productive in D than in C++ with
strings.
Boost's string algorithms library helps fill the gap (and at
least
you only have one place to look for documentation when you are
using
it) but overall I prefer my experience working in D with
pseudo-member chains.
I found that what I got out of taking the time to learn
std.algorithm
and std.range was worth far more than the effort invested.
Agreed. Except for some hiccups and those terrible error messages
I find std.algorithm and std.range to be a work of genius. I envy
them every day while I'm stuck using C++ at work.
T