blog.herby.sk

Sus scrofa f. sapiens

Categories

Archives

24 January 2012
Tuesday

Empowered data

This proposal tries not to add new entities into the language, it only tries to take what already is there and reusing it mercilessly. Also number of abstractions is lowered, since some of them could be implemented with existing ones, with minimal changes. The result is compact, lesser, more uniform and much more powerful language. ;-) Forward-compatible.

Motivation.

Blocks of code enclosed in curly braces were of two natures in ES3 and ES5 - there were code blocks, containing sequence of instructions to perform, and there was the object literal, which contained recipe for building a structured piece of data.

ES.next introduced some powerful additions to the object literal, introduced new use for it (.{...} and <| {...} operations) and brought in a new type of {...} block - the class block. The class block borrows many new features of object literal, but itself is something in-between.

Driven by the feeling that having more types of {...} source code constructs brings more confusion led to the thoughts about their nature and their similarities. This proposal wants to take this train of thought to the extreme by proposing only two types of "brave new curly block" constructs with strict role split - the imperative one for control flow and the declarative one for data structures, while building on similarities between them and radically empower the declarative one in the process, and not losing forward compatibility (by this I mean old-style constructs work in the new proposal). Classes are made as one case of the declarative construct with class-specific extensions, changing existing class syntax very slightly and not losing any semantics.

Curly blocks are similar. Reuse for much power with few features.

A simple code block:

{
  x = 4;
  receiver.f(x);
  function g() {
    do { nothing; } while (false);
  }
  x++;
  const prop = 5;
  if (x>5) {
    process.exit();
  }
  x--
}

Basic structure of this block is: there are simple statements, which are terminated by a semicolon (ignoring general semicolon insertion here). The last simple statement in a code block does not need a semicolon, though it can have it. These statements include assignment and function call.

Then there are structured statements which do not need to be ended with semicolon (do-while is a nasty exception), since they are ended with a sub-block. These are if/else, while loops, function declarations etc.

Not that this is correct explanation of code block structure (I for example ignore cases where if/else/while/... sub-statements are simple statments, not sub-blocks. For now, let us assume there are always sub-blocks). I also intentionally dismissed variable declarations, since they are not needed for this topic and would make things a little more complicated (look at that const line as an assignment ;-) ).

Now, for the simple object literal (with ES.next extensions):

{
  x: 4,
  g() {
    do { nothing; } while (false);
  }
  y: { foo: "bar" },
  get prop () { return 5; }
  z: 0
}

Basic structure of this block is: there are simple productions, which are terminated by a colon. The last simple production in a literal block does not need a colon, though it can have it. These productions are property initializations.

Then there are enhanced productions which do not need to be ended with colon, since they are ended with a sub-block. These are get, set and method declarations.

Even when the range of possible building elements of object literal is smaller than that of the code block, the similarities can be seen pretty well. There is undoubtful similarity between x = 4; and x: 4,, not only syntactical, but semantical, too. There is strong syntactical similarity between declaration of function g in code block and method g in the literal. Semantically it is also pretty similar, though not as much as the previous case.

Previous examples showed that there are formally (simple-simple, structured-enhanced), syntactical and functionally similar pairs of constructs between code block and object literal. These elements are, more-or-less, about the same thing. The difference between them is given by the context: assignment and function declaration do actions (they are imperative), field specification and method specification produce data (they are declarative).

It can be said, with lot of grains of salt, that code block is "(ordered) collection of imperative elements, simple, semicolon delimited, as well as structured, undelimited" and object literal is "(unordered) collection of declarative elements, simple, colon delimited, as well as structured, undelimited", but matching elements appear in both. This strawman is about completing this element similarity, mainly drawing from useful code elements and bringing their counterpart to the data domain.

1. if & Co. Conditional data structures.

The first idea to borrow from code domain is the if statement - in this case, not a statement, but a data production. You surely had a situation when writing an object literal and wanted to have a field or two only when specific condition is met. The solution nowadays is either not put it in and add it afterwards with if statement in the code (which is not correct, a conditional data field was wanted, not a conditional action that assigns to that field) or put the field in with ?: or && operators, so the field has null value in the case it should not be there at all.

Why not to have something like this?

{
  x: 4,
  g() {
    do { nothing; } while (false);
  }
  y: { foo: "bar" },
  if (bar > cowboy) { jar: ["whiskey"], wall: ["bottle", "bottle"] }
  get prop () { return 5; }
  z: 0
}

The data-domain if, in accordance with its code counterpart, is the structured element, that does not need a colon at the end, since it ends with a sub-block. But the data-if governs data-block. The curly block that is guarded by data-if should be a normal data-block which is included if the condition is met, and is not included when the condition is not met.

Of course we can have if/else if/else combination, like in { name: name, if (age > 60) { retired: true } else if (age < 18) { minor: true } else { workplace: company } age: age }.

If the if/else could only govern (data) blocks, it would not be the true compilation of code-if. To be true, it should take both simple elements ended by comma as well as blocks into its syntax, so this should be possible, too: { name: name, if (age > 60) retired: true, else if (age < 18) minor: true, else workplace: company, age: age }. To not create inconsistencies, I would allow this syntax, as well. Be as true to code-if as possible. In one line, this may look inferior, but when indented, it can be

{
  name: name,
  if (age > 60) retired: true,
  else if (age < 18) minor: true,
  else workplace: company,
  age: age
}
or
{
  name: name,
  if (age > 60)
    retired: true,
  else if (age < 18)
    minor: true,
  else
    workplace: company,
  age: age
}
which is not that bad.

Another conditional that can readily be adopted into the data domain is switch. It's fall-throgh, implicit block, break-finished semantic is a bit unwieldy for a one-liner, like { name: name, switch (role) { case "manager": canSeeReports: true, case "admin": aceessToServerRoom: true, break, case "developer": accessToLibrary: true, default: needsTask: true } if (boss) reportsTo: boss }, but again, formatting helps, and frankly, switch is not used that often in the code, it won't be used that much in data either, but sometimes it is really helpful. For the sake of completeness, it should be in the data, as well.

2. f(x, y). Data-production macros.

Code has a function call amongst its "simple" building blocks. It allows to define a little piece of code in one place and issue it later in many other places, possibly parametrized. Why not to have something like that in data, too? What about these data productions?

{
  name("Doe", "John"),
  people.counter(),
  position: "manager",
  salary: 100000
}
{
  name("White Daemon", "Jinx Perry"),
  dogs.counter(),
  race: "cavalier King Charles spaniel",
  colors: [ white, brown ]
}

What are name(...) and repositorty.counter(), function calls? Not exactly - in code it would be calls to functions or methods that would do some imperative sequence of actions. In data, it "invokes" a named data production, which is just like function or a method, but its block is declarative. Otherwise, they are defined the same way as functions or methods, with exception of % character used as a modifier, analogically with * modifier of generators:

function% name(surname, givenNames) {
  fullname: (locale == "hu" || locale == "jp") ?
    surname+" "+givenNames : givenNames+" "+surname,
  catalogName: surname+", "+givenNames,
  givenNames: givenNames,
  surname: surname
}
class Repository {
  ...
  %counter() { id: this.maxId++, creationDate: Date.now() }
  ...
}

I call % functions and methods data-production macros. They are not in fact true functions - the semantics of dogs.counter() is to include id: dogs.maxId++, creationDate: Date.now() in the object literal. The semantics is this for a reason - so implementors can optimize it to any level they see fit. It is "just" an inclusion of a parameterized preready data production.

On the other hand, dynamics of true functions / methods and easy interoperability with code must be present, macros must be as flexible as code functions are. For this, I'd propose these rules:

  • macro is first-class object that is accessible by its property name for reading and writing (if not made const etc.)
  • you can create macro object inline by function% (args) { macro body }
  • typeof macro is "function", it has no [[Construct]] and behaviour of [[Call]] is deliberately undefined (to allow implementors freedom to use it as they see fit)
  • issuing non-macro object with typeof "function" from inside data block as a macro results in throwing TypeError

As for the [[Call]] implementation specific, how do you reuse a macro from inside code? Simply: obj.{ macro(...) }. This is officially recommended (and only supported) way of reusing macro directly from code.

And yes, you can have recursion with macros. You are encouraged to.

One more note: macros can be even more powerful if they cleverly use the [expr]: expr data production. It is part of ES.next-enhanced object literal. The word is "cleverly", it can be colossally abused. You have been warned.

No loops, no variables. "Functional" object production.

There can be two paths with continuing the approach above. One is to adopt everything, however imperative, which is possible, from the code side to the data side, so we can have variables and loops in data side, as well and can issue something like this:

{
  operation: "square",
  min: 1,
  max: 10,
  for (var i = this.min; i <= this.max; i++) { [i]: i*i }
}
I argue that when you have this kind of imperativity, (if is conditional descriptive; macro, even if powerful through recursion, is less imperative than loop and variable), you can as well do it in plain code. After all, code is better for imperative things:
var result = {
  operation: "square",
  min: 1,
  max: 10
}
for (var i = result.min; i <= result.max; i++) result.{ [i]: i*i };
I used this mechanical translation and not used result[i] = i*i; for purpose of genericity: you can issue loops in code but still use all of the power of enhanced descriptive blocks using .{ data-production... } construct.

If "side-effect" imperative things like variable, and, consequently, loops, were exempt from data-production blocks (and nothing other which is imperative in nature is added later; and all things that would be added would be "side-effect-free" and non-imperative), we will end up with a thing I'd call "functional data production". I think it is desirable trait of a data-production.

By "functional" I now mean the trait that is inherent to code in functional languages - if issued, with parameters, it produces value from them, but this value production has no side-effects. The most prominent of these side-effects is setting a value of a variable. One may also call this "stateless". Data production should be stateless, imperative code is one that should be stateful.

Being stateless (of course, the data production is not stateless in strict sense - the values are computed by stateful code expressions, and [expr]: expr can bring expressions in keys as well; but avoiding variables and loops makes data production still less stateful) allows doing things that are typical for functional code (various behind-the-scene optimizations, mainly; but also some proofs of correctness) for the data -production blocks. Since data production is descriptive thing, one almost naturally expects from it to be sort-of "producing a value" instead of "start a process of manufacturing a value". Though I can not give a convicing case for this, I beilieve it is Good Thing (tm) to let the data production be stateless. In the long run it will bring its fruit.

Parsing: ambiguities; syntax as opt-in philosophy.

This and lots of similar extensions are in some time questioned by the parsing problems. For example:

{
  if (typeof window === "undefined") { server: true }
  else { broswer: true }
}
is interpreter as code, with two expression statements labeled "server" and "browser", both statements being "true", if encountered in code. When parsed in expression contexts (after assigment "=" or after function call "("), it parses as data production.

The condensed example of this phenomenon is:

{}.f()
If encoutered in code context, it is syntax error, because {} is code block and "." is unexpected token. If encountered in data context, it is the value of calling f method of {} object.

This untreatable ambiguity may render any proposals as this doomed. But it is not that. Even plain {} does not work - and we got used to put parentheses around it whenever it appears at the beginning of an expression statement (it is not so common to start an expression statement with object literal, but when it happens, almost always dot is following and it produces early syntax error). So this is annoying, but already known phenomenon, and we learn to live with it. Bottom line is, it is orthogonal to this proposal.

One possible parsing problem is combination of method declaration (f(args) {body)) with macro invoking (f(args)). But hopefully there will not be a problem, because the latter needs a comma delimiter unless last in the block.

One paragraph for "syntax as opt-in" mindset, which seems to be part of ES.next. Conditionals and/or macro calls inside data production block are to be treated as ES.next syntax and, consequently, opt-it in. The same is the case of function% and %-prefixed method names. The question of scope of opt-in is still debated, but overall, this proposal seems to favor program-wide opt-in. It needs the review of others to see full consequences for "syntax as opt-in" if this proposal is considered. It brings some (not breaking) changes to the basic ECMAScript matter, that is, to the object literal. Also, if there were parsing guesses based on containing if, switch or function call, they are invalidated.

3. Class is glorified declaration of prototype.

No offense meant. One of the motivation behind all this was the fact that class block was neither imperative nor declarative but (at least syntactically) something from both, and by need of having only two kinds of {...} - imperative (with all its consequences and common functionality all over) and declarative (ditto). And as I see it (I hope I am not alone), class is a way to describe the prototype (and constructor at the same time, but it is already nicely integrated). So taking example from class proposal (comments shortened; private changed to @ use, see below),

class Monster {
  // The contextual keyword "constructor" ... defines the body
  // of the class’s constructor function.
  constructor(name, health) {
    public name = name;
    @health = health;
  }
 
  // An identifier followed by an argument list and body defines a method. 
  attack(target) {
    log('The monster attacks ' + target);
  }
 
  // The contextual keyword "get" followed by an identifier and
  // a curly body defines a getter in the same way that "get"
  // defines one in an object literal.
  get isAlive() {
    return @health > 0;
  }
 
  // Likewise, "set" can be used to define setters.
  set health(value) {
    if (value < 0) {
      throw new Error('Health must be non-negative.')
    }
    @health = value
  }
 
  // After a "public" modifier, an identifier ... declares a prototype
  // property and initializes it
  public numAttacks = 0;
 
  // After a "public" modifier, the keyword "const" followed by an identifier
  // and an initializer declares a constant prototype property.
  public const attackMessage = 'The monster hits you!';
}
we can embrace "just describe the prototype object" and do this instead:
class Monster {
  // A method defined with name "constructor" is processed specially:
  // tt _has_ [[Construct]] and is made a constructor of this class.
  // If not explicitly generated, empty one is provided.  
  constructor(name, health) {
    public name = name;
    @health = health;
  }
 
  // A method, as in every object literal.
  attack(target) {
    log('The monster attacks ' + target);
  }

  // A getter, as in every object literal. 
  get isAlive() {
    return`@health > 0;
  }
 
  // A setter, as in every object literal.
  set health(value) {
    if (value < 0) {
      throw new Error('Health must be non-negative.')
    }
    @health = value
  }
 
  // A property definition, as in every object literal.
  numAttacks: 0,
 
  // A "const" property definition, as in every object literal.
  // (syntax of const property production is not yet agreed upon,
  // just use any one which is selected in the end)
  attackMessage := 'The monster hits you!'
}

Note to private removal: It seems private keyword will be removed in favor of foo.@bar syntax to access foo's property with private name bar. I am embracing this syntax in the document.

Apart from the different comments, which just show the different implementation provide semantically same result, the class code is nearly identical. Gone is (superfluous) public keyword in context of the prototype, I'd say it could go from constructor method as well (this.name = name; works fine and does not create any exceptional situations for constructor/non-constructor). If you see at it, the class block really only did (declaratively) describe the prototype. So let us make class Clazz [extends Superclazz] an operator on the generic data-production block, which creates the class machinery from it and returns constuctor function. It can be de-facto desugared to something like:


var _proto = (Superclazz || Object).prototype <| {
  ... the class body ...
};
if (!_proto.constructor) { _proto.{ constructor() {} } }
var _ctr = _proto.constructor;
__allowConstruct__(_ctr);
_ctr.prototype = _proto;
return _ctr;
except for the __allowConstruct__ will be inherent, not issued afterwards. Pros are clearly visible: less kinds of abstraction, no management of making features in class and object literal work consistently (class declaration is an object literal, everything works automatically).

There are some open issues, definitely. The class proposal continues with this:

class Monster {
  // "static" places the property on the constructor.
  static allMonsters = [];
 
  // "public" declares on the prototype.
  public numAttacks = 0;
 
  // Although "public" is not required for prototype methods, 
  // "static" is required for constructor methods
  static numMonsters() { return Monster.allMonsters.length; }
}
which can be straightforwardly rewritten as
class Monster {
  // "static" places the property on the constructor.
  static allMonsters: [],
 
  // plain declares on the prototype.
  numAttacks: 0,
 
  // "static" is required for constructor methods
  static numMonsters() { return Monster.allMonsters.length; }
}
and yes, object literal needs to know static keyword if used in context of class operator. Yes, an exception, but pretty clear one. We can live with it. The question appears: "What about static in macros?", which is not really easy to answer. One possibility may be to allow it (and any use of static) and throw an error if it is not (directly or included) happening inside class operator.

To end this paragraph more positively, if you define class block to be a data-production block, you can make the language more cohesive and features reused instead of coordinated, which should be a plus. Also adoption should be less fearful, because you do not any "class magic", you are simply "declaring the structure of a prototype" (while constructor and static are taken care by class operator for you).

Classes + macros = free trait-based composition.

Obvious sexy freebie. Put traits into macros (you can create middlemen by another macros importing and glueing some of them), and then use them in class production.

function% Pointish() {
  get r() { return Math.sqrt(this.x*this.x, this.y*this.y); }
  get phi() { return ... this.x }
  set r(newR) { ... }
  set phi(newPhi) { ... }
}

function% Circlish() {
  get area()  { return Math.PI*this.radius*this.radius; }
  get diameter() { return 2*this.radius; }
  get cirumference() { return 2*Math.PI*this.radius; }
}

function% Translatable() {
  translate(dx, dy) { this.x += dx; this.y += dy; }
}

function% Rotatable() {
  rotate(angle) { this.phi += angle; }
  grow(quotient) { this.r *= quotient; }
}

class BasicPoint {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
  
  Pointish()
}

class Vector extends BasicPoint {
  constructor(x, y) { super(x, y); }
  Rotatable(),
  Translatable()
}

class Circle extends BasicPoint {
  constructor(x, y, radius) {
    super(x, y);
    this.radius = radius;
  }
  
  Circlish(),
  Translatable(),
  
  grow(quotient) { this.radius *= quotient; }
}

//etc
And you can parametrize them, if you see the use (for example with names of properties to use for x, y, radius, ... while having defaults).

That's it.

Known problems, open questions.

What if I want to include a trait to class or sub-data into an object, but do not want to call a macro, which must be evaluated? For performance reasons, there should be some kind of direct import there.
Do not worry and use parameterless macros. Premature optimization is the root of all evil. Leave the evil to the compiler. If your macro does not rely on side-effects, its invoking can be considerably optimized by ECMAScript itself, down to one if from PIC and then inlining it. If you make the macro const, even that if can be probably eliminated.

Bonus: arrays and generators.

This is just a bonus idea, which sprang up from including code-like features into data. The array literal was not enhanced any way yet. But it is natural - in array literal you rarely need to have some elements optional and some not. So no ifs here. As for the couterpart of a macro, let's postpone it for a while.

Arrays have "listish", "linear" feel. So does loops. ;-) But loops are not the right addition to the data production - they use variables and are very code-like. But there is another element, which is "listish" and "linear" - a generator. So, if you have defined some generator with

function* fib (upTo) {
  ...
}
why not to include it when bulding array literal, like:
[ "fibonacci", 10, *fib(10) ]
Not as sexy as traits though macros, but occasionally, usable. Especially in bare [ *gen(args) ] form to have intrinsic toArray. And generators are essentially macros of the array world, with a grain of salt.

And I think [ ..., *foo, ...] syntax could work for any iterable thing. Why only generators?


Thanks for patience, Herby

04 January 2012
Wednesday

Boolean shortcuts

Booleans are very nice concept, but can get bulky. That is why people may avoid using it, sometimes. Moreover, booleans are often put inside nice "traity" names like isRetired or done which are nice in constructs like if (!retired) {...}. It would be nice if they could be declared using shorter, more natural way (more descriptive) instead of using explicit true and false value assignment (imperative). Booleans have some instrinsic non-imperative, descriptive feel, so I'd like to be able to express it.

In literals. In literals, let it be possible to use not only foo:true and baz:false productions (comma-terminated except the very last one), but also simplified foo and !baz productions. This will allow for example this nice thing:

Object.defineProperty(foo, "id", {
  value: "Moo",
  !enumerable, configurable, writable
});

By this I have stolen ! character from Allen Wirfs-Brock's use in literal extension (http://wiki.ecmascript.org/doku.php?id=harmony:concise_object_literal_extensions), but I believe ! is associated with boolean not and this use would be more fit for it. Literals created this way (if booleans have "traity" names) are shorter and naturally readable, so if this would be accepted for literals only, it would be nice. Though I can envision the use of this in the rest of boolean-value-assignment contexts, like:

In variable declarations. In variable declaration it would be nice if instead of var done = false; it could be simply put that var !done;. I am not proposing this for general assign (it would be awkward), only for initialization in declaration.

This brings a sad assymetry that I can not use similar streamlined var soFarSoGood; instead of var soFarSoGood = true;. false have its shortcut initalization, and true does not? I do have a workaround proposal for this, though it it less nice, but it is an idiom known to all Javascripters with some experience. Let us allow use var !!soFarSoGood; as well. It can then be used as a shortcut true if developer sees it fit for the situation.

The above paragraphs should of course hold for let and const declarations, as well.

In classes. In classes (that is, in their prototypes, which are outlined by class {...} block) the use of booleans is not very common, but for the sake of orthogonality and not creating special cases (it is a curly block, too, same as object literal and code block), let it be used in public declaration akin to var above. This can allow for example creating Smalltalkish isDuckType constructs like this:

class Animal {
  public !!isAnimal;
  ...
}

class Ape extends Animal {
  ...
}

class Human extends Ape {
  public !isAnimal;
  public !!isPerson;
  ...
}

(not that it is a great use-case, but just to show there is some; I would propose it even if I knew of none, not to create differences between {...} constructs.)

Herby

EDIT: As pointed out by François Remy:

Doesn't play nice with already existing blocks:
  eval("{ !window }") // returns false;

The (free) code-block is a problematic feature that blocks empowering data {...} since the parser can treat it as code block and in some cases it is hard to tell the two apart.

EDIT2: It does not matter, I was freaked out. This proposal is for the object initializer context. The fact that there is ambiguity if a statement begins with { and parentheses should be used if you want it to process it as an expression statement is fine (like ({}).foo;).

04 April 2011
Monday

Móresy T-Mobilu

V poslednej dobe sa mi stali dve veci v súvislosti s vyššieuvedenou firmou.

Prvú by som nazval neľudskosťou. Keďže nemám veľa peňazí, tak som platil svoje faktúry často tak, že som bol vždy mesiac v sklze – hoci keď sa vyskytlo viac peňazí, tak som ho dorovnal. Viac ako mesiac som v sklze nebol (lebo vtedy už človeka vypínali). Ale minulý mesiac sa ma začali bombardovať výhražnými SMS o odpojení, hoci som mal nezaplatenú „iba“ jednu faktúru dozadu. Chlapovi pri prepážke som sa potom sťažoval, že čo to sú za nové móresy, a on že, prišli sem aj ľudia, ktorých odpojili prvý deň po nezaplatení prvej faktúry.

Preskočilo im?

Ja viem, že zmluvy sa majú plniť, ale vypnúť niekoho deň po nezaplatení faktúry? Niečo ako vážiť si zákazníka a spolupracovať pri riešení jeho problémov? Alebo si myslia že „choďte všetci čo nie ste dokonalí okamžite do p, nemáme o vás záujem“ je skvelý nový biznis plán?

Ak to nekorigovali, tak ma napozajtre vypnú. Ale neviem, čo tým chcú dosiahnuť. Nikdy predtým som o odchode od nich neuvažoval. Teraz už hej.

Ďalšia vec sa mi stala dnes. Nazval by som ju „blbosť“.

Mal som hovor z ich automatickej linky 0903903903. Reku, čo, idete ma j s tým, že mám zajtra termín splatnosti? Znechutene som zdvihol.

Ale nie, ozvalo sa niečo ako  Vážený zákazník (no už vidím aký vážený), radi by sme ... dajaké bullshity o ponuke pre môj telefón... Prosím počkajte, prepojíme Vás na pracovníka.

A minúta vyzváňania...

Tak prečo mi, do riti volajú, keď majú preplnené kapacity a potom ma nechajú minútu čakať? To nedokážu počet volaných bullshitov prispôsobiť obsadenosti voľných bullshiterov?

Zložil som.

06 October 2010
Wednesday

Zhutnená ľudská blbosť (odpoveď z Tatra Banky)

Na moju nie príliš zdvorilú, ale predsa len reakciu na to, že sa Tatra Banka snaží premigrovať svoj internet banking na Flash:

Ad: novy Internet banking vo Flashi

A myslite aj na nas, ktori sme pripojeni cez GPRS? To mame kvoli vykonaniu platby cakat 5 minut kym sa ten niekolko(desat)megabajtovy moloch natiahne (lebo na tom, aby bola aplikacia mala, Vam pravdepodobne bude zalezat az v poslednom rade, nehovoriac o tom, ze vycackana Flash aplikacia nikdy nie je mala)? Nie kazdy je na broadbande.

som dostal túto nádhernú **** odpoveď tety z Tatra Banky:

Vazeny pan Vojcik,

Sucasny Internet banking Tatra banky je uz viac ako 10 rokov zalozeny na rovnakej technologii.
Cielom prechodu na novu technologiu Adobe Flex ®, ktora je sucastou Adobe ® Flash ® platformy, bolo pri zachovani vysokej bezpecnosti zabezpecit jeho pouzivatelom zjednodusenie prace a sprehladnenie ponukanych funkcionalit.
Radi by sme Vas ubezpecili, ze Tatra banka tento krok dokladne zvazila a tak ako standardne pri zmenach elektronickeho bankovnictva, aj tento proces, vratane bezpecnostnych testov, podliehal auditu tretich stran - renomovanych spolocnosti zaoberajucich sa bezpecnostou na internete.
Otazka bezpecnosti elektronickeho bankovnictva je pre nas vzdy najvyssou prioritou a bezpecnost noveho Internet bankingu je preto zosilnena nad ramec sucasneho Internet bankingu. Vyuzivat bude standardne zabezpecenie komunikacie pomocou sifrovaneho protokolu tak, ako je to v dnesnom Internet bankingu a zabezpecenie integrity a autenticity komunikacie.
Uvedomujeme si, ze podmienenost tohto Internet bankingu prave verziou Adobe ® Flash ® Player 10.0, resp. vyssou moze byt na prvy pohlad prekazkou pre jeho pouzivatelov.
Novu technologiu Adobe ® Flash ® Player 10.0, resp. vyssiu, uz dnes vyuziva 98 % nasich aktivnych klientov (okrem pouzivatelov mobilnych platforiem – tzv. smartphones), preto budu moct takmer vsetci nasi klienti vyuzivat novy Internet banking bez akejkolvek instalacie. Taktiez to, ze je tato technologia volne dostupna na stiahnutie velmi jednoduchym sposobom z nej vytvara pristupny nastroj k efektivnejsim a technologicky vyspelejsim rieseniam.
Aby si vsetci pouzivatelia Internet bankingu mohli na jeho novy vzhlad a vyuzitie zvyknut, proces spustenia novej verzie bude postupny. Po dostatocne dlhe obdobie, minimalne sest mesiacov, bude nadalej zabezpecene fungovanie jeho sucasnej verzie spolu s novou beta verziou. Po ukonceni beta verzie noveho Internet bankingu, tento nahradi sucasnu verziu, ktora bude nasledne vypnuta.
Vazime si nasich klientov a snazime sa transparentne a vcas komunikovat zmeny, ktore sa ich tykaju. Aj z toho dovodu sme v dostatocnom predstihu pred spustenim novej beta verzie zverejnili na prihlasovacej stranke do Internet bankingu pre klientov oznam o pripravovanych zmenach. Presny termin spustenia novej beta verzie zatial nevieme zaväzne potvrdit, ale urcite to bude v kratkom case.
Verime, ze v konecnom dosledku uvitate novy dizajn a pouzivatelske rozhranie, ako aj nove prvky ovladania aplikacie, pricom bezpecnost prace s Vasimi financiami zostane zachovana. Nova platforma vytvara priestor pre dalsie inovativne funkcionality, ktore Vam sprehladnia pracu s Vasimi financiami, ako aj pracu so samotnym Internet bankingom.


V pripade akychkolvek dalsich otazok nas kontaktujte na nasej webovej stranke www.tatrabanka.sk alebo na nasej non-stop telefonickej sluzbe Dialog na telefonnych cislach blabla.

Dakujeme za porozumenie.

M--- T---
Tatra banka, a.s.
Odd. rozvoja elektronickych distribucnych kanalov

Takže Vážia si svojich klientov. Inovatívne riešenia? Bla bla bezpečnosť bla bezpečnosť bla bezpečnosť bla ešte raz bezpečnosť? (na ňu som sa, do r***, nepýtal!) Uvítam nové rozhranie? Fakt??? Kto mi v tejto „odpovedi“ nájde jediný náznak, že ženská chápe, na čo som sa pýtal a nepracuje na danom vznešene znejúcom oddelení omylom, má u mňa hlbokú poklonu.

Herby 

19 January 2010
Tuesday

Consolidated core

Finally I have made a version which is consolidated to my liking. The need to setup the domain at two places was solved by putting a default domain in the storage and slightly modifying boot.js:

// (c) 2009, Herbert Vojčík
// Licensed by MIT license (http://www.opensource.org/licenses/mit-license.php)

(function() {
  var _an = appjet._native;
  var tableid = _an.storage_get("obj-root", "bootTable").value;
  var appBoot = _an.storage_get(tableid, request.headers.Host).value;
  var name, queue = appjet._internal.queue = appBoot ? appBoot.split(",") : ["0", "", "lib-0"];
  appjet.appName = queue.shift();
  appjet.mainDomain = queue.shift() || _an.storage_get("obj-root", "defaultDomain").value;
  while((queue = appjet._internal.queue) && (name = queue.shift())) {
    var agent = import({}, name);
    if (typeof agent.runAgent === "function") { agent.runAgent(queue); }
  }
})();

This change is also incorporated into the configure.js.

Then, there is bigger change - the generated storage ids are finally out. The code of lib-admin is now completely free of using appjet._native methods and uses only the standard storage library. This also allowed to make code slightly shorter and to reduce number of "app–storage api" methods from five to four. The filestoring script had not been changed; here is the new configure.js:

// (c) 2009, Herbert Vojčík
// Licensed by MIT license (http://www.opensource.org/licenses/mit-license.php)

// ====== variables to configure ======
var pwd = "undefined";
var domain = "appdomain.net";
// please configure the path to the save script in saveRawSource below if needed
// ====== verbatim copy of lib-admin functions ====== begin ======
function appData(app) {
    var result = storage.apps[app];
    if (result) {
        return result;
    }
    printp(request.path, ": unknown app: ", app);
    response.stop();
}

function appDataNew(app) {
    var result = storage.apps[app];
    if (result) {
        return null;
    }
    _apps[app] = {};
    return _apps[app];
}

function saveRawSource(files) {
    var info = {pwd:storage.pwd};
    for (var p in files) {
        info[p + ".js"] = files[p];
    }
    wpost("http://staticdomain.net/fs.rb", info);
}

function saveAppSource(name, code, doBackup) {
    var appdata = appData(name);
    if (doBackup) {
        appdata["server.bak"] = appdata["server"];
    }
    var files = {};
    files["lib-" + name] = "/* appjet:version 0.1 */\n" + "/* appjet:library */\n" + code;
    saveRawSource(files);
    appdata["server"] = code;
}

function get_main() {
    print(OL(LI(link("/rescue")), LI(link("/edit")), LI(link("/create"))));
}

function get_rescue() {
    var app = request.params.app;
    var appdata = appData(app);
    var rescue = appdata["server.bak"];
    saveAppSource(app, rescue);
    printp("Last known good code of ", app, " restored.", BR(), CODE(rescue));
    printp(link("http://admin." + appjet.mainDomain + "/edit?app=" + app, "Go edit it!"));
}

function get_edit() {
    var app = request.params.app;
    var appdata = appData(app);
    page.head.write("<script>\n" + "    var c=" + {s:appdata.server} + ".s;\n" + "    window.onload=function(){var ta=document.getElementById('code');ta.defaultValue=ta.value=c;};\n" + "<" + "/script>");
    page.setTitle(app + html(" &ndash; ") + "admin editor");
    printp(B(app, ".", appjet.mainDomain), BR(), B({style:"color:#0c0"}, "/* appjet:server */"), BR(), FORM({method:"post", action:request.path}, TEXTAREA({id:"code", name:"code", style:"width:100%;height:80ex"}), INPUT({type:"hidden", name:"app", value:app}), INPUT({type:"submit"}), " ", INPUT({type:"reset", value:"Undo"})));
}

function post_edit() {
    var app = request.params.app;
    var appdata = appData(app);
    var code = request.params.code;
    if (code) {
        saveAppSource(app, code, true);
    }
    response.redirect(request.path + "?app=" + encodeURIComponent(app));
}

function get_create() {
    page.setTitle("admin app creation");
    print(FORM({method:"post", action:request.path}, "Supply application name:", INPUT({name:"app"}), INPUT({type:"submit"})));
}

function post_create() {
    var app = request.params.app;
    if (!app.match(/[a-z][a-z0-9-]+[a-z0-9]/)) {
        printp("Application name must", UL(LI("be at least three characters long,"), LI("begin with smallcaps letter,"), LI("end with smallcaps letter or digit,"), LI("and contain only smallcaps letters, digits, or minus sign/dash (-) characters.")));
        get_create();
        return;
    }
    var appdata = appDataNew(app);
    if (appdata === null) {
        printp("Application ", B(CODE(app)), " already exists.");
        get_create();
        return;
    }
    appdata["server.bak"] = "printp(\"Hello, world!\");";
    response.redirect("http://admin." + appjet.mainDomain + "/rescue?app=" + app);
}
// ====== verbatim copy of lib-admin functions ====== end ======

switch (request.path) {
case '/1':
        var adminBuilder = [
"// (c) 2009, Herbert Voj\u010d\xedk", """
// Licensed by MIT license (http://www.opensource.org/licenses/mit-license.php)

"""
];

        [appData, appDataNew, saveRawSource, saveAppSource, get_main, get_rescue, get_edit, post_edit, get_create, post_create, ].
        forEach(function(f) { adminBuilder.push(f); });

        adminBuilder.push("""

import("storage");
appjet._internal.global = this; //dispatch fix
dispatch();""");

        // ====== Storage dump, edited ====== begin ======
       (function () {
       var _an = appjet._native;
       _an.storage_create("obj-MFc9TBCDz");
       _an.storage_create("obj-MFc9TB7MD");
       _an.storage_create("obj-M1OtYQoF");
       _an.storage_create("obj-MFc7dzAMR");
       _an.storage_create("obj-MFc9TB2DK");
       _an.storage_put("obj-MFc9TBCDz", "server.bak", "string", adminBuilder.join(""));
       _an.storage_put("obj-MFc9TB7MD", "server.bak", "string", "printp(\"Hello, world!\");");
       _an.storage_put("obj-root", "pwd", "string", pwd);
       _an.storage_put("obj-root", "apps", "object", "obj-MFc7dzAMR");
       _an.storage_put("obj-root", "bootTable", "object", "obj-M1OtYQoF");
       _an.storage_put("obj-root", "defaultDomain", "string", domain);
       _an.storage_put("obj-M1OtYQoF", "admin." + domain, "string", "admin,,lib-admin");
       _an.storage_put("obj-MFc7dzAMR", "0", "object", "obj-MFc9TB2DK");
       _an.storage_put("obj-MFc7dzAMR", "admin", "object", "obj-MFc9TBCDz");
       _an.storage_put("obj-MFc7dzAMR", "worker", "object", "obj-MFc9TB7MD");
       _an.storage_put("obj-MFc9TB2DK", "server.bak", "string", "// (c) 2009, Herbert Voj\u010d\xedk\n// Licensed by MIT license (http://www.opensource.org/licenses/mit-license.php)\n\nif (request.headers.Host === \"admin.\"+appjet.mainDomain) {\n    appjet.appName = \"admin\";\n    appjet._internal.queue.unshift(\"lib-admin\");\n} else {\n    import(\"lib-worker\");\n}");
       })();
        // ====== Storage dump, edited ====== end ======

        printp("Storage configured. Backup version of admin, 0 and worker apps included; you may restore them afterwards using admin."+domain+"/rescue.");
        printp("MD5 of password is:", BR(), md5(pwd), BR(),
                "Please update the hash in the filesaving script and proceed to ", link("/2", "step 2"), " to restore lib-admin.js.");
        break;

case '/2':
        import("storage");
        request.params.app="admin";
        get_rescue();
        printp("admin.js restored.");
        printp("You may stop running configure.js. ",
        "Start appjet with boot.js as the main file.");
        break;

default:
        printp(H1("Platform configuration"));
        printp(BIG(link("/1", "Start the wizard")));
        break;
}

Things for the future are: first, change the filesaving script so it understands the "empty file means delete" protocol. After that, I can announce the Solid Base, as mentioned in previous post.

Then, it is awkward that all the app-related manipulation is done only in interactive lib-admin. It should be done in form of an API of some sort, which lib-admin can use, but also other apps can use (with some sort of pluggable authorization). But for now, I am not going to pursue this goal directly, I leave this thing to evolve naturally, while having the neccessity of it in mind. After some time, when API crystallizes, the Stable Base 2 can be made from an api library and its interactive shell (like lib-admin is now).

On top of this core, the true multiapp platform should be built - that means (pluggable?) AAA stack, virtualization of storage and cron jobs, making appjet conventions work (code sections, working links in footer etc.), better editor and some version control / team work support. Here, there is still lot of work beyond the implementation itself - I'd like to have the system nicely modular with clearly split responsibilities, and AAA/virtualization/API are somewhat overlapping. I hope I make the right decisions.

As for the source code management, I have the idea of using external repositories which users can set up anywhere on the web, and communicating with them via HTTP (in fact, only getting the file(s)). This way, one can use his own stack of editors/repositories, obnly concern is to make the latest sourcecode available under a specified (non-changing) URL. I must investigate more into it, but it seems there are some repositories than allow this kind of hosting.

And if I am to make my own editor, I think in the way of Smalltalk browser (or Java Browsing perspective of Eclipse, which was directly inspired by the former). So that I can edit more apps at once, and having only one section occupying the editor at a time.

28 August 2009
Friday

With libraries and microkernel

As I was writing in the previous article, I had to make a decision whether to go microkernel or monolithic kernel way, and I chose the microkernel. Now it is not only a proposed code, but the platform is actually moved to be based on it.

Second big change I made, and by this decision I lost _some_ of the possibilities that were default to appjet, is the decision to merge the world of applications and libraries.

Of course these two coexisted peacefully before, but there still were /* appjet:server */ and /* appjet:library */ sections in library file. Now, the question was: keep this feature (which on the pros side has its simplicity to add testing code to the lib itself), or go another route, by throwing away all mentions of /* appjet:library */ from the developer.

To understand why this question appeared at all, one must understand how multi-app is (to be) implemented in this platform - an app is started by import("lib-something") and lib-something is built by the platform itself, just to make it possible to run the application code by calling the import.

Mechanistic approach is: for any application foo, let us create, for example, lib-!foo library, and let us prohibit "!" in regular names. That means for application fb-statuses, platform would create lib-!fb-statuses to run its server code section. For application lib-mmos-view, the platform creates lib-!lib-mmos-view to run the server code section, but it also creates lib-mmos-view, to enable importing its library section by other applications. I hope it's not too complicated.

Now the question was: what if we simply forget that applications are of two types - apps and libs, and there were only one type, but it could be utilised in two ways: either you run it, as an application, by writing http://foo.appdomain.net/, or you import it in any other application by calling import("lib-foo")? It looks like the application is there two times: once as an application, and once as a library. But if you forget app/lib dichotomy, there is just one bunch of code, which can work simultaneously as an app or as a lib.

This would greatly simplify the innards. For the app/lib foo there would only be one file - lib-foo. If you want to create an application, you create foo and then run it through url. If you want to create a library, you create bar and then import("lib-bar"). And if you can utilise both approaches, you are free to do it.

So, as everyone already guessed, I decided for this unified, one-file way. That means I cannot write the tests for a lib into its code, but no one prevents me to just create bar-tests application which runs the tests. The elegance of one-file solution is gone, but I finally let it go (partially because I do not plan to use full-file editing, anyway, but edit each code section separately; in that case I lost nothing, since there is no notion of "one file" anymore).

I restructuralized the platform to consist of boot.js (not editable anymore, only libs are), lib-admin which includes all the rescue, create and edit functionality, and the rest (which is now only two very little libs: lib-worker and lib-0). As in previous case, the platform is set up by running the server with configure.js as the main file, in two steps, with first step setting up the storage and the second step rescuing lib-admin. It's similar in this to previous configure, only it sets up a little differently structured beast:

// (c) 2009, Herbert Vojčík
// Licensed by MIT license (http://www.opensource.org/licenses/mit-license.php)

// ====== variables to configure ======
var pwd = "undefined";
var domain = "appdomain.net";

// please configure the path to the save script in saveRawSource below if needed
// ====== verbatim copy of lib-admin functions ====== begin ======
function appDataSafe(app) {
    try {
        return getStorable("obj-! " + app);
    }
    catch (ex) {
        return null;
    }
}

function appData(app) {
    var result = appDataSafe(app);
    if (result) {
        return result;
    }
    printp(request.path, ": unknown app: ", app);
    response.stop();
}

function appDataForce(app) {
    var result = appDataSafe(app);
    if (result) {
        return result;
    }
    appjet._native.storage_create("obj-! " + app);
    result = appData(app);
    storage.apps.add(result);
    return result;
}

function saveRawSource(files) {
    var info = {pwd:storage.pwd};
    for (var p in files) {
        info[p + ".js"] = files[p];
    }
    wpost("http://staticdomain.net/fs.rb", info);
}

function saveAppSource(name, code, doBackup) {
    var appdata = appData(name);
    if (doBackup) {
        appdata["server.bak"] = appdata["server"];
    }
    var files = {};
    files["lib-" + name] = "/* appjet:version 0.1 */\n" + "/* appjet:library */\n" + code;
    saveRawSource(files);
    appdata["server"] = code;
}

function get_main() {
    print(OL(LI(link("/rescue")), LI(link("/edit")), LI(link("/create"))));
}

function get_rescue() {
    var app = request.params.app;
    var appdata = appData(app);
    var rescue = appdata["server.bak"];
    saveAppSource(app, rescue);
    printp("Last known good code of ", app, " restored.", BR(), CODE(rescue));
    printp(link("http://admin." + appjet.mainDomain + "/edit?app=" + app, "Go edit it!"));
}

function get_edit() {
    var app = request.params.app;
    var appdata = appData(app);
    page.head.write("<script>\n" + "    var c=" + {s:appdata.server} + ".s;\n" + "    window.onload=function(){var ta=document.getElementById('code');ta.defaultValue=ta.value=c;};\n" + "<" + "/script>");
    page.setTitle(app + html(" &ndash; ") + "admin editor");
    printp(B(app, ".", appjet.mainDomain), BR(), B({style:"color:#0c0"}, "/* appjet:server */"), BR(), FORM({method:"post", action:request.path}, TEXTAREA({id:"code", name:"code", style:"width:100%;height:80ex"}), INPUT({type:"hidden", name:"app", value:app}), INPUT({type:"submit"}), " ", INPUT({type:"reset", value:"Undo"})));
}

function post_edit() {
    var app = request.params.app;
    var appdata = appData(app);
    var code = request.params.code;
    if (code) {
        saveAppSource(app, code, true);
    }
    response.redirect(request.path + "?app=" + encodeURIComponent(app));
}

function get_create() {
    page.setTitle("admin app creation");
    print(FORM({method:"post", action:request.path}, "Supply application name:", INPUT({name:"app"}), INPUT({type:"submit"})));
}

function post_create() {
    var app = request.params.app;
    var appdata = appDataSafe(app);
    if (appdata) {
        printp("Application ", B(CODE(app)), " already exists.");
        get_create();
        return;
    }
    if (!app.match(/[a-z][a-z0-9-]+[a-z0-9]/)) {
        printp("Application name must", UL(LI("be at least three characters long,"), LI("begin with smallcaps letter,"), LI("end with smallcaps letter or digit,"), LI("and contain only smallcaps letters, digits, or minus sign/dash (-) characters.")));
        get_create();
        return;
    }
    appdata = appDataForce(app);
    appdata["server.bak"] = "printp(\"Hello, world!\");";
    response.redirect("http://admin." + appjet.mainDomain + "/rescue?app=" + app);
}
// ====== verbatim copy of lib-admin functions ====== end ======

switch (request.path) {
case '/1':
	var adminBuilder = [
"""// (c) 2009, Herbert Vojčík
// Licensed by MIT license (http://www.opensource.org/licenses/mit-license.php)

"""
];

	[appDataSafe, appData, appDataForce, saveRawSource, saveAppSource, get_main, get_rescue, get_edit, post_edit, get_create, post_create, ].
	forEach(function(f) { adminBuilder.push(f); });
	
	adminBuilder.push("""

import("storage");
appjet._internal.global = this; //dispatch fix
dispatch();""");
	
	// ====== Storage dump, edited ====== begin ======
   (function () {
       var _an = appjet._native;
       _an.storage_create("obj-! 0");
       _an.storage_create("obj-M1OtYQoF");
       _an.storage_create("obj-! admin");
       _an.storage_create("obj-! worker");
       _an.storage_coll_create("coll-LyqehQFl7");
       _an.storage_put("obj-! 0", "server.bak", "string",
"""// (c) 2009, Herbert Vojčík
// Licensed by MIT license (http://www.opensource.org/licenses/mit-license.php)

if (request.headers.Host === "admin."+appjet.mainDomain) {
    appjet.appName = "admin";
    appjet._internal.queue.unshift("lib-admin");
} else {
    import("lib-worker");
}""");
       _an.storage_put("obj-root", "pwd", "string", pwd);
       _an.storage_put("obj-root", "bootTable", "object", "obj-M1OtYQoF");
       _an.storage_put("obj-root", "apps", "object", "coll-LyqehQFl7");
       _an.storage_put("obj-M1OtYQoF", "admin."+domain, "string", "admin,,lib-admin");
       _an.storage_put("obj-! admin", "server.bak", "string", adminBuilder.join(""));
       _an.storage_put("obj-! worker", "server.bak", "string", "printp(\"Hello, world!\");");
       _an.storage_coll_add("coll-LyqehQFl7", "obj-! worker");
       _an.storage_coll_add("coll-LyqehQFl7", "obj-! admin");
       _an.storage_coll_add("coll-LyqehQFl7", "obj-! 0");
   })();
	// ====== Storage dump, edited ====== end ======
	
	printp("Storage configured. Backup version of admin, 0 and worker apps included; you may restore them afterwards using admin."+domain+"/rescue.");
	printp("MD5 of password is:", BR(), md5(pwd), BR(),
		"Please update the hash in the filesaving script and proceed to ", link("/2", "step 2"), " to restore lib-admin.js.");
	break;

case '/2':
	import("storage");
	request.params.app="admin";
	get_rescue();
	printp("admin.js restored.");
	printp("You may stop running configure.js. ",
	"As the next step, set up your domain ("+domain+") in the last line of boot.js. ",
	"Then start appjet with boot.js as the main file.");
	break;

default:
	printp(H1("Platform configuration"));
	printp(BIG(link("/1", "Start the wizard")));
	break;
}

Moving from old boot.js to microkernel and lib-admin enabled one thing - the cumbersome use of appjet._native.storage_xxx is no longer needed and lib-admin (being an app, a special one, but an app) can simply import("storage") (the reason why appjet._boot in previous boot.js could not import("storage") as well lies in the security, to be able to virtualize storage it must not be imported and used by the platform itself, but the user must be the one who imports it as the first - otherwise storage.js caching permits access to unwanted parts of the storage. I'll write more of this when I actually do the storage virtualization).

Second thing, which I already mentioned in "Microkernel" post, is that normal apps are run straight, without loading and compiling admin parts, like the filesaving. It is needed only by lib-admin, so only there it is.

Of course, fs.rb is also part of the distribution, but it was not changed at all, so I do not copy it again. Last, but not least, the configure.js does _not_ write down boot.js (it writes down lib-admin), so boot.js is also part of the distribution, as the third file:

/* appjet:version 0.1 */
// (c) 2009, Herbert Vojčík
// Licensed by MIT license (http://www.opensource.org/licenses/mit-license.php)

(function(amd) {
  var _an = appjet._native;
  var tableid = _an.storage_get("obj-root", "bootTable").value;
  var appBoot = _an.storage_get(tableid, request.headers.Host).value;
  var name, queue = appjet._internal.queue = appBoot ? appBoot.split(",") : ["0", "", "lib-0"];
  appjet.appName = queue.shift();
  appjet.mainDomain = queue.shift() || amd;
  while((queue = appjet._internal.queue) && (name = queue.shift())) {
    var agent = import({}, name);
    if (typeof agent.runAgent === "function") { agent.runAgent(queue); }
  }
})("appdomain.net");

You must manually setup your domain in boot.js, as configure.js tells you. And that's it. (Oh yes, and you should rescue the 0 and the worker). The setup process is not as nice as previous one - you must configure your domain in two places. I think I'll fix it for the next phase, and configure.js will write boot.js down as well.

As you may have noticed, the most precious part of the platform is now lib-admin, much like boot.js was before. It contains all the rescue, create and edit functionality (as yet). That's why there is a "safety belt" in lib-0 (as you can see from its code in configure.js) - if the standard, straight running of admin.appdomain.net url stops working for some reason, and the fallback lib-0 is imported instead, it tests for admin specifically and schedules it to run anyway, if the url matches.

What I plan to do next: first, get rid of the "obj-! "+name constructed storage ids, they are dangerous to use. There is a bug in storage implementation, and if you recreate existing id, it disappears after some random time, and keeps disappearing if created again. You must somehow export the storage, delete it, let the appjet create new one and import data back.

Next, the fixes to configure to be able to setup domain ar one place only.

Then there are more challenges, but I will probably set as a medium sized goal the ability to actually make the platform safely public, so multiapp, some kind of security and a little better editor (as a standalone app, not in lib-admin, it's an "emergency" editor, in fact, there must be one, but I don't plan to develop it further, it does its work decently).

ps. Yes, and I hope to create solid base soon (by solid base I mean such a "distribution" (that is, configure.js), which will not have to be changed any more, from which working base of a platform can be set up; and any future version of the platform could be built form this base by just adding new applications, changing existing ones (admin, 0, worker) or using admin consoie. In other words, so every future version can be defined just as The Base + bunch of sourcecode to add to it via editor(s) / to run in admin console). Even this one looked promising, but not yet. The next one, hopefully.

pps. Yes, and one more little thing. :-/ I will need to have to change the filesaving service. I realized I not only need to write files, but also to delete them, sometimes... I will do it simply - if I want to delete the file, I just ask the filesaving service to write nothing to it (truncate it to empty file), and use this as a protocol for "please, delete the file").

20 August 2009
Thursday

Microkernel

I mentioned in the comments that I probably have to solve the microkernel vs. monolithic kernel dilemma here, or, more precisely, its equivalent for the appjet platform clone. That is, to decide whether the boot.js will contain just the bare minimum for application booting (the microkernel way), in which case all other system services must be provided by another modules, or to provide the whole pack of system-related functionality in one file (the monolithic kernel way).

In present state, boot.js contained some unneeded functionality, for example, the two filesaving methods. They are needed for system-related apps, like the editor or the rescuer, but they are definitely useless for majority of real apps, if I imagine them being there (there are none, so far). I finally decided to go the microkernel way - to provide the minimum needed to run and communicate - exactly what the real microkernels do. All the rest must be provided by additional libs.

Not that I moved that way very much - I did not reworked any part of the system yet. But, I decided what the code of the microkernel will be (and this code, I very hope, stays unchanged for ever after - if you see some bugs, tell me, please):

(function() {
  var _an = appjet._native;
  var tableid = _an.storage_get("obj-root", "bootTable").value;
  var appBoot = _an.storage_get(tableid, request.headers.Host).value;
  var name, queue = appjet._internal.queue = appBoot ? appBoot.split(",") : ["0", "", "lib-0"];
  appjet.appName = queue.shift();
  appjet.mainDomain = queue.shift() || "appdomain.net";
  while((queue = appjet._internal.queue) && (name = queue.shift())) {
    var agent = import({}, name);
    if (typeof agent.runAgent === "function") { agent.runAgent(queue); }
  }
})();

Well, it may look complicated, but it's just a little dense. At first, it is backwards-compatible with traditional appjet way of thinking - certain host is always mapped to certain code. That will be implemented by adding a field for that host into storage.bootTable. Additionally, field for source.app.appdomain.net should be filled, with appropriate data. At the simplest case, you simply fill the app field with "appname,,lib-appname-impl" by which you say app name is "appname", main domain is the default (you must set it in the code, once), and "lib-appname-impl" should be imported; the source field can be something like "show-source,,lib-show-source" and it can then parse the host.

This may be enough and you can be happy with it. But these few lines of the code open lots of another possibilities to organize and run your code. You can more actively use the queue. For example, if you can put anything in the queue, why not to put something like "show-source,,lib-show-source,appname" into the source.appname.appdomain.net field? No parsing is then needed, you just get the app name to show source for from the queue itself.

If you begin to use queue actively inside the applications as well, you can create software agents (each lib representing one), which are able to send messages (putting the recipient into the queue, with optional arguments for him).

If you push recipient to the queue, with its optional arguments (these must be taken out by him, explicitly), then you are sending a message in a queue manner, it is appended to the back of the queue. You can also unshift instead of push - this is much more akin to unix exec() call - this message is then put in front of the queue, which means, in will be processed first, before any other ones that were there. In other view, you are replacing yourself by another agent (and if it supports identical set of arguments, you can just leave them there and unshift only its name).

But generally, unshifting behaviour is taken as inappropriate and pushing is favoured whenever possible in messaging agents paradigm. There must be a serious reason for unshift.

Another thing you can actively use, is runAgent function. If you define the function in your agent (that is, lib), it will be called for every occurence of the lib in the queue, that is, for every message. This is the normal thing for reusable agent, it should have the function defined. But if you choose not to include it, you have a single-action agent - it only processes first message, all other times, just nothing happens (courtesy of appjet lib caching, such agent should be parameterless, obviously). So the "microkernel" really passes messages between modules, in a way (the real microkernel does basically this and nothing more).

And again, you can just write old-fashioned apps and ignore all this - it will work without hassle.

ps. The code in this article is licensed by MIT license, (c) 2009 Herbert Vojčík

26 July 2009
Sunday

More editor functionality

Now I'm going to scare you ;-)
I got over to another important milestone: multiple applications, possibility to create new applications and editor able to edit any of the applications (boot, and the editor, of course, are also applications, so you can edit them while they run). Everything was done inside the platform, without external action.
Inside configure.js I also reused the trick I used before the multiapp editor and so the files needed to deploy are only two. First of them is the filesaving script fs.rb, which wasn't changed at all, the second is large configure.js. Here it is:

// ====== variables to configure ======
var pwd = "undefined";
var domain = "appdomain.net";

// please configure the path to save script in saveRawSource below if needed
// ====== verbatim copy of appjet._boot definition from boot.js ====== begin ======
appjet._boot = {
run: 
function (amd) {
    appjet.mainDomain = amd;
    if (request.headers.Host == "rescue." + amd) {
        var app = request.params.app;
        var appdata = new this.storable("obj-! " + app);
        if (appdata.doesNotExist) {
            printp("rescue: unknown app: ", app);
            response.stop();
        }
        var rescue = appdata.getString("server.bak");
        this.saveAppSource(app, rescue);
        printp("Last known good code of ", app, " restored.", BR(), CODE(rescue));
        printp(link("http://edit."+amd+"/?app="+app, "Go edit it!"));
        response.stop();
    } else if (request.headers.Host == "edit." + amd) {
        import("lib-! edit");
    } else if (request.headers.Host == "create." + amd) {
        import("lib-! create");
    } else {
        import("lib-! worker");
    }
}
,

saveRawSource: 
function (files) {
    var info = {pwd:new this.storable("obj-root").getString("pwd")};
    for (var p in files) {
        info[p + ".js"] = files[p];
    }
    wpost("http://staticdomain.net/fs.rb", info);
}
,

storable: 
function (id) {
    if (appjet._native.storage_getById(id).status != 0) {
        return {doesNotExist:true};
    }
    this.getString = function (key) {
        return appjet._native.storage_get(id, key).value;
    };
    this.putString = function (key, value) {
        return appjet._native.storage_put(id, key, "string", value).status == 0;
    };
}
,

saveAppSource: 
function (name, code, doBackup) {
    var appdata = new this.storable("obj-! " + name);
    if (doBackup) {
        appdata.putString("server.bak", appdata.getString("server"));
    }
    var files = {};
    files["lib-! " + name] = "/* appjet:version 0.1 */\n" + "/* appjet:library */\n" + code;
    files[name] = "/* appjet:version 0.1 */\n" + code;
    this.saveRawSource(files);
    appdata.putString("server", code);
}
,

};
// ====== verbatim copy of appjet._boot definition from boot.js ====== end ======

switch (request.path) {
case '/1':
    var bootBuilder = ["// (c) 2009, Herbert Voj\u010d\xedk"+"""
// Licensed by MIT license (http://www.opensource.org/licenses/mit-license.php)

(appjet._boot = {
"""];
    eachProperty(appjet._boot, function(name, f) {
        bootBuilder.push(name, ": ", f, ",\n\n");
    });
    bootBuilder.push("}).run(\"", domain, "\");");
    
    // ====== Storage dump, edited ====== begin ======
       (function () {
           var _an = appjet._native;
           _an.storage_create("obj-! create");
           _an.storage_create("obj-! worker");
           _an.storage_create("obj-! edit");
           _an.storage_create("obj-! boot");
           _an.storage_coll_create("coll-LyqehQFl7");
           _an.storage_put("obj-! create", "server.bak", "string", "// (c) 2009, Herbert Voj\u010d\xedk \r\n// Licensed by MIT license (http://www.opensource.org/licenses/mit-license.php)\r\n\r\nimport(\"storage\");\r\n(function(app){\r\n\r\nif (!app) {\r\n    return;\r\n}\r\nvar id = \"obj-! \"+app;\r\nif (appjet._native.storage_getById(id).status == 0) {\r\n    printp(\"Application \", B(CODE(app)), \" already exists.\");\r\n    return;\r\n}\r\nif (!app.match(/[a-z][a-z0-9-]+[a-z0-9]/)) {\r\n    printp(\"Application name must\",UL(\r\n        LI(\"be at least three characters long,\"),\r\n        LI(\"begin with smallcaps letter,\"),\r\n        LI(\"end with smallcaps letter or digit,\"),\r\n        LI(\"and contain only smallcaps letters, digits, or minus sign/dash (-) characters.\")\r\n    ));\r\n    return;\r\n}\r\n\r\nappjet._native.storage_create(id);\r\nvar appdata = getStorable(id);\r\nappdata[\"server.bak\"] = 'printp(\"Hello, world!\");';\r\nstorage.apps.add(appdata);\r\nresponse.redirect(\"http://rescue.\"+appjet.mainDomain+\"/?app=\"+app);\r\n\r\n})(request.params.app);\r\n\r\nprint(FORM(\"Supply application name:\", INPUT({name:'app'}), INPUT({type:'submit'})));\r\n");
           _an.storage_put("obj-root", "pwd", "string", pwd);
           _an.storage_put("obj-root", "apps", "object", "coll-LyqehQFl7");
           _an.storage_put("obj-! worker", "server.bak", "string", "printp(\"Hello, world!\");");
           _an.storage_put("obj-! edit", "server.bak", "string", "// (c) 2009, Herbert Voj\u010d\xedk \r\n// Licensed by MIT license (http://www.opensource.org/licenses/mit-license.php) \r\n\r\nvar app = request.params.app;\r\nvar appdata = new appjet._boot.storable(\"obj-! \"+app);\r\nif (appdata.doesNotExist) { printp(\"editor: unknown app: \", app); response.stop(); }\r\n\r\nvar _code = request.params.code;\r\nif (_code) {\r\n\tappjet._boot.saveAppSource(app, _code, true);\r\n\tresponse.redirect(request.path+\"?app=\"+encodeURIComponent(app));\r\n}\r\n\r\nprintp(\r\nB(app,\".\",appjet.mainDomain),BR(),\r\nB({style:'color:#0c0'},\"/* appjet:server */\"),BR(),\r\nFORM({method:'post', action:request.path},\r\n\tTEXTAREA({name:'code', style:'width:100%;height:80ex'},\r\n\t\thtml(appdata.getString(\"server\"))\r\n\t),\r\n\tINPUT({type:'hidden', name:'app', value:app}),\r\n\tINPUT({type:'submit'})\r\n));\r\n");
           _an.storage_put("obj-! boot", "server.bak", "string", bootBuilder.join(""));
           _an.storage_coll_add("coll-LyqehQFl7", "obj-! worker");
           _an.storage_coll_add("coll-LyqehQFl7", "obj-! edit");
           _an.storage_coll_add("coll-LyqehQFl7", "obj-! boot");
           _an.storage_coll_add("coll-LyqehQFl7", "obj-! create");
       })();
    // ====== Storage dump, edited ====== end ======
    
    printp("Storage configured. Backup version of boot, create, edit and worker apps included; you may restore them afterwards using rescue."+domain+".");
    printp("MD5 of password is:", BR(), md5(pwd), BR(),
        "Please update the hash in the filesaving script and proceed to ", link("/2", "step 2"), " to restore boot.js.");
    break;

case '/2':
    var appdata = new appjet._boot.storable("obj-! boot");
    var rescue = appdata.getString("server.bak");
    appjet._boot.saveAppSource("boot", rescue);
    printp("boot.js restored.");
    printp("You may stop running configure.js and start running boot.js.");
    break;

default:
    printp(H1("Platform configuration"));
    printp(BIG(link("/1", "Start the wizard")));
    break;
}

Bulk data for the configure.js were prepared by the platform itself (the code of the appjet._boot as well as storage dump were presented by the platform and were only copypasted into configure.js; the storage dump was edited to be minimal needed).
The structure of boot.js being

(appjet._boot={
... big inline object ..
... where all functionality resides ...
}).run("appdomain.net");

allowed for a cool metacircularity, which is present in configure.js as well.

  1. There is verbatim copy of the big inline object in the code of configure.js, as a living code, not as a string.
  2. Then, due to structure of boot.js, its complete source is prepared in a few lines using reflexion on this live instance.
  3. Then, later, this live instance is used to write its own code that was created from it into the boot.js file.

I simply like it.

Configure now has three pages, path / leads to welcome screen with link to /1 which fills the storage and emits the password hash, but does not save anything (the hash may need to be updated, the saves would not work anyway). From there link goes to path /2 which then simply rescues (using the live instance of appjet._boot) the boot application, thus creating boot.js file.

After successful configuring, there are no applications except integrated rescue, but it can be used to unleash the rest, which is only present as backup in storage: edit, create, worker.

Platform now recognizes multiple urls.

  • rescue.appdomain.net/?app=xxx rescues application xxx from backup (every app has now its own backup).
  • edit.appdomain.net/?app=xxx edits application xxx. Application "edit" must be present, rescue it first.
  • create.appdomain.net/ creates new application. Application "create" must be present, rescue it first.
  • anythingelse.appdomain.net/ runs the application "worker" (properly parsing hostname was postponed for later, multiapp editor was more urgent). Of course, the worker also must be rescued first, or you get an error.

I also think that now my idea of using import instead of eval is much more clearly visible, in the appjet._boot; in the way how saveAppSource saves the sourcecode and how run runs multiple applications.

To finish it, one of the cutest things is this: running rescue.appdomain.net/?app=boot is probably doomed. If you screwed your boot.js, it probably won't be working to rescue itself. No problem, stop the server (it's useless with broken boot.js anyway), start it with configure.js and run just the second step...
and it wasn't planned, it just came out this way.

22 July 2009
Wednesday

The spartan miniplatform

The previous one was much too stiff, but now I have a real minimal platform ready, based on ideas I was sketching out before. It consists of three files. First, called configure.js (I've got it in the main dir of the platform), is used to set up the initial image (read: storage). The server should be run with it as the main file and (at least) one page requested.

import("storage");
storage.pwd = "undefined";
storage["worker.js.bak"] =
"""var _code = request.params.code;
if (_code) {
    appjet._boot.saveWorkerSource(_code);
    response.redirect(request.path);
}

print(FORM({method:'post'},
    TEXTAREA({name:'code'},
        html(appjet._boot.workerCode)
    ),
    INPUT({type:'submit'})
));
""";
printp("Configured. MD5 of password is:", BR(), md5(storage.pwd));

Before running it, you may fill the password (it's used to authenticate to file saving plugin, so only you can save files, not any malicious app calling filesaving webservice). It creates the initial storage and prints the hash of a password for you.

Then, there is, slightly changed, filesaving local webservice, fs.rb (I've got it in www subdirectory, where static files are served from and ruby scripts can be called as well):

#!/usr/local/bin/ruby

require "cgi"
require "digest/md5"

cgi = CGI.new

hashpwd = Digest::MD5.hexdigest(cgi["pwd"]);
cgi.params.delete("pwd");
if ("5e543256c480ac577d30f76f9120eb74" != hashpwd) then
  raise "password does not match";
end

cgi.params.each do |name, code|
  File.open("../apps/"+name, "wb") do |f| f.syswrite(code) end
end

cgi.out do "" end

If you edited password in configure.js, edit also its hash in this file.

And last, but not least, is the bootstrapper, boot.js (I've got it in apps subdirectory, where all the apps and libs in the platform should reside). A bit longer than the previous one:

(appjet._boot = {
    rawSaveSource: function (file, code) {
        var info = { pwd: this.getString("pwd") };
        info[file+".js"] = code;
        wpost("http://staticdomain.net/fs.rb", info);
    },

    saveWorkerSource: function (code) {
        if (this.workerCode) {
            this.putString("worker.js.bak", this.workerCode);
        }
        this.rawSaveSource("lib-! worker",
            "/* appjet:version 0.1 */\n" +
            "/* appjet:library */\n" + code
        );
        this.putString("worker.js", code);
    },

    getString: function (key) {
        return appjet._native.storage_get("obj-root", key).value;
    },

    putString: function (key, value) {
        return appjet._native.storage_put("obj-root", key, "string", value).status == 0;
    },

    run: function (amd) {
        appjet.mainDomain = amd;
        if (request.headers.Host == "rescue."+amd) {
            var rescue = this.getString("worker.js.bak");
            this.saveWorkerSource(rescue);
            printp("Last known good code restored.", BR(), CODE(rescue));
            response.stop();
        }
        this.workerCode = this.getString("worker.js");
        import("lib-! worker");
    },

}).run("appdomain.net");

You should edit url to fs.rb (top) and the domain of your platform itself (bottom), then run the server with -c apps boot.js. First page you should request is rescue.appdomain.net. The rescue function restores last code that worked. If you enter code that breaks, simply load rescue.appdomain.net again.
The miniplatform itself resides in any other subdomain (like foo.appdomain.net). It starts with really spartan interface, the lone TEXTAREA tag with worker code to edit and the submit button. You can immediately begin to improve and evoive your platform (first change I did was adding style:'width:100%;height:50ex' to the textarea, so I can see what I edit). And it is a platform - it really saves your changes into a file and allows you to save another files beyond the worker (including boot.js itself, when you're ready) using appjet._boot.rawSaveSource call.

11 July 2009
Saturday

A living thing

I started making my own Appjet clone as well, though it is not public yet (it is on friend's server where I began squatting). Very quickly I got to the "reflexive", "eat you own dog food way". That is, to design the clone not for performance or "business value", but, more academically, so that as much as possible is done inside the platform itself and as low as possible is left for some sort of external support.

For imports to work (and for applications as well, since I tried to go the no-eval way and use import instead), the files must be stored on disk. This is the thing that AppJet itself cannot do, so this platform needs supports in this aspect.

But, over time, I came to the conclusion that. in fact, this is the only thing where external support is needed, every other thing, from innards of multiuser platform with virtualized storage space, to the IDE, can be written inside AppJet. So the files began getting smaller, the half-implemented features went away (YAGNI for the moment) and finally, I aimed at creating as small as possible bootstrap of a platform. Something very small but sort-of living and capable of evolving in itself.

First thing: file saving. I decided to go webservice way: I call a service (through wpost) and it saves the files for me. It should have no other responsibility. Here's the code.

#!/usr/bin/env ruby

require "cgi"
cgi = CGI.new
File.umask(0002)

cgi.params.each do |name, code|
  File.open("../apps/"+name, "wb") do |f| f.syswrite(code) end
end

cgi.out do "" end

Yes, in the future it will definitiely needs some improvements, like some sort of security, or anyone calls it to write files virtually anywhere (where the script has access, of course). Sources are in apps subdirectory, this file (fs.rb) and static web if it will be used later is in www subdirectory, in main directory I have the appjet jar.

And now for the living being, Here it is.

var info = {};
info[appjet.appName] = appjet._native.codeSection("server")[0].display;
wpost("http://foo.net/fs.rb", info);

It resides in apps subdirectory and is used as the main file for running appjet.jar.

Look at it. It's already living. Whenever you load a page, it keeps its own life - it writes its new copy over itself. It is not capable of more than keeping the "life", but it is good enough. This is the starting point. A few more lines, and it gains the ability to evolve. Anyway, this is phase 1.

PS.: For those who don't get it. This is not the standalone application. It replicates itself - it really writes over the file it resides in, even if it is with the same content. It's new file, and if you reload the page, this new file will serve the page. This is the minimal (very uncapable) Appjet platform.
PPS.: Yes, you can write eval(request.query) and get much smaller beast, but I don't want to use eval. It's a cheat (it's deus ex machina - without input it does not do anything in itself and stagnates, it's empty jar filled with eval's orders, but has no own behaviour; the above one does and it's what is wanted, plus the capability acquire new behaviour (which it lacks)). And eval has its own problems, I simply don't like it.
PPPS.: Feel free to clone it and lead it along, too. If you like.

08 December 2007
Saturday

Wishlist

No, tento blog som poriadne zanedbával, medzitým sa stalo hrozne veľa vecí. Asi si dám novoročné predsavzatie, že naozaj budem písať pravidelne... Ale nie o tom som chcel, úplne neromanticky a pragmaticky som si zriadil wishlist (zoznam želaní), takže keď niekto nevie, aký mi dať darček, môže sa pozrieť... je to na wiki, čiže aktualizácia je hračka, dokonca aj koordinácia, aby som to nedostal x-krát (keď sa niekto rozhodne, že mi niečo z toho podaruje, môže to označiť, aby to nezháňal nik iný).

Je tu: http://wiki.herby.sk/default.aspx/Blackboard/HerbysWishlist.html

24 January 2007
Wednesday

Sláva Ľuborovi

Mal som pokazený jeden TX6000 (hard pad s barom). Asi len po dvoch mesiacoch od kúpy a po asi len dvoch týždňoch aktívneho používania mi začal blbnúť, vypadávali mi šípky, a keď som hľadal (a lokalizoval), kde je ukrytý problém, tak mi prestal ísť úplne.

A keď som sa pýtal Ľubora, či sa v tom nechce povŕtať, povedal, že hej. Dnes večer prišiel, odkryl problematické miesto, došiel k logickému záveru, že ten jeden uvoľnený čierny drôtik má byť asi priletovaný tam, kde je druhý čierny drôtik, a voila, všetko funguje ako má. Takže mám zasa konečne dve hard pady.

03 November 2006
Friday

Čo vyrástlo pri Michalskej bráne

Včera som objavil veľmi roztomilú vec. Vyskytol som sa pri Michalskej bráne, a videl som poskakovať nejaké deti na kúsku zelene pred bankami. Zároveň do toho zneli také vianočné zvončenky.

Po chvíli mi bolo jasné, že tie zvuky sú silne prepojené so skákaním tých detí. Na trávniku bolo také malé DDRko bez šípiek a bez monitora, ktoré na každý step zazvonilo.

Dnes ráno som ho šiel vyskúšať. No, nie je Cobalt Flux (ani TX). Navyše bolo mokro a dosť sa to kĺzalo. Ale aj tak je to veľmi roztomilý nápad.

Skúsil som si na tom zopár vecí. Rolls to nezvláda (nezazvoní to na každý step). Keď vám neprekáža, že si zamažete ruky, dá sa dať aj viac než dve naraz (skúsil som sprofanované C-E-G).

Snáď len to ma zamrzelo, že to nie je nejaký originálny nápad niekoho od nás, ale výrobok nejakej GmbH.

14 September 2006
Thursday

Design patterns ukazujú na slabosti

Blaine Buxton má vo svojom článku odkaz na skvelú úvahu Sama Griffitha. Táto v skratke hovorí, že design pattern (okrem toho, že je to skvelá vec na pomoc programovaniu v existujúcom jazyku) je vlastne ukážka toho, kde je tento slabý, a že zadefinovanie design patternu je len začiatok - ďalší krok má byť oprava jazyka tak, aby už slabý nebol.

A ukazuje dôkazy na historických design patternoch.

07 July 2006
Friday

Som v Top Ten!

Tento článok píšem z Festivalu Fantazie v Chotěboři. V podstate som sem prišiel z jediného hlavného dôvodu: konali sa tu majstrovstvá ČR v DDR s úmyslom prihlásiť sa do nich, keď tam nebudem úplne zavadzať.

Zo začiatku to vôbec nevyzeralo nádejne. Z domu som bol zvyknutý na soft pad a tu boli, samozrejme, hard pady. Niektoré naučené pohyby nefungovali a musel som sa preučiť. Nakoniec sa mi to ale podarilo.

Počas samotných majstrovstiev ma zradila tréma. Možno som ju naozaj zažil po prvý krát v živote. Prvé kolo, osemnožičkové pesničky z originálneho DDR na heavy, som ešte zvládol. Ďalšie tri kolá už ma tréma naplno ovládla: bol som roztrasený, nedokázal som sa uvoľniť a sústrediť na šípky. Raz darmo, na psychike to fakt stojí.

Nakoniec som bol ozaj príjemne prekvapený. Skončil som desiaty zo sedemnástich a porazil (sice tesne, ale predsa) aj veľmi dobrých hráčov z Czech DDR.

(Citát z vyhlasovania výsledkov (boli vyhlasované odzadu): "A teď Top Ten. Desátým nejlepšim DDRkářem v republice je ... Herby!" "Ve které republice?" "V Československé.") Pre úplnosť dodám, že to boli naozaj majstovstvá ČR, teda zúčastniť sa síce mohol každý, kto bol na FF, ale ak by nebol Čech, nemohol by postúpiť ďalej; a ďalšie tri kolá boli osemnožičkové pesničky z ITG na hard, deväťnožičkové pesničky z originálneho DDR na heavy a napokon deväťnožičkové z ITG na expert.

Takže som z toho celkom rád.

Čo rád? Nadšený som z toho! S takou trémou sa dostať do Top 10, to je predsa úžasné. Ďakujem všetkým, čo mi držali palce.

05 June 2006
Monday

Kód ako próza

Jeden z mojich najobľúbenejšich bloggerov, Blaine Buxton, napísal ďalší zo svojich výborných postrehov.

Writing your program for the computer is flat out wrong. In reality, the computer never really reads your code nor does it have to. It's the poor person awoken at three o'clock in the morning because a developer forgot to check for a null condition. It's the team interfacing their project on your library. In short, it's humans.

Tesať do kameňa by sa to malo. A je to smutné, keď človek vidí, ako je to v skutočnosti (a ako sa to doteraz na mnohých miestach učí).

Celý článok je tu: http://blog.blainebuxton.com/2006/06/code-as-prose.html

P.S.: Samozrejme, ako veľký open-source skeptik si myslím, že to v poslednej vete s tým optimizmom dosť prehnal. Ale nech si užije.

25 March 2006
Saturday

Prvý zničený pad

rozrthnutý senzor spodnej šípkyTak sa mi podarilo zničiť svoj prvý softpad. Ten dierkovaný molitan vyzerá síce celkom k svetu, ale ten roztrhnutý plast s vodivou vrstvou už celkom nie... Myslel som si, že tá mriežka je navrhnutá tak redundantne, že to ten prúd vždy nejako obíde, kým to nie je roztrhnuté úplne, ale spodná šípka sa už, asi navždy, odporúčala...:'(

03 March 2006
Friday

Zmeňte svoj život (k lepšiemu)!

softpadEšte pred pol rokom som bol iný človek. Teda, aspoň mnohí tvrdia, že teraz som iný než som bol. Hoci mne samotnému sa nezdalo, že by došlo k nejakej zmene. Ešte stále som melancholický "tragický romantik", mávam často depresie a záchvaty beznádeje a svetabôľu. Tak aká zmena?

Ale nakoniec som im uveril. Je to asi naozaj tak, že už je tých zlých vecí menej, a hlavne, zrejme je zo mňa cítiť omnoho viac radosti zo života a pozitívnej energie. A schudol som – už mi pri preventívnej prehliadke žiadna lekárka diagnózu "ľahká obezita" nenájde.

A to všetko preto, nad čím sa teraz každý nafúkaný elitársky intelektuál (teda človek ako ja) pohŕdavo uškrnie (podobne ako nad mobilmi, napríklad), až kým to nevyskúša a nestane sa to novou súčasťou jeho životného štýlu – DDR, alias "Dance Dance Revolution", po slovensky "Tancuj, tancuj, vykrúcaj:-D) – hra, ktorá vyžaduje pripojiť také zariadenie, aké je na obrázku, k PC (pôvodne to bolo na herné konzoly), pustiť správny software a hrať.

Asi niekedy okolo vlaňajšieho októbra som totiž bol na Moorcone (akcia s vynikajúcou atmosférou, ale to je asi dané jej komornosťou a schvaľovaním prihlásených) a tam Pogo priviezol svoje DDR. Ako hanblivá osoba som sa nestrápnil tým, že by som to verejne skúšal – vedel som, aký by som bol ľavý, ale tá pozitívna energia a ten "fun", ktorý okolo toho vznikol, davy ľudí, ktorý skákali aj naprázdno, čakajúc vo fronte, kám sa tam dostanú, to ma naozaj dostalo. Takže som si to samozrejm hneď zohnal domov.

Na hranie tejto hry je potrebné celého človeka – treba mať nohy, oči, uši (na veľmi vysokých leveloch aj ruky resp. kolená). Ja osobne ešte používam aj hlavu, "pattern recognition", ergo "rozpoznávanie vzorov" je predsa výsostne mozgová záležitosť. Hra sa hrá v konečnom dôsledku nohami – v tej správnej chvíli je potrebné trafiť nohou správnu šípku. K tomu vám dopomáhaj cit pre rytmus (hra totiž hrá hudbu a šípky sú s ňou presne synchronizované – preto to ostatne môže pripomínať tanec a preto teda taký názov), na vyšších leveloch už spomínaný pattern recognition (pred očami sa vám odvíja prúd šípok, ktoré bude nutné trafiť v najbližšej budúcnosti, a preto je dobré ich vedieť preložiť do budúcich pohybov) a na úplne najvyšších leveloch fyzická kondícia. Ktorú mám, mimochodom (subjektívne) o dosť lepšiu než kedysi.

A skoro nič to nestojí. Objednáte si dance pad (to je insiderský názov toho zariadenia, na ktorom sa skáče, linky hľadajte na http://ddr.pocitac.com), software je zadarmo (http://www.stepmania.com), pesníčky sú trochu problematické, väčšina ľudí ich má nelegálne, skopírované z netu alebo od ľudí, čo už DDR majú; je ale možnosť získať ich aj legálne (hoci je to trochu komplikované a drahé).

Odporúčam, skúste, naozaj. Antidepresívum, sociálny zbližovač, zdroj pozitívnej energie, ešte je to aj zdravé a je to extra fun. Veď uznajte, musí to byť sila, keď to zo mňa dokázalo urobiť lepšieho človeka.

(A kto to už pozná, nech mi dá za pravdu, že je to úžasné, že?:-))