www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Another Phobos2 test

reply bearophile <bearophileHUGS lycos.com> writes:
I've found another taks in the rosettacode.org site:
http://rosettacode.org/wiki/Tic-tac-toe

The program itself is not so interesting, and probably there are better ways to
implement a Tic-tac-toe player program. But it's a good enough example to test
Phobos2, to see how much handy it is when you compare it to the original Python
version.

The original Python3 code was first translated to Python2.x, then translated to
D2. The translation to D was a bit of pain, for many things there was a need to
do some experiments to find a syntax that works. This is bad.

Some stats:
- Python2 source code: 91 lines, 2385 bytes.
- D2 source code: 134 lines, 3620 bytes.
- D2 binary, default compilation: 338 KB on Windows with DMD.

Here are small parts of the Python2 version followed by the D2 translation,
sometimes followed by some comments. The D translation may be improved, if you
see something, feel free to comment.

----------------------------

'''
Tic-tac-toe game player.
Input the index of where you wish to place your mark at your turn.
'''

import random


/**
Tic-tac-toe game player.
Input the index of where you wish to place your mark at your turn.
*/

module tic_tac_toe;

import std.stdio, std.random, std.string, std.algorithm,
       std.array, std.typecons, std.conv;


The D version requires many more imports because Python has a tons of built-in
features. The comment in the Python version is a docstring of the module, about
as in D. But in Python you are able to access the docstrings from the code
itself (see below the print __doc__), this allows to write those comments just
once in the Python program, while they are written twice in the D code.

I'd like the DMD compiler to be able to produce a JSON tree out of the current
module during compilation, through a library, and allow the normal code to use
it at compile time. So with a library you are able to extract any kind of data
about the module and its code. This will be a good material (a way to perform
static introspection) to later build a user-defined attributes feature on.

All the Python2.x and D2 code shown is tested, unless otherwise specified.

----------------------

board = list('123456789')
wins = ((0, 1, 2), (3, 4, 5), (6, 7, 8),
        (0, 3, 6), (1, 4, 7), (2, 5, 8),
        (0, 4, 8), (2, 4, 6))


alias char[] TBoard;
TBoard board = "123456789".dup;
enum WINS = [[0, 1, 2], [3, 4, 5], [6, 7, 8],
             [0, 3, 6], [1, 4, 7], [2, 5, 8],
             [0, 4, 8], [2, 4, 6]];


An alias is not as good as a typedef, to make statically sure the various
functions do take a board and nothing else as input.

----------------------

def print_board():
    print '\n-+-+-\n'.join('|'.join(board[x:x+3]) for x in (0, 3, 6))


auto print_board() {
    string[] parts;
    foreach (x; [0, 3, 6])
        parts ~= array(map!(to!string)(board[x .. x+3])).join("|");
    writeln(parts.join("\n-+-+-\n"));
}


I'd like join to work with a lazy range and on chars too, allowing code like
(untested):

auto print_board() {
    string[] parts;
    foreach (x; [0, 3, 6])
        parts ~= board[x .. x+3].join("|");
    writeln(parts.join("\n-+-+-\n"));
}


This means:
join("123", "x") ==> "1x2x3"

And:
join(map!(to!string)([1,2,3]), "x") ==> "1x2x3"

See:
http://d.puremagic.com/issues/show_bug.cgi?id=4468
http://d.puremagic.com/issues/show_bug.cgi?id=5542

----------------------

def score(board=board):
    for w in wins:
        b = board[w[0]]
        if b in "XO" and all(board[i] == b for i in w):
            return (b, [i+1 for i in w])
    return None


auto score(TBoard sboard=board) {
    foreach (w; WINS) {
        auto b = sboard[w[0]];
        bool found = canFind!((i){ return sboard[i] != b; })(w);
        if ((b == 'X' || b == 'O') && !found) {
            return new Tuple!(char, int[])(b, array(map!q{a+1}(w)));
        }
    }
    return cast(typeof(return))null;
}


This function has required several tries to be written. That final cast allows
code to be more DRY, and it comes from some experiments. Here we have a "auto"
return type and a typeof(return) in the same function.

There is no clean way to create a tuple on the heap, the way I have used is the
best I have found.

I'd like Python simple functions all() and any() in Phobos2 too. With it you
are able to write code a bit less ugly like (untested):


auto score(TBoard sboard=board) {
    foreach (w; WINS) {
        auto b = sboard[w[0]];
        if ((b == 'X' || b == 'O') && all!((i){ return board[i] == b; })(w)) {
            return new Tuple!(char, int[])(b, array(map!q{a+1}(w)));
        }
    }
    return cast(typeof(return))null;
}


Compared to the original Python code this D2 code is not so good still.

See:
http://d.puremagic.com/issues/show_bug.cgi?id=5544

----------------------

def finished():
    return all(b in 'XO' for b in board)


auto finished() {
    return reduce!q{a && b}(true, map!q{a == 'X' || a == 'O'}(board));
}


This D2 code comes from some experiments (while the Python code often is
correct at my first try, because it's more clean, less noisy and it's usually
much less fragile).

If Phobos2 adds an all() it becomes (untested), that's much better:

auto finished() {
    return all!q{a == 'X' || a == 'O'}(board);
}


Of course the introduction of lazy & eager sequence comphrensions like Python
ones allows to reduce the need of lot of map, filter, etc.

----------------------

def space(board=board):
    return [b for b in board if b not in "XO"]


auto space(TBoard sboard=board) {
    return to!(char[])(array(filter!q{a != 'X' && a != 'O'}(sboard)));
}


That to!(char[]) was necessary because here array() returns an array of
dchar... This is not a so nice thing.

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. So immutable(char)[] and string become two different
types and then lazy HOFs like filter() are able to tell them apart, and act
differently on them (returning a char[] if the input of a filter is a char[]
and returning a dstring if the input of a filter is a string?).

There are many situations where I'd like afilter() === array(filter()) and
amap() == array(map()).

----------------------

def my_turn(xo, board):
    options = space()
    choice = random.choice(options)
    board[int(choice)-1] = xo
    return choice


auto my_turn(char xo, TBoard sboard) {
    auto options = space();
    char choice = options[uniform(0, options.length)];
    sboard[to!int(choice~"") - 1] = xo;
    return choice;
}


to!int(choice) doesn't do what you think it does, it converts the choice char
to its integer representation:

import std.conv: to;
void main() {
    assert(to!int("1") == 1);
    assert(cast(int)'1' == 49);
    assert(to!int('1') == 49);
}

But I think this may be better: to!int('1') === to!int("1")


I'd like a _very_ handy std.random.choice(), that allows to write code like
(untested):

auto my_turn(char xo, TBoard sboard) {
    auto options = space();
    char c = choice(options);
    sboard[to!int(c ~ "") - 1] = xo;
    return c;
}


See for the choice():
http://d.puremagic.com/issues/show_bug.cgi?id=4851


If also to!int('1') == 1 the code becomes (untested):

auto my_turn(char xo, TBoard sboard) {
    auto options = space();
    char c = choice(options);
    sboard[to!int(c) - 1] = xo;
    return c;
}


See for the to!int:
http://d.puremagic.com/issues/show_bug.cgi?id=5543

------------------------

def my_better_turn(xo, board):
    """Will return a next winning move or block your winning move if possible"""
    ox = 'O' if xo == 'X' else 'X'
    one_block = None
    options  = [int(s)-1 for s in space(board)]
    for choice in options:
        brd = board[:]
        brd[choice] = xo
        if score(brd):
            break
        if one_block is None:
            brd[choice] = ox
            if score(brd):
                one_block = choice
    else:
        choice = one_block if one_block is not None else random.choice(options)
    board[choice] = xo
    return choice+1


/// Will return a next winning move or block your winning move if possible
auto my_better_turn(char xo, TBoard sboard) {
    auto ox = xo == 'X' ? 'O' : 'X';
    int one_block = -1;
    auto options = array(map!((s){ return s-'0'-1; })(space(sboard)));

    int choice;
    bool unbroken = true;
    foreach (c; options) {
        choice = c;
        auto brd = sboard.dup;
        brd[choice] = xo;
        if (score(brd)) {
            unbroken = false;
            break;
        }

        if (one_block == -1) {
            brd[choice] = ox;
            if (score(brd))
                one_block = choice;
        }
    }

    if (unbroken)
        choice = one_block != -1 ? one_block : options[uniform(0,
options.length)];

    sboard[choice] = xo;
    return choice + 1;
}


In Python both for and while loops have an optional "else" clause, that runs if
the loop is not exited by a break. It is a handy thing, that allows to avoid
the "unbroken" boolean I have used in D.


In the map at the top I have had to use a non portable:
s-'0'-1

because s is a dchar, so you can't currently join it to a "" literal...

------------------------

def your_turn(xo, board):
    options = space()
    while True:
        choice = raw_input("\nPut your %s in any of these positions: %s "
                           % (xo, ''.join(options))).strip()
        if choice in options:
            break
        print "Whoops I don't understand the input"
    board[int(choice)-1] = xo
    return choice


auto your_turn(char xo, TBoard sboard) {
    auto options = space();
    string choice;
    while (true) {
        writef("\nPut your %s in any of these positions: %s ", xo, options);
        choice = readln().strip();
        if (choice.length == 1 && std.algorithm.indexOf(options, choice[0]) !=
-1)
            break;
        writeln("Whoops I don't understand the input");
    }
    sboard[to!int(choice) - 1] = xo;
    return choice;
}


Here
choice in options
becomes
(choice.length == 1 && std.algorithm.indexOf(options, choice[0]) != -1)
Because D2 refused to use a handy "in" for strings, this is awful.

I have had to use indexOf with module qualification to avoid errors.

Regarding raw_input() see:
http://d.puremagic.com/issues/show_bug.cgi?id=4716

------------------------

def me(xo='X'):
    print_board()
    print '\nI go at', my_better_turn(xo, board)
    return score()

def you(xo='O'):
    print_board()
    # Call my_turn(xo, board) below for it to play itself
    print '\nYou went at', your_turn(xo, board)
    return score()


print __doc__
while not finished():
    s = me('X')
    if s:
        print_board()
        print "\n%s wins along %s" % s
        break
    if not finished():
        s = you('O')
        if s:
            print_board()
            print "\n%s wins along %s" % s
            break
else:
    print '\nA draw'


auto me(char xo='X') {
    print_board();
    writeln("\nI go at ", my_better_turn(xo, board));
    return score();
}

auto you(char xo='O') {
    print_board();
    // Call my_turn(xo, board) below for it to play itself
    writeln("\nYou went at ", your_turn(xo, board));
    return score();
}

void main() {
    writeln("Tic-tac-toe game player.");
    writeln("Input the index of where you wish to place your mark at your
turn.");

    bool unbroken = true;
    while (!finished()) {
        auto s = me('X');
        if (s) {
            print_board();
            writefln("\n%s WINS along %s", s.tupleof);
            unbroken = false;
            break;
        }
        if (!finished()) {
            s = you('O');
            if (s) {
                print_board();
                writefln("\n%s WINS along %s", s.tupleof);
                unbroken = false;
                break;
            }
        }
    }

    if (unbroken)
        writeln("\nA draw");
}


The print __doc__ is replaced by two writeln. There is another usage of
unbroken.

------------------------

Bye,
bearophile
Feb 07 2011
next sibling parent reply Adam Ruppe <destructionator gmail.com> writes:
My gut tells me you'd get much better results if you tried to
write D in D instead of Python in D.

I might take a stab at this myself. I can see lots of improvements
to the original code.
Feb 07 2011
next sibling parent bearophile <bearophileHUGS lycos.com> writes:
Adam Ruppe:

 My gut tells me you'd get much better results if you tried to
 write D in D instead of Python in D.

That's really beside the point. The point of the post is that there are some spots where I'd like to see Phobos improved. (And I am willing to write part of the Phobos code I am asking for).
I might take a stab at this myself. I can see lots of improvements to the
original code.<

The original Python code is globally quite bad. Don't waste your time trying to improve it. Improve Phobos instead :-) Bye, bearophile
Feb 07 2011
prev sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Monday, February 07, 2011 15:34:26 bearophile wrote:
 Adam Ruppe:
 My gut tells me you'd get much better results if you tried to
 write D in D instead of Python in D.

That's really beside the point. The point of the post is that there are some spots where I'd like to see Phobos improved. (And I am willing to write part of the Phobos code I am asking for).

Actually, it's not beside the point at all. Regardless of what language you're programming in, it's generally best to program in the typical paradigms of that language. Trying to contort it to act like another language is _not_ going to result in optimal code. Now, that's not to say that Phobos can't be improved upon (it certainly can be), but if you focus too much on how it doesn't do something like some other language does, you'll miss what it _can_ do. And it's quite possible that it actually does what you're trying to do quite well if you'd just stop trying to contort it to act like another language (be it Python or Haskell or Rust or Go or Java or C++ or C or Ruby or...). - Jonathan M Davis
Feb 07 2011
parent reply "Nick Sabalausky" <a a.a> writes:
"Jonathan M Davis" <jmdavisProg gmx.com> wrote in message 
news:mailman.1382.1297122691.4748.digitalmars-d puremagic.com...
 On Monday, February 07, 2011 15:34:26 bearophile wrote:
 Adam Ruppe:
 My gut tells me you'd get much better results if you tried to
 write D in D instead of Python in D.

That's really beside the point. The point of the post is that there are some spots where I'd like to see Phobos improved. (And I am willing to write part of the Phobos code I am asking for).

Actually, it's not beside the point at all. Regardless of what language you're programming in, it's generally best to program in the typical paradigms of that language. Trying to contort it to act like another language is _not_ going to result in optimal code. Now, that's not to say that Phobos can't be improved upon (it certainly can be), but if you focus too much on how it doesn't do something like some other language does, you'll miss what it _can_ do. And it's quite possible that it actually does what you're trying to do quite well if you'd just stop trying to contort it to act like another language (be it Python or Haskell or Rust or Go or Java or C++ or C or Ruby or...).

Using std.algorithm qualifies as contorting D?
Feb 07 2011
parent reply "Nick Sabalausky" <a a.a> writes:
"Nick Sabalausky" <a a.a> wrote in message 
news:iiq4pa$28aa$1 digitalmars.com...
 "Jonathan M Davis" <jmdavisProg gmx.com> wrote in message 
 news:mailman.1382.1297122691.4748.digitalmars-d puremagic.com...
 On Monday, February 07, 2011 15:34:26 bearophile wrote:
 Adam Ruppe:
 My gut tells me you'd get much better results if you tried to
 write D in D instead of Python in D.

That's really beside the point. The point of the post is that there are some spots where I'd like to see Phobos improved. (And I am willing to write part of the Phobos code I am asking for).

Actually, it's not beside the point at all. Regardless of what language you're programming in, it's generally best to program in the typical paradigms of that language. Trying to contort it to act like another language is _not_ going to result in optimal code. Now, that's not to say that Phobos can't be improved upon (it certainly can be), but if you focus too much on how it doesn't do something like some other language does, you'll miss what it _can_ do. And it's quite possible that it actually does what you're trying to do quite well if you'd just stop trying to contort it to act like another language (be it Python or Haskell or Rust or Go or Java or C++ or C or Ruby or...).

Using std.algorithm qualifies as contorting D?

(Although, I didn't read the OP very closely, so maybe I'm off-base.)
Feb 07 2011
next sibling parent bearophile <bearophileHUGS lycos.com> writes:
Nick Sabalausky:

 (Although, I didn't read the OP very closely, so maybe I'm off-base.)

They are right, I have done a "strategic" error. In the original post I have mixed two kinds of very unrelated things: very small suggestions to improve (in my opinion) Phobos, plus some critiques to the D2 language compared to Python. People have rightly not appreciated my post because of the second group of useless comments. I am sorry, I am just stupid. Regarding the original post, in Bugzilla I have added only Phobos-related things. Bye, bearophile
Feb 07 2011
prev sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Monday, February 07, 2011 17:11:50 bearophile wrote:
 Nick Sabalausky:
 (Although, I didn't read the OP very closely, so maybe I'm off-base.)

They are right, I have done a "strategic" error. In the original post I have mixed two kinds of very unrelated things: very small suggestions to improve (in my opinion) Phobos, plus some critiques to the D2 language compared to Python. People have rightly not appreciated my post because of the second group of useless comments. I am sorry, I am just stupid. Regarding the original post, in Bugzilla I have added only Phobos-related things.

Stupid is going a bit far. You do seem to have a tendancy however to at least present things as if D should act like python, which isn't necessarily a good thing. Different points of view on how to go about solving problems can lead to better solutions however. It's just that it doesn't necssarily make sense for D to act like python, and you often come across like D is defficient when it does things differently than python. - Jonathan M Davis
Feb 07 2011
prev sibling next sibling parent reply Adam Ruppe <destructionator gmail.com> writes:
My implementation

http://arsdnet.net/tictactoe.d
source: 138 lines, 2420 bytes

You can see the byte count is comparable to the python 2, but I have
more lines. This reflects my preferences of one line = one instruction.

I usually prefer "Introduction to Programming" style code than
"functional code golf" style, so you see that too.

I also put in a lot of contracts just because i can. I'm trying to
get into the habit of that.

 Input the index of where you wish to place your mark at your turn.

writef("Input the index of your move (from %s): ", 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. You can see that I used a simple string literal for this, declaring it where it is used.
 I'd like the DMD compiler to be able to produce a JSON tree out of > the
current

it at compile time. 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. However, see my comment above on how I feel about the need for this...
 An alias is not as good as a typedef, to make statically sure the
 various functions do take a board and nothing else as input.

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.
 I'd like join to work with a lazy range and on chars too, allowing > code like

I think that could be useful, though it's possible today already for this example (see toImpl for arrays)
 There is no clean way to create a tuple on the heap, the way I have
 used is the best I have found.

The method you used looks like the right way to do it - you just "new" it, like anything else.
 I'd like Python simple functions all() and any() in Phobos2 too.
 With it you are able to write code a bit less ugly like (untested):

I think these would be useful too. There's already one similar thing here: http://dpldocs.info/allSatisfy But it doesn't work on the right types it looks like. If we made that a little more generic I think we'd have a winner.
 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. http://digitalmars.com/d/2.0/phobos/std_array.html ElementType!(String)[] array(String)(String str); Convert a narrow string to an array type that fully supports random access. This is handled as a special case and always returns a dchar[], const(dchar)[], or immutable(dchar)[] depending on the constness of the input. 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. You probably could have done reduce!("a ~ b")("", filter....) to get the char[]... Though, I'd rethink this whole design. The board isn't a string, so you shouldn't represent it as one. I used int[9] in my code.
 There are many situations where I'd like afilter() ===
 array(filter()) and amap() == array(map()).

But, this isn't orthogonal!
 auto my_turn(char xo, TBoard sboard) {
    auto options = space();
    char choice = options[uniform(0, options.length)];
    sboard[to!int(choice~"") - 1] = xo;
    return choice;
 }

I wrote this thus: int suggestMove(int player) out(ret) { assert(ret >= 0); assert(ret < 9); assert(isAvailable(ret)); } body { return randomCover(openPositions(), Random(unpredictableSeed)).front; } 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. 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.
 to!int(choice) doesn't do what you think it does, it converts the
 choice char to its integer representation:

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).
 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.
 In Python both for and while loops have an optional "else" clause,
 that runs if the loop is not exited by a break. It is a handy
 thing, that allows to avoid the "unbroken" boolean I have used in D.

Yes, that's somewhat useful, but not enough to warrant a new language feature. I find it is usually better to make the condition explicit, or avoid it altogether with a better design.
 Because D2 refused to use a handy "in" for strings, this is awful.

Isn't canFind() <http://dpldocs.info/std.algorithm.canFind> that same thing? if(str.canFind('c'))
 Regarding raw_input() see:
 http://d.puremagic.com/issues/show_bug.cgi?id=4716

That might be useful. I even put something like it in my GUI code. 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); 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...).
 There is another usage of unbroken.

That's only because of the code's very, very bizarre design. There's a lot of code duplication and a really strange control flow. 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.
Feb 07 2011
next sibling parent reply bearophile <bearophileHUGS lycos.com> writes:
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
Feb 08 2011
parent reply Adam Ruppe <destructionator gmail.com> writes:
bearophile:
 This kind of indentations is interesting:

I'm still trying to find something I like for this. Currently, I see the contracts as part of the public interface, just like the name, so I'm indenting them like I would if the argument list ran too long. void doSomethingBig( int arg1, int arg2) { } (Sometimes I put the { on the same line as the ) but then I often find it hard to see the difference between the arguments and local variables.) So, similarly, the in and out get indented. It would be messy to shove them all on one line, but without the indent, it'd look like it's all part of the body.
  In such Python/D scripts I don't write stuff like this:
 [snip toString ]

One practical reason for such separation is this (seen in Python but D does it too): >>> s = [1,2,3] >>> print s[34], s[44] Traceback (most recent call last): File "<stdin>", line 1, in <module> IndexError: list index out of range Error messages give a line number... but if there's still several points of similar failure on that one line, it doesn't help as much as it could. This leads me to write things like this sometimes too: auto author = stuff["data"] [0] ["from"] ["name"]; So if the data isn't what I expected, the line number in the error message tells exactly what was missing. (This is also a good example of why semicolons are a gift from God)
 s = ((1,2), (3,4), (5,6))
 print s[1]



Ahhh, I wanted a child of it! Now, I'm forced to do it on one monster line...
 print s[1][3]



File "<stdin>", line 1, in <module> IndexError: tuple index out of range Gah, which one? I guess I'll use a temp variable... Anyway, sometimes this can be fixed in the runtime by including the data and the index as part of the exception, but not always (what if you did [0][0]?). Putting them on separate lines is an easy, reliably way to get more information out of the error.
 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.

We could do that today with a combination of -X, -J, and a CTFE JSON parser (it's possible that std.json would work today. I haven't tried it specifically, but ctfe tends to surprise me with its capabilities). Of course, you'd have to run the compiler twice, but there's other advantages to that too (like getting dependencies - my build tool does this - and the first run is fast anyway. I'm tempted to do it now just to prove we can... but I'm already a bit loaded with stuff to do.
 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.

Maybe a generic toHeap!(T) function would be good for this. It takes an existing object and copies it to the heap if necessary. return toHeap!(tuple(1, 2, 3)); However, it seems to me that most tuples are likely to be small value types anyway. You had an int, char[]. When I use tuples, they are usually small collections of ints and strings too. Value types don't really need to be on the heap. You can just return them and pass them to functions normally and it works fine.
 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.

Maybe try ubyte[]?
 I know, but programming life is not orthogonal :-)

That's part of the reason I said it... I'm only half serious. Though, on the other hand, I've used the ncurses library which does this kind of thing. The number of function names is obscene, and the benefit is quite small. I'm not convinced the parentheses are a big deal. (Hell, I've done lisp before... and kinda liked it. :P)
 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.

The way I see it is if you are working with single chars, it is probably because you want to do some work with the char itself - it's numeric value. Thus it fits the same general area as int. Perhaps you want to just work with one length strings? str[0..1] instead of str[0].
Feb 08 2011
parent reply bearophile <bearophileHUGS lycos.com> writes:
Adam Ruppe:

 I'm still trying to find something I like for this.

Me too.
 Error messages give a line number... but if there's still several
 points of similar failure on that one line, it doesn't help as much
 as it could.

I understand. But splitting lines too much make the code a bit too much thin. I think a better solution is this request (from Lewis): http://d.puremagic.com/issues/show_bug.cgi?id=5521
 We could do that today with a combination of -X, -J, and a CTFE
 JSON parser

 (it's possible that std.json would work today. I haven't
 tried it specifically, but ctfe tends to surprise me with its
 capabilities).

Currently the JSON data is not computed at compile-time, and it's written as file text on the disk, while a library is meant to not touch the disk. So the situation isn't good enough yet.
 Maybe try ubyte[]?

There are moments when I want an array of ubytes, other moments when I want an array of 8 bit chars, and other situations where I want a string of chars. An ubyte[] is a workaround for a type system not good enough yet.
 Though, on the other hand, I've used the ncurses library which does
 this kind of thing. The number of function names is obscene, and
 the benefit is quite small.

I agree that too many functions are going to give problems. But an "a" suffix is burden smaller than two names fully new.
 I'm not convinced the parentheses are
 a big deal. (Hell, I've done lisp before... and kinda liked it. :P)

Scala, Ruby, ML-class languages like Haskell, etc have means to reduce parentheses count. Reducing syntax noise in functional-style code is something that a lot of people wants (me too). I have used Scheme, but parentheses tire me after a while.
 Perhaps you want to just work with one length strings? str[0..1]
 instead of str[0].

I have done this some times. I presume I will need to use this more. Bye and thank you, bearophile
Feb 08 2011
parent reply Adam D. Ruppe <destructionator gmail.com> writes:
bearophile:
 I understand. But splitting lines too much make the code a bit
 too much thin.

I like it being thin so for me, it's a win/win. I use small and split windows as well as 8 character tab stops. (4 characters just blend into the background..)
 I think a better solution is this request (from Lewis):
 http://d.puremagic.com/issues/show_bug.cgi?id=5521

That wouldn't help with runtime errors... and I don't think it would help much for compile either, as a general rule. Something like "cannot call function foo() with args abc" puts you in the right place anyway, even on a pretty complex line.
 Currently the JSON data is not computed at compile-time, and it's
 written as file text on the disk, while a library is meant to not
 touch the disk. So the situation isn't good enough yet.

dmd -X -Xf- file.d That dumps it to stdout, which you can pipe out to where it needs to be. Though, getting it into the program being compiled might be a little tricky without touching the disk. But then again, does it really matter? The compile process writes files as part of its normal operation anyway.
 An ubyte[] is a workaround for a type system not good enough yet.

I wouldn't say that it isn't good enough. It's just that you and Andrei have a different philosophy about this.
 Reducing syntax noise in functional-style code is something that
 a lot of people wants (me too).

Remember, one man's noise is another's parsing anchors. For example, I find an if without parens to just look... naked.
Feb 08 2011
parent reply bearophile <bearophileHUGS lycos.com> writes:
Adam D. Ruppe:

 I like it being thin so for me, it's a win/win.
 I use small and split windows as well as
 8 character tab stops. (4 characters just blend into the background..)

I see. I prefer to see a chunk of code that does something, on the screen, and to not scroll too much. For lot of time the common indent for Delphi code was of 2 spaces :-)
 That wouldn't help with runtime errors... and I don't think
 it would help much for compile either, as a general rule.
 Something like "cannot call function foo() with args abc" puts
 you in the right place anyway, even on a pretty complex line.

The column number of errors helps a little when you have lines of code like (and in other situations): void main() { int[2] f; auto r = f[1] + f[2]; }
 Currently the JSON data is not computed at compile-time, and it's
 written as file text on the disk, while a library is meant to not
 touch the disk. So the situation isn't good enough yet.

dmd -X -Xf- file.d That dumps it to stdout, which you can pipe out to where it needs to be.

I meant something different. I don't want to convert JSON tree from-to text, I'd like to bypass the text representation fully. So the compile-time JSON Phobos library returns a data structure that represents the JSON tree (created by the compiler) in memory. There are zero files, stdout, pipes and text streams.
 Though, getting it into the program being compiled might
 be a little tricky without touching the disk. But then again,
 does it really matter? The compile process writes files as
 part of its normal operation anyway.

It matters if you want to use the JSON data in a larghish program as a static reflection mean to implement good user defined attributes. The less you convert and parse data, the faster and more usable the whole game is.
 An ubyte[] is a workaround for a type system not good enough yet.

I wouldn't say that it isn't good enough. It's just that you and Andrei have a different philosophy about this.

I don't remember Andrei's opinion about that idea.
 Reducing syntax noise in functional-style code is something that
 a lot of people wants (me too).

Remember, one man's noise is another's parsing anchors. For example, I find an if without parens to just look... naked.

Removing some syntax noise is not a so optional thing in functional languages. In Haskell you often have code like: vmoot xs = (xs++).map (zipWith (+) lxs). flip matmul r_90. map (flip (zipWith (-)) lxs) .reverse . init $ xs where lxs = last xs If you start putting parentheses everywhere, you produce something less readable than Lisp. I have never suggested to introduce optional parenthesis in D (the opposite: I'd like function calls to always require them in D), I have just suggested to replace two functions like array(map()) with amap(). Bye, bearophile
Feb 08 2011
parent Adam D. Ruppe <destructionator gmail.com> writes:
bearophile:
 I see. I prefer to see a chunk of code that does something, on the > screen,
and

That's what functions are for!
 I meant something different. I don't want to convert JSON tree
 from-to text, I'd like to bypass the text representation fully.

Yes, that would be the ideal implementation. But as a practical matter, who's going to write that code? I don't know the compiler well enough to do it myself, but I am confident that I could write a little program to call and parse dmd -X; it's something we could do ourselves without bugging Walter. We need to look more at imperfect solutions we can do ourselves with the existing tools than the perfect solutions that need smart people like Walter and Don to do for us.
 It matters if you want to use the JSON data in a larghish program
 as a static reflection mean to implement good user defined
 attributes. The less you convert and parse data, the faster and
 more usable the whole game is.

If the build tool was sufficiently smart (something as simple as plain old make is good enough for this), it could only parse the json once. Again not ideal, but something simple we could do ourselves. On user defined attributes, I wish the attrs were done differently, but what's done is done and I don't know the compiler well enough to write a patch. Instead, we might look at some template ideas that define some enums we can use to access the attributes. As I understand it, Ruby does something similar to this too.
 I don't remember Andrei's opinion about that idea.

It's been discussed on the Phobos list pretty recently. Look for the threads about new string types (I don't remember the exact subject, but Steven did a lot of work on it so searching for his name with the string keyword should find the thread.)
 If you start putting parentheses everywhere, you produce
 something less readable than Lisp.

Gah, that Haskell already is significantly less readable to Lisp! Actually, on optional parens in D, I'm almost split on that. I resisted dropping them from templates at first, but now I love it. I like the property syntax just the way it is too (well, usually. It's annoying when it calls the function when I don't want it to though!)
Feb 08 2011
prev sibling parent reply Hamad <h.battel hotmail.com> writes:
== Quote from Adam Ruppe (destructionator gmail.com)'s article
 My implementation
 http://arsdnet.net/tictactoe.d

Feb 08 2011
next sibling parent bearophile <bearophileHUGS lycos.com> writes:
Hamad:

 == Quote from Adam Ruppe (destructionator gmail.com)'s article
 My implementation
 http://arsdnet.net/tictactoe.d


On Rosettacode they don't want too much long lines, so I suggest you to reduce indents to just 4 spaces, keeping lines under 75 chars. Bye, bearophile
Feb 08 2011
prev sibling parent Adam Ruppe <destructionator gmail.com> writes:
Hamad wrote:
 after your permtion i post your code in http://rosettacode.org/wiki/Tic-tac-toe

Cool.
Feb 08 2011
prev sibling next sibling parent spir <denis.spir gmail.com> writes:
On 02/08/2011 04:11 PM, Adam Ruppe wrote:
 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.


JSON parser (it's possible that std.json would work today. I haven't tried it specifically, but ctfe tends to surprise me with its capabilities). Of course, you'd have to run the compiler twice, but there's other advantages to that too (like getting dependencies - my build tool does this - and the first run is fast anyway. I'm tempted to do it now just to prove we can... but I'm already a bit loaded with stuff to do.

What I dream of is something a bit different: a D "decoder" (lexical, syntactic, semantic(*) analyser) that constructs an AST as a plain D data structure --without any fancy stuff. And writes it out on demand as a D module (in static this(), since for a reason I haven't yet caught data description can only go there). I can't even start to imagine all what we could then do /easily/. (we would even have type defs in D... would change from obscure RTTI) ================================= import std.stdio; a = 1; void main () { writeln(a); } ================================= ==> ================================= import AST; // Node types, mainly Module module; static this () { module = Module ([ Import("std.stdio"), Assignment("a", Integer(1)), FunctionDef( /* name */ "main", /* params */ [], /* block */ [ FunctionCall( /* name */ "writeln", /* args */ [Symbol(a)] ) ] ) ]); } ================================= Too bad we're missing named args, would be highly helpful here; but we can probably survive that... (Yes, Bearophile, there's certainly a bug report for this ;-) I would enjoy writing a prototype when I have some time (not tomorrow), for a tiny subset of D (kind of proof of concept). Denis (*) As far as possible. -- _________________ vita es estrany spir.wikidot.com
Feb 08 2011
prev sibling next sibling parent spir <denis.spir gmail.com> writes:
On 02/08/2011 10:11 PM, bearophile wrote:
 Adam Ruppe:

 I'm still trying to find something I like for this.

Me too.

I like Adam's solution as well, but it's not perfect. The only other solution (like for constraints) would be a syntactic difference, but since we're limited by keyboard keys, this would instead certainly augment visual noise.
 Error messages give a line number... but if there's still several
 points of similar failure on that one line, it doesn't help as much
 as it could.

I understand. But splitting lines too much make the code a bit too much thin. I think a better solution is this request (from Lewis): http://d.puremagic.com/issues/show_bug.cgi?id=5521

Just done it for my parser. Once you get the line nr...
 Maybe try ubyte[]?

There are moments when I want an array of ubytes, other moments when I want an array of 8 bit chars, and other situations where I want a string of chars. An ubyte[] is a workaround for a type system not good enough yet.

After some more interrogations on the topic, I think there should be 4 somewhat related types. * Bytes --> processing of data at plain binary/numeric level, no link to text (eg pixmap) * ByteString --> single-byte charset text (ascii, latin-X,...) On the implementation side, there no difference in data structure (not even code check like for D chars). But conceptually and semantically, these types are unrelated. (It would be a bit strange for me to process a pixmap using a type that exposes tons of text-processing functionality.) ByteString is, I guess, more or less what Steven proposed. * D' utf-8 string for i/O of unicode text without any manipulation (except concat). * A Text like what I proposed for unicode text manipulation, conceptually an array of univoque text-characters (an array of no-copy-slices into a normalised utf-8 string).
 Though, on the other hand, I've used the ncurses library which does
 this kind of thing. The number of function names is obscene, and
 the benefit is quite small.

I agree that too many functions are going to give problems. But an "a" suffix is burden smaller than two names fully new.

I like your proposal. But -a suffixes are no good, use -Array or array- prefix instead.
 I'm not convinced the parentheses are
 a big deal. (Hell, I've done lisp before... and kinda liked it. :P)

Scala, Ruby, ML-class languages like Haskell, etc have means to reduce parentheses count. Reducing syntax noise in functional-style code is something that a lot of people wants (me too). I have used Scheme, but parentheses tire me after a while.

Editors do that for you, don't they? Even for languages they don't know... (never been annoyed by paren counts, not even in Lisp-like slangs) Denis -- _________________ vita es estrany spir.wikidot.com
Feb 08 2011
prev sibling parent spir <denis.spir gmail.com> writes:
On 02/09/2011 01:35 AM, bearophile wrote:
 I meant something different. I don't want to convert JSON tree from-to text,
I'd like to bypass the text representation fully. So the compile-time JSON
Phobos library returns a data structure that represents the JSON tree (created
by the compiler) in memory. There are zero files, stdout, pipes and text
streams.

Just what I would like to rip from a D parser in D (which doesn't prevent to write it into a D source module if useful). Denis -- _________________ vita es estrany spir.wikidot.com
Feb 08 2011