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

Reply via email to