Skip to content
This repository has been archived by the owner on Jul 21, 2023. It is now read-only.

Commit

Permalink
Foundation to provide cross-compatibility with native
Browse files Browse the repository at this point in the history
  • Loading branch information
Risto-Stevcev committed Mar 18, 2020
1 parent 951e35e commit cd485a1
Show file tree
Hide file tree
Showing 4,201 changed files with 9,910 additions and 290 deletions.
The diff you're trying to view is too large. We only load the first 3000 changed files.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,5 @@ lib/bs
.bsb.lock
lib
node_modules
_build/
_opam/
22 changes: 22 additions & 0 deletions .ocamlformat
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
version=0.13.0
margin=100
break-cases=fit-or-vertical
break-collection-expressions=fit-or-vertical
break-fun-decl=fit-or-vertical
break-infix=fit-or-vertical
break-struct=force
break-sequences=true
field-space=loose
if-then-else=keyword-first
indicate-multiline-delimiters=no
infix-precedence=parens
parens-tuple=multi-line-only
sequence-style=terminator
type-decl=sparse
wrap-comments=false
wrap-fun-args=false
space-around-arrays=false
space-around-lists=false
space-around-variants=false
space-around-records=true
doc-comments=before
29 changes: 29 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
## Potentially breaking changes

- `Float.Show` now uses `string_of_float` for cross-compatibility. This might be undesirable
depending on your use cases, in which case `JsFloat.Show` is provided which uses
`Js.Float.toString`. See [bucklescript#3412][1].

- The `JsArray` module is provided for users that want to have the transpiled bucklescript code use
the builtin `Array.prototype` api instead of the version used for Ocaml compatibility. Using this
on the frontend would mean potentially smaller bundle sizes (implementations are built into JS),
and possible performance improvements since the javascript engines know explicitly what the code
is trying to do.

- `Result.Unsafe` functions now raise an Ocaml `Invalid_argument` error instead of a JS type error
on failure. No js compat is currently provided since these are unsafe functions to begin with and
shouldn't be used.


## Notes

These are updates that shouldn't cause any breaking changes but are documentated here

- `Int.EuclideanRing` also uses the builtin `min` function now. This functions behaves identically
to the js counterpart `Js.Math.min`, it's just noted here for reference.

- `Option.getWithDefault` copies the same implementation from bucklescript's stdlib so that it works
on native as well.


[1]: https://github.com/BuckleScript/bucklescript/issues/3412
74 changes: 74 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
all: build

.PHONY: clean-bs
clean-bs:
bsb -clean-world

.PHONY: clean-native
clean-native:
dune clean

.PHONY: clean-docs
clean-docs:
rm -rf docs/**

.PHONY: clean
clean: clean-bs clean-native clean-docs

.PHONY: build-bs
build-bs:
bsb -make-world

.PHONY: build-native
build-native:
dune build @all

.PHONY: build
build: build-bs build-native

.PHONY: fmt
fmt:
dune build @fmt --auto-promote

.PHONY: docs
docs: clean-docs
dune build @doc

.PHONY: copy-docs
copy-docs: docs
cp -r _build/default/_doc/_html/** docs/

.PHONY: open-docs
open-docs: copy-docs
xdg-open docs/index.html

.PHONY: test-bs
test-bs:
yarn test

.PHONY: test-native
test-native:
dune runtest --no-buffer

.PHONY: test
test: test-bs test-native

.PHONY: watch-native
watch:
dune build @all -w

.PHONY: watch-bs
watch-bs:
bsb -make-world -w

.PHONY: watch-test-bs
watch-test-bs:
yarn run watch-test

.PHONY: watch-test-native
watch-test:
dune runtest --no-buffer -w

.PHONY: utop
utop:
dune utop .
178 changes: 89 additions & 89 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,140 +1,140 @@
# bs-abstract
# ocaml-abstract

Bucklescript interfaces and implementations for category theory and abstract algebra
A ReasonML/Ocaml library for category theory and abstract algebra.

<img src="https://raw.githubusercontent.com/Risto-Stevcev/bs-abstract/master/cantellated_tesseract.png" height="100" width="100"/>


## Documentation

See [documentation][1]


## Installation

Install the project:

`npm install bs-abstract --save`
`npm install ocaml-abstract --save`

And add the dependency to your bs-dependencies in `bsconfig.json`:

```json
"bs-dependencies": [
"bs-abstract"
"ocaml-abstract"
]
```

The project will be available under the `BsAbstract` namespace

## Project Layout

This is the current layout of the project. It's subject to change:

- [src/interfaces/Interface.re][1] - Contains the category theory and abstract algebra interfaces
- [src/interfaces/Verify.re][2] - Contains property based tests to verify that implementations are lawful
- [src/interfaces/Infix.re][3] - Contains functors to generate infix operators for the interfaces. Modules implementing interfaces contain an already instantiated Infix module for convenience where appropriate
- [src/utilities/Default.re][4] - Contains default implementations for interface functions
- [src/utilities/Functors.re][5] - Contains already instantiated functors for common data combinations for convenience
- [src/functions/Functions.re][6] - Contains generic functions that are built on top the abstract interfaces
- [src/implementations/][7] - Contains implementations for common bucklescript types
## Examples

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.

## Suggested Usage
```ocaml
# #require "ocaml_abstract";;
# open Ocaml_abstract;;
# module T = Functors.ListF.Option.Traversable;;
module T = Ocaml_abstract.Functors.ListF.Option.Traversable
- 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:
# T.sequence [Some "foo"; Some "bar"];;
- : string list option = Some ["foo"; "bar"]
```reason
module I = Functions.Infix.Monad(BsEffects.Effect.Monad);
# Functors.ListF.Int.Show.show [1; 1; 2; 3; 5; 8];;
- : string = "[1, 1, 2, 3, 5, 8]"
```

let exclaim_file = path => BsEffects.Effect.Infix.({
read_file(path) >>= contents => {
write_file(path, contents ++ "!")
}
});
```
## Suggested Usage

Which looks like this using do notation (in haskell):
### Use kliesli composition

```haskell
contents <- read_file "foo"
_ <- write_file "foo" (contents ++ "!")
```
The monadic equivalent of the `|>` operator is `flat_map` (`>>=`). Both are very useful for writing
code that's easy to reason about as data all flows in one direction.

This can be written with kliesli composition like this:
However, function composition (`>.`) and kliesli composition for monads (`>=>`) are often underused
in code. Composing functions and monads monoidally like this in a concatenative style can make a
codebase easier to read and can also prevent devs on the team from duplicating code.

```reason
module Effect_Infix = Functions.Infix.Monad(BsEffects.Effect.Monad);
let ((>=>), (>.)) = (Effect_Infix.(>=>), Function.Infix.(>.));
Consider a typical use case. Often in a codebase you have something that fetches a value that may or
may not exist (`'a option`). Splitting out this code into smaller functions and combining (and
reusing) them with composition leads a lean code style:

let exclaim = Function.flip((++))("!");
let exclaim_file = path => Function.const(read_file(path)) >=> (exclaim >. write_file(path));
```
```ocaml
# let ((>=>)) = Option.Infix.((>=>));;
val ( >=> ) : ('a -> 'b option) -> ('b -> 'c option) -> 'a -> 'c option =
<fun>
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.
# type form = { name: string; address: string option };;
type form = { name : string; address : string option; }
# let get_form () =
(* Assume some side effect got the form here *)
Some { name = "Foo"; address = Some "123 Bar St." };;
val get_form : unit -> form option = <fun>
- For interfaces based on functors, Use already instantiated functors if available to avoid the extra boilerplate, ie:
```reason
ArrayF.Int.Additive.Fold_Map.fold_map
```
# let get_address form = form.address;;
val get_address : form -> string option = <fun>
- 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 get_form_address = get_form >=> get_address;;
val get_form_address : unit -> string option = <fun>
```reason
let ((<.), (>.)) = Function.Infix.((<.), (>.))
```
# get_form_address ();;
- : string option = Some "123 Bar St."
```

- 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.
### Instantiated Functors

For interfaces based on functors, use already instantiated functors if available to avoid the extra
boilerplate:

Example code:
```reason
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]));
```ocaml
# Functors.ArrayF.Int.Additive.Fold_Map.fold_map
- : ('a -> int) -> 'a array -> int = <fun>
```

See the unit tests for many more examples
### Don't Overuse Infix

## Side effects / IO
Don't overuse infix operators. If the code is combinatorial it can make it more readable, but in a
lot of cases the prefix operators are simpler and easier to read. If you do use infix operators,
prefer local opens over global opens to avoid polluting the toplevel:

See the [bs-effects][8] package for sync and async implementations of the "IO monad", and
the [bs-free][9] package for free monads and other free structures.

## Use with ppx_let
```ocaml
# let trim_all strings =
let open List.Infix in
StringLabels.trim <$> strings;;
val trim_all : string list -> string list = <fun>
You can integrate monads with [ppx_let](https://opam.ocaml.org/packages/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:
# trim_all ["foo "; "bar"; " baz"];;
- : string list = ["foo"; "bar"; "baz"]
```

```ocaml
module OptionLet = PPX_Let.Make(Option.Monad);;
### Use Abbreviated Modules

let add_optionals = fun x y ->
let open OptionLet in
let%bind x' = x in
let%bind y' = y in
Some (x' + y');;
Abbreviated modules can make code both terser and easier to read in some situations, like for
example where two different semigroups are used in the same function and infix operators can't be
used:

Js.log @@ add_optionals (Some 123) (Some 456);; (* Some 579 *)
```ocaml
# type game = { score: int; disqualified: bool };;
type game = { score : int; disqualified : bool; }
# let total_score a b =
let module I = Int.Additive.Semigroup in
let module B = Bool.Disjunctive.Semigroup in
{ score = I.append a.score b.score; disqualified = B.append a.disqualified b.disqualified };;
val total_score : game -> game -> game = <fun>
# let result =
let game_1 = { score = 4; disqualified = false }
and game_2 = { score = 2; disqualified = true }
in
total_score game_1 game_2;;
val result : game = {score = 6; disqualified = true}
```

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


## License

Licensed under the BSD-3-Clause license. See `LICENSE`

See [LICENSE][2]


[1]: https://github.com/Risto-Stevcev/bs-abstract/blob/master/src/interfaces/Interface.re
[2]: https://github.com/Risto-Stevcev/bs-abstract/blob/master/src/interfaces/Verify.re
[3]: https://github.com/Risto-Stevcev/bs-abstract/blob/master/src/interfaces/Infix.re
[4]: https://github.com/Risto-Stevcev/bs-abstract/blob/master/src/utilities/Default.re
[5]: https://github.com/Risto-Stevcev/bs-abstract/blob/master/src/utilities/Functors.re
[6]: https://github.com/Risto-Stevcev/bs-abstract/blob/master/src/functions/Functions.re
[7]: https://github.com/Risto-Stevcev/bs-abstract/blob/master/src/implementations
[8]: https://github.com/Risto-Stevcev/bs-effects
[9]: https://github.com/Risto-Stevcev/bs-free
[1]: https://risto-stevcev.github.io/ocaml-abstract
[2]: https://github.com/Risto-Stevcev/bs-abstract/blob/master/LICENSE
9 changes: 6 additions & 3 deletions bsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@
"bsc-flags": ["-bs-super-errors"],
"warnings": {
"error" : "+101+25+8+9+27",
"number": "-102-48"
"number": "-102-48-3"
},
"sources": [{
"dir": "src",
"dir": "ocaml_abstract",
"subdirs": true
}, {
"dir": "test",
"dir": "ocaml_abstract_js/src",
"subdirs": true
}, {
"dir": "ocaml_abstract_js/test",
"type": "dev",
"subdirs": true
}],
Expand Down
Loading

0 comments on commit cd485a1

Please sign in to comment.