a side-by-side reference sheet
arithmetic and logic | strings | lists and tuples | data types | functions and scope | execution control | environment and i/o
modules | objects | repl | contact
standard ml (1990) | ocaml (1996) | scala (2003) | haskell (1990) | |
---|---|---|---|---|
version used | SML NJ 110.71 | 3.11.0 | 2.8 | 6.12.1 |
version | SML NJ displays version on startup | $ ocaml -version | $ scala -version | $ ghc --version |
interpreter | $ ocaml foo.ml | $ scala foo.scala | $ runghc foo.hs | |
shebang | none | #!/usr/bin/env ocaml | #!/bin/sh exec scala $0 $@ !# |
#!/usr/bin/env runghc |
bytecode compiler and interpreter | $ ocamlc foo.ml -o foo $ ocamlrun foo |
$ scalac Foo.scala $ scala Foo |
none | |
native compiler | $ ocamlopt foo.ml -o foo $ ./foo |
none | $ ghc -o foo foo.hs $ ./foo |
|
library which is always imported | Pervasives | Prelude | ||
statement terminator | ; | ;; | ; or sometimes newline | next line has equal or less indentation, or ; |
blocks | ( expr ; … ) | ( expr ; … ) begin expr ; … end |
{ } | offside rule or { } |
single line comment | none | none F#: // comment |
// comment | -- comment |
multi-line comment | (* comment another comment *) |
(* comment another comment *) |
/* comment another comment */ |
{- comment another comment -} |
hello world | print "hello world\n"; | print_string "hello world\n";; print_endline "hello world";; F#: printfn "hello world";; |
println("hello world") | main = putStrLn "hello world" |
value | val a = 3; | let a = 3;; | val a = 3 | a = 3 |
variable | val a = ref 3; a := 4; !a + 7; |
let a = ref 3;; a := 4;; !a + 7;; |
var a = 3 a = 4 a + 7 |
a <- return 3 |
unit | () | () | () | () |
conditional expression | val x = 3; if x < 0 then ~x else x; |
let x = 3;; if x < 0 then -x else x;; |
val x = -3 if (x < 0) -x else x |
x = 3 if x < 0 then -x else x |
branch type mismatch | compilation error: if true then "hello" else 3; |
compilation error: if true then "hello" else 3;; |
expression has type Any: if (true) { "hello" } else { 3 } |
compilation error: if True then "hello" else 3 |
null | NONE | None | null | Nothing |
type declaration | 1: Double | 1 :: Double | ||
arithmetic and logic | ||||
standard ml | ocaml | scala | haskell | |
true and false | true false | true false | true false | True False |
boolean type | bool | bool | Boolean | Bool |
logical operators | andalso orelse not | && || not | && || ! | && || not |
relational operators | = <> < > <= >= | = <> < > <= >= | == != < > <= >= | == /= < > <= >= |
numeric types | int real | int float other numeric types: int32 int64 nativeint |
types of numeric literals: Int Double other numeric types: Byte Short Float BigInt |
Integer Double |
integer operators | + - * div mod | + - * / mod mod is an infix operator. F# uses % instead of mod |
+ - * / % | + - * div rem div and rem are functions, not infix operators |
integer exponentiation | none | none | 2 ^ 3 | |
largest integer | int: 230-1 | Int: 231-1 Long: 263-1 BigInt: none |
none | |
integer overflow | Overflow exception | modular arithmetic | modular arithmetic for all types except BigInt | no overflow |
integer division by zero | raises Division_by_zero F#: System.DivideByZeroException |
java.lang.ArithmeticException | Exception: divide by zero | |
integer division, remainder, float division | 7 div 3 7 mod 3 real 7 / real 3 |
7 / 3 7 mod 3 float 7 /. float 3 |
7 / 3 7 % 3 (7: Double) / 3 |
div 7 3 rem 7 3 7 / 3 |
negative integer literal | ~4 | -4 | -4 | -4 |
float operators | + - * / | +. -. *. /. F#: + - * / |
+ - * / | + - * / |
float exponentiation | Math.pow (2.0,3.0); | 2.0 ** 3.0;; | math.pow(2.0,3.0) scala 2.7: Math.pow(2.0,3.0) |
2.0 ** 3.0 |
float functions | sqrt exp log sin cos tan asin acos atan atan2 | math.sqrt math.exp math.log math.sin math.cos math.tan math.asin math.acos math.atan math.atan2 use Math in scala 2.7 |
sqrt exp log sin cos tan asin acos atan atan2 | |
add integer and float | real 3 + 7.0; | float 3 +. 7.0;; | 3 + 7.0 | 3 + 7.0 |
arithmetic truncation | round 3.14 trunc 3.14 floor 3.14 ceil 3.14 |
?? truncate 3.14 floor 3.14 returns float ceil 3.14 returns float F# has round |
3.14.round ?? 3.14.floor returns Double 3.14.ceil returns Double |
round 3.14 truncate 3.14 floor 3.14 ceiling 3.14 |
min and max | min 1 2 max 1 2 |
math.min 1 2 math.max 1 2 use Math in scala 2.7 |
min 1 2 max 1 2 |
|
float overflow | infinity | Infinity | Infinity | |
float division by zero | infinity or neg_infinity | Infinity or -Infinity | Infinity or -Infinity | |
sqrt -2.0 | Math.sqrt ~2.0: nan | sqrt (-2.0): nan | math.sqrt(-2.0): NaN | sqrt (-2.0): NaN |
random integer, random float, normal float | Random.int 100 Random.float 1.0 none |
val r = new java.util.Random r.nextInt(100) r.nextDouble r.nextGaussian |
import System.Random getStdRandom (randomR (0, 99)) getStdRandom (randomR (0.0, 1.0)) none |
|
bit operators | 1 lsl 4 1 lsr 4 1 land 3 1 lor 3 1 lxor 3 lnot 1 |
1 << 4 1 >> 4 1 & 3 1 | 3 1 ^ 3 ~ 1 |
import Data.Bits x = 1 :: Integer y = 3 :: Integer shiftL x 4 shiftR x 4 x .&. y x .|. y xor x y complement x |
|
strings | ||||
standard ml | ocaml | scala | haskell | |
literal | "Hello, World!" | "Hello, World!" | "Hello, World!" | "Hello, World!" |
string and char types | string char | string char | java.lang.String Char | String Char |
string escapes | \000 \a \b \f \n \r \t \v \040 | \b \n \r \t \" \' \\ \040 \x7F | \b \f \n \r \t \" \' \uhhhh \o \oo \ooo | \0 \a \b \f \n \r \t \v \" \& \' \\ \x3bb \955 |
concatenation | "Hello" ^ ", " ^ "World!" | "Hello" ^ ", " ^ "World!" F#: "Hello" + ", " + "World!" |
"Hello" + ", " + "World!" | "Hello" ++ ", " ++ "World!" |
length | size "hello" | String.length "hello" F#: "hello".Length |
"hello".length | length "hello" |
index of substring | "hello".indexOf("hell") | |||
extract substring | substring ("hello",0,4) | String.sub "hello" 0 4 | "hello".substring(0,4) | drop 0 (take 4 "hello") |
number to string | Int.toString 3 Real.toString 3.14 |
string_of int 3 string_of_float 3.14 |
3.toString 3.14.toString |
show 3 show 3.14 |
string to number | int_of_string "3" float_of_string "3.14" |
"3".toInt "3.14".toFloat raises NumberFormatException if string doesn't completely parse |
read "3"::Integer read "3.14"::Double raises exception if string doesn't completely parse |
|
case manipulation | String.uppercase "hello" String.lowercase "HELLO" String.capitalize "hello" |
"hello".toUpperCase "HELLO".toLowerCase "hello".capitalize |
||
character access | String.sub ("hello",0) | "hello".[0] | "hello"(0) | "hello" !! 0 |
character literal | #"h" | 'h' | 'h' | 'h' |
chr and ord | ord #"a" chr 97 |
Char.code 'a' Char.chr 97 |
'a'.toInt 97.toChar |
Char.ord 'a' Char.chr 97 |
lists and tuples | ||||
standard ml | ocaml | scala | haskell | |
list literal | [1,2,3] | [1;2;3] | List(1,2,3) | [1,2,3] |
list element access | List.nth ([1,2,3],0) | List.nth [1;2;3] 0 | List(1,2,3)(0) | [1,2,3] !! 0 |
list head | List.hd [1,2,3] | List.hd [1;2;3] F#: List.head [1;2;3] |
List(1,2,3).head | head [1,2,3] |
list tail | List.tl [1,2,3] | List.tl [1;2;3] F#: List.tail [1;2;3] |
List(1,2,3).tail | tail [1,2,3] |
cons | 1::[2,3] | 1::[2;3] | 1 :: List(2,3) | 1 : [2,3] |
reverse | List.rev [1,2,3] | List.rev [1;2;3] | List(1,2,3).reverse | reverse [1,2,3] |
sort | List.sort min [1;3;2;4] List.sort max [1;3;2;4] |
List(1,3,2,4).sort((x,y)=>x<y) List(1,3,2,4).sort(_<_) List(1,3,2,4).sort((x,y)=>x>y) List(1,3,2,4).sort(_>_) |
List.sort [1,3,2,4] | |
append | [1,2] @ [3,4] | [1;2] @ [3;4] List.append [1;2] [3;4] |
List(1,2) ::: List(3,4) List(1,2) ++ List(3,4) |
[1,2] ++ [3,4] |
concatenate list of lists | List.concat [[1,2],[3,4]] | List.concat [[1;2];[3;4]] | List(List(1,2), List(3,4)).flatten | |
length | List.length [1,2,3] | List.length [1;2;3] | List(1,2,3).length | length [1,2,3] |
each | fun f i = print ((Int.toString i) ^ "\n"); List.app f [1,2,3]; |
let f i = print_endline (string_of_int i);; List.iter f [1;2;3];; |
List(1,2,3).foreach(i=>println(i)) | mapM_ print [1,2,3] |
map | List.map (fn (x) => x + 2) [1,2,3]; | List.map (( * ) 2) [1;2;3];; | List(1,2,3).map(x=>2*x) List(1,2,3).map[Int](2*_) |
map (\x -> x * x) [1,2,3] |
filter | List.filter (fn (x) => x > 2) [1,2,3]; | List.filter ((<) 2) [1;2;3];; | List(1,2,3).filter(x=>x>2) | filter (\x -> x > 2) [1,2,3] |
fold left | List.foldl (op +) 0 [1,2,3]; | List.fold_left (+) 0 [1;2;3];; F#: List.fold (-) 0 [1;2;3];; |
List(1,2,3).foldLeft(0)(_+_) List(1,2,3).foldLeft(0)((x,y)=>x+y) |
foldl (+) 0 [1,2,3] |
fold right | List.foldr (op -) 0 [1,2,3]; | List.fold_right (-) [1;2;3] 0;; | List(1,2,3).foldRight(0)(_-_) | foldr (-) 0 [1,2,3] |
tuple | (1,"hello",true) | (1,"hello",true) | (1,"hello",true) | (1,"hello",True) |
tuple element access | #1 (1,"hello",true) | match (1,"hello",true) with _,x,_ -> x | (1,"hello",true)._1 | (\(a, _, _) -> a) (1,"hello",True) |
pair element access | #1 (12,"December") #2 (12,"December") |
fst (12, "December") snd (12, "December") |
(12, "December")._1 (12, "December")._2 |
fst (12, "December") snd (12, "December") |
data types | ||||
standard ml | ocaml | scala | haskell | |
algebraic sum type | datatype color = Red | Green | Blue | type color = Red | Green | Blue | abstract class Color case object Red extends Color case object Blue extends Color case object Green extends Color |
data Color = Red | Green | Blue |
algebraic product type | type customer = {id:int, name:string, address:string} | type customer = {id:int; name:string; address:string} | case class Customer(id: Int, name: String, address: String) | data CustomerType = Customer { customerId :: Integer, name :: String, address :: String } |
algebraic product literal | {id=7, name="John", address="Topeka,KS"} | {id=7; name="John"; address="Topeka,KS"} | Customer(7,"John","Topeka,KS") scala 2.8: Customer(id=7, name="John", address="Topeka,KS") |
Customer { customerId=7, name="John", address="Topeka, KS" } |
type synonym | type name = string | type name = string | type Name = String | type Name = String |
type constructor with argument | datatype special_int = SpecialInt of int; val x = SpecialInt 7; |
type special_int = SpecialInt of int;; let x = SpecialInt 7;; |
class SpecialInt(x: Int) val x = new SpecialInt(7) |
data SpecialIntType = SpecialInt Integer x = SpecialInt 7 |
type constructor with tuple argument | datatype int_pair = IntPair of int * int; val y = IntPair (7,11); |
type int_pair = IntPair of int * int;; let y = IntPair ( 7, 11 );; |
class IntPair(a: Int, b: Int) val x = new IntPair(7,11) |
data IntPairType = IntPair Integer Integer y = IntPair 7 11 |
parametric type constructor | datatype ('a,'b) twosome = Twosome of 'a * 'b; val z = Twosome ("pi",3.14); |
type ('a,'b) twosome = Twosome of 'a * 'b;; let z = Twosome ("pi",3.14);; |
class Twosome[A,B](a: A, b: B) val p = new Twosome("pi",3.14) |
data TwosomeType a b = Twosome a b z = Twosome ("pi", 3.14) |
recursive type | datatype binary_tree = Leaf of int | Tree of binary_tree * binary_tree; | type binary_tree = Leaf of int | Tree of binary_tree * binary_tree;; | abstract class BinaryTree case class Tree(left: BinaryTree, right: BinaryTree) extends BinaryTree case class Leaf(x: Int) extends BinaryTree |
data BinaryTree = Leaf Integer | Tree BinaryTree BinaryTree |
match/with case/of | val c = Red; case c of Red => "red" | Blue => "blue" | Green => "green"; |
let c = Red;; match c with Red -> "red" | Blue -> "blue" | Green -> "green";; |
val c:Color = Red; c match { case Red => "red"; case Green => "green"; case Blue => "blue" } |
c = Red case c of Red -> "red" Green -> "green" Blue -> "blue" |
match guard | none, use if | match i with j when i < 0 -> -j | j -> j;; | match { case i: Int if i < 0 => - i; case i: Int => i } | none, use if or piecewise function definition |
match catchall | fun to_s c = case c of Red => "red" | _ => "not red"; | let to_s c = match c with Red -> "red" | _ -> "not red";; to_s Green;; |
val c : Color = Green c match { case Red => "red"; case _ => "not red" } |
c = Green case c of Red -> "red"; _ -> "not red" |
null type | type list_option_int = int option list; val list = [SOME 3,NONE, SOME ~4]; |
type list_option_int = int option list;; let list = [Some 3; None; Some (-4)];; |
val list = List(Some(3),null,Some(-4)) | list = [Just(3),Nothing,Just(-4)] |
functions and scope | ||||
standard ml | ocaml | scala | haskell | |
let ... in ... | val z = let val x = 3.0 val y = 2.0 * x in x * y end; |
let z = let x = 3.0 in let y = 2.0 *. x in x *. y;; |
val z = { val x = 3.0 val y = 2.0 * x x * y } |
z = let x = 3.0 y = 2.0 * x in x * y |
where | none | none | none | z = x * y where x = 3.0 y = 2.0 * x |
function | fun average a b = ( a + b ) / 2.0; | let average a b = ( a +. b ) /. 2.0;; | def average(a: Double,b: Double) = (a + b) / 2.0 if body not an expression or if return type not inferable: def average(a: Double,b: Double): Double = { (a + b) / 2.0 } |
average a b = (a + b) / 2.0 |
lambda | fn x => fn y => (x+y) / 2.0 | fun x -> fun y -> (x +. y) /. 2.0 | (x:Double, y:Double) => (x+y) / 2.0 | \x y -> (x+y) / 2.0 |
piecewise defined function | val to_s = fn Red => "red" | Green => "green" | Blue => "blue"; |
let to_s = function Red -> "red" | Green -> "green" | Blue -> "blue";; |
none | to_s Red = "red" to_s Green = "green" to_s Blue = "blue" |
recursive function | fun range a b = if a > b then [] else a :: range (a+1) b; |
let rec range a b = if a > b then [] else a :: range (a+1) b;; |
def range(a:Int, b:Int): List[Int] = if (a > b) List() else a :: range(a+1,b) | range a b = if a > b then [] else a : range (a+1) b |
mutually-recursive-functions | let rec even n = if n = 0 then true else odd (n-1) and odd n = if n = 0 then false else even (n-1);; |
|||
named parameter | let subtract ~m ~s = m - s;; subtract ~s: 3 ~m: 7;; |
scala 2.8: def subtract(m: Int, s: Int) = m - s subtract(s=3, m=7) |
none | |
named parameter default value | let logarithm ?(base = (exp 1.0)) x = log x /. (log base);; logarithm 2.718;; logarithm ~base: 2.0 10.0;; |
scala 2.8: def logarithm( x: Double, base: Double = math.exp(1)) = math.log(x) / math.log(base) logarithm(2.718) logarithm(10, base=2) |
none | |
infix operator in prefix position | (op * ) (3,4) | ( * ) 3 4;; | none | ( * ) 3 4 |
function in infix position | none | unary methods can be used as binary operators | add x y = x + y 3 `add` 4 |
|
currying | fun plus x y = x + y; val plus2 = plus 2; plus2 7; |
let plus2 = (+) 2;; | def plus(x: Int)(y: Int) = x + y plus(3)(7) def plus2 = plus(2) _ plus2(7) |
plus2 = (+) 2 |
composition | f x = x + 2 g x = x * 3 (f . g ) 4 |
|||
function composition operator | fun double x = 2 * x; val quadruple = double o double; |
none | none | double x = 2 * x quadruple x = double . double |
lazy evaluation | let first_arg x y = x;; first_arg 7 (lazy (1/0) );; |
def first_arg(x: => Double, y: => Double): Double = x first_arg(7, 1/0) |
lazy evaluation is default: first_arg x y = x first_arg 7 (error "bam!") |
|
strict evaluation | first_arg x y = seq y x first_arg 7 (error "bam!") |
|||
execution control | ||||
standard ml | ocaml | scala | haskell | |
if | if x > 0 then print "pos\n" else (); |
if x > 0 then print_string "pos\n";; |
if ( x > 0 ) println("pos") |
if x > 0 then putStrLn "pos" else return () |
if else-if else | if x > 0 then print "pos" else if x < 0 then print "neg" else print "zero"; | if x > 0 then print_string "pos" else if x < 0 then print_string "neg" else print_string "zero";; |
if (x > 0) println("pos") else if (x < 0) println("neg") else println("zero") |
if x > 0 then putStrLn "pos" else if x < 0 then putStrLn "neg" else putStrLn "zero" |
sequencing | print_string "one\n"; print_string "two\n"; print_string "three\n" |
println("one") println("two") println("three") |
do putStrLn "one" putStrLn "two" putStrLn "three" |
|
while | let i = ref 0;; while !i < 10 do print_string (string_of_int !i); i := !i + 1 done;; |
var i = 0 while (i<10) { printf("%d\n", i) i = i+1 } |
none | |
for | for i = 1 to 10 do let s = string_of_int i in print_string (s ^ "\n") done;; |
for (i <- 1 to 10) println(i) |
none | |
for in reverse | for i = 10 downto 1 do let s = string_of_int i in print_string (s ^ "\n") done;; |
none | none | |
list iteration | none | for (i <- List.range(1, 11).reverse) println(i) |
none | |
loop | let rec loop i = if i <= 10 then begin print_endline (string_of_int i); loop (i+1) end in loop 0;; |
none | none | |
raise error | raise (Failure "bam!");; or failwith "bam!";; |
throw new Exception("bam!") | error "bam!" | |
handle error | let x = try 1 / 0 with Division_by_zero -> 0;; | import java.lang._ val x = try { 1 / 0 } catch { case e: ArithmeticException => 0 } |
||
type of exceptions | exn | |||
user defined exception | exception Foo of string;; raise (Foo "invalid input");; |
|||
standard exceptions | Division_by_zero Failure string Not_found Invalid_argument string Match_failure (string, int, int) Assert_failure (string, int, int) Out_of_memory Stack_overflow |
|||
assert | assert(1 = 0);; | assert(1==0) | ||
environment and i/o | ||||
standard ml | ocaml | scala | haskell | |
command line args | for i = 0 to Array.length Sys.argv - 1 do print_endline i Sys.argv.(i) done |
object Test { def main(args: Array[String]) { for (arg <- args) println(arg) } } |
import System printArgs args = do if length args == 0 then return () else do putStrLn (head args) printArgs (tail args) main = do a <- getArgs printArgs a |
|
read from file | fun displayFile(file: string) = let val f = TextIO.openIn file fun iter(s: string option) = case s of NONE => (TextIO.closeIn f) | SOME(line) => (print line; iter(TextIO.inputLine f)) in iter(TextIO.inputLine f) end displayFile("/etc/passwd"); |
let ic = open_in "/etc/passwd" in let line = input_line ic in print_endline line;; |
import scala.io.Source val src = Source.fromFile("/etc/passwd") for (line <- src.getLines) print(line) |
import IO readAndPrintLines h = do eof <- hIsEOF h if eof then return () else do line <- hGetLine h putStrLn line readAndPrintLines h main = do h <- openFile "/etc/passwd" ReadMode readAndPrintLines h |
write to file | val file = "/tmp/test-sml"; val f = TextIO.openOut file; TextIO.output(f, "hello out\n"); TextIO.closeOut f; |
open Printf let oc = open_out "/tmp/test-ocaml" in fprintf oc "hello out\n"; close_out oc;; |
val out = new java.io.FileWriter("/tmp/test-scala") out.write("hello out\n") out.close |
s = "hello out\n" f = "/tmp/test-haskell" main = writeFile f s |
modules | ||||
standard ml | ocaml | scala | haskell | |
module example | Baz.scala package Foo.Bar; class Baz { def say() { println("hello"); } } Main.scala import Foo.Bar.Baz; object Main { def main(args : Array[String]) { val baz = new Baz; baz.say(); } } to compile and run $ scalac Baz.scala $ scalac Main.scala $ scala Main hello |
Foo/Bar.hs module Foo.Bar where data Baz = Baz say Baz = putStrLn "hello" Main.hs module Main where import Foo.Bar baz = Baz main = say baz to compile and run $ ghc -c Foo/Bar.hs $ ghc Main.hs $ ./Main hello |
||
namespaces | values, constructors, type variables, type constructors, type classes, modules | |||
file name restrictions | module Foo.Bar must be in Foo.ml | none | module Foo.Bar must be in Foo/Bar.hs | |
import | open Graphics;; | import Data.Bytestring | ||
module creation | put code in file MODULE_NAME.ml | |||
module alias | module Gr = Graphics;; | import qualified Data.Bytestring as B | ||
module separator | . | . | ||
submodule | in A.ml: module B = sig val display_instruction : unit -> unit end = struct let msg = "attack" let display_instruction () = print_endline msg end in client source: A.B.display_instruction;; |
|||
inspect module | module M = List;; | |||
objects | ||||
standard ml | ocaml | scala | haskell | |
class definition | class counter = object val mutable n = 0 method incr = n <- n+1 method get = n end;; |
class Counter { private var n = 0 def incr(): Unit = { n = n+1 } def get(): Int = { n } } |
||
object creation | let c = new counter;; | val c = new Counter | ||
method invocation | c#incr;; c#get;; |
c.incr c.get |
||
field access | none | |||
inheritance | ||||
repl | ||||
standard ml | ocaml | scala | haskell | |
repl | $ sml | $ ocaml to start F# repl, highlight code in visual studio and press ALT+ENTER |
$ scala | $ ghci |
repl limitations | Must use let to define values and functions; when defining functions with multiple equations the equations must be separated by semicolons; the clauses of case/of statements must be separated by semicolons; it is not possible to define data types. | |||
repl last value | it | none F#: it |
res0, res1, … | it |
help | none | :help | :? | |
inspect type | repl displays the type of any expression entered | repl displays the type of any expression entered | let a = 3 :type a |
|
load source file | use "hello.ml"; | #use "hello";; | :edit hello.hs :load hello |
|
search path | #directory "libdir";; | |||
set search path on command line | ocaml -Ilibdir | |||
__________________________________________ | __________________________________________ | __________________________________________ | __________________________________________ |
version used
Versions used to test the code samples in the cheat sheet.
version
How to get the version.
interpreter
How to run the interpreter on a file of source code.
scala:
Scala can be run "Perl style" like this:
scala foo.scala
or "Java style" like this:
scala Foo
When the code is run "Java style", the code to be executed must be in the main method of an object with the same name as the file. When the code is run "Perl style" the statements o be executed should be at the top level outside of any object, class, or method.
shebang
How to use the interpreter in a shebang.
scala
To use scala as a shebang, it is necessary to terminate the shell script portion of the script with !#
#!/bin/sh
exec scala $0 $@
!#
println("hello world")
bytecode compiler and interpreter
How to compile source to bytecode and run it.
ocaml:
It is not necessary to invoke ocamlrun on the bytecode; the bytecode can be executed directly because the bytecode compiler puts a shebang invocation at the top of the file.
native compiler
How to compile source to native code and run it.
library which is always imported
The name of the library containing the types and functions which are always available.
statement terminator
ocaml:
;; is the ocaml statement separator. It is not necessary at the end of the line if the following line starts with an open or let keyword or at the end of the file.
scala:
Scala infers the existence of a semicolon at the end of a newline terminated line if none of the following conditions hold:
- the line ends with a infix operator, including a period
- the following line begins with a word that is not legal at the start of a statement
- the line ends inside parens or square brackets, neither of which can contain multiple statements
blocks
single line comment
multi-line comment
ocaml:
(* *) style comments can be nested.
hello world
scala
In order for a scala program to be compiled and run, there must be a top level object with a main method:
object Hello {
def main(args: Array[String]) {
println("hello world")
}
}
value
variable
unit
null
Arithmetic and Logic
true and false
boolean type
logical operators
relational operators
sml:
Comparisons between reals with = and <> are not permitted in SML/NJ, but are permitted in Alice.
numeric types
scala:
Arithmetic operators can be used on values of type Char, which then behaves as a 16 bit unsigned integer. Integer literals are of type Int unless suffixed with L:
scala> 9223372036854775807L
res24: Long = 9223372036854775807
scala> 9223372036854775807
<console>:1: error: integer number too large
integer operators
integer exponentiation
ocaml:
How to define a function which computes the power of an integer:
let integer_exponent b e =
let rec aux x i =
if i = e then x else aux (x * b) (i + 1)
in
aux 1 0;;
largest integer
scala:
The largest integers are available in the constants Int.MaxValue and Long.MaxValue.
integer overflow
integer division by zero
integer division, remainder, float division
negative integer literal
float operators
float exponentiation
float functions
add integer and float
ocaml:
OCaml also can convert a integer to float with float_of_int.
arithmetic truncation
min and max
float overflow
float division by zero
sqrt -2.0
random integer, uniform float, normal float
scala:
The scala native math (named Math before Scala 2.8) library has a function for returning a random double in uniform distribution on the unit interval:
math.random
bit operators
ocaml:
Also has operators which perform arithmetic shift: asl and asr. When performing an arithmetic shift, the sign of the integer is preserved.
haskell:
Haskell does not assign a default size or type to numeric literals. Hence numeric literals must have their type declared for bit operations to be performed on them.
Strings
literal
concatenation
f#:
F# supports (with a warning) the ^ operator for compatibility with OCaml.
string escapes
scala:
Unicode escapes might not work when scala is installed on a Mac because the encoding is set by default to MacRoman:
scala> System.getProperty("file.encoding")
res0: java.lang.String = MacRoman
This can be fixed by passing the following flag to java in the scala shell script:
-Dfile.encoding=UTF-8
Lists and Tuples
list literal
list element element
list head
f#:
Supports List.hd (with a warning) to be compatible with OCaml.
list-tail
Supports List.tl (with a warning) to be compatible with OCaml.
tuple
tuple element
Data Types
algebraic sum type
algebraic product type
type synonym
Functions and Scope
let … in …
How to define local variables.
ocaml:
OCaml uses let to define a value and let with in to define values in a local scope. OCaml follows the usage of the original dialect of ML in this respect.
OCaml can define multiple values with a single let and in by conjoining the definitions with and. The defintiions are performed in parallel, so later definitions cannot use the earlier definitions:
let z =
let x = 3
and y = 4 in
x * y;;
scala:
Blocks can be used in Scala exclusively to define scope. Furthermore blocks are expressions and evaluate to their last statement.
haskell:
Haskell uses let with in to define local scope. In addition, ghci uses let without in to define values.
where
How to define local variables with definitions after the expression that uses them.
function
How to define a function.
scala
Recursive functions must have their return type declared because the Scala compiler cannot infer it.
lambda
How to define an anonymous function.
piecewise defined function
How to define a function with multiple equations and matching on the arguments.
recursive function
How to define a recursive function.
mutually recursive functions
How to define two functions which call each other. Mutual recursion can be eliminated by inlining the second function inside the first function. The first function is then recursive and can be defined independently of the second function.
named parameter
How to define and invoke a function with named parameters.
ocaml:
Multiple parameters can share a name. In the function definition colons are used to rename the parameters for use in the function body.
let add_xs ~x:x1 ~x:x2 = x1 + x2;;
add_xs ~x:3 ~x:7;;
named parameter default value
How to make named parameters optional by providing a default value in the definition.
ocaml:
For a named parameter to be optional, it must be following by an unnamed parameter in the definition. This permits the parser to unambiguously determine if the optional parameter has been provided or not. If the optional parameter is not followed by an unnamed parameter in the definition, then named parameter is not optional. If the function is invoked without the parameter, it returns a curried version of the function which expects the missing named parameter as an argument.
infix operator in prefix position
How to invoke an infix operator in prefix position.
function in infix position
How to invoke a function in infix position.
currying
How to create a curried function by providing values for some of the arguments of a function.
scala:
Functions can only be curried if they are defined with special syntax. Functions defined with this syntax must be invoked with a pair of parens for each argument.
function composition operator
An operator which takes two functions as arguments and returns a function constructed from them by composition.
lazy evaluation
How to evaluate the arguments to a function in a lazy manner.
standard ml:
Alice provides the lazy function. It is up to the caller to specify that the argument is to be evaluated lazily.
ocaml:
OCaml provides the lazy function. It is up to the caller to specify that the argument is to evaluated lazily.
scala:
Functions can be defined to evaluate their arugments lazily by putting a => operator between the colon and the type of the parameter in the function signature.
haskell:
Haskell evaluates arguments lazily by default.
strict evaluation
haskell:
The seq function evaluates its first argument and then returns the second argument.
Execution Control
if
if else-if else
sequencing
while
ocaml:
There is no break or continue statement. In addition to using references, it is possible to use exceptions to break out of a while loop.
for
How to loop over a range of integers.
sml:
How to define a for loop in SML:
datatype for = to of int * int
| downto of int * int
infix to downto
val for =
fn lo to up =>
(fn f => let fun loop lo = if lo > up then ()
else (f lo; loop (lo+1))
in loop lo end)
| up downto lo =>
(fn f => let fun loop up = if up < lo then ()
else (f up; loop (up-1))
in loop up end)
How to use the for loop:
for (1 to 9)
(fn i => print (Int.toString i))
for (9 downto 1)
(fn i => print (Int.toString i))
for in reverse
How to iterate over a reversed range of integers.
list iteration
How to iterate over the members of a list.
loop
An infinite loop.
raise error
How to raise an error.
handle error
How to handle an error.
Environment and I/O
Modules
module example
namespaces
file name restrictions
import
module creation
module alias
module separator
submodule
inspect module
Objects
REPL
repl
repl limitations
repl last value
help
ocaml
The OCaml top level provides these directives:
#cd "DIRNAME";;
#directory "DIRNAME";;
#install_printer PRINTER_NAME;;
#label BOOL;;
#load "FILENAME";;
#print_depth N;;
#print_length N;;
#quit;;
#remove_printer PRINTER_NAME;;
#trace FUNCTION_NAME;;
#untrace FUNCTION_NAME;;
#untrace_all;;
#use "FILENAME";;
#warnings "WARNINGS_LIST";;
inspect type
load source file
search path
set search path on command line
SML
Programming in Standard ML '97
Standard ML Basis Library
Notes on Programmming in SML/NJ (pdf)
Syntax Across Languages: SML
MLton Documentation
Alice Homepage
OCaml
The Objective-Caml system, release 3.11
Objective CAML Tutorial
Introduction to Objective Caml (pdf) Hickey
Standard ML and Objective Caml, Side by Side
Caml Programming Guidelines
Syntax Across Languages: OCaml
The F# Survival Guide
Scala
The Scala Language Specification (pdf)
Scala 2.7.7 API
Java 1.6 API
Haskell
Haskell 98 Language and Libraries
What follows is an explanation of traits which make Haskell different from OCaml and Scala.
lazy evaluation of arguments by default
In Haskell as in most languages it is possible to provide expressions as arguments to a function when it is invoked. In most other languages the expressions are evaluated before the invoked function is executed. In Haskell the expressions are wrapped in closures called thunks and passed to the executed function which evaluates them as needed. As a result the following Haskell code runs and evaluates to 7, whereas the equivalent code in most other language would raise a division by zero error:
first_arg x y = x
first_arg 7 (1/0)
In Lisp terminology, a functions which evaluates its arguments lazily is called an expr or a fexpr. A function which performs strict evaluation is called a subr or a fsubr. exprs and fexprs were used in old Lisp dialects to implement conditional expressions such as if and cond. The terminology is still encoutered, though in modern Lisp dialects if and cond are implemented with macros.
type system identifies pure functions
The Haskell type system distinguishes between pure and impure functions. Impure functions are called actions and usually have a type of the form IO t where t is a type variable. The main routine has type IO ().
no loops
OCaml, Scala, and Haskell all perform tail call optimization so that loops can be replaced with equally efficient code that performs recursion. Actually, in the case of Haskell, loops must be replaced with recursion because Haskell doesn't provide any functions or keywords to implement loops.
indentation defines blocks
main function marks initial execution point
History
Lambda Calculus
An Introduction to Lambda Calculus (pdf) Barendregt 1994
Developed by Alonzo Church in the 1930s, the lambda calculus is a model of computation. Church used it in 1936 to show that arithmetic as formalized by a Peano system is undecidable; that is, there are theorems which are true, but which cannot be shown to be true using the axioms of arithmetic.
As a model of computation, the lambda calculus is equal in expressive power to Turing machines. No computational models have been discovered which have greater expressive power than the lambda calculus or Turing machines. The unprovable Church-Turing thesis states that there are none.
Statements in the lambda calculus are built up with two operations: abstraction and application. Abstraction can be interpreted as defining a mathematical function, and application can be interpreted as use of a mathematical function.
Turing machines, in contrast, consist of a tape, a head, a table of instructions, and a state register. These concepts correspond to the memory, the address of the next instruction to execute, the set of machine instructions, and the registers of a computer with the von Neumann architecture.
The lambda caculus can treat functions as data. Indeed, in pure lambda calculus, functions are the only kind of data there is. The lambda calculus can nevertheless model the natural numbers by assigning a function to represent each natural number. These functions are the Church numerals. Functions can also be assigned to represent the values true and false, and the logical operators and, or, and not; these functions are the Church booleans.
The Kleene-Rosser paradox, obtaining by defining k=(λ x.~(xx)) and then applying k to itself, shows that the lambda calculus cannot be used as a basis for mathematics in a manner similar to first order predicate logic and the Zermelo axioms of set theory. Typed systems of lambda calculus attempt to address this deficiency by assigning types to functions and data and forbidding application of a function to a datum if the types do not match. Although typed systems can avoid the Kleene-Rosser paradox, they are not as expressive as the untyped lambda calculus.
Assigning a type to a function in the lambda calculus is comparable to assigning a type to a variable or a region of memory in a C program. In the case of the C program, the type remembers that the memory contains data that should be interpreted as, say, an integer and not a floating point number or string data. Assigning types to memory thus prevents programming errors that would result in nonsense. Types can also prevent programs from being written that would crash because of incorrect use of memory containing the address of another memory location.
Typed lambda calculi are central to the Curry-Howard correspondence, which is a one-to-one relationship between mathematical proofs and a class of computer programs.
Functional Programming
The Next 700 Programming Languages (pdf) Landin 1966
Landin's 1966 article introduces a theoretical core for future languages called ISWIM. Languages built on this core would have a "functional" subset of features, and portions of the program written entirely in the functional subset could be reasoned about in the same manner that mathematicians reason about algebraic equations. In particular, the principle of substitution holds: when two different expressions yield the same value, one expression can be substituted for the other with no change in result.
Landin's article shows that computer scientists were already distinguishing between imperative programming and functional programming. Landin isolates two features of imperative languages which invalidate the applicability of substitution: jumps and assignment.
TODO: referential transparency. In algebraic equations. In lambda calculus. Order of calculuation not significant.
Functional programming got its start with Algol, which introduced lexical scope, and Lisp 1.5, which put an emphasis on programming with expressions instead of statements. ISWIM employs the let and where keywords for lexical scope, where differing from let in that it places the definitions after the expression that uses them. The language CPL, described in 1963 and unimplemented as of 1966, was already using where in this manner. The language BASIC developed at Dartmouth in 1964 used let for dynamic assignment. let was adoped by future dialects of Lisp.
Landin notes that many Algol programmers were making it a practice to eliminate jumps or the goto statement where possible. In 1968 Dijkstra would write an influential letter to the ACM entitled "GOTO considered harmful".
Landin uses let rec and where rec for defining recursive functions. This notation persists in OCaml but not SML. Landin also proposed using indentation, the so-called off-sides rule, for defining blocks, a practice that was picked up by Haskell but not the ML dialects.
TODO: the development of Scheme
ML History
Edinburgh LCF Milner 1978
How ML Evolved (pdf) Milner 1982
Milner etal. began work on a theorem proving assistant called LCF at Stanford in 1971. Work continued at Edinburgh in 1974. Milner needed a language which supported higher order functions; i.e. functions which accept functions as values. He chose ISWIM as the basis for his language. Milner needed the language to be typed so that theorems being proved could be distinguished from the tactics for proving theorems.
Typed, higher order functions have complex declarations, and Milner developed and described Algorithm W in 1978 for the inference of type. Milner later learned that some of his results had already been arrived at by Hindley and Church.
ML was initally implemented in the Stanford dialect of Lisp. Luca Cardelli implemented a compiled version of ML in 1981. Specifically, a compiler implemented in Pascal took the ML source and converted it to an intermediate form which could be executed on an abstract machine implemented in assembler. The compiler and abstract machine were available for VAX machines running Unix. All type checks took place in the compiler.
Standard ML History
Hope: an Experimental Applicative Language (pdf) Burstall MacQueen Sannella 1980
The Definition of Standard ML (Revised) Milner etal 1997
SML '97 Conversion Guide MacQueeen 1999
The original version of ML was somewhat vaguely specified, and Cardelli's Pascal implementation introduced some changes and improvements. Furthermore, another statically typed functional language called Hope appeared in 1980 which introduced algebraic data types and pattern matching. Milner realized that ML had value independent of its role in the theorem proving assistant LCF, and that it would benefit from additions and standardization.
Hence Milner published "A Proposal for Standard ML" in December 1983. The article was followed by a three day conference in June 1984. Numerous changes were made to the language, and MacQueen designed a module system for ML. The formal description of Standard ML was published in book form in 1990 and a revision published in book form in 1997.
Polymorphism Newsletter
The Polymorphism Newsletter was edited by Cardelli and MacQueen, at the time both working at Bell Labs.
Polymorphism I.0 (pdf) Cardelli and MacQueen, Nov 1982
- Known VAX-ML System Locations
Polymorphism I.1 (pdf) Cardelli and MacQueen, Jan 1983
- How ML Evolved, Milner
- Unambiguous Syntax for ML, Sethi
- The Functional Abstract Machine, Cardelli
- SERC ML/LCF/Hope Meeting at Rutherford Labs
Polymorphism I.2 (pdf), Cardelli and MacQueen, Apr 1983
- Poly Report, Matthews
- Introduction to Poly, Matthews
- Ponder and its Type System, Fairbairn
- Recent Developments in LCF: Examples of Structural Induction, Paulson
- Rewriting in Cambridge LCF, Paulson
- A Revised Logic PPLambda: A Reference Manual, Paulson
- ML Under Eunice, Kitchen and Linch
Polymorphism I.3 (pdf) Cardelli and MacQueen, Dec 1983
- A Proposal for Standard ML, Milner
- ML under Unix, Cardelli
- Modules for ML, MacQueen
- Stream Input/Output, Cardelli
Polymorphism II.1 (pdf) Cardelli and MacQueen, Jan 1985
- Report on the Standard ML Meeting, MacQueen
- Basic Polymorphic Type Checking, Cardelli
- Annotated Bibliography on LCF, Paulson
Polymorphism II.2 (pdf) MacQueen, Oct 1985
- The Standard ML Core Language, Milner
- Modules for ML, MacQueen
- Standard ML Input/Output, Harper
SML Implementations
Standard ML of New Jersey
Standard ML of New Jersey
Rabbit: A Compiler for Scheme Steele 1978
Caml Trading Talk at CMU (video) Minsky 2009, opinions on SML implementations at 51:10
Compiling with Continuations Appel 1991
MacQueen and Appel started work on SMLNJ in 1986, and they spent over five years developing a complete programming environment for SML. The SMLNJ compiler reduces SML source code to an intermediate representation which uses continuation passing style (CPS). CPS was first used for a Scheme compiler written by Steele in 1978, and since CPS can be expressed in Scheme, the intermediate representation used by the Scheme compiler was a subset of Scheme. SMLNJ uses ML data types to represent CPS expressions, which provide type safety not available to a Scheme IR.
SMLNJ provides both a compiler and an interpreter. It is still actively being worked on; the version 110.72 was releases in February 2010.
MLton
MLton
MLton Performance Compared to Other ML Compilers
MLton is an SML to C compiler. Work was initiated by Stephen Weeks in 1997, and MLton was first released in 1999. MLton is a "whole program compiler". By 2002 the ability to generate native code had been added.
MLton appears to be the fastest SML compiler. The most recent release (as of May 2010) was in August 2007.
Alice
SML.NET
Moscow ML
Caml History
A brief History of Caml
Categorical Abstract Machine (CAM)
INRIA became interested in the ML language as early as 1980. The first version of Caml was released in 1987. The name refers to the Categorical Abstract Machine, a model of computation of interest to the researchers at INRIA. The first version of Caml, retroactively known as Caml Heavy, compiled to the LLM3 virtual machine used by Le Lisp. In 1991 Xavier Leroy released Caml Light which had a bytecode interpreter written in C. In 1996 OCaml was released. The name referred to the object system which was added to Caml; the release included a native code compiler.
Don Syme worked on the generics for C# which were introduced in Visual Studio 2005. F# has been available as a download since 2007 and is included with Visual Studio 2010. F# is close to OCaml; it had been preceded by an attempt to bring Haskell to .NET which was put aside because too much monadic code was required to interact with the .NET libraries. F# was also preceded by an SML port to .NET.
Scala History
The Origins of Scala Odersky 2009
Join-calculus
Scala Version History: 2.5 thru 2.7
Scala Version History: 1.0 thru 2.4
Martin Odersky and Phil Wadler developed a functional language called Pizza for the JVM in 1996. Pizza has generics — what would eventually become the generics in Java 5 — as well as higher order functions and pattern matching. Odersky next developed a JVM language called Funnel which is based on the join-calculus.
Odersky's third language was Scala; the first version was released in 2003. The Scala compiler was rewritten in Scala in 2006.
Haskell History
A History of Haskell (pdf) Hudak Hughes Jones Wadler 2007
Notions of computations and monads (pdf) Moggi 1991
Comprehending Mondas (pdf) Wadler 1992
Arrows wikipedia
David Turner implemented a pure version of ISWIM in 1972, and he made the language (called SASL) non-strict or lazy in 1976. He followed this language up with KRC (1981) which contained pattern matching, guards, and list comprehensions. This in turn was followed by Miranda (1985), which had polymorphic types in the manner of ML.
Miranda is closed source. The FP community resolved to design an open source lazy functional language in 1987, and the new language was specified in the Haskell Report (1990) and the Haskell 98 Report (1999). The Glasgow Haskell Compiler was first available as a beta version in 1991.
A notable distinction between Miranda and Haskell is the use of category theory, in particular monads, in the type system to distinguish pure and impure functions. Arrows are a generalization of monads.