Affordances: Literals

Every programming language I’m familiar with provides some kind of
syntax for literal values of “primitive” types like numbers,
characters, and strings. Many languages go further and allow you to
create literal values of more complex types, like arrays and hashes.

Of the three languages I use the most, C++98 is the most limited in
this regard:

Complex Literals in C++98

// C-style arrays are OK

intarray[]={1,2,3};

// No literal syntax for standard library collections

std::vector<int>v;

for(inti=0;i!=3;++i)v.push_back(i);

// Similarly for maps

std::map<int,std::string>m;

m[1]="foo";

m[42]="bar";

m[123]="baz";

// Structs work well

point_structp1={1,2};

// So do arrays of structs

point_structpoints1[]={{1,2},{3,4}};

// Objects are OK

point_classp2(1,2);

// Arrays of objects not so much

point_classpoints2[2];

points2[0]=point_class(1,2);

points2[1]=point_class(3,4);

Smalltalk is also somewhat limited:

Complex Literals in Smalltalk

"Arrays of literals are OK"

array:=#(123).

"Other collection types need a bit more work"

set:=#(123)asSet.

"or"

set:=SetwithAll:#(123).

"Dictionaries have no literal syntax"

dict:=(Dictionarynew)

at:1put:'foo';

at:42put:'bar';

at:123put:'baz';

yourself.

"Objects in general also have no literal syntax"

p:=Pointx:1y:2.

"But the built-in Point class has a shorthand notation"

p2:=1@2.

"Arrays of objects are verbose"

points:=Arraywith:(Pointx:1y:2)with:(Pointx:3y:4).

"If your Smalltalk supports brace constructors, it's a bit better"

points:={(Pointx:1y:2).(Pointx:3y:4)}.

Ruby fares much better:

Complex Literals in Ruby

# Simple arrays are simple

array=[1,2,3]

# Other collection types need a bit more work

set=[1,2,3].to_set

# or

set=Set[1,2,3]

# Hashes also have a literal syntax

hash={1=>'foo',42=>'bar',123=>'baz'}

# But objects don't

p=Point.new(1,2)

# Array literals work with any kind of object

points=[Point.new(1,2),Point.new(3,4)]

C++11’s new uniform initialization mechanism is much better than
C++98:

Complex Literals in C++11

// C-style arrays are still OK

intarray[]={1,2,3};

// Standard library collections now work the same way

std::vector<int>v={1,2,3};

// Maps are now much better

std::map<int,std::string>m={{1,"foo"},{42,"bar"},{123,"baz"}};

// Structs continue to work well

point_structp1={1,2};

// So do arrays of structs

point_structpoints1[]={{1,2},{3,4}};

// Objects can now use uniform initialization

point_classp2={1,2};

// So can arrays of objects

point_classpoints2[]={{1,2},{3,4}};

Because of C++11’s explicit types, we can even go one step further.
It is possible to use uniform initialization to create temporary
objects to pass to a method or function:

Passing Objects

voidf(constpoint_class&p);

// C++98:

point_classp(1,2);

f(p);

// C++11

f({1,2});

Each of these languages provides different affordances for working
with simple collection and object types. These affordances impact the
kind of code you choose to write in those languages. Smalltalk’s
relatively verbose syntax drives different solutions than Ruby’s more
compact syntax.

Which is better? I’m torn. I like the cleanliness of Ruby’s rich
affordances a lot. It results in cleaner-looking code. I haven’t
worked with C++11 much yet, but I expect I will like uniform
initialization a lot as I start to work with it more. Smalltalk’s
lack of affordances in this area sometimes frustrates me.

On the other hand, I think these affordances make it easier to fall
into the trap of
primitive obsession.
Smalltalk’s more verbose syntax drives me to look for solutions that
involve domain concepts rather than built-in basic types.