Modules
A module is the unit of code organisation on the BEAM: a named bundle of functions, where only the ones you export are callable from outside. The shared task is a tiny Math module with double/1 and square/1, called from another scope. Notice how Erlang and LFE list exports explicitly with -export/(export ...), Elixir flips the default by marking private functions with defp, statically typed Gleam exposes a function simply by writing pub, and Lua (Luerl) - which has no module keyword - returns a table of functions instead.
-module(math_utils).
%% Only the listed name/arity pairs are visible outside.
-export([double/1, square/1]).
double(X) -> X * 2.
square(X) -> X * X.
%% From another module you qualify the call with Module:Fun.
%% math_utils:double(21). => 42
%% math_utils:square(9). => 81
An Erlang module is one .erl file named by -module, and -export([double/1, square/1]) is the allowlist of name/arity pairs that callers reach with the math_utils:double(...) syntax; anything not exported stays private.
defmodule Math do
# Public by default: `def` is exported, `defp` is private.
def double(x), do: x * 2
def square(x), do: x * x
end
# Call from outside with the full module name.
Math.double(21)
# => 42
Math.square(9)
# => 81
Elixir inverts Erlang's rule: every def is public, so you mark internal helpers with defp; callers use the dotted Math.double/1 form on the module's alias.
import gleam/io
import gleam/int
// `pub` exports a function from this module; plain `fn` is private.
pub fn double(x: Int) -> Int {
x * 2
}
pub fn square(x: Int) -> Int {
x * x
}
pub fn main() {
// In Gleam the file is the module; you import it elsewhere by name.
io.println(int.to_string(double(21)))
io.println(int.to_string(square(9)))
// prints "42" then "81"
}
In Gleam the file is the module, and visibility is just the pub keyword on each typed function; another module would import math and call math.double(21) with full type checking and no exceptions.
(defmodule math-utils
;; export lists each function as (name arity).
(export (double 1) (square 1)))
(defun double (x) (* x 2))
(defun square (x) (* x x))
;; From elsewhere, call with (module:fun args ...).
;; (math-utils:double 21) => 42
;; (math-utils:square 9) => 81
LFE compiles to the same BEAM module as Erlang, so (export (double 1) (square 1)) names the visible name/arity pairs and outside callers use the (math-utils:double 21) colon-qualified form.
-- Lua has no module keyword; a "module" is just a table you return.
local Math = {}
function Math.double(x)
return x * 2
end
function Math.square(x)
return x * x
end
return Math
-- Elsewhere: local Math = require("math_utils")
-- print(Math.double(21)) --> 42
-- print(Math.square(9)) --> 81
Lua builds a module by hand as a table of functions and returning it; visibility is by convention - only what you attach to Math is reachable after a caller does require("math_utils"), while local helpers stay hidden.