Thursday, April 24, 2008

Re: Recommendations for the Fourth Edition of the ECMAScript Programming Language Specification

Doug Crockford has posted some recommendations to improve JavaScript.

I have some comments in the context of my experience with live-patching JavaScript issues with libraries.

New Features

Function Reflection

Doug recommends that function objects contain a "name" property. Some versions of JavaScript actually have this already. This could be handy but I haven't needed this and would not be likely to use it. I was surprised that FireFox had this when I used a Function in a with block. I've learned my lesson.

However, Doug also recommends the addition of an arguments member to Function objects. This is a critical feature. The arguments array would contain the names of all of the functions declared argument variables. Function objects already have a length member that corresponds to the length of this hypothetical arguments array. There was some buzz about using this value for method overloading and it permitted me to create an elegant partial application decorator. If we had access to the names of a function's argument declarations, I could implement a decorator for supporting Python-style positional and named arguments. Decorated functions would accept an array/list of positional arguments and an object/dict/map of keyword arguments and the decorator would translate them to an array for a wrapped Function.apply. That decorator or further decorators could support default values and automatic argument length assertions.

typeOf

Doug notes that JavaScript's typeof "function" is broken because it returns the wrong strings for null and arrays. All that and he hints that using camelCase instead of lowercase would have been more in keeping with the language's aesthetic. He recommends that we deprecate typeof and replace it with a fixed version called typeOf. I would go a step further. typeof returns a string. I propose that it ought to return the type object. This would jive well with instanceof, instanceOf, or isInstance. This means there should be a Null type that would construct null and an Undefined type to construct undefined.

Object Methods

Don't Enum

I don' particularly care whether Object has a dontEnum method since I don't augment base types and wrap them when I need reliable collection types, but this would help ameliorate integration woes with prototype so it's a good idea.

Array of Keys

Doug wants a Object.arrayOfKeys method. I want a keys method that returns a Set. I would also like keysIter and, less emphatically, keysArray. I don't want the name arrayOfKeys because it introduces an unnecessary Of and doesn't sort as well as keys*.

Array of Values.

Doug also wants Object.arrayOfValues. I want values and valuesIter. These should return Array and Iter object respectively.

toJsonString

Object.toJSONString(memberName). The first thing that comes to mind to improve this idea is to rename it toJsonString; I prefer to consider acronyms words and I would have also called XMLHttpRequest XmlHttpRequest. This name jives well with toString, but I would have called that string. But, I actually would call this repr and made it a global function. repr would work on arbitrary objects, a subset of which would be serializable JSON. The behavior of repr would be overridable by providing a repr method on an object.

Beget Object

Doug recommends that Prototype inheritance be a little more lucid by providing a beget function that returns a new, empty Object that inherits from the previous. I like this idea. I called it inherit in Chiron.

isEmpty

isEmpty would return whether an object has any properties, other than those inherited. Sounds fine to me, although I probably would have just used number and boolean casting. This is a good idea too though, because in JavaScript, it's hard to distinguish an Object that is being used as the base of the inheritance tree and an Object that is being used as a hash-map. To that end, I recommend the addition of a Map type. Map would have isEmpty, number, and boolean functions and would implicitly cast to Number and Boolean based on their length that would be reflected publically by the getLength function.

Array Methods

Doug recommends the addition of some of Mozilla's JavaScript 1.6 Array methods. I support all of them. However, some nomenclature refinements and specifications:

indexOf should be augmented by a find function that throws a KeyError instead of returning a negative index.

lastIndexOf should be augmented by a findLast function similar to find.

every should be called all. This function should accept an iterator or iterable and short circuit on the first failure.

filter should be called where. Filtering implies the opposite of finding. To filter something is to remove it from a stream if it passes a particular condition. where implies that the outgoing stream should contain only those elements from the original stream that affirm the guard.

forEach is good as is. I would like to specify that it should return this so that forEach calls can be chained. This has implications on iterations that are partially consumed by a forEach call that throws a StopIteration exception in its continuation.

map is good as is. As a member of an array, each should be a synonym. These functions should also be declared in global scope with opposite argument order: map(function, collection, [context]) vs each(collection, function, [context]).

some should be called any. This function should accept an iterator or iterable and short circuit on the first success.

String Methods

trim is good as is. There should also be trimBegin and trimEnd or trimFirst and trimLast.

string.eval is a good idea, but I've got my own ways to launder the scope chain for eval.

Date Methods

Date.toJSONString and Date.toISOString are both great ideas; not having them has been a problem for me in the past. I recommend calling them toJsonString and toIsoString.

Corrections

Reserved Words

Doug argues that identifiers in object literal notation and member dot notation should allow reserved words. I concur. It's a small syntax shortcut that identifiers used in member selection and object literal notation are not required to be enquoted; the language should be equally permissive for both syntax forms.

Doug also argues that the list of reserved words is too long. I have mixed feelings. On one hand, having a long list of reserved identifiers leaves open the door for future advances in the language (some of which include type annotations, which I find dubious). On the other hand, they muddy the name space for current code. On the latter note, it's important that all browsers are equally strict. Safari, at the moment, is much more strict than other browsers, so I've been surprised.

Object Literal Notation

I agree that commas should be more regular throughout the language. They should be permitted after any value, including the last in an Object or Array literal without affecting the length of either.

arguments

arguments should definitely be an instance of an Arguments type that inherits from Array.

Inner Functions and the Context Object (this)

I agree that this should not be the global object (window) in inner or anonymous functions. this should be acquired from the scope chain in such closures. Doug claims that this is not the standard. I recall being corrected on this point, but I also recall having been under the same impression. I leave this as an exercise to the reader to determine the current state of affairs in various browsers.

Tail Recursion

Tail recursion would be nice.

Deprecation

Primitive wrappers should be eliminated. Boxing is almost never necessary. In fact, the use of the new keyword could be completely obviated and code would become much more reusable since there would be no distinction between a factory method and an object constructor.

I actually use the with statement in a couple cases that are really important. I don't recommend using it for its designed purpose, but I really do need it to stay in the language for my module system and my JavaScript shell to keep working. FireBug, in particular, depends on this feature.

Semicolon insertion was silly. Off with its head.

I don't particularly care about arguments.callee.

typeof has to go.

Again, eval is a necessary part of my perverse world. I can do my own laundry. There are, however, irresponsible uses of eval that I do not condone.

Monday, March 24, 2008

De Schpugeti

You've heard of syntactic sugar. After a syntax and vocabulary provides all of the normal forms necessary to express all of the programs possible in a language, yummy shortcuts bridge the gap between expressiveness and usability. For example, c++ is a delicious variation of (temp = c, c = c + 1, temp).

You've also heard of CRUD, an acronym for the normal forms for a data programming interface (databases, data structures). If you design an interface library for data and fail to cover all of these bases, you deserve what's coming to you — and I can't promise that it won't involve a big hole in the ground and Hello Kitty.

CRUD stands for create, read, update, and delete. Those functions encompass all of the necessary data interactions; however, they are not sufficient. While CRUD may provide all the nutrients, vitamins, and minerals your data API might need, it certainly won't taste good without sugar.

Enter: Chiron.

del, set, cut, has, put, get. De Schpugeti. All of these names are three letters long, should be familiar by their names, and are sufficient in addition to necessary. These methods are the interface of Chiron's Dict, for manipulating item data: data that have keys and corresponding values. They are also implemented in a consistent manner by List presuming that indices are like keys.

del
Deletes an item for a key.
Returns the mutated collection.
For ordered collections, accepts a beginning key and an ending key, deleting all items in that range of keys excluding the ending key.
Implemented in terms of remove.
set
Adds or overwrites an item for a key.
Returns the mutated collection.
Implemented in terms of insert.
cut
Deletes an item for a key.
Throws a key error of no item has that key.
Returns the corresponding value.
Implemented in terms of get and del.
has
Returns whether key is among items.
Implemented in terms of has.
put
Adds an item for a key.
Throws a key error if there is already an item with that key. returns the mutated collection.
In an ordered collection, reserves the right to change the keys of some other items to make room for the new item.
Implemented in terms of set and has.
get
Returns a value for the given key.
Throws a key error if there is no value for the given key.
Implemented in terms of retrieve and has.

Also, Lists, Sets, and, by extension, Dicts have functions for managing collections of values that do not have keys. Dict is a Set of items instead of mere values that are compared and hashed on their key.

has
Returns whether a collection contains a particular value.
insert
Adds or overwrites a value.
Returns the mutated collection.
Defaults to an implementation in terms of find and set.
retrieve
Returns the contained value that is naturally equivalent to a given value.
Throws a value error if none exists.
Defaults to an implementation in terms of find and get.
remove
Removes a value.
Throws a value error if the value isn't in the collection.
Returns the mutated collection.
Defaults to an implementation in terms of find and del.
discard
Removes a value if it exists.
Returns the mutated collection.
Implemented in terms of has and remove.
find
Returns the key for a given value.
Accepts an optional equivalence relation to override the default of eq.

Now accepting ideas for a good mnemonic.

The fun trick is that List, Dict, and Set implement all of these functions. While it may be more appropriate to use one collection type for one kind of data or one algorithm, you can use any of these types interchangeably.

Thursday, March 20, 2008

Caption Contest

I found this gem around to corner from my current workplace:

Please send me your submissions.

(I say current because we're (FastSoft) moving to Padadena at the end of this month.)

Thursday, February 21, 2008

Unicode

What I've learned today—

In order for a program or library to operate in a Unicode compatible fashion, all strings must be in Unicode. All input strings must be brought into Unicode, and all output strings must be sent out of Unicode at the very last possible moment. This is because, outside of Unicode strings, encoding is not a function of type and type information does not generally cross API boundaries accurately, plus regular expressions don't play well against mixed-length characters.

Django works entirely in Unicode and drops a string to UTF-8 at the last moment. I had need for Python's Textile library to transform text, but it only dealt with byte strings. Anyhow, It turned out to be quick work to change all of its strings to unicode and not bother with encoding and decoding.

Tuesday, February 19, 2008

Integrating Simile

Not to name names, but I've been working on integrating code from Simile from MIT into Chiron. Refactoring an existing JavaScript project highlights all the things you get for free in Chiron.

Simile has it's own XHR engine, DOM event wrappers, DOM layout and style functions, PNG transparency solution, and a SortedArray type that provides binary search functions. Here are some of my observations.

  • Simile's layout getSize was better than mine. I will rectify this.
  • Not having a module system makes us reinvent the wheel: frequently.
  • It's hard to write a good XHR engine. There are a lot of XHR modules out there, most of them have some issue or another: missing browser, doesn't report OK status on local files, doesn't unify browser caching inconsistencies, doesn't support timeouts, doesn't expose XML (the X in AJAX) in IE, or so on. If you're going to make a new one, you should use these as references and do some serious research, development, and testing. Otherwise, you should copy or use the best of them (jQuery, in my opinion). Also, it needs to support asynchronous (the A in AJAX) requests, and you need to use them as often as possible.
  • Not having a solid, modular library makes us lazy. The inconvenience of name-spacing makes us lazy. This causes us to write sloppy code. For example, we should always use an enquote function when we're string interpolating HTML attributes and an inoculate function when we're interpolating HTML, or we should use DOM functions or a DOM wrapper API to generate our HTML.
  • As I integrate code from other libraries, a pattern emerges. In my first pass, I collapse the name-spaces. Every module is a name-space, so all the manual creating of hierarchies like Simile.DOM (Simile = {} presumed, then Simile.DOM = {}, then endless repetition of Simile.DOM to augment or use its contents) is unnecessary and undesirable.
  • Referencing URL's of resources, like other scripts and images, relative to the URL of the script you're currently in, is hard. Starting from scratch, this usually means you're going to have a global URL constant. This means domain-coupling. Maybe you make the URL relative to the root. This means domain-coupling. Maybe you provide it as a configuration variable. This means site-coupling. Maybe you scrape the script tags on the page for the URL of your script then resolve the URL relative to your own URL. This means you're going to write a lot of slow code for what you perceive to be little value. In Chiron, you can get a function called resolve from http.js that resolves a URL relative to a base URL. Chiron also provides your modules with a moduleUrl variable that is the URL of the script you're in. resolve also implicitly uses this variable as your base URL if you don't provide a second argument(include('http.js'); resolve('images/blah.gif')).

    Chiron grabs the script tag href of modules.js and removes the script object from the DOM (so other scripts can't sniff it) exactly once, since it needs that URL to resolve other module URL's. From there, Chiron keeps track of where all of your modules are relative to it and provides that information to each module.

  • About SortedArray:
    • A collection type should create an empty instance if you pass no arguments in.
    • A collection type should populate itself from the values of another collection if you pass one in as its first argument. This should always be the first argument, even if you frequently create empty collections with overrides on later arguments. Force your user to pass in a null or undefined.
    • Try to accept null and undefined as equivalent unless the distinction is meaningful.
    • Try to distinguish null and undefined from 0 in all meaningful cases.
    • Invariants like "sorted" are a promise. Guarantee your invariants across all function calls, including construction. If this means an unacceptable performance degradation, permit the user to suppress whatever code you need to verify the invariant if they are willing to provide treated data.
    • If there is a reasonable default, it should always be implicit. I should not have to explicitly send the global compare function into a SortedArray if I want a SortedArray of types supported by compare.
  • Not having a system of base types makes for noisy API's where names from different organizations have different meanings. For example, find functions should always accept the same kinds of arguments and return the same kinds of values. Simile's name choices are very close to mine, to the effect that they could almost be used as partially implemented duck types for mine, but some of the names would have to be realigned. find in Simile accepts a comparator and returns an acceptable index to insert or remove a particular element. find in Chiron returns an index or key at which an item can be inserted or removed, and guarantees that it will be the first occurrence of a given value (not a comparator). It was very easy to refactor SortedArray to subscribe to the strict model. Also, removeAll needed to be clear, length and getCount both needed to be getLength, getIterator needed to be iter, next needed to throw StopIteration once in a while, among others.

I'm looking forward to having a semblance of Simile Timeplot and Timechart in the Chiron family.

Monday, February 18, 2008

Polymorphic repr

I'm not about to debate whether debugging is a valuable exercise in JavaScript, nor whether introspection or reflection tools are useful, nor whether they would be especially helpful in a dynamic language. Ruby has inspect. Python has repr. The Chiron JavaScript library has repr too.

Notionally, repr is the inverse of eval for a reasonable subset of JavaScript. There are a lot of object hierarchies that cannot be reconstructed from a repr serialization, but as a debugging tool, repr is indispensable.

repr is a polymorphic function you can import from base.js. If you pass repr an object that implements a repr member function, repr will defer to your overridable repr. Otherwise, repr returns reasonable defaults for other types. For example, repr provides defaults for Array, Object, String, Number, Boolean, and Date. repr also recursively represents members of arrays and objects, but provides circular reference protection by tracking visited objects in a memo Set.

Chiron's debugger uses repr to convert the value of an expression on the command line to a human-readable string.

j$ 1
1
j$ repr(1)
"1"
j$ "hi"
"hi"
j$ repr("hi")
"\"hi\""
j$ true
true
j$ {a: 10}
{"a": 10}
j$ [1, 2, 3]
[1, 2, 3]
j$ [{a: 10}]
[{"a": 10}]
j$ var a = {}; a.a = a; a
{"a": <cycle>}
j$ type()()
<instance run.html#0 0>
j$ type({'repr': function () {return "x"}})()
x

Sunday, January 27, 2008

Tale Mailing List

Please subscribe to Tale's mailing list if you are interested in joining the invitation alpha in July or the beta in November.