In PHP, comments are written with a // prefix.

// Hello, Joe!

Multi line comments may be written like so:

 * Hello, Joe!

In PHP, above trait, interface, class, member, function declarations there can be docblocks like so:

 * a very special trait.
trait Foo {}

 * A Bar class
class Bar {}

 * A quux function.
 * @var string $str        String passed to quux
 * @return string          An unprocessed string
function quux(string $str) : string { return $str; }

Documentation blocks (docblocks) are extracted into generated API documentation.


In Gleam, comments are written with a // prefix.

// Hello, Joe!

Comments starting with /// are used to document the following function, constant, or type definition. Comments starting with //// are used to document the current module.

//// This module is very important.

/// The answer to life, the universe, and everything.
const answer: Int = 42

/// A main function
fn main() {}

/// A Dog type
type Dog {
  Dog(name: String, cuteness: Int)

// comments are not used while generating documentation files, while //// and /// will appear in them.


You can rebind variables in both languages.


$size = 50;
$size = $size + 100;
$size = 1;

In local scope PHP has no specific variable keyword. You choose a name and that’s it!

In class scope for property declaration PHP uses at least one related modifier keyword to create properties such as: public, private, protected, static or readonly (var is deprecated).


Gleam has the let keyword before its variable names.

let size = 50
let size = size + 100
let size = 1

Match operator


PHP supports basic, one directional destructuring (also called unpacking). Tuple of values can be unpacked and inner values can be assigned to left-hand variable names.

[$a, $b] = [1, 2];
// $a == 1
// $b == 2

[1 => $idx2] = ['foo', 'bar', 'quux'];
// $idx2 == 'bar'

["profession" => $job] = ['name' => 'Joe', 'profession' => 'hacker'];
// $job == 'hacker'


In Gleam, let and = can be used for pattern matching, but you’ll get compile errors if there’s a type mismatch, and a runtime error if there’s a value mismatch. For assertions, the equivalent let assert keyword is preferred.

let #(a, _) = #(1, 2)
// a = 1
// `_` matches 2 and is discarded

let assert [] = [1] // runtime error
let assert [y] = "Hello" // compile error, type mismatch

Asserts should be used with caution.

Variables type annotations


PHP is a dynamically typed language. Types are only checked at runtime and a variable can have different types in its lifetime.

PHP gradually introduced more and more type hints that are optional. The type information is accessible via get_type() at runtime.

These hints will mainly be used to inform static analysis tools like IDEs, linters, etc.

class Foo {
  private ?string $bar;

PHP’s array structure is an ordered map, as stated in the PHP manual, and it can be treated as an array, dictionary, etc. While creating arrays in PHP the type of its elements cannot be set explicitly and each element can be of a different type:

$someList = [1, 2, 3];
$someTuple = [1, "a", true];
$someMap = [0 => 1, "foo" => "bar", true => false];

Single variables cannot be type-annotated unless they are class or trait members.


In Gleam type annotations can optionally be given when binding variables.

let some_list: List(Int) = [1, 2, 3]
let some_string: String = "Foo"

Gleam will check the type annotation to ensure that it matches the type of the assigned value. It does not need annotations to type check your code, but you may find it useful to annotate variables to hint to the compiler that you want a specific type to be inferred.



In PHP, you can define functions with the function keyword. One or many return keywords are optional.

function hello($name = 'Joe') : string
  if ($name == 'Joe') {
    return 'Welcome back, Joe!';
  return "Hello $name";

function noop()
  // Will automatically return NULL

Anonymous functions returning a single expression can also be defined and be bound to variables.

$x = 2;
$phpAnonFn = function($y) use ($x) { return $x * $y; }; // Creates a new scope
$phpAnonFn(3); // 6
$phpArrowFn = fn ($y) => $x * $y; // Inherits the outer scope
$phpArrowFn(3); // 6


Gleam’s functions are declared like so:

fn sum(x, y) {
  x + y

Gleam’s anonymous functions have the same basic syntax.

let mul = fn(x, y) { x * y }
mul(1, 2)

A difference between PHP’s and Gleam’s anonymous functions is that in PHP they create a new local scope, in Gleam they close over the local scope, aka create a copy and inherit all variables in the scope. This means that in Gleam you can shadow local variables within anonymous functions but you cannot influence the variable bindings in the outer scope. This is different for PHP’s arrow functions where they inherit the scope like Gleam does.

The only difference between module functions and anonymous functions in Gleam is that module functions heads may also feature argument labels, like so:

// In some module.gleam
pub fn distance(from x: Int, to y: Int) -> Int {
  x - y |> int.absolute_value()
// In some other function
distance(from: 1, to: -2) // 3

Exporting functions


In PHP, top level functions are exported by default. There is no notion of private module-level functions.

However at class level, all properties are public, by default.

class Foo {
  static $bar = 5;
  private $quux = 6;

  static function batz() {
    return "Hello Joe!";

  private static function kek() {
    return "Hello Rasmus!";
echo Foo::$bar; // 5
echo Foo::$quux; // Error
echo Foo::batz(); // "Hello Joe"
echo Foo::kek(); // Error


In Gleam, functions are private by default and need the pub keyword to be marked as public.

// this is public
pub fn sum(x, y) {
  x + y

// this is private
fn mul(x, y) {
  x * y


Global functions may exist in a global scope, and to execute functions or create objects and invoke methods at some point they have to be called from the global scope. Usually there is one index.php file whose global scope acts as if it was the main() function.


Gleam does not support a global scope. Instead Gleam code is either representing a library, which can be required as a dependency, and/or it represents an application having a main module, whose name must match to the application name and within that main()-function which will be called via either gleam run or when the is executed.

In contrast to PHP, where any PHP file can contain a global scope that can be invoked by requiring the file, in Gleam only code that is within functions can be invoked.

On the Beam, Gleam code can also be invoked from other Erlang code, or it can be invoked from browser’s JavaScript, Deno or NodeJS runtime calls.

Function type annotations


Type hints can be used to optionally annotate function arguments and return types.

Discrepancies between type hints and actual values at runtime do not prevent interpretation of the code. Static code analysers (IDE tooling, type checkers like phpstan) will be required to detect those errors.

function sum(int $x, int $y) : int {
    return $x + $y;

function mul(int $x, int $y) : bool {
    # no errors from the interpreter.
    return $x * $y;


Functions can optionally have their argument and return types annotated in Gleam. These type annotations will always be checked by the compiler and throw a compilation error if not valid. The compiler will still type check your program using type inference if annotations are omitted.

fn add(x: Int, y: Int) -> Int {
  x + y

fn mul(x: Int, y: Int) -> Bool {
  x * y // compile error, type mismatch

Referencing functions


As long as functions are in scope they can be assigned to a new variable. As methods or static functions classes, functions can be accessed via $this->object_instance_method() or self::static_class_function().

Other than that only anonymous functions can be moved around the same way as other values.

$doubleFn = function($x) { return $x + $x; };
// Some imaginary pushFunction
pushFunction($queue, $doubleFn);

However in PHP it is not possible to pass around global, class or instance functions as values.


Gleam has a single namespace for constants and functions within a module, so there is no need for a special syntax to assign a module function to a variable.

fn identity(x) {

fn main() {
  let func = identity

Labelled arguments

Both PHP and Gleam have ways to give arguments names and in any order.


When calling a function, arguments can be passed:

// Some imaginary replace function
function replace(string $each, string $with, string $inside) {
  // TODO implementation
// Calling with positional arguments:
replace(",", " ", "A,B,C");
// Calling with named arguments:
replace(inside: "A,B,C", each: ",", with: " ");


In Gleam arguments can be given a label as well as an internal name. Contrary to PHP, the name used at the call-site does not have to match the name used for the variable inside the function.

pub fn replace(inside str, each pattern, with replacement) {
replace(",", " ", "A,B,C")
replace(inside: "A,B,C", each: ",", with: " ")

There is no performance cost to Gleam’s labelled arguments as they are optimised to regular function calls at compile time, and all the arguments are fully type checked.


Operator PHP Gleam Notes
Equal == == In Gleam both values must be of the same type
Strictly equal to === == Comparison in Gleam is always strict. (see note for PHP)
Reference equality instanceof   True only if an object is an instance of a class
Not equal != != In Gleam both values must be of the same type
Not equal !== != Comparison in Gleam is always strict (see note for PHP)
Greater than > > In Gleam both values must be Int
Greater than > >. In Gleam both values must be Float
Greater or equal >= >= In Gleam both values must be Int
Greater or equal >= >=. In Gleam both values must be Float
Less than < < In Gleam both values must be Int
Less than < <. In Gleam both values must be Float
Less or equal <= <= In Gleam both values must be Int
Less or equal <= <=. In Gleam both values must be Float
Boolean and && && In Gleam both values must be Bool
Logical and &&   Not available in Gleam
Boolean or || || In Gleam both values must be Bool
Logical or ||   Not available in Gleam
Boolean not xor   Not available in Gleam
Boolean not ! ! In Gleam both values must be Bool
Add + + In Gleam both values must be Int
Add + +. In Gleam both values must be Float
Subtract - - In Gleam both values must be Int
Subtract - -. In Gleam both values must be Float
Multiply * * In Gleam both values must be Int
Multiply * *. In Gleam both values must be Float
Divide / / In Gleam both values must be Int
Divide / /. In Gleam both values must be Float
Remainder % % In Gleam both values must be Int
Concatenate . <> In Gleam both values must be String
Pipe -> |> Gleam’s pipe can chain function calls. See note for PHP

Notes on operators



In PHP, constants can only be defined within classes and traits.

class TheQuestion {
  public const theAnswer = 42;
echo TheQuestion::theAnswer; // 42


In Gleam constants can be created using the const keyword.

// the_question.gleam module
const the_answer = 42

pub fn main() {

They can also be marked public via the pub keyword and will then be automatically exported.



PHP blocks are always associated with a function / conditional / loop or similar declaration. Blocks are limited to specific language constructs. There is no way to create multi-line expressions blocks like in Gleam.

Blocks are declared via curly braces.

function a_func() {
  // A block starts here
  if ($foo) {
    // A block here
  } else {
    // A block here
  // Block continues


In Gleam curly braces, { and }, are used to group expressions.

pub fn main() {
  let x = {
  // Braces are used to change precedence of arithmetic operators
  let y = x * {x + 10}

Unlike in PHP, in Gleam function blocks are always expressions, so are case blocks or arithmetic sub groups. Because they are expressions they always return a value.

For Gleam the last value in a block’s expression is always the value being returned from an expression.

Data types


In PHP strings are stored as an array of bytes and an integer indicating the length of the buffer. PHP itself has no information about how those bytes translate to characters, leaving that task to the programmer. PHP’s standard library however features a bunch of multi-byte compatible functions and conversion functions between UTF-8, ISO-8859-1 and further encodings.

PHP strings allow interpolation.

In Gleam all strings are UTF-8 encoded binaries. Gleam strings do not allow interpolation, yet. Gleam however offers a string_builder via its standard library for performant string building.


$what = 'world';
'Hellø, world!';
"Hellø, ${what}!";


"Hellø, world!"


Tuples are very useful in Gleam as they’re the only collection data type that allows mixed types in the collection.


PHP does not really support tuples, but its array type can easily be used to mimick tuples. Unpacking can be used to bind a name to a specific value of the tuple.

$myTuple = ['username', 'password', 10];
[$_, $pwd, $_] = $myTuple;
echo $pwd; // "password"
// Direct index access
echo $myTuple[0]; // "username"


let my_tuple = #("username", "password", 10)
let #(_, pwd, _) = my_tuple
io.print(pwd) // "password"
// Direct index access
io.print(my_tuple.0) // "username"


Arrays in PHP are allowed to have values of mixed types, but not in Gleam.


PHP does not feature special syntax for list handling.

$list = [2, 3, 4];
$head = array_slice($list, 0, 1)[0];
$tail = array_slice($list, 1);
# $head == 2
# $tail == [3, 4]
$arr = array_merge($tail, [1.1]);
# $arr == [3, 4, 1.1]


Gleam has a cons operator that works for lists destructuring and pattern matching. In Gleam lists are immutable so adding and removing elements from the start of a list is highly efficient.

let list = [2, 3, 4]
let list = [1, ..list]
let [1, second_element, ..] = list
[1.0, ..list] // compile error, type mismatch


In PHP, the array type can also be treated as a dictionary and can have keys of any type as long as:

In Gleam, dicts can have keys and values of any type, but all keys must be of the same type in a given dict and all values must be of the same type in a given dict. The type of key and value can differ from each other.

There is no dict literal syntax in Gleam, and you cannot pattern match on a dict. Dicts are generally not used much in Gleam, custom types are more common.


["key1" => "value1", "key2" => "value2"]
["key1" => "1", "key2" => 2]


import gleam/dict

dict.from_list([#("key1", "value1"), #("key2", "value2")])
dict.from_list([#("key1", "value1"), #("key2", 2)]) // Type error!


PHP and Gleam both support Integer and Float. Integer and Float sizes for both depend on the platform: 64-bit or 32-bit hardware and OS and for Gleam JavaScript and Erlang.


While PHP differentiates between integers and floats it automatically converts floats and integers for you, removing precision or adding floating point decimals.

1 / 2 // 0.5


1 / 2 // 0
1.5 + 10 // Compile time error

You can use the gleam standard library’s int and float modules to convert between floats and integers in various ways including rounding, floor, ceiling and many more.

Flow control


case is how flow control is done in Gleam. It can be seen as a switch statement on steroids. It provides a terse way to match a value type to an expression. It is also used to replace if/else statements.


PHP features 3 different expressions to achieve similar goals:

function http_error_impl_1($status) {
  if ($status === 400) {
      return "Bad request";
  } else if ($status === 404) {
      return "Not found";
  } else if ($status === 418) {
      return "I'm a teapot";
  } else {
    return "Internal Server Error";

function http_error_impl_2($status) {
  switch ($status) {
    case "400": // Will work because switch ($status) compares non-strict as in ==
      return "Bad request";
      break; // Not strictly required here, but combined with weak typing multiple cases could be executed if break is omitted
    case 404:
      return "Not found";
    case 418:
      return "I'm a teapot";
      return "Internal Server Error";

function http_error_impl_3($status) {
  return match($status) { // match($status) compares strictly
    400 => "Bad request",
    404 => "Not found",
    418 => "I'm a teapot",
    default => "Internal Server Error"


The case operator is a top level construct in Gleam:

case some_number {
  0 -> "Zero"
  1 -> "One"
  2 -> "Two"
  n -> "Some other number" // This matches anything

As all expressions the case expression will return the matched value.

They can be used to mimick if/else or if/elseif/else, with the exception that any branch must return unlike in PHP, where it is possible to mutate a variable of the outer block/scope and not return at all.

let is_status_within_4xx = status / 400 == 1
case status {
  400 -> "Bad Request"
  404 -> "Not Found"
  _ if is_status_within_4xx -> "4xx" // This works as of now
  // status if status / 400 == 1 -> "4xx" // This will work in future versions of Gleam
  _ -> "I'm not sure"

if/else example:

case is_admin {
  True -> "allow access"
  False -> "disallow access"

if/elseif/else example:

case True {
  _ if is_admin == True -> "allow access"
  _ if is_confirmed_by_mail == True -> "allow access"
  _ -> "deny access"

Exhaustiveness checking at compile time, which is in the works, will make certain that you must check for all possible values. A lazy and common way is to check of expected values and have a catchall clause with a single underscore _:

case scale {
  0 -> "none"
  1 -> "one"
  2 -> "pair"
  _ -> "many"

The case operator especially coupled with destructuring to provide native pattern matching:

case xs {
  [] -> "This list is empty"
  [a] -> "This list has 1 element"
  [a, b] -> "This list has 2 elements"
  _other -> "This list has more than 2 elements"

The case operator supports guards:

case xs {
  [a, b, c] if a >. b && a <=. c -> "ok"
  _other -> "ko"

…and disjoint union matching:

case number {
  2 | 4 | 6 | 8 -> "This is an even number"
  1 | 3 | 5 | 7 -> "This is an odd number"
  _ -> "I'm not sure"


In Gleam most functions, if not all, are data first, which means the main data value to work on is the first argument. By this convention and the ability to specify the argument to pipe into, Gleam allows writing functional, immutable code, that reads imperative-style top down, much like unix tools and piping.


PHP does not offer pipes but it can chain calls by making functions return objects which in turn ship with their list of methods.

// Imaginary PHP code
(new Session($request))
  ->setSuccessFlash('Logged in successfully!')
  ->setFailureFlash('Failed to login!')


// Imaginary Gleam code
|> session.authorize()
|> flash.set_success_flash('Logged in successfully!')
|> flash.set_failure_flash('Failed to login!')
|> response.redirect_to_requested_url()

Despite being similar to read and comprehend, the PHP code creates a session object, and calls the authorize method of the session object: That session object then returns another object, say an AuthorizedUser object - you don’t know by looking at the code what object gets returned. However you know it must implement a setSuccessFlash method. At the last step of the chain redirect is called on an object returned from setFailureFlash.

In the Gleam code the request data is piped into’s first argument and that return value is piped further down. It is readability sugar for:

      'Logged in successfully!'
    'Failed to login!'


Error management is approached differently in PHP and Gleam.


PHP uses the notion of exceptions to interrupt the current code flow and pop up the error to the caller.

An exception is raised using the keyword throw.

function aFunctionThatFails() {
  throw new RuntimeException('an error');

The callee block will be able to capture any exception raised in the block using a try/catch set of blocks:

// callee block
try {
    echo 'this line will be executed and thus printed';
    echo 'this line will not be executed and thus not printed';
} catch (Throwable $e) {
    var_dump(['doing something with the exception', $e]);


In contrast in Gleam, errors are just containers with an associated value.

A common container to model an operation result is Result(ReturnType, ErrorType).

A Result is either:

Handling errors actually means to match the return value against those two scenarios, using a case for instance:

case parse_int("123") {
  Ok(i) -> io.println("We parsed the Int")
  Error(e) -> io.println("That wasn't an Int")

In order to simplify this construct, we can use the try keyword that will:

let a_number = "1"
let an_error = Error("ouch")
let another_number = "3"

try int_a_number = parse_int(a_number)
try attempt_int = parse_int(an_error) // Error will be returned
try int_another_number = parse_int(another_number) // never gets executed

Ok(int_a_number + attempt_int + int_another_number) // never gets executed

Type aliases

Type aliases allow for easy referencing of arbitrary complex types. PHP does not have this feature, though either regular classes or static classes can be used to design custom types and class definitions in take can be aliased using class_alias().


A simple variable can store the result of a compound set of types.

class Point {
  // Can act as an opaque type and utilize Point
  // Can be class_aliased to Coordinate

class Triangle {
  // Can act as an opaque type definition and utilize Point


The type keyword can be used to create aliases.

pub type Headers =
  List(#(String, String))

Custom types


Custom type allows you to define a collection data type with a fixed number of named fields, and the values in those fields can be of differing types.


PHP uses classes to define user-defined, record-like types. Properties are defined as class members and initial values are generally set in the constructor.

By default the constructor does not provide base initializers in the constructor so some boilerplate is needed:

class Person {
  public function __construct(public string $name, public int $age) { }
$person = new Person(name: "Joe", age: 40);
$person->name; // Joe


Gleam’s custom types can be used as structs. At runtime, they have a tuple representation and are compatible with Erlang records (or JavaScript objects).

type Person {
  Person(name: String, age: Int)

let person = Person(name: "Joe", age: 40)
let name =

An important difference to note is there is no Java-style object-orientation in Gleam, thus methods can not be added to types. However opaque types exist, see below.


PHP generally does not support unions with a few exceptions such as:

In Gleam functions must always take and receive one type. To have a union of two different types they must be wrapped in a new custom type.


class Foo {
  public ?string $aStringOrNull;


type IntOrFloat {

fn int_or_float(X) {
  case X {
    True -> AnInt(1)
    False -> AFloat(1.0)

Opaque custom types

In PHP, constructors can be marked as private and opaque types can either be modelled in an immutable way via static classes or in a mutable way via a factory pattern.

In Gleam, custom types can be defined as being opaque, which causes the constructors for the custom type not to be exported from the module. Without any constructors to import other modules can only interact with opaque types using the intended API.


class PointObject
  private function __construct(public int $x, public int $y) {

  public static function spawn(int $x, int $y) {
    if ($x >= 0 && $x <= 99 && $y >= 0 && $y <= 99) {
      return new self($x, $y);
    return false;
PointObject::spawn(1, 2); // Returns a Point object

This requires mutation, but prohibits direct property changes.

PHP allows to skip object mutation by using static classes:

class PointStruct
  public static function spawn(int $x, int $y) {
    if ($x >= 0 && $x <= 99 && $y >= 0 && $y <= 99) {
      return compact('x', 'y') + ['struct' => __CLASS__];
    return false;
PointStruct::spawn(1, 2); // Returns an array managed by PointStruct

However PHP will in this case not prohibit the direct alteration the returned structure, like Gleam’s custom types can.


// In the point.gleam opaque type module:
pub opaque type Point {
  Point(x: Int, y: Int)

pub fn spawn(x: Int, y: Int) -> Result(Point, Nil) {
  case x >= 0 && x <= 99 && y >= 0 && y <= 99 {
    True -> Ok(Point(x: x, y: y))
    False -> Error(Nil)

// In the main.gleam module
pub fn main() {
  assert Ok(point) = Point.spawn(1, 2)



PHP does not feature modules, but many other containers such as classes, traits and interfaces. Historically a single file can contain many classes, traits and interfaces one after another, though it is best practise to only contain one such declaration per file.

Using PHP namespaces, these can be placed in a registry that does not need to map to the source code file system hierarchy, but by convention should.

In src/Foo/Bar.php:

// Anything declared in this file will be inside namespace Foo
namespace Foo;

// Creation of (static) class Bar in Foo, thus as Foo/Bar
class Bar {
  public static function identity($x) {
    return $x;

Making the static class available in the local scope and calling the function index.php (aka PHP’s main function):

// After auto-loading has happened
use Foo\Bar;

Bar::identity(1); // 1


Coming from PHP the closest thing PHP has that are similar to Gleam’s modules are static classes: Collections of functions and constants grouped into a static class.

In comparison Gleam modules can also contain custom types.

A gleam module name corresponds to its file name and path.

Since there is no special syntax to create a module, there can be only one module in a file and since there is no way name the module the filename always matches the module name which keeps things simple and transparent.

In /src/foo/bar.gleam:

// Creation of module function identity
// in module bar
pub fn identity(x) {

Importing the bar module and calling a module function:

// In src/main.gleam
import foo/bar // if foo was in a directory called `lib` the import would be `lib/foo/bar`.

pub fn main() {
  bar.identity(1) // 1



PHP features ways to load arbitrary PHP code: require, include and autoload such as spl_autoload_register. Once class paths are known and registered for autoloading, they can brought into the scope of a file by using the usestatement which is part of PHP’s namespacing. Also see

Inside src/Nasa/MoonBase.php

// Makes available src/nasa/RocketShip.php
use Nasa\RocketShip;

class MoonBase {
  public static function exploreSpace() {


Imports are relative to the app src folder.

Modules in the same directory will need to reference the entire path from src for the target module, even if the target module is in the same folder.

Inside module src/nasa/moon_base.gleam:

// imports module src/nasa/rocket_ship.gleam
import nasa/rocket_ship

pub fn explore_space() {

Named imports


PHP features namespaces which can be used to rename classes when they clash:

// Source files must first be added to the auto-loader
use Unix\Cat;
use Animal\Cat as Kitty;
// Cat and Kitty are available


Gleam has as similar feature:

import unix/cat
import animal/cat as kitty
// cat and kitty are available

This may be useful to differentiate between multiple modules that would have the same default name when imported.

Unqualified imports


use Animal\Cat\{
  function stroke
$kitty = new Cat(name: "Nubi");


import animal/cat.{

pub fn main() {
  let kitty = Cat(name: "Nubi")

Importing common types such as gleam/order.{Lt, Eq, Gt} or gleam/option.{Some,None} can be very helpful.


To iterate a few foundational differences:

  1. Programming model: Java-style object-orientation VS functional immutable programming
  2. Guarantees: weak dynamic typing VS strong static typing
  3. Runtime model: request-response script VS Erlang/OTP processes
  4. Error handling: exceptions VS result type
  5. Language reach

Programming model

Guarantees and types

Runtime model

Error handling

Language reach