Bucklescript interfaces and implementations for category theory and abstract algebra
Install the project:
npm install bs-abstract --save
And add the dependency to your bs-dependencies in bsconfig.json
:
"bs-dependencies": [
"bs-abstract"
]
The project will be available under the BsAbstract
namespace
This is the current layout of the project. It's subject to change:
The rest of the files under src
are implementations based on data type (ie: String.re
for strings). These files and their corresponding unit tests in the test
folder will give you an idea on how to use and implement the interfaces for your own data structures.
The suggested way to combine monadic code is to use kliesli composition instead of flat_map
. For example, given a
type that's a monad, a very common pattern is to get the inner value and pass it in as an argument to a
subsequent function, like so:
module I = Functions.Infix.Monad(BsEffects.Effect.Monad);
let exclaim_file = path => BsEffects.Effect.Infix.({
read_file(path) >>= contents => {
write_file(path, contents ++ "!")
}
});
module I = Functions.Infix.Monad(BsEffects.Effect.Monad)
let exclaim_file path =
let open BsEffects.Effect.Infix in
(read_file path) >>= (fun contents -> write_file path (contents ^ "!"))
Which looks like this using do notation (in haskell):
contents <- read_file "foo"
_ <- write_file "foo" (contents ++ "!")
This can be written with kliesli composition like this:
module Effect_Infix = Functions.Infix.Monad(BsEffects.Effect.Monad);
let ((>=>), (>.)) = (Effect_Infix.(>=>), Function.Infix.(>.));
let exclaim = Function.flip((++))("!");
let exclaim_file = path => Function.const(read_file(path)) >=> (exclaim >. write_file(path));
module Effect_Infix = Functions.Infix.Monad(BsEffects.Effect.Monad)
let ((>=>),(>.)) = (Effect_Infix.(>=>), Function.Infix.(>.))
let exclaim = Function.flip (^) "!"
let exclaim_file path =
(Function.const (read_file path)) >=> (exclaim >. (write_file path))
Building up functions using function and kliesli composition is a good litmus test that your program is built up from generic, pure abstractions. Which means that the code is easy to abstract to make it reusable in many other contexts, and abstractions are easy to decompose when requirements change.
For interfaces based on functors, Use already instantiated functors if available to avoid the extra boilerplate, ie:
ArrayF.Int.Additive.Fold_Map.fold_map
let _ = ArrayF.Int.Additive.Fold_Map.fold_map
Don't overuse infix operators. If the code is combinatorial it can make it more readable, but a lot of times prefix operators are simpler and easier to read
If you do use infix operators, prefer local opens over global opens, and prefer explicit unpacking over local opens, ie:
let ((<.), (>.)) = Function.Infix.((<.), (>.))
let ((<.),(>.)) = let open Function.Infix in ((<.), (>.))
Abbreviated modules can make code terser and easier to read in some situations (ie: A.map
), especially in situations where infix operators can't be used because they would introduce ambiguity, like for example when two different monoids are used in the same function.
Example code:
module T = ListF.Option.Traversable;
assert(T.sequence([Some("foo"), Some("bar")]) == Some(["foo", "bar"]));
Js.log(ListF.Int.Show.show([1,1,2,3,5,8]));
module T = ListF.Option.Traversable
let _ =
assert
((T.sequence
[((Some ("foo"))[@explicit_arity ]);
((Some ("bar"))[@explicit_arity ])])
= ((Some (["foo"; "bar"]))[@explicit_arity ]))
let _ = Js.log (ListF.Int.Show.show [1; 1; 2; 3; 5; 8])
See the unit tests for many more examples
See the bs-effects package for sync and async implementations of the "IO monad", and the bs-free package for free monads and other free structures.
You can integrate monads with ppx_let, a ppx rewriter that provides
"do notation" sugar for monads. The rewriter expects a Let_syntax
module to be in scope, which you can construct
using PPX_Let.Make
, like so:
module OptionLet = PPX_Let.Make(Option.Monad);;
let add_optionals = fun x y ->
let open OptionLet in
let%bind x' = x in
let%bind y' = y in
Some (x' + y');;
Js.log @@ add_optionals (Some 123) (Some 456);; (* Some 579 *)
Currently as of this writing, there's no support for let%bind
style syntax for ReasonML, but it
should be available in one of the next releases
Licensed under the BSD-3-Clause license. See LICENSE