\( \newcommand\D{\mathrm{d}} \newcommand\E{\mathrm{e}} \newcommand\I{\mathrm{i}} \newcommand\bigOh{\mathcal{O}} \newcommand{\cat}[1]{\mathbf{#1}} \newcommand\curl{\vec{\nabla}\times} \newcommand{\CC}{\mathbb{C}} \newcommand{\NN}{\mathbb{N}} \newcommand{\QQ}{\mathbb{Q}} \newcommand{\RR}{\mathbb{R}} \newcommand{\ZZ}{\mathbb{Z}} \)
UP | HOME

Coding Conventions - SML

Table of Contents

1. Naming Conventions

1.1. Prefixes

  • is prefixes predicates, e.g., isNegative

1.2. Cases (snake, upper, title, Pascal, etc.)

Chris Okasaki uses the following conventions:

  • User-defined types (and type constructors) are written in PascalCase
  • Datatype constructors in algebraic datatypes are written using PascalCase
  • Function names use camelCase
  • Variable names use snake_case

1.2.1. Modules, Signatures, Functors

  • Signatures are written SCREAMING_SNAKE_CASE
  • Functors are written in PascalCase
  • Structures are written in PascalCase

1.2.2. Filenames

  • Functors are in functor-name.fun or functor-name.sml
  • Signatures are in signature-name.sig
  • Structures are in structure-name.sml
  • Everything else (i.e., the default situation): file-name.sml

Only MLton separates out functors into functor-name.fun

2. Compiling

This is unpleasant, since each compiler has its own conventions. And it seems only Poly/ML really runs on a Raspberry Pi.

Poly/ML expects a fun main () = ... somewhere. When compiled, Poly/ML will interpret the main() function as the entry point.

For MLton, I believe, it will only run a function if it's executed. That is to say, we need to have val _ = main(); somewhere, if we want MLton to execute main(). But Poly/ML doesn't like this, and won't compile it happily.

If we want to support compiling with both Poly/ML and MLton, we need to also have main.sml file which consists of a single line:

(* main.sml *)

val _ = main ();

MLton will compile everything, including main.sml, while Poly/ML will just omit main.sml from compilation.

2.1. Build Scripts: Using the Standard Library

We need to tell the SML compiler to include the SML standard basis library. Each compiler has its own way to do this.

MLton has .mlb basis files to guide compilation, but "basis files" are unique/idiosyncratic to MLton. (MLKit supports .mlb files, but only a fragment of MLton's syntax is supported.)

Poly/ML has build scripts (e.g., polybuild).

SML/NJ has its own compilation manager (the aptly named "Compilation Manager") which examines .cm files to guide compilation.

HaMLet does not seem to have any build script (well, it's an interpreter, so you'd need to use files).

I won't pretend to understand Moscow ML's build process.

3. Typesetting in LaTeX

The following is what Chris Okasaki does in his book, Purely Functional Data Structures, and it works well.

Reserved keywords (like of, struct, fun, let, in, val, etc.) are written in bold.

Datatype constructors are written in small-caps.

Signatures are typeset by:

  • Step 1: convert SCREAMING_SNAKE_CASE into Pascal snake case Screaming_Snake_Case
  • Step 2: delete underscores, converting Screaming_Snake_Case into ScreamingSnakeCase
  • Step 3: typeset the result of step 2 in small-caps.

Type variables (the 'a in declarations like datatype 'a Foo), including the apostrophe, are converted to lowercase Greek counterparts (so we would get: "datatype \(\alpha\) Foo").

Comments are italicized or slanted.

Everything else seems to be typeset "as expected".

A kludge which accomplishes the typesetting for signatures (the only downside is that I need to manually add signature names to the morekeywords={[2]...} parameter):

\documentclass{article}
\usepackage{mfirstuc}
\makeatletter


% I need to do something like
% https://tex.stackexchange.com/a/448770/14751
% to transform the "\lst@um_" back to "_"

\def\@Screaming@Snake@To@Pascal@Case#1\_#2\end@Screaming@Snake@To@Pascal@Case{%
  \makefirstuc{\MakeLowercase{#1}}%
\ifx#2\@empty\else\ignorespaces\@Screaming@Snake@To@Pascal@Case#2\end@Screaming@Snake@To@Pascal@Case\fi}

\newcommand\ScreamingSnakeToPascalCase[1]{%
  \ifx\@empty#1\else{\normalfont\scshape\@Screaming@Snake@To@Pascal@Case#1\_\@empty\end@Screaming@Snake@To@Pascal@Case}\fi}

% https://tex.stackexchange.com/questions/439396/listings-highlight-a-prefixed-keyword-starting-with-a-single-quote
\makeatother

\usepackage{listings}

\makeatletter

\def\@setlststyle{%
  \unskip\edef\lt@temp{\unskip\noexpand\ScreamingSnakeToPascalCase{\expandafter\the\lst@token\unskip\relax\unskip}}%
  \unskip\global\lst@token=\expandafter{\lt@temp}%
  \unskip\the\lst@token
}

\begingroup
\catcode`\_=11

\gdef\scsty#1{
  \unskip\edef\lsttokens{\the\lst@token}
  \unskip\global\lst@token={}
  \unskip\expandafter\replaceUM\lsttokens\noexpand\lst@um_\relax\@end
  \unskip\unskip\@setlststyle
}

\gdef\appendall#1\@endAppend{\unskip
  \global\lst@token=\expandafter{\the\lst@token#1}
}
\gdef\replaceUM#1\lst@um_#2\@end{\unskip
  \if\relax\detokenize{#1}\relax
  \else
    \ifx\relax#2
      \appendall#1\@endAppend
    \else
      \appendall#1\_\@endAppend
      \replaceUM#2\@end
    \fi
  \fi
}
\endgroup

\makeatother

\def\haskWildcard{\kern0.06em \vbox{\hrule width .5em}}
\lstset{
  columns=flexible,
  alsoletter={_},
  basicstyle = {\ttfamily},
  keywordstyle = [2]\scsty,
  morekeywords = [2]{SOME,NONE,GREATER,EQUAL,LESS,STRETCH_GOAL},
  keywordstyle = {\bfseries\sffamily},
  morekeywords={abstype,and,andalso,as,case,do,datatype,else,end,%
       eqtype,exception,fn,fun,functor,handle,if,in,include,infix,%
       infixr,let,local,nonfix,of,op,open,orelse,raise,rec,sharing,sig,%
       signature,struct,structure,then,type,val,with,withtype,while},%
   sensitive,%
   morecomment=[n]{(*}{*)},%
   morestring=[d]",
   literate={JOB}{{\ttfamily\scshape Job}}3 {'a}{{$\alpha$}}1 {'b}{{$\beta$}}1
               {\ o\ }{{$\circ$}}3 {+}{{$+$}}1 {/}{{$/$}}1 {*}{{$*$}}1 {=}{{$=$\ }}1
               {>}{{$>$}}1 {<}{{$<$}}1
               {\\\\}{{\char`\\\char`\\}}1
               {->}{{$\rightarrow$}}2 {>=}{{$\geq$}}2 {<-}{{$\leftarrow$}}2
               {|}{{$\mid$\ }}1 {\ _}{{\ \haskWildcard}}2  
}
\begin{document}


\begin{lstlisting}
signature STRETCH_GOAL = sig
  val will_work : bool;
  val issued : bool option;
end;

fun giveRaise (SOME true) = true
  | giveRaise _ = false;
\end{lstlisting}
\end{document}

4. Emacs

SML-mode provides all the functionality we need. Just add to the init.el file:

(use-package sml-mode
  :ensure t
  :defer t)
(add-to-list 'auto-mode-alist '("\\.\\(sml\\|sig\\|fun\\)\\'" . sml-mode))

5. References

Last Updated 2022-01-14 Fri 11:30.