Thursday, December 31, 2015

Don't learn X

All too often, on various forums I see someone asking something like "Convince me to learn X".   I'm immediately tempted to simply answer "Don't learn it" .... and be prepared to be downvoted into oblivion.

Almost invariably, learning X will take some time ... a lot of time if you are to learn it well.

If you are not willing to spend a much, much smaller amount of time finding out on your own if X is worth learning, then you definitely won't find the time to learn it.

If you do not have the time to figure out from what's already available about whether or not it's worth learning X, why should I expect that you will give any weight to my opinion? Why should I spend my time writing down reasons as to why you should learn it?

If your question is a vague "Should I learn X or Y?", then it will only give rise to some quasi-religious wars between fans of X and fans of Y, reflecting only the readership of that forum.

If you want to know if it would be better to learn X or Y to accomplish a specific task, then it is a different question, which may very well deserve an answer based on the specificity of the task.

However, if you want to know if you should learn X ... do everyone a favour and simply start learning it: you will soon be able to decide on your own if you should pursue or not.   And if you do not like this answer, then "Don't learn X".

Wednesday, December 30, 2015

i18n: an unusual scenario

Reeborg's World currently has a "default" English version, a French version, and a partially implemented Korean version. Even if one forgets about the fairly extensive documentation [en, fr, ko], creating a new language version can be a rather daunting task.

For most applications, creating a new language version requires to create a collection of strings as a template for translation and provide a translation for each string in that template. The standard approach for doing this is to use gettext [0].  Would-be translators then have to either install some software (poedit is the standard) or register as a translator on some web-based site (e.g. https://translatewiki.net/) where they may have to prove their worth before being approved as a translator.   These extra steps (installing some software or jumping through some hoops to register on some site) can be a turn off. [1]

One of my motivations for dropping RUR-PLE in favour of a web-based version was to reduce the friction for the end user.  Initially, installing RUR-PLE meant 1) installing Python; 2) installing the corresponding version of wxPython; 3) downloading RUR-PLE itself as a zip file.   Eventually, some volunteers helped to create various installers for it ... but this was not something I could easily do myself and it still required end-users (say: teachers in a classroom) to install some "unusual" software which, I found out, was not (easily) possible to do in some school environment.

By contrast, using Reeborg's World simply requires to open the site in a browser [2].  By choice, no login is required as I want the end-user experience to be as painless as possible; everything is run client-side.  I would like to create an "as painless as possible" experience as well for would-be translators.

To understand what is required for a full translation (excluding the documentation!) of the user interface, I will use a simple example.  Imagine that we want the end-user to be able to run the following program (using Python):

move()
take("token")

This being a tool to teach Python, we have also to enable the end-user to type

help(take)

and get some appropriate help.  Here's the result of doing so:



Since Reeborg's World also support Javascript, we could run

move();
take("token");

Finally, since it supports Blockly as well, the same program could be executed via:



where is the representation of a "token" in Reeborg's World, one of a dozen basic objects that can be interacted with by Reeborg. Here's how the same program would appear to a French user using Blockly



from which you can easily deduce how to write the corresponding program using either Python or Javascript.

Having this basic scenario in mind, here's what's needed to translate:

1. The main html page (currently world.html for the English version and monde.html for the French version - but this will likely change in the near future).  This can be done using a template and creating static pages by inserting the appropriate translation strings into the template.  (This is not how it is done currently ... but should soon be.)

2. Create a complete Python module for the target language (currently reeborg_en.py for the English version) containing the appropriate function definitions something like

def take(obj=None):
    """Takes an object.  If more than one type of objects is at 
       Reeborg's location, the type must be specified as an 
       argument, otherwise an exception will be raised.
    """
    if obj is None:
        _take_()
    else:
        _take_(obj)

3. Create a complete Javascript module (currently reeborg_en.js) for a similar definition

var take, ...;
reset_definitions = function () {
  ...
  take = _take_;
}

4. Create the appropriate translation strings so that Blockly programs can be translated into the target language (or, in some cases, ensure that a consistent English-version is used), something like

translation["take"] = "prend";
translation["token"] = "jeton";
translation_to_english["jeton"] = "token";

Like Blockly, I do not plan to make special allowance for plural forms of words.  While 1 and most of 4 above can be done using the standard gettext approach, 2 and 3 require a different approach.  I have asked for suggestions about this on HackerNews ... and, unsurprisingly, my query received no visible attention.[3]   So, I have come up with a plan to implement a custom and "unified" solution, applicable to all four steps above and somewhat inspired by the gettext approach. I want all of this to be done (client-side) via a custom page on my site, similar to the idea of translatewiki, but without requiring any login. The translators would have to save a file (end result) to their computer and email it to me ... which, hopefully, would not be seen as being too onerous for someone wanting support in their own language.  (I expect that most translations will be provided by educators wanting to use Reeborg's World.)  Of course, alternatively, they could clone the repo, make the required changes, and submit a pull request ... but that's not exactly painless!

If you have read this far, it likely means that you've had some experience with i18n strategies ... and perhaps have suggestions to offer which may help me avoid reinventing the wheel! ;-)

[0] Indeed this is what I (mostly) ended up using with RUR-PLE, whose interface is available in 7 languages).

[1] For the record, I wanted to correct a translation for Blockly where "to" was correctly translated into French as "à" except in one context where it should have been translated as "pour" ... but, upon loggin in, I was presented with a completely unrelated other translation task, with some hint that additional tasks were to come before I could be approved...  and with no guarantee that the correction I wanted to make would be accepted.  In the end, I just gave up and implemented the correction in the version I use on Reeborg's World.  I don't think that my reluctance at jumping through the hoops mentioned is particularly unusual ... especially to those that have done editing on Wikipedia :-/

[2] I am aware that some schools have filters that prevent browsing on some non-approved site. From what I understand, it is easier to get permission to add new allowed sites than it is to install some "unusual" software.  Furthermore, unlike RUR-PLE, Reeborg's World can be used from someone else's computer (e.g. from a Public Library) where it would definitely be impossible to install some software.

[3] I didn't bother with StackOverflow as this was the type of question often flagged as not being too vague or something similar.

Wednesday, December 23, 2015

Planning to drop CoffeeScript support for Reeborg's World

When I started working on Reeborg's World, I thought it would eventually be a good site to learn the basics of various programming languages, with fun puzzles to solve. However, I found that it myself including more material for Python (and Javascript) including the recent Blockly addition, with no new programming language easily added.   I also have been reading various posts about people suggesting that CoffeeScript was good to use/learn only after having had a good grasp of Javascript.  By then, programmers would find the tasks on Reeborg's World rather trivial to solve and, in the absence of a good dedicated tutorial to CoffeeScript (or other languages), the appeal of using Reeborg's World to learn CoffeeScript would be rather minimal.

So, CoffeeScript support will be dropped, thus reducing slightly the code requiring to be maintained.

Of course, anyone finding it of value could just clone the repo and recover the CoffeeScript support.

Blockly + Reeborg = fun

I've retrofitted Reeborg's World to include Blockly.  You can select Blockly from the top.  If you do so, I suggest you keep the editor open to see the generated code prior to its execution.

Note that this is a first implementation done in just a couple of days of programming.  Bugs are likely present...

Tuesday, December 08, 2015

Reeborg: two major steps forward, a huge step back with Firefox

UPDATE: The Firefox problem was caused by using a function argument named "watch" ... which seemed like a good name...  However, it appears to conflict with https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/watch

=======

I'm really happy with two new cool features for Reeborg's World ... but this happiness is nearly crushed by the fact that Reeborg's World is completely broken by Firefox 42.0.  It still works with Google Chrome and even works with Microsoft Edge (gasp!) ... but not with Firefox.  I tried to load a year old version of the site that did work with older version of Firefox: no go.

The two new features: it is possible to enable a "variable watch".


Of course, the speed of the animation can be controlled and even paused.

It is also possible to control Reeborg (or, write any Python code, really) using an REPL.





Wednesday, December 02, 2015

Revisiting an old friend, yet again

The most popular blog post I ever wrote was on a suggested alternative for type hinting in Python. This was likely due to the fact that the blog post was linked in PEP 484 which codified a syntax for type hinting.

The alternative syntax I had been suggesting was to use a clause introduced by a new keyword: where. (I had first discussed this idea more than 10 years ago.)

For example, instead of the approved type hinting syntax

def twice(i: int, next: Function[[int], int]) -> int:
    return next(next(i))

I was suggesting to use something like the following:

def twice(i, next):
    where:
        i: int
        next: Function[[int], int]
        returns: int
    return next(next(i))

Similarly, instead of using a comment to introduce type hinting for single variables

x = 3 # type: int

one would have used the following:

x = 3
where:
    x: int

However, this alternative was never considered as a serious contender for type hinting since it was not compatible with Python 2.7, unlike the accepted syntax from PEP 484.

Still, as an experiment, I decided to see if I could use the approach mentioned in my last few blog posts and use an nonstandard where clause.  And, of course, it is possible to do so, and it is surprisingly easy. :-)

The main details are to be found in three  previous blog posts. However, to summarize, suppose you with to use a where clause like the one described above which would not have an effect on the actual execution of the Python program (same as for the current type hinting described in PEP 484). All you need to do is

1. include the line

from __nonstandard__ import where_clause

in your program.

2a) if the program to be run is the main program, instead of running it via

python my_program.py

you would do

python import_nonstandard.py my_program

instead, where import_nonstandard.py is in the directory "version 5" of this repository, and the relevant where_clause.py is in "version 6".

2b) If instead of having it run as the main program, you would like to import it, then you would include an extra import statement:

import import_nonstandard
import my_program
# rest of the code

and run this program in the usual way. By importing "import_nonstandard" first, a new import hook is created which pre-processes any modules to be imported afterwards - in this case, to remove the where clause or, as I have described in previous posts, to define new keywords or even an entirely different syntax (French Python).

Note: None of the recent experiments are to be taken as serious proposals to modify Python.
Instead, they are a demonstration of what can be done (often in a surprisingly easy way)
with Python as it exists today.

Tuesday, December 01, 2015

French Python ?

In two previous post, I showed how it was possible to transform some source code prior to having it executed.  My original motivation was to see how one could add a new keyword ("repeat") to use as "repeat n:" and it be equivalent to "for _ in range(n):".

As part of the additional motivation for my experiment, I mentioned the following:

In one of his posts to python-ideas, Terry Jan Reddy mentioned a discussion on the idle-dev list about making idle friendlier to beginners. In one of his post, he mentioned the idea of having non-English keywords. This idea is not new. There already exists an unmaintained version with Chinese Keywords as well as a Lithuanian and Russion version. Maintaining a version based on a different language for keywords is surely not something simple ... nor I think it would be desirable. However, it might be possible to essentially achieve the same goal by using an approach I describe in the next section.

Even just adding new keywords can be quite difficult. For example, in this post, Eli Bendersky explains how one can add a new keyword to Python. "All" you ned to do is

  1. Modify the grammar to add the new keyword
  2. Modify the AST generation code; this requires a knowledge of C
  3. Compile the AST into bytecode
  4. Recompile the modified Python interpreter
Not exactly for the faint of heart...

I thought I should revisit the idea I had to see how difficult it might be to create a French Python syntax. As it turned out, it was even simpler than implementing a new keyword. In addition to the code mentioned previously, the new function needed is simply:

def transform_source_code(text):
    dictionary = {...}
    toks = tokenize.generate_tokens(StringIO(text).readline)
    result = []
    for toktype, tokvalue, _, _, _ in toks:
        if toktype == tokenize.NAME and tokvalue in dictionary:
            result.append((toktype, dictionary[tokvalue]))
        else:
            result.append((toktype, tokvalue))
    return tokenize.untokenize(result)

where the dictionary contains the equivalent (e.g. "Vrai": "True", etc.)

The one change I made from the previous version was to replace __experimental__ by __nonstandard__.

For example, here's a test program:

from __nonstandard__ import french_syntax 
de math importe pi  

imprime(pi)  
imprime("The first 5 odd integers are:") 
pour i dans intervalle(1, 11, 2):  
imprime(i)  

imprime("This should be false:", Vrai et Faux)  

si pi == 3:  
imprime("We must be in Indiana") 
ousi pi > 4:  
print("Non standard Euclidean space") 
autrement:  
print("There should be nothing about Indiana nor Euclidean space.")

The code found in version 5 of this repository. Using this approach, it would be trivial to create localized Python versions suitable to introduce absolute beginners to programming concepts. I would not do this myself, here in Canada, but I could see the appeal in some other countries especially those where the English alphabet is not very well known by young learners.

Now, if only I could figure out how to use importlib instead of imp ...