Skip to content

Commit fc8b0bc

Browse files
committed
A section on the module system.
1 parent f62a0fa commit fc8b0bc

File tree

2 files changed

+247
-1
lines changed

2 files changed

+247
-1
lines changed

futhark.tex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
val,
2222
while,
2323
with,
24+
module,
2425
},
2526
sensitive=true, % keywords are not case-sensitive
2627
morecomment=[l]{--}, % l is for line comment

main.tex

Lines changed: 246 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1139,7 +1139,251 @@ \subsection{When To Use In-Place Updates}
11391139
\section{Modules}
11401140
\label{sec:modules}
11411141

1142-
Yet to be written...
1142+
When most programmers think of module systems, they think of rather
1143+
utilitarian systems for namespace control and splitting programs
1144+
across multiple files. And in most languages, the module system is
1145+
indeed little more than this. But in Futhark, we have adopted an
1146+
ML-style higher-order module system that permits \textit{abstraction}
1147+
over modules. The module system is not just a method for organising
1148+
Futhark programs, but also the sole facility for writing generic code.
1149+
1150+
\subsection{Simple Modules}
1151+
1152+
At the most basic level, a \textit{module} (called a \textit{struct}
1153+
in Standard ML) is merely a collection of declarations
1154+
1155+
\begin{lstlisting}
1156+
module AddI32 = {
1157+
type t = i32
1158+
fun add (x: t) (y: t): t = x + y
1159+
val zero: t = 0
1160+
}
1161+
\end{lstlisting}
1162+
1163+
Now, \texttt{AddI32.t} is an alias for the type \texttt{i32}, and
1164+
\texttt{Addi32.add} is a function that adds two values of type
1165+
\texttt{i32}. The only peculiar thing about this notation is the
1166+
equal sign before the opening brace. The declaration above is
1167+
actually a combination of a *module binding*
1168+
1169+
\begin{lstlisting}
1170+
module ADDI32 = ...
1171+
\end{lstlisting}
1172+
1173+
And a \textit{module expression}
1174+
1175+
\begin{lstlisting}
1176+
{
1177+
type t = i32
1178+
fun add (x: t) (y: t): t = x + y
1179+
val zero: t = 0
1180+
}
1181+
\end{lstlisting}
1182+
1183+
In this case, the module expression is just some declarations enclosed
1184+
in curly braces. But, as the name suggests, a module expression is
1185+
just some expression that returns a module. A module expression is
1186+
syntactically and conceptually distinct from a regular value
1187+
expression, but serves much the same purpose. The module language is
1188+
designed such that evaluation a module expression can always be done
1189+
at compile time.
1190+
1191+
Apart from a sequence of declarations, a module expression can also be
1192+
merely the name of another module
1193+
1194+
\begin{lstlisting}
1195+
module Foo = AddInt32
1196+
\end{lstlisting}
1197+
1198+
Now every name defined in \texttt{AddInt32} is also available in
1199+
\texttt{Foo}. At compile-time, only a single version of the
1200+
\texttt{add} function is defined.
1201+
1202+
\subsection{Module Types}
1203+
1204+
What we have seen so far is nothing more than a simple namespacing
1205+
mechanism. The ML module system only becomes truly powerful once we
1206+
introduce module types and parametric modules (in Standard ML, these
1207+
are called \textit{signatures} and \textit{functors}).
1208+
1209+
A module type is the counterpart to a value type. It describes which
1210+
names are defined, and as what. We can define a module type that
1211+
describes \texttt{AddInt32}
1212+
1213+
\begin{lstlisting}
1214+
module type Int32Adder = {
1215+
type t = i32
1216+
val add: t -> t -> t
1217+
val zero: t
1218+
}
1219+
\end{lstlisting}
1220+
1221+
As with modules, we have the notion of a \textit{module type expression}. In
1222+
this case, the module type expression is a sequence of \textit{specs}
1223+
enclosed in curly braces. A spec is a requirement of how some name
1224+
must be defined: as a value (including functions) of some type, as a
1225+
type abbreviation, or as an abstract type (which we will return to
1226+
later).
1227+
1228+
We can assert that some module implements a specific module type via
1229+
module type ascription
1230+
1231+
\begin{lstlisting}
1232+
module Foo = AddInt32 : Int32Adder
1233+
\end{lstlisting}
1234+
1235+
Syntactical sugar that allows us to move the module type to the left
1236+
of the equal sign makes a common case look smoother
1237+
1238+
\begin{lstlisting}
1239+
module AddInt32: Int32Adder = {
1240+
...
1241+
}
1242+
\end{lstlisting}
1243+
1244+
When we are ascribing a module with a module type, the module type
1245+
functions as a filter, removing anything not explicitly mentioned in
1246+
the module type
1247+
1248+
\begin{lstlisting}
1249+
module Bar = AddInt32 : { type t = int
1250+
val zero: t }
1251+
\end{lstlisting}
1252+
1253+
An attempt to access \texttt{Bar.add} will result in a compilation
1254+
error, as the ascription has hidden it. This is known as an
1255+
\textit{opaque} ascription, because it obscures anything not
1256+
explicitly mentioned in the module type. The module systems in
1257+
Standard ML and OCaml support both opaque and \textit{transparent}
1258+
ascription, but in Futhark we support only the former. This example
1259+
also demonstrates the use of an anonymous module type. Module types
1260+
work much like structural types known from e.g. Go ("compile-time duck
1261+
typing"), and are named only for convenience.
1262+
1263+
We can use type ascription with abstract types to hide the definition
1264+
of a type from the users of a module
1265+
1266+
\begin{lstlisting}
1267+
module Speeds: { type thing
1268+
val car: thing
1269+
val plane: thing
1270+
val futhark: thing
1271+
val speed: thing -> int } = {
1272+
type thing = int
1273+
1274+
val car: thing = 0
1275+
val plane: thing = 1
1276+
val futhark: thing = 2
1277+
1278+
fun speed (x: thing): int =
1279+
if x == car then 120
1280+
else if x == plane then 800
1281+
else if x == futhark then 10000
1282+
else 0 -- will never happen
1283+
}
1284+
\end{lstlisting}
1285+
1286+
The (anonymous) module type asserts that a distinct type \texttt{thing}
1287+
must exist, but does not mention its definition. There is no way for
1288+
a user of the \texttt{Speeds} module to do anything with a value of type
1289+
\texttt{Speeds.thing} apart from passing it to \texttt{Speeds.speed} (except
1290+
putting it in an array or tuple, or returning it from a function).
1291+
Its definition is entirely abstract. Furthermore, no values of type
1292+
\texttt{Speeds.thing} exist except those that are created by the \texttt{Speeds}
1293+
module.
1294+
1295+
\subsection{Parametric Modules}
1296+
1297+
While module types serve some purpose for namespace control and
1298+
abstraction, their most interesting use is in the definition of
1299+
parametric modules. A parametric module is conceptually
1300+
equivalent to a function. Where a function takes a value as input and
1301+
produces a value, a parametric module takes a module and produces a
1302+
module. For example, given a module type
1303+
1304+
\begin{lstlisting}
1305+
module type Monoid = {
1306+
type t
1307+
val add: t -> t -> t
1308+
val zero: t
1309+
}
1310+
\end{lstlisting}
1311+
1312+
We can define a parametric module that accepts a module satisfying
1313+
the \texttt{Monoid} module type, and produces a module containing a
1314+
function for collapsing an array
1315+
1316+
\begin{lstlisting}
1317+
module Sum(M: Monoid) = {
1318+
fun sum (a: []M.t): M.t =
1319+
reduce M.add M.zero a
1320+
}
1321+
\end{lstlisting}
1322+
1323+
There is an implied assumption here, which is not captured by the type
1324+
system: the function \texttt{add} must be associative and have
1325+
\texttt{zero} as its neutral element. These constraints are from the
1326+
parallel semantics of \texttt{reduce}, and the algebraic concept of a
1327+
\textit{monoid}. Note that in \texttt{Monoid}, no definition is given
1328+
of the type \texttt{t} - we only assert that there must be some type
1329+
\texttt{t}, and that certain operations are defined for it.
1330+
1331+
We can use the parametric module \texttt{Sum} thus
1332+
1333+
\begin{lstlisting}
1334+
module SumI32s = Sum(AddInt32)
1335+
\end{lstlisting}
1336+
1337+
We can now refer to the function \texttt{SumI32s.sum}, which has type
1338+
\texttt{[]i32 -> i32}. The type is only abstract inside the definition of
1339+
the parametric module. We can instantiate \texttt{Sum} again with
1340+
another module; this one anonymous
1341+
1342+
\begin{lstlisting}
1343+
module Prod64s = Sum({
1344+
type t = 64
1345+
fun (x: f64) (y: f64): f64 = x * y
1346+
fun zero: f64 = 1.0
1347+
})
1348+
\end{lstlisting}
1349+
1350+
The function \texttt{Prodf64s.sum} has type \texttt{[]f64 -> f64}, and computes
1351+
the product of an array of numbers (we should probably have picked a
1352+
more generic name than \texttt{sum} for this function).
1353+
1354+
Operationally, each application of a parametric module results in
1355+
its definition being duplicated and references to the module parameter
1356+
replace by references to the concrete module argument. This is quite
1357+
similar to how C++ templates are implemented. Indeed, parametric
1358+
modules can be seen as a simplified variant with no specialisation,
1359+
and with module types to ensure rigid type checking. In C++, a
1360+
template is type-checked when it is instantiated, whereas a
1361+
parametric module is type-checked when it is defined.
1362+
1363+
Parametric modules, like other modules, can contain more than one
1364+
declaration. This is useful for giving related functionality a common
1365+
abstraction, for example to implement linear algebra operations that
1366+
are polymorphic over the type of scalars. This example uses an
1367+
anonymous module type for the module parameter, and the \texttt{open}
1368+
declaration, which brings the names from a module into the current
1369+
scope
1370+
1371+
\begin{lstlisting}
1372+
module Linalg(M: {
1373+
type scalar
1374+
val zero: scalar
1375+
val add: scalar -> scalar -> scalar
1376+
val mul: scalar -> scalar -> scalar
1377+
}) = {
1378+
open M
1379+
1380+
fun dotprod (xs: [n]scalar) (ys: [n]scalar): scalar =
1381+
reduce add zero (zipWith mul xs ys)
1382+
1383+
fun matmul (xss: [n][p]scalar) (yss: [p][m]scalar): [n][m]scalar =
1384+
map (\xs -> map (dotprod xs) (transpose yss)) xss
1385+
}
1386+
\end{lstlisting}
11431387

11441388
\section{Benchmarking}
11451389
\label{sec:benchmarking}
@@ -2413,4 +2657,5 @@ \chapter{Tool References}
24132657
%%% Local Variables:
24142658
%%% mode: latex
24152659
%%% TeX-master: t
2660+
%%% TeX-engine: luatex
24162661
%%% End:

0 commit comments

Comments
 (0)