From de837bbf3b0745ec29f0ef5c4af484bcf35b27d2 Mon Sep 17 00:00:00 2001 From: aaron-jack-manning Date: Sun, 19 Dec 2021 08:08:10 +1100 Subject: [PATCH] option monad, and some design changes --- README.md | 9 +++++---- lib/{types.ml => exposed.ml} | 9 ++++++++- lib/{types.mli => exposed.mli} | 11 ++++++++++- lib/functions.ml | 5 ----- lib/functions.mli | 8 -------- lib/list.ml | 2 +- lib/list.mli | 2 +- lib/main.ml | 2 +- lib/makefile | 9 ++++----- lib/option.ml | 23 +++++++++++++++++++++++ lib/option.mli | 23 +++++++++++++++++++++++ lib/queue.ml | 2 +- lib/queue.mli | 2 +- lib/stack.ml | 2 +- lib/stack.mli | 2 +- lib/tree.ml | 2 +- lib/tree.mli | 2 +- 17 files changed, 82 insertions(+), 33 deletions(-) rename lib/{types.ml => exposed.ml} (64%) rename lib/{types.mli => exposed.mli} (55%) delete mode 100644 lib/functions.ml delete mode 100644 lib/functions.mli create mode 100644 lib/option.ml create mode 100644 lib/option.mli diff --git a/README.md b/README.md index 22d0a8a..60b8a9b 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ This library includes the following custom modules: - Int (exposure of basic integer arithmetic functions) - Float (exposure of basic float arithmetic functions) +- Option (functions for working with the option monad) - List (functional list data structure) - Queue (functional queue implemented as two lists) - Set (functional set implemented as a red-black tree) @@ -21,13 +22,13 @@ This library includes the following custom modules: ## Exposure of Functions from Standard Library -With respect to the exposed parts of the standard library, these are all handled in the `FromStdlib` module, which redefines some definitions directly from the standard library so that this file can be safely included separately, exposing only the desired functions. As such, it is recommended that this file is opened in the code that uses this library, while others are not, and referenced from the module level instead (with one additional exception of `Types`, mentioned in the following section). +With respect to the exposed parts of the standard library, these are all handled in the `FromStdlib` module, which redefines some definitions directly from the standard library so that this file can be safely included separately, exposing only the desired functions. As such, it is recommended that this file is opened in the code that uses this library, while others are not, and referenced from the module level instead (with one additional exception of `Exposed`, mentioned in the following section). All files are compiled with `-nopervasives` except `FromStdlib` (to avoid the headaches in exposing functions like `printf` which have many dependencies). Linking is also done without `-nopervasives` so that `fromStdlib.cmx` can find the corresponding functions. Hence any new files added to the project are recommended to be compiled separately with `nopervasives` and then linked via the `.cmx` file. -## Type Declarations +## Type Declarations and General Functions -In order to prevent duplicate definitions of common types like collections, but still allow things like list literals to work, and to prevent the need of a type annotation at the module level, a `Types` module is provided to be opened in code files which exposes types like `'a queue` and other collections. +In order to prevent duplicate definitions of common types like collections, but still allow things like list literals to work, and to prevent the need of a type annotation at the module level, a `Exposed` module is provided to be opened in code files which exposes types like `'a queue` and other collections. `Exposed` also includes generic operators that should be opened file wide, such as function composition (`>>`). ## Build Process @@ -46,4 +47,4 @@ Also take note of the fact that I typically compile everything with `-S` and `-O ## The Core Library -One of the unfortunate consequences of the way OCaml's compilation works, is that there is a library called the core library, documented [here](https://ocaml.org/manual/core.html), which contains some definitions for types and exceptions, yet does not include the code from the stdlib that uses them. When compiling with the `-nopervasives` flag, this is still included but without the standard library. While this makes sense from the perspective of having some fundamental exceptions always available, having types like `list` included makes it very annoying when implemented a custom standard library. This quirk is why my library has no type definition for `list`, `bool`, `option`, etc. but still uses these types. +One of the unfortunate consequences of the way OCaml's compilation works, is that there is a library called the core library, documented [here](https://ocaml.org/manual/core.html), which contains some definitions for types and exceptions, yet does not include the code from the stdlib that uses them. When compiling with the `-nopervasives` flag, this is still included but without the standard library. While this makes sense from the perspective of having some fundamental exceptions always available, having types like `list` included makes it very annoying when implemented a custom standard library. This quirk is why my library has no type definition for `list`, `bool`, `option`, etc. but still uses these types. \ No newline at end of file diff --git a/lib/types.ml b/lib/exposed.ml similarity index 64% rename from lib/types.ml rename to lib/exposed.ml index 8ea371b..6996d6b 100644 --- a/lib/types.ml +++ b/lib/exposed.ml @@ -10,4 +10,11 @@ type 'a stack = type 'a tree = | Leaf - | Branch of 'a * 'a tree list \ No newline at end of file + | Branch of 'a * 'a tree list + + +let id (x : 'a) = x + +let ( >> ) f g x = g (f x) + +let ( << ) g f x = g (f x) \ No newline at end of file diff --git a/lib/types.mli b/lib/exposed.mli similarity index 55% rename from lib/types.mli rename to lib/exposed.mli index 5cd2683..adaf392 100644 --- a/lib/types.mli +++ b/lib/exposed.mli @@ -13,4 +13,13 @@ type 'a stack = (* A purely functional tree with arbitrarily many branches at each node. *) type 'a tree = | Leaf - | Branch of 'a * 'a tree list \ No newline at end of file + | Branch of 'a * 'a tree list + +(** Identity function. *) +val id : 'a -> 'a + +(** Function composition. (f >> g) x represents g (f x). *) +val ( >> ) : ('a -> 'b) -> ('b -> 'c) -> 'a -> 'c + +(** Function composition. (f << g) x represents f (g x). *) +val ( << ) : ('a -> 'b) -> ('c -> 'a) -> 'c -> 'b \ No newline at end of file diff --git a/lib/functions.ml b/lib/functions.ml deleted file mode 100644 index b888f84..0000000 --- a/lib/functions.ml +++ /dev/null @@ -1,5 +0,0 @@ -let id (x : 'a) = x - -let ( >> ) f g x = g (f x) - -let ( << ) g f x = g (f x) \ No newline at end of file diff --git a/lib/functions.mli b/lib/functions.mli deleted file mode 100644 index 64f82b6..0000000 --- a/lib/functions.mli +++ /dev/null @@ -1,8 +0,0 @@ -(** Identity function. *) -val id : 'a -> 'a - -(** Function composition. (f >> g) x represents g (f x). *) -val ( >> ) : ('a -> 'b) -> ('b -> 'c) -> 'a -> 'c - -(** Function composition. (f << g) x represents f (g x). *) -val ( << ) : ('a -> 'b) -> ('c -> 'a) -> 'c -> 'b \ No newline at end of file diff --git a/lib/list.ml b/lib/list.ml index 76d1940..884b2f4 100644 --- a/lib/list.ml +++ b/lib/list.ml @@ -1,5 +1,5 @@ open FromStdlib -open Types +open Exposed let empty : 'a list = [] diff --git a/lib/list.mli b/lib/list.mli index e11f17a..5716fdb 100644 --- a/lib/list.mli +++ b/lib/list.mli @@ -1,4 +1,4 @@ -open Types +open Exposed (** The empty list *) val empty : 'a list diff --git a/lib/main.ml b/lib/main.ml index 7ac0171..c3519e9 100644 --- a/lib/main.ml +++ b/lib/main.ml @@ -1,3 +1,3 @@ -open FromStdlib open Types +open FromStdlib open Exposed let _ = printf "Hello, World\n" \ No newline at end of file diff --git a/lib/makefile b/lib/makefile index f85bcb8..a13ff00 100644 --- a/lib/makefile +++ b/lib/makefile @@ -4,14 +4,13 @@ build: ocamlopt -i fromStdlib.ml > fromStdlib.mli ocamlopt -S -O3 -c fromStdlib.mli fromStdlib.ml - # types for other modules separated to easily expose without module reference in projects - ocamlopt -S -O3 -nopervasives -c types.mli types.ml + # exposed types and functions, that can be opened module wide + ocamlopt -S -O3 -nopervasives -c exposed.mli exposed.ml # the following files make up the core custom standard library code ocamlopt -S -O3 -nopervasives -c int.mli int.ml ocamlopt -S -O3 -nopervasives -c float.mli float.ml - ocamlopt -S -O3 -nopervasives -c float.mli float.ml - ocamlopt -S -O3 -nopervasives -c functions.mli functions.ml + ocamlopt -S -O3 -nopervasives -c option.mli option.ml ocamlopt -S -O3 -nopervasives -c stack.mli stack.ml ocamlopt -S -O3 -nopervasives -c list.mli list.ml ocamlopt -S -O3 -nopervasives -c map.mli map.ml @@ -24,7 +23,7 @@ build: ocamlopt -S -O3 -nopervasives -c main.mli main.ml # after all files are individually compiled with -nopervasives, this is compiled with it so that fromStdlib has the necessary linking - ocamlopt -S -O3 fromStdlib.cmx types.cmx int.cmx float.cmx functions.cmx stack.cmx list.cmx map.cmx queue.cmx set.cmx tree.cmx main.cmx -o program + ocamlopt -S -O3 fromStdlib.cmx exposed.cmx int.cmx float.cmx option.cmx stack.cmx list.cmx map.cmx queue.cmx set.cmx tree.cmx main.cmx -o program # clean removes all except source files. autogenerated mli files are also removed. clean: diff --git a/lib/option.ml b/lib/option.ml new file mode 100644 index 0000000..8830bfd --- /dev/null +++ b/lib/option.ml @@ -0,0 +1,23 @@ +open Exposed + +let return (x : 'a) : 'a option = Some x + +let ( ~= ) = return + +let bind (x : 'a option) (f : 'a -> 'b option) : 'b option = + match x with + | None -> None + | Some a -> f a + +let ( >>= ) = bind + +let binary_operator (x : 'a option) (y : 'b option) (f : 'a -> 'b -> 'c) : 'c option = + x >>= (fun a -> y >>= (fun b -> ~= (f a b))) + +let map (x : 'a option) (f : 'a -> 'b) : 'b option = + x >>= (fun a -> ~= (f a)) + +let compose (f : 'a -> 'b option) (g : 'b -> 'c option) : 'a -> 'c option = + (fun x -> f x >>= g) + +let ( >=> ) = compose \ No newline at end of file diff --git a/lib/option.mli b/lib/option.mli new file mode 100644 index 0000000..546e4c7 --- /dev/null +++ b/lib/option.mli @@ -0,0 +1,23 @@ +(* Monadic return for option type. Trivially boxes the variable of type 'a into a 'a option by applying the Some constructor. *) +val return : 'a -> 'a option + +(* Monadic return for option type. Trivially boxes the variable of type 'a into a 'a option by applying the Some constructor. *) +val ( ~= ) : 'a -> 'a option + +(* Monadic bind for option type. Passes the 'a stored within the 'a option through the supplied function. If the first argument is None, the result is None. *) +val bind : 'a option -> ('a -> 'b option) -> 'b option + +(* Monadic bind for option type. Passes the 'a stored within the 'a option through the supplied function. If the first argument is None, the result is None. *) +val ( >>= ) : 'a option -> ('a -> 'b option) -> 'b option + +(* Binary operator, applied to the variables of type 'a and 'b respectively in the first two arguments. *) +val binary_operator : 'a option -> 'b option -> ('a -> 'b -> 'c) -> 'c option + +(* Maps the supplied function to the 'a within the first argument. *) +val map : 'a option -> ('a -> 'b) -> 'b option + +(* Monadic function composition. *) +val compose : ('a -> 'b option) -> ('b -> 'c option) -> 'a -> 'c option + +(* Monadic function composition. *) +val ( >=> ) : ('a -> 'b option) -> ('b -> 'c option) -> 'a -> 'c option \ No newline at end of file diff --git a/lib/queue.ml b/lib/queue.ml index 5821518..8b77e65 100644 --- a/lib/queue.ml +++ b/lib/queue.ml @@ -1,5 +1,5 @@ open List -open Types +open Exposed let enqueue (a : 'a) (qu : 'a queue) : 'a queue = { qu with front = a :: qu.back } diff --git a/lib/queue.mli b/lib/queue.mli index 77802b4..4b7cd1c 100644 --- a/lib/queue.mli +++ b/lib/queue.mli @@ -1,4 +1,4 @@ -open Types +open Exposed (** Adds an element to the back of the queue, returning the new queue. Runs in O(1). *) val enqueue : 'a -> 'a queue -> 'a queue diff --git a/lib/stack.ml b/lib/stack.ml index 53dfa4d..5d9204f 100644 --- a/lib/stack.ml +++ b/lib/stack.ml @@ -1,5 +1,5 @@ open FromStdlib -open Types +open Exposed let pop (st : 'a stack) : 'a option * 'a stack = match st with diff --git a/lib/stack.mli b/lib/stack.mli index 3ad478b..a73fadc 100644 --- a/lib/stack.mli +++ b/lib/stack.mli @@ -1,4 +1,4 @@ -open Types +open Exposed (** Removes the top element from the stack, returning a tuple of the new stack, and None if the stack was empty, or Some [x] if [x] was on top of the stack. Popping an empty stack will result in the returned stack also being empty. Runs in O(1). *) val pop : 'a stack -> 'a option * 'a stack diff --git a/lib/tree.ml b/lib/tree.ml index 65c3718..45b9b65 100644 --- a/lib/tree.ml +++ b/lib/tree.ml @@ -1,5 +1,5 @@ open FromStdlib -open Types +open Exposed let combine (tr1 : 'a tree) (tr2 : 'a tree) (topBranch : 'a) : 'a tree = Branch (topBranch, tr1 :: tr2 :: []) diff --git a/lib/tree.mli b/lib/tree.mli index 2cb077c..7c86b99 100644 --- a/lib/tree.mli +++ b/lib/tree.mli @@ -1,4 +1,4 @@ -open Types +open Exposed (* Combines two trees of the same type, with the specified value at the new top node. Runs in O(1). *) val combine : 'a tree -> 'a tree -> 'a -> 'a tree