Another Phobos2 test
bearophile
bearophileHUGS at lycos.com
Tue Feb 8 03:01:21 PST 2011
Adam Ruppe:
> My implementation
> http://arsdnet.net/tictactoe.d
Thank you for your answers and code. Your code is better.
This kind of indentations is interesting:
string positionString(int pos)
in {
assert(...);
}
out(ret) {
assert(...);
}
body {
// ...
}
> I don't think it helps your case at all to use such horribly
> awful code to make points. Half of your statements are the direct result of
> the source code being garbage.
The original code was not mine, and I know it's bad code. That's why I have suggested you to not work on it. I have used it just because it contains several compact constructs used in Python. My next posts I'll use a different strategy to explain things.
> source: 138 lines, 2420 bytes
> You can see the byte count is comparable to the python 2, but I have
> more lines.
Replacing the tabs with spaces, as in the original Python and D versions, and using Windows newlines, it becomes 3278 bytes.
> I usually prefer "Introduction to Programming" style code than
> "functional code golf" style, so you see that too.
Your code is not functional-style, it's procedural and contains some mutables. My discussion was about improving Phobos things that are especially useful if you write code in functional style.
Programs in ML-derived functional languages are often written in a more compact style. This doesn't make them significantly more bug-prone because purity, immutability, very strong type systems, and some other of their features avoid many bugs.
> I also put in a lot of contracts just because i can. I'm trying to
> get into the habit of that.
Very good, I do the same.
That code was not production code, it was an experiment focused on showing some possible improvements for Phobos. Adding contracts (and annotations as pure, const, immutable), and several other things to the code was just a way to distract the people that read that code from the purposes of the discussion.
D2 shows that there are three (or more) "sizes" of functional programming:
- Micro scale: when you use a line of code that uses array(map(filter())).
- Medium scale: when you use normal procedural code inside small functions/methods, but the small functions are mostly pure and use mostly const/immutable data.
- Large scale: when your functions are mostly procedural and sometimes use mutable inputs too, but the main fluxes of data in your large application are managed in a immutable/functional way (like from the DBMS, etc).
Large-scale functional programming is done in Java programs too, and it's not much influenced by Phobos, it's a matter of how your very large program is structured.
Medium-scale functional programming is well doable in D2 thanks to pure annotations and transitive const/immutable.
So a question is how much micro-scale functional programming is right/acceptable in a very little or very large D2 program. I don't have an answer yet (probably the answer is: not too much). Some Scala programmers use lot of micro-scale functional style (see some little examples here: http://aperiodic.net/phil/scala/s-99/ ), but Scala allows to write that code with a significantly less noisy syntax. What I am able to say is that currently using a micro-scale functional programming style in D2 is awkward, there's too much syntax noise, making the code not so readable and maintenable. But that tictactoe code was an experiment, you need to perform some experiments, because they reveal you new things.
In production code, in larger programs written in a mostly procedural language, I usually write code more similar to yours, it's just better if you need to maintain lot of code for years. In a more functional language I use a different style, but I avoid golfing if the code needs to be used for a lot of time.
In script-like programs I sometimes use a little more compact style, but not as compact as the original Python code. In such Python/D scripts I don't write stuff like this:
string toString() {
string lineSeparator = "-+-+-\n";
string row(int start) {
return format("%s|%s|%s\n",
positionString(start + 0),
positionString(start + 1),
positionString(start + 2));
}
string ret;
ret ~= row(0);
ret ~= lineSeparator;
ret ~= row(3);
ret ~= lineSeparator;
ret ~= row(6);
return ret;
}
Using what Python offers functional-style allows to write code about equally safe. Too many lines of code require more time to be written (experiments have shown that programmer tend to write approximately the same number of lines / hour. This doesn't hold up to code golfing, of course). This style of writing code is seen as "bloated" and Java-style by lot of people (by me too). This is why Java programs are sometimes 10+ times longer than Python ones, I prefer more compact Python code. On the other hand too much compact code gives problems. So as usual in programming you need to find a balance (the original tic-tac-toe program was not balanced, it was more like an experiment).
> End user instructions have no place as documentation comments. Being
> able to print out documentation comments at runtime is almost useless -
> doing so indicates a high probability of bad comments.
I have seen several Python programs print out documentation comments at the beginning, they avoid you to write them two times in the program. When the command-line program starts (or when a help is required by the user) the program shows a help, that's also the docstring of the whole module. It's handy and useful.
> dmd -X to generate it, then dmd -J to load it up for use at compile
> time. Documentation comments are already included in the json output.
I know, but I was suggesting something different, to turn the JSON creation into some kind of Phobos library that you may call at compile-time from normal D code. Then a compile-time JSON reader in Phobos will allow to perform certain kinds of static introspection, that later will be quite useful to create user-defined @annotations.
> I wrote: struct Board { }
>
> If you want a separate type, make a separate type. I'm sad to see
> typedef go too, personally, but this is a very poor use case for it.
> typedef is actually pretty rare. A struct or class is usually
> a better choice.
One of my main usages of typedef was to give a specific type to arrays, as shown in that code. In this newsgroups I have shown some other usages of mine of typedef. You are able to avoid using most usages of typedef if you write code in object oriented style.
> The method you used looks like the right way to do it - you just
> "new" it, like anything else.
With "new Tuple!()()" you need to manually specify the types of all the fields, while "tuple()" spares this need, but allocates on the stack. A newTuple() function allows to do both, but I don't know how much often it is needed.
>> My suggestion to avoid this quite bad situation is to look at
>> sboard, it's a char[] and not a string. So a solution to this messy
>> situation is to make string a strong typedef.
>
> This behavior is by design.
I know, and I've suggested a possible alternative design, using a strong typedef to allow some higher order functions to tell apart an array of chars from a string of chars.
> The idea is if you ask for an array, it's because you want to do
> O(1) random access, which, assuming you want code points, means
> dchar.
What I'd like is a way to tell the type system that I am creating a sequence of 8-bit chars, and not a string of chars. If I have an array of single chars, and the type system has a way to understand this, then converting them to dchars is stupid. While if I have a string of chars, then converting their chars into dchars when I iterate on them is acceptable.
> The board isn't a string,
Right, it's an array of 8 bit chars. This was my point.
>> There are many situations where I'd like afilter() ===
>> array(filter()) and amap() == array(map()).
>
> But, this isn't orthogonal!
I know, but programming life is not orthogonal :-) Sometimes other considerations too need to be taken into account.
>From what I have seen I need array() often enough in D2, I can't keep lazy ranges around as often as I do in Python.
One of the problems of using functional-style code in D is the syntax noise. A amap() allows to avoid two parentheses of array(map()), avoiding some noise.
> You can see it is a one-liner, yet not an unreadable one. The reason
> is because I picked a more correct representation of the board.
But your code doesn't allow to show why a choice() is useful, so you have failed :o)
> It makes no sense whatsoever to populate an array with its indexes,
> then get indexes to get a value which is then converted back into an
> index!
>
> I you use an index in the first place, which makes conceptual sense,
> you save piles of busy work.
I agree.
> That's actually exactly what I think it does, coming from C. I'd be
> ok with just making cast() be the thing to do though. I usually
> cast for this use case anyway (again coming from C, that's just the
> natural way that comes to mind).
Coming from Python it's not so natural (in Python single-char strings are used to represent single chars: "foo"[0][0][0][0] == "f").
I think to!int('1') == 1 is useful, but I am not sure if C-derived programmers accept/like to!int to work differtly from cast(int) in this case.
>> I'd like a _very_ handy std.random.choice(), that allows to write
>> code like (untested):
>
> I don't see the big benefit over randomCover here, but I wouldn't
> veto it's inclusion (if I had such power). It's a trivial function
> though.
Right, it's trivial, but it's used often (as sorted()), and it allows to give a name to a purpose: give me one random element from a random-access sequence (a choice() may be specialized for the associative arrays too).
> Yes, that's somewhat useful, but not enough to warrant a new language feature.
Python designers (and Knuth too, I remember) have had a different opinion. I find it useful.
> I find it is usually better to make the condition explicit,
> or avoid it altogether with a better design.
Using "else" for loops is usually a good enough design. For Python programmers it's clear and I don't remember one problem caused by it.
But of course there are ways to avoid it, if you don't want or you can't use it.
> I'd say make it do a combination of writef though, so you can do more
> complex prompts.
>
> auto number = ask!int("Enter a number, %s", name);
This comment looks good for that little enhancement request of mine on Bugzilla :-)
> I think I found a bug in readf() in my implementation too! It doesn't
> seem to handle unexpected data very well (not even offering an error
> code like C and C++ do...).
Curiously I have found no bugs in both D and Phobos while I have translated that Python2 code. I think it's the first time it happens for a so long proggy. This means that the D debugging efforts are giving their first results!! :-) Eventually most 100 lines long D2 programs will usually find no new bugs.
Bye,
bearophile
More information about the Digitalmars-d
mailing list