Functional programming using SML/NJ

So I started collecting some basic SML ideas that are useful when one encounters a course website or a tool that is coded in SML/NJ. It is a work in progress.

The first aspect to consider is the IDE. Mine is now Doom Emacs. Why ?

group
(* CM allows you to selectively export defined modules (structures,
signatures and functors) by listing them here. It's useful for
libraries. *)

source (-) (* export all defined modules *)

structure Main (* OR, export selectively *)
signature FOO
functor Foo
structure Test(* OR, export selectively *)
signature THEOREM
functor Theorem
is
(* Import the SML standard library, aka Basis. *)
(* See: http://sml-family.org/Basis/ *)
$/basis.cm

(* Import the SML/NJ library *)
(* Provides extra data structures and algorithms. *)
(* See: https://www.smlnj.org/doc/smlnj-lib/Manual/toc.html *)
$/smlnj-lib.cm

(* List each source file you want to be considered for compilation. *)
./main.sml
./foo.sig
./foo.fun
./test.sml
./theorem.sig
./theorem.fun

The main purpose is a straightforward build and test environment. I don’t code SML but port it to Racket or OCaml. I copy pieces of code and test them. So here a _Functor_ is used only for convenience. There is no attempt to abstract anything.

And I will strive to add more details as I learn.

Dune is a Ocaml build system

Here is my attempt to properly build a toy Ocaml project using Dune.

Since this is the learning phase the Ocaml code may not be idiomatic.

My unit test framework is Alcotest

As is the case with other Ocaml tools and techniques information about this is sketchy. I wish there were more articles and examples as it is fun to work with this language..

I will add more details as I research this further. But for now here is a brief description of the dune build file.

  • dune runtest executes  all the tests
  • Dune does not install dependencies automatically . So, for example, I have to execute ‘opam install alcotest’. That is how one installs any opam package we need generally.

graph.opam

This looks like the file in which one specifies the framework versions and build instructions.

opam-version: "2.0"
authors: [ "Mohan" ]
synopsis: "Learning Dune"
description: """
Learning Dune
"""
tags: []
depends: [
"ocaml" {
>= "4.02.3"}
"dune"
"alcotest" {with-test}
]
build: [
["dune" "subst"] {pinned}
["dune" "build" "-p" name "-j" jobs]
["dune" "runtest" "-p" name "-j" jobs] {with-test}
]

Dune dependencies

This specifies the dependencies and the modules. My code module is ‘graph‘ and my test module is ‘kruskaltest‘.

  •  
(library
(modules graph)
(name graph)
)
(test
(name kruskaltest)
(modules kruskaltest)
(libraries alcotest graph))
view raw dune.lisp hosted with ❤ by GitHub

Main module

type edge = ( int * int * int ) list
module type EDGECOMPARE = sig
type t
val compare : 'a list -> 'a list ->bool
end
module EDGECOMPARE_weight : EDGECOMPARE with type t = edge = struct
type t = edge
let compare l r = ( List.nth l ( List.length l/ 2 )) == ( List.nth r (List.length r/2))
end
view raw kruskal.ml hosted with ❤ by GitHub

Test

let testcompare () = Alcotest.(check bool) "Test for weight comparison" true (Graph.EDGECOMPARE_weight.compare [1,2,4] [ 5,6,7] )
let () =
Alcotest.run "Weights"
[
("test compare weights of edges", [
Alcotest.test_case "Compare weights" `Quick testcompare;
]);
]
view raw kruskaltest.ml hosted with ❤ by GitHub

mirage@mirage:~/theorem$ dune runtest
kruskaltest alias runtest
Testing Weights.
This run has ID `D7DAB7A8-A60A-4522-9732-54FAE2331A72`.
[OK] test compare weights of edges 0 Compare weights.
The full test results are available in `/home/mirage/theorem/_build/default/_build/_tests/D7DAB7A8-A60A-4522-9732-54FAE2331A72`.
Test Successful in 0.000s. 1 test run.

Joy of OCaml

screenshot-from-2016-12-03-19-14-34

I have spent most of last week with my Emacs editor and the OCaml development environment. Since I have some OCaml code to complete I will add more details soon.

Suffice it to say that this setup taxed me so much. OPAM does not seem to install easily in Windows. As is my wont in such cases I started with Cygwin and after two days switched to a Ubuntu VM. I didn’t think I was gaining much by reporting Cygwin permission issues to owners of OPAM Windows installers.

Emacs company mode for autocompletion

The toolchain includes company as well as Merlin
and Tuareg.

screenshot-from-2016-12-03-19-00-53

Utop is a toplevel for OCaml

screenshot-from-2016-12-03-19-10-58

Emacs elisp

It looks like this at this time and I use Gist because WordPress does not support Lisp or OCaml or Haskell yet. Filed a support ticket.


(package-initialize)
(load "/home/mohan/.opam/4.02.1/share/emacs/site-lisp/tuareg-site-file")
(let ((opam-share (ignore-errors (car (process-lines "opam" "config" "var"
"share")))))
(when (and opam-share (file-directory-p opam-share))
;; Register Merlin
(add-to-list 'load-path (expand-file-name "emacs/site-lisp" opam-share))
(autoload 'merlin-mode "merlin" nil t nil)
;; Automatically start it in OCaml buffers
(add-hook 'tuareg-mode-hook 'merlin-mode t)
(add-hook 'caml-mode-hook 'merlin-mode t)
;; Use opam switch to lookup ocamlmerlin binary
(setq merlin-command 'opam)))
(company-mode)
(add-to-list 'load-path "/home/mohan/.opam/4.02.1/share/emacs/site-lisp")
(require 'ocp-indent)
(autoload 'utop-minor-mode "utop" "Minor mode for utop" t)
(autoload 'utop-setup-ocaml-buffer "utop" "Toplevel for OCaml" t)
(autoload 'merlin-mode "merlin" "Merlin mode" t)
(utop-minor-mode)
;; Important to note that setq-local is a macro and it needs to be
;; separate calls, not like setq
(setq-local merlin-completion-with-doc t)
(setq-local indent-tabs-mode nil)
(setq-local show-trailing-whitespace t)
(setq-local indent-line-function 'ocp-indent-line)
(setq-local indent-region-function 'ocp-indent-region)
(custom-set-variables
;; custom-set-variables was added by Custom.
;; If you edit it by hand, you could mess it up, so be careful.
;; Your init file should contain only one such instance.
;; If there is more than one, they won't work right.
'(package-selected-packages (quote (company))))
(custom-set-faces
;; custom-set-faces was added by Custom.
;; If you edit it by hand, you could mess it up, so be careful.
;; Your init file should contain only one such instance.
;; If there is more than one, they won't work right.
)
; Make company aware of merlin
(with-eval-after-load 'company
(add-to-list 'company-backends 'merlin-company-backend))
; Enable company on merlin managed buffers
(add-hook 'merlin-mode-hook 'company-mode)

view raw

ocaml.lisp

hosted with ❤ by GitHub

More about OCaml code later. This creates an associative list of tuples containing characters and the number of times they occur in a String. MultiSet is a module that is not shown either but as I mentioned I have more to write about this wonderful programming language.


let insert l a =
if List.mem_assoc a l
then
let n = List.assoc a l in (a, n+1)::(List.remove_assoc a l)
else (a, 1)::l
let letters (word : string) : char MultiSet.t =
let rec insert (l : char MultiSet.t) (c : string) (i : int) : char MultiSet.t =
if ( String.length c > 1 ) then
insert ( MultiSet.insert l (String.get c i) ) ( String.sub c 1 ((String.length c) – 1) ) 0
else
MultiSet.insert l (String.get c 0)
in insert MultiSet.empty word 0
;;

view raw

letter.ml

hosted with ❤ by GitHub