A proposal to add String.prototype.format

Adam Shannon adam at ashannon.us
Wed Mar 9 14:36:02 PST 2011

I rather like the idea of having this syntax for string formatting:

"name: {name:first} {name.last}".format(name)

It allows for more complex operations

"name: {person.firstName} \nstart: {myEvent.startTime}".format(myEvent, person)

Also, it doesn't require mundane fixes later on and keeps things
simple for the developer. (No needed knowledge or maintenance of
things based on position.)

On Wed, Mar 9, 2011 at 15:36, Shanjian Li <shanjian at google.com> wrote:
> I like this idea.  I thought a lot about how to support those locale
> specific stuff like plural and gender. Your suggestion provide an elegant
> way to transfer the responsibility to a more appropriate party.
> shanjian
> On Wed, Mar 9, 2011 at 1:18 PM, Bob Nystrom <rnystrom at google.com> wrote:
>>>> It doesn't specify how to print objects, except for %s, which says that
>>>> if the argument is not
>>>> a string, convert it to string using .toString().
>>> If the format specifier does not apply to the argument given, it should
>>> raise exceptions. Except string conversion, no other conversion will be
>>> done.
>> I like your first six points, but the formatting string stuff feels odd to
>> me: neither dynamic nor object-oriented. C needs format specifiers because
>> it doesn't otherwise know now to interpret the bits you give. ES doesn't
>> have that problem.
>> At the same time, baking a rigid set of formatting instructions into
>> string.format() feels like a poor separation of concerns. string.format()'s
>> just is to compose a string out of smaller pieces. Why should it need to
>> know anything about numbers or dates?
>> Could we just say that this:
>>     "hi, {0:blah}.".format(someObj);
>> Is (conceptually) desugared to:
>>     ("hi, " + someObj.toFormat("blah") + ".")
>> So anything after the ":" in an argument (the format string) gets passed
>> to the object itself by way of a call to toFormat() (or some other method
>> name) on it. Then each object can decide what format strings are appropriate
>> for it.
>> This keeps the responsibilities separate: string.format() does
>> composition, and the composed object own their own formatting. It's also
>> extensible: you can define your own formatting capabilities for your types
>> and use them with string.format() by defining toFormat(). (By default, I
>> would assume that Object.prototype.toFormat() just calls toString()).
>> This, I think, helps with locale issues too. Types like Date that care
>> about locale will be able to handle it themselves in their call to
>> toFormat() without string.format() needing to deal with it.
>> - bob
>>>> The string conversion should probably use the internal ToString function
>>>> instead (which works for null
>>>> and undefined too).
>>> Agree.
>>>> For formats expecting numbers, it should convert the argument to a
>>>> number using ToNumber.
>>> Probably not. As string is the thing being constructed, it make sense to
>>> offer "hidden" string conversion. In my experience using this feature in
>>> Python, it is within expectation and offer some convenience. Any further
>>> "hidden" conversion should really be avoided.
>>>> Rounding is specified as "math.round(n - 0.5)" (capital M in Math?).
>>> Right, thanks.
>>>> This leaves it open whether overwriting Math.round should change the
>>>> behavior of format. It probably
>>>> shouldn't (i.e., again it would be better to specify in terms of
>>>> internal, non-use-modifiable functions).
>>> Agree.
>>>> The rounding is equivalent to Math.floor(n) (aka round towards
>>>> -Infinity), if I'm not mistaken, so why
>>>> not just use that?
>>> In this example, 8 / (3 - 8 / 3) , the display will be 23.99999999999999.
>>> So the internal representation could be a little bit more or a little bit
>>> less than the theoretical value due to float precision. Math.round might
>>> generate less surprise results than Math.floor.  Of cause, the internal
>>> implementation shouldn't rely on either of these two.
>>>> (Personally I would prefer truncation (round towards zero), if
>>>> conversion to integer is necessary).
>>>> Why can octal, binary and hexidecimal forms only be used on integers?
>>>> Number.prototype.toString with
>>>> an argument works on fractions too (try Math.PI.toString(13) for laughs
>>>> :).
>>>> Why only fixed bases (2,8,10,16)? How about adding an optional base
>>>> parameter to number display (with
>>>> x, d, o, b as shorthands for the more standard bases)? Again,
>>>> Number.prototype.toString means that it's
>>>> already in the language. (I know that step 7 says copy the format of
>>>> other languages, but that seems
>>>> shortsighted since ECMAScript is not those languages, and only copying
>>>> functionality from C verbatim
>>>> seems like tying your shoelaces together before the race).
>>> The question for both questions is how useful is that. If it is only
>>> needed in one or few rare occasions, it is probably not a good idea to
>>> complicate the language.
>>>> "Placeholder used in format specifier part can not have format
>>>> specifier. This prevent the replacement
>>>> from embedding more than one level."
>>>> Should that be "... can not have a placeholder."?
>>> No.   The former prevent any format specifier (including embedded
>>> placeholder). Refer to the Python specification, it does make sense.
>>>> If the placeholder value is not a string, it should be converted to a
>>>> string.
>>>> If it is not a valid format, what happens then?
>>> Raise exception?
>>>> Is the following valid:
>>>>  "{x} and {1[y]}".format({x:42},{y:37})
>>>> I.e., can object property shorthands ({x} instead of {0[x]}) be used if
>>>> there are more than one argument?
>>> Good points. Possible choices:
>>> 1. {x} always refer to the first object given.
>>> 2. {x} only works when there is one and only one object argument.
>>> 3. {x} will be replaced by the first object that has property x, ie. the
>>> following should work too.
>>>     "{x}, {z} and {1[y]}".format({x:42}, {z:43, y:37})
>>> I prefer 1.
>>>> And some arbitrary ideas for extension:
>>>> How about a boolean test that checks for falsy-ness of the argument and
>>>> acts as one of two other
>>>> formats or literals?
>>>> E.g.
>>>>  "{0:s} drew {1:?his|her} gun.".format(person.name, person.isMale)
>>>>  "Please press return{0:?.|{1}}".format(notCritical, " and run!")
>>> Interesting. In example 1, the issue is literal get into the placeholder,
>>> that could make things messy.
>>>> Or allow computed indices?
>>>>  "{0[{1}][he]} drew {0[{1}][his]}
>>>> gun.".format({male:{he:"He",his:"his"}, female:{he:"She",his:"her"}},
>>>> "female");
>>> Allow embedded placeholder inside the field part (not the format
>>> specifier part) of a placeholder is something that I will be very cautious
>>> about.
>>> shanjian
>>>> /L
>>>> --
>>>> Lasse Reichstein - reichsteinatwork at gmail.com
>>> _______________________________________________
>>> es-discuss mailing list
>>> es-discuss at mozilla.org
>>> https://mail.mozilla.org/listinfo/es-discuss
> _______________________________________________
> es-discuss mailing list
> es-discuss at mozilla.org
> https://mail.mozilla.org/listinfo/es-discuss

Adam Shannon
UNI Freshman
Web Developer

More information about the es-discuss mailing list