Python declarative

Chris Angelico rosuav at gmail.com
Sat Jan 25 22:38:42 EST 2014


On Sun, Jan 26, 2014 at 1:33 PM, Steven D'Aprano
<steve+comp.lang.python at pearwood.info> wrote:
>> Here is your version -
>>
>> mainwindow = GTK2.Window(0)->add(GTK2.Vbox(0,0)
>>     ->add(GTK2.Label("About Gypsum: big long multi-line string"))
>>     ->add(GTK2.HbuttonBox()
>>         ->add(GTK2.Button("Close"))
>>         ->add(GTK2.Button("Foobar"))
>>     )
>> );
>
> That's not Python code, but it's reasonably concise and readable. (By the
> way, what language is it?) The meaning is fairly obvious, and it's all
> pretty simple.

It's Pike, and the above example is pure Pike without any of my own
helpers. If you fire up Pike's interactive mode and type two commands
"GTK2.setup_gtk();" and "start backend" first, you can paste that in
and it'll create you a window. (It won't DO anything, but it'll sit
there looking like a window.) It tends to get a little verbose after a
while (hence the need for helper functions to combat the complexity),
but the advantage of this system is that it took me absolutely zero
code. I didn't have to write any framework.

> I'd like to see a more human readable GUI designer
> language:
>
> # hypothetical code in a DSL for creating GUI elements
> create new window mainwindow
> with mainwindow
>     add vbox at 0,0
>     add label "About Gypsum: big long multi-line string"
>     add hbuttonbox
>     add button "Close"
>     add button "Foobar"
>
> but what you've got there is okay. Seven lines of readable code.

Sure. That would make reasonable sense, as a DSL. And, in fact, if I
were making a Python GUI, I would want to add something that would let
me do it that way - at least compared to PyGTK, which insists on every
box getting a temporary name, since the add() methods don't chain. But
that's a cost, that has to be written. I'm happy with the no-code
version to start with. Now, with something as complicated as the
character sheet, well, that's different. I think if I did all the
charsheet code as pure no-helpers Pike, it would be a dozen pages of
code and indented about fifty tabs. At that point, it's worth putting
some effort into taming the beast :) Here's an actual example from the
charsheet:

GTK2.Hbox(0,10)->add(GTK2Table(({
    ({"Name",ef("name",12),0,0,"Char level",num("level",8)}),
    ({"Race",ef("race",8),"HD",ef("race_hd"),"Experience",num("xp",8)}),
    ({"Class",ef("class1",12),"Level",num("level1"),"To next
lvl",calc("`+(@enumerate(level,1000,1000))-xp")}),
    ({"Class",ef("class2",12),"Level",num("level2"),"Size",select("size",sizes)}),
    ({"Class",ef("class3",12),"Level",num("level3"),
        "Grapple",calc(grapple_formula,"grapple","string")
    }),
    ({"Class",ef("class4",12),"Level",num("level4")}),
}))->set_col_spacings(4))
->add(GTK2.Frame("Wealth")->add(GTK2Table(({
    ({"Platinum",num("wealth_plat",7)}),
    ({"Gold",num("wealth_gold",7)}),
    ({"Silver",num("wealth_silver",7)}),
    ({"Copper",num("wealth_copper",7)}),
    ({"Total gp",calc("(wealth_plat*1000+wealth_gold*100+wealth_silver*10+wealth_copper)/100")}),
}))))


It's a mix of basic Pike and GTK functions (GTK2.Frame is one of the
standard widgets) and helpers (num, ef, and calc) that return widgets,
maybe with child widgets. GTK2Table takes an array and plops
everything into it at appropriate locations. I use that function
*everywhere* in the charsheet - mainly because I was porting from a
spreadsheet, but D&D character sheets are inherently tabular.

(Note that I've reformatted this a bit from the original, and it might
be disrupted a bit by the email/newsgroup format. In the original,
this is all a few tabs in, but as long as your editor isn't wrapping
the lines, it's pretty readable.)

At what point is it a DSL rather than just "Pike code with helper
functions"? This is particularly true with Python code; it wouldn't be
hard to make a DSL that's derived from Python.

(By the way, your translation "add a vbox at 0,0" isn't accurate; the
0,0 are parameters to the Vbox itself, being the homogenous flag (if
it's nonzero, it forces its children to the same height and/or width,
but 0 means each child gets what it asks for) and the padding (0 puts
children hard up against one another, nonzero puts that much room
between them). A window has only a single child, which occupies its
entire space; that's why it's conventional to add a layout manager as
the one child, and then add multiple children to that. Means the
window needn't care about the minutiae of child positioning. But if
you call that "add a vbox 0,0", that'd work fine; and if you're making
a DSL, I'd make 0,0 the defaults and say "add a vbox padding 10" if I
want to override.)

The only problem with your DSL as I see it is that it doesn't mark the
beginning and end of child widget layouts. This needs to be a tree.
But Python already has a notation for that: indent! So if the two
buttons are indented, they'll be inside the hbuttonbox (which is a
type of horizontal box), which is within the vertical box (again,
indent everything that's inside it), which is within the main window.

Of course, then you need to work out how to attach code to buttons,
but let's leave that aside... mainly because it's a nightmare to do in
XML, but pretty easy (if a little tedious in large quantities) in
code. :)

ChrisA



More information about the Python-list mailing list