FYI:
Jared Forsyth: A ReasonReact Tutorial was updated to Reason 3 shortly after itâs release Oct. 27 - the tutorial isnât a bad introduction into ReasonReact.
I also came across TDD a ReasonML function. bs-jest
seems to work reasonably well. I like to use unit tests to capture my experiments with the language - in this case without having to worry about React. Unfortunately itâs in the old syntax so it needs some fixing up for Reason 3.
bsconfig.json:
{
"name": "kata-map",
"version": "0.1.0",
"refmt": 3,
"sources": [
{ "dir": "src"},
{ "dir": "__tests__",
"type": "dev" }
],
"bs-dev-dependencies" : [
"bs-jest"
]
}
package.json:
{
"name": "kata-map",
"version": "0.1.0",
"scripts": {
"clean": "bsb -clean-world",
"build": "bsb -make-world",
"test": "npm run build && jest",
"watch": "bsb -make-world -w"
},
"keywords": [
"BuckleScript"
],
"license": "MIT",
"devDependencies": {
"bs-jest": "^0.2.0"
}
}
Note: bs-platform 2.1.0 was installed globally (and $ npm link bs-platform
)
/* file: __tests__/Listy_test.re
https://jaketrent.com/post/tdd-reasonml-function/
*/
open Jest;
let _ =
describe("map", () => {
open Expect;
test("map []", () => {
let noop = Js.Obj.empty;
expect(Listy.map(noop, [])) |> toEqual([])
});
test("map square", () => {
let square = x => x * x;
expect(Listy.map(square, [1, 2, 3])) |> toEqual([1, 4, 9])
});
test("map String.toUpperCase", () =>
expect(Listy.map(Js.String.toUpperCase, ["hello","reason"])) |> toEqual(["HELLO","REASON"])
);
test("map String.length", () =>
expect(Listy.map(Js.String.length, ["hello","reason"])) |> toEqual([5, 6])
);
});
let _ = describe("trampoline", () => {
open Expect;
open! Expect.Operators;
let trampoline = Listy.trampoline;
let tDone = (x) => Listy.Done(x);
let tCall = (thunk) => Listy.Call(thunk);
let rec even = (n) =>
switch n {
| 0 => tDone(true)
| _ => tCall(() => odd(n - 1))
}
and odd = (n) =>
switch n {
| 0 => tDone(false)
| _ => tCall(() => even(n - 1))
};
let trampolineFn = (fn) =>
((n, _)) => (n, trampoline(fn(n)));
let mapActual = (fn, expected) =>
List.map(trampolineFn(fn), expected);
test("trampolining even", () => {
let expected = [(0,true), (1,false), (2,true), (3,false)];
expect(mapActual(even, expected)) == expected;
});
test("trampolining odd", () => {
let expected = [(0,false), (1,true), (2,false), (3,true)];
expect(mapActual(odd, expected)) == expected;
});
});
/* file: src/Listy.re */
/* https://gist.github.com/fogus/1224507 */
type bounce('a) =
| Done('a)
| Call(unit => bounce('a));
/* Generated JS uses while loop instead of stack */
let rec trampoline = (b) =>
switch b {
| Done(x) => x
| Call(thunk) => trampoline(thunk())
};
/* Alternately explicitly use a loop
let bounceIt = (start) => {
let nextBounce = ref(true)
and result = ref(start);
while (nextBounce^) {
switch result^ {
| Call(thunk) => result := thunk()
| Done(_) => nextBounce := false
};
};
result^;
};
let rec trampoline = (b) => {
let result = bounceIt(b);
switch result {
| Done(x) => x /* Result */
| Call(thunk) => trampoline(thunk()) /* Never gonna happen */
};
};
*/
let rec reverseT = ((xs, zs)) =>
switch xs {
| [] => Done(zs)
| [y, ...ys] => Call(() => reverseT((ys, [y, ...zs])))
};
let rec mapReverseT = ((fn, xs, zs)) =>
switch xs {
| [] => Done(zs)
| [y, ...ys] => Call(() => mapReverseT((fn, ys, [fn(y), ...zs])))
};
/* (1) Generated JS uses stack for recursion
let rec map = (fn, lst) =>
switch lst {
| [] => []
| [x, ...xs] => [fn(x), ...map(fn, xs)]
};
(2) Generated JS uses while loops instead of stack
...
(3) Use explicit trampoline
let map = (fn, lst) =>
switch lst {
| [] => []
| lst =>
let zs = trampoline(mapReverseT((fn, lst, [])));
trampoline(reverseT((zs, [])))
};
*/
/* (2) Generated JS uses while loops instead of stack */
let rec reverseR = (xs, zs) =>
switch xs {
| [] => zs
| [y, ...ys] => reverseR(ys, [y, ...zs])
};
let rec mapR = (fn, xs, zs) =>
switch xs {
| [] => reverseR(zs,[])
| [y, ...ys] => mapR(fn, ys, [fn(y), ...zs])
};
let map = (fn, xs) =>
switch xs {
| [] => []
| [y, ...ys] => mapR(fn, ys, [fn(y)])
};