Polyglot Primer

Chapter 2: Variables, Constants and Types

How to declare things, what the built-in types are, what "nothing" looks like, and how each language treats conversion, equality and operators.

Declaring variables

var count int = 10     // explicit
var name = "Ada"       // type inferred
age := 36              // short declaration (inside functions only)
var a, b = 1, 2        // multiple
x, y := 3.5, "hi"      // multiple, mixed types
int count = 10;
var name = "Ada";            // Java 10+: local inference only, still statically typed
int a = 1, b = 2;
final int limit = 5;         // cannot be reassigned
let count = 10;              // reassignable, block scoped
const name = "Ada";          // binding cannot be reassigned
var legacy = 1;              // function scoped, hoisted: avoid in new code
count = 10                   # no keyword: assignment creates the name
name = "Ada"
a, b = 1, 2                  # tuple unpacking
a, b = b, a                  # swap, no temp variable
GoJavaJavaScriptPython
Keywordvar or :=type name or varlet / constnone
Re-typing allowed?NoNoYesYes (name can rebind to any type)
Scopeblockblocklet/const block, var functionfunction / module / class (no block scope)
Unused localcompile errorwarningokok
Redeclare in same scopeerror (except := with at least one new name)errorlet/const error, var okn/a (just rebinds)

Constants

const Pi = 3.14159              // untyped constant: usable as int, float, etc. where it fits
const (
    Sunday = iota               // 0
    Monday                      // 1
    Tuesday                     // 2
)
public static final double PI = 3.14159;   // class-level constant
const PI = 3.14159;
Object.freeze(obj);              // shallow-freeze an object (const alone does not)
PI = 3.14159                     # a convention only; nothing stops reassignment
from typing import Final
MAX: Final = 10                  # checkers (mypy) complain; runtime does not

Quirks:

Primitive and built-in types

ConceptGoJavaJavaScriptPython
Booleanboolbooleanbooleanbool
Small intsint8, int16, int32, int64byte, short, int, long--
Default intint (32 or 64 bit by platform)int (32 bit)number (64-bit float)int (arbitrary precision)
Unsigneduint, uint8...uint64none (use Integer.toUnsignedString helpers)--
Floatsfloat32, float64float, doublenumberfloat (64-bit)
Big integersmath/big.Intjava.math.BigIntegerBigInt (123n)int (built in)
Exact decimalsnone in stdlib (big.Float/big.Rat)BigDecimalnonedecimal.Decimal (stdlib)
Characterrune (= int32, a Unicode code point), bytechar (16-bit UTF-16 unit)string of length 1string of length 1
Textstring (immutable bytes, UTF-8 by convention)String (immutable, UTF-16)string (immutable, UTF-16)str (immutable, Unicode code points)
Complexcomplex128nonenonecomplex
Nothingnilnullnull and undefinedNone

Highlights:

Zero values and defaults

GoJavaJavaScriptPython
Uninitialised localzero value (0, "", false, nil)compile error (must assign)undefined (with let)NameError (name does not exist)
Uninitialised fieldzero value0, false, nullundefinedn/a (set in __init__)

Go's "make the zero value useful" is a design principle: a sync.Mutex or bytes.Buffer works with no setup.

Type inference and annotations

x := 42            // int
y := 3.14          // float64
z := 'a'           // rune (int32), NOT byte or string
var list = new ArrayList<String>();   // inferred, locals only
// no annotations at runtime. TypeScript (third party) adds them.
age: int = 36                          # hint only; Python never enforces it
def greet(name: str) -> str: ...

Python hints are for tools (mypy, IDEs). Passing the wrong type still runs until something breaks.

Type conversion

f := 3.9
i := int(f)              // 3, truncates; Go never converts implicitly
s := strconv.Itoa(42)    // "42"
n, err := strconv.Atoi("42")   // returns an error value if invalid
double d = 3.9;
int i = (int) d;                      // 3, explicit cast required when narrowing
long big = i;                          // widening is implicit
String s = String.valueOf(42);
int n = Integer.parseInt("42");        // throws NumberFormatException
Number("42");  parseInt("42px", 10);   // 42  (parseInt reads a prefix)
String(42);    (42).toString();
Number("abc"); // NaN, no exception
int(3.9)         # 3, truncates toward zero
int("42")        # ValueError if invalid
str(42)
float("3.5")

Rule of thumb: Go and Java are strict (explicit conversions, errors on bad parses); JavaScript converts silently and gives NaN; Python converts only when you ask but raises exceptions on failure.

Booleans and truthiness

ValueGo ifJava ifJS ifPython if
0error (needs bool)errorfalsyfalsy
""errorerrorfalsyfalsy
nil / null / Noneerrorerrorfalsyfalsy
empty list/arrayerrorerrortruthyfalsy
empty object/maperrorerrortruthyfalsy
NaNn/an/afalsytruthy

Go and Java require an actual boolean in a condition. JS and Python allow any value, but disagree about empty collections - a classic bug when porting code.

JS falsy list: false, 0, -0, 0n, "", null, undefined, NaN.

Nothing: nil, null, undefined, None

Go gotcha: an interface holding a nil pointer is not == nil. The interface has a type, so it is non-nil.

Operators

GoJavaJavaScriptPython
Arithmetic+ - * / %+ - * / %+ - * / % **+ - * / // % **
Integer division7 / 2 == 37 / 2 == 3no integer type: Math.trunc(7/2)7 // 2 == 3 (floors), 7 / 2 == 3.5
Exponentmath.Pow(a, b)Math.pow(a, b)a ** ba ** b
Incrementi++ (statement only)i++, ++i (expressions)i++, ++ii += 1 (no ++)
Logical&& || !&& || !&& || ! and ??and or not
Ternarynone (use if)c ? a : bc ? a : ba if c else b
Bitwise& | ^ &^ << >>& | ^ ~ << >> >>>& | ^ ~ << >> >>> (32-bit!)& | ^ ~ << >>

Modulo of negatives differs: -7 % 3 is -1 in Go, Java and JS but 2 in Python (result takes the sign of the divisor).

Go has no ternary and no ++i: i++ is a statement, so x = i++ is illegal. This is deliberate to keep expressions simple.

Equality and comparison

GoJavaJavaScriptPython
Value equality== (for comparable types).equals() for objects, == for primitives=== (strict)==
Identitypointer comparison p1 == p2== on referencesObject.is, === on objectsis
Loose equalitynonenone== (coerces: "1" == 1)none
0 == "";        // true   (avoid ==)
0 === "";       // false  (use ===)
NaN === NaN;    // false  (use Number.isNaN)
[] == false;    // true   (!)
Integer a = 127, b = 127;  a == b;   // true  (cached small values)
Integer c = 1000, d = 1000; c == d;  // false (different objects!) - use equals
"hi" == new String("hi");            // false; use "hi".equals(...)
[1, 2] == [1, 2]    # True  (value)
[1, 2] is [1, 2]    # False (different objects)
1 < x < 10          # chained comparison works

Go can compare structs and arrays with == if all fields are comparable; slices, maps and functions can only be compared to nil.

Floating point

All four use IEEE 754 doubles: 0.1 + 0.2 != 0.3 everywhere. Use integers of cents or a decimal type for money (Java BigDecimal, Python decimal).

Quirks and gotchas

What is idiomatic

Checklist

Exercises

In each language:

  1. Declare an integer, a float, a string, a boolean and a "nothing" value. Print each with its type (%T, getClass(), typeof, type()).
  2. Parse the string "42abc" to an integer. Handle the failure appropriately in each language (error value, exception, NaN, ValueError).
  3. Compute 0.1 + 0.2 and print it; then print the result of 7 / 2 and -7 % 3. Write down all four answers before running.
  4. Overflow a 32-bit integer on purpose (not possible in Python - try 2**100 instead) and observe what happens.
  5. Write a function isEmpty(x) that treats 0, "", empty collections and nothing as "empty". Notice how much work that takes in Go/Java and how little in Python.