Syntax styles in programming languages
Up: Programming language design
This is a collection of different syntactical styles in programming languages. It’s woefully incomplete.
Directly relevant:
- PLDB
- Syntax Design (Ray Toal)
- Microfeatures I’d like to see in more languages (Hillel Wayne)
Other resources:
- Notes on a smaller Rust - Without boats, dreams dry up
- Producing HTML using string templates has always been the wrong solution
Comments
Single line comments:
-
// abc
(C, Java, …) -
# abc
(Ruby, bash, …) -
; abc
(Lisp family, …) -
% abc
(TeX) -
-- abc
(Lua)
Multi-line comments:
-
/* abc */
(C, Java, …) -
(* abc *)
(Pascal) -
"abc"
(Smalltalk)
Multi-line comments might not really be that useful.
Indentation and bracing
Braces (C, Java, … style):
stuff {
thing
}
do
–end
(Ruby style):
stuff do
thing
end
Explicitly naming the thing to end:
stuff
thing
end stuff
There’s also bash’s bizarre way of ending statements, with reverse keywords like elihw
and esac
, but that’s just silly.
Calls
Regular math-style or C-style function calls:
cos(pi)
plus(2, 3)
Haskell-style calls (without parentheses):
cos pi
plus 2 3
OO-style calls (Smalltalk style):
pi cos
2 plus: 3
OO-style calls (Ruby style, i.e. no parentheses needed):
pi.cos
2.plus(3)
This approach makes it non-trivial to get hold of the method as an object.
OO-style calls (JavaScript style):
pi.cos()
2.plus(3)
Getting hold of the method itself is simple in this case.
The question of how the method is bound remains. If you get the method, is it still bound to the original object, or not? If not, how do you call it? Do you pass self
as the first argument, perhaps?
Concatenative style (like Forth and Factor):
pi cos
2 3 plus
To do: Describe named parameters
Function definitions
No braces, implicit return:
fun greeting()
"Hello!"
end
The parentheses, if no parameters are available, could be left out:
fun greeting
"Hello!"
end
Braces, explicit return:
fun greet() {
return "Hello!"
}
Braces, shorthand, implicit return:
fun greet() = "Hello!"
With positional parameters:
fun greet(name)
return "Hello, ${name}"
end
With default value, assigned using =
(Python style):
fun greet(name = "stranger")
return "Hello, ${name}"
end
With default value, assigned using :
(Ruby style):
fun greet(name: "stranger")
return "Hello, ${name}"
end
Using :
can conflict with type declarations.
Different parameter name when calling and inside function definition (like Swift):
func greet(person: String, from hometown: String) -> String {
// use `hometown` here
}
greet(person: "Jake", from: "Berlin")
Positional parameters can be opt-in, using _
:
func greet(_ person: String, from hometown: String) -> String {
// use `hometown` here
}
greet("Jake", from: "Berlin")
To do: Describe variable-argument definitions
Pointers
Dereferencing in C, C++, …:
*pointer
Dereferencing in Zig:
pointer.*
In Swift, an argument can be passed in to be mutated by specifying inout
on the parameter:
func play(game: inout Game) {
// …
}
In C#, on the caller side, ref
is used, but the parameter is the same:
Game Update(Game game, int guess) {
// …
}
Play(ref game);
Method definitions
To do: associated functions
To do: instance variables
To do: self (this instance), e.g. this.doSomething()
To do: self (this type), e.g. new thisClass
Strings
Double-quoted:
"Hello"
With interpolation, Ruby style:
"Hello, #{name}"
With interpolation, Python style:
f"Hello, {name}"
With interpolation, JavaScript style:
`Hello, ${name}`
With interpolation, Swift style:
"Hello, \(name)"
Characters
Ruby: "a"
or ?a
— there is no difference between characters and strings.
Crystal: 'a'
Iteration
I’m leaving out old-school C-style for loop iteration because I think it’s too old-school.
Dedicated for
construct:
for elem in iterable {
// …
}
Function:
iterable.each(func (elem) {
// …
})
Note that the above can’t use return
to break out of the loop. Smalltalk-style non-local returns would allow that, but it’d return from the function entirely:
"a strange way to get the first element of an iterable"
iterable do: [:elem | ^elem]
Ruby has blocks and functions, and thus has nonlocal returns and can break out of blocks. Ruby also has a ton of custom iterators, like #times
:
12.times do
# …
end
Structs
C:
struct Something {
Type myProp;
};
Crystal:
struct Point
property x, y
def initialize(@x : Int32, @y : Int32)
end
end
Crystal has explicit initializer. The initializer syntax is nicer than Ruby’s, in that it removes some duplication by allowing params to be declared as ivars.
Zig:
const Point = struct {
x: f32,
y: f32,
};
const p = Point {
.x = 0.12,
.y = 0.34,
};
Using the same syntax for types (const Point = struct …
) as for values (const p = Point …
) is interesting. Maybe confusing, though?
Nim:
type
Animal* = object
name*, species*: string
age: int
var carl: Animal
carl = Animal(name : "Carl",
species : "L. glama",
age : 12)
I like how types can be used as functions to construct instances.
Ranges
-
1..100
- In Ruby, is an inclusive range (contains 100)
- In Rust, is an exclusive range (does not contain 100)
-
1..=100
- In Rust, is an inclusive range (contains 100)
-
1...100
- In Ruby, is an exclusive range (does not contain 100)
-
range(1, 100)
- In Python, is an exclusive range (does not contain 100)
Equality
Ugh! This one is complicated. Common ways to check for equality are:
==
===
====
-
equals()
(Java) oreql?()
(Ruby) -
equal?()
(in Ruby: identity, i.e. same object)
Different types of equality to take into account:
- Is this identical, i.e. same object?
- Are the types equal, and the values equal?
- Are the types different, but the values equal? (e.g.
1.0
and1
)
Ruby’s ===
is confusing (“case equality” is ambiguous and hard to explain).
See Equality in programming languages.
Discard return value
Some programming languages require a return value to be used, or error instead.
_ = myFunction()
C++ can use [[nodiscard]]
to require a value to be used, but it’s opt-in:
[[nodiscard]]
int update() {
// …
}
Nim uses the discard
keyword:
discard myFunction()
Pure functions and constant expressions
GCC allows adding __attribute__((const))
to claim that a function is pure (even if it is not!):
__attribute__((const))
auto update(Game game, int guess) -> Game {
// …
}
GCC also allows constexpr
to mark something as being able to be executed at compile time:
constexpr
auto update(Game game, int guess) -> Game {
// …
}
Rust supports const
functions, which are also able to be executed at compile time:
const fn update(game: Game, guess: int32) -> Game {
// …
}
Effects
Koka defines a handful of effect types, including:
-
total
(mathematically total function) -
exn
(may raise an exception) -
div
(may not terminate, i.e. may diverge) -
console
(may write to console) -
ndet
(non-deterministic)
Additionally, pure
is an alias for exn
and div
.
More:
alloc<h>
blocking
fsys
handled
handled1
local
net
read
write
local
scope
ui