Reputation: 2350
I am new at reason / ocaml / functional programming.
I know about List.append
and [] @ []
but these functions will create new list but how to populate existing list / array?
let coords: array point = [];
Reason code:
type point = {x: int, y: int};
let coords: list point = [];
let append raw =>
Array.iter
(
fun data => {
let p = {x: data.x, y: data.y};
/* how to append p to coords */
()
}
)
raw;
JS analogue:
const coords = [];
const append = raw => raw.forEach({x, y} => {
coords.push({
x: process(x),
y: process(y)
});
});
Upvotes: 1
Views: 1950
Reputation: 703
Here are few different ways to do it. If you want to stick with Js.Array
as you noted in the comments, you can do it like this:
Js.Array
and Array.iter
type point = {
x: int,
y: int,
};
let points: array(point) = [|{x: 2, y: 4}|];
let rawArray = [|[|1, 2|], [|3, 4|], [|9, 4|]|];
let append = raw => {
Array.iter(
a => {
let data: point = {x: a[0], y: a[1]};
Js.Array.push(data, points)->ignore;
},
raw,
);
};
append(rawArray);
Js.log2("new points", points);
...or with tuples with `Js.Array`
let rawTuples = [|(3, 5), (4, 9), (9, 4)|];
append(rawTuples);
Js.log2("new points", points);
[ { x: 2, y: 4 }, { x: 3, y: 5 }, { x: 4, y: 9 }, { x: 9, y: 4 } ]
If you want to do it with Array.fold_left
and Array.append
as suggested by @chenglou, you can try it with
rawArray|>Array.fold_left((a, b) =>
Array.append(a, [|{x: b[0], y: b[1]}|]), [||]);
It might be cleaner with some helper functions like:
let concatMap = f =>
Array.fold_left((a, b) => Array.append(a, f(b)), [||]);
let newPoint = coord => [|{x: coord[0], y: coord[1]}|];
Then calling:
let rawTuples = [|(3, 5), (4, 9), (9, 4)|];
let arrayTuples = rawTuples |> concatMap(a => [|{x: fst(a), y: snd(a)}|]);
Using helpers is also helpful for my understanding of what each part of the function is doing.
Works with tuples
too since those are just arrays in ReasonML/Ocaml/Rescript
let rawTuples = [|(3, 5), (4, 9), (9, 4)|];
let arrayTuples = rawTuples |> concatMap(a => [|{x: fst(a), y: snd(a)}|]);
You have the option of creating your array with mutable records
.
This is what update an array of mutable records. It not much different.
Here we are using a function to alter the data in the array.
let printCoords = coords => Array.iter(Js.log, coords);
type mutablePoint('a, 'b) = {
mutable x: 'a,
mutable y: 'b,
};
let data1: mutablePoint(int, int) = {x: 2, y: 4};
let data2: mutablePoint(int, int) = {x: 3, y: 4};
let data3: mutablePoint(int, int) = {x: 4, y: 4};
let isEven = n => {
n mod 2 == 0;
};
let multiplyByY = data => data.x = data.x * data.y;
let makeItOdd = data => data.x = data.x + 1;
let updateData = data => data.x->isEven
? data->makeItOdd : multiplyByY(data);
let points: array(mutablePoint('x, 'y)) = [|data1, data2, data3|];
let append = (fn, data) => {
Array.iter(x => {fn(x)}, data);
data;
};
points |> append(updateData);
Js.log("points after");
printCoords(points);
// points after { x: 3, y: 4 } { x: 12, y: 4 }{ x: 5, y: 4 }
Your question was about updating from some raw data so here is an example of that where we are taking the rawdata at index i and using it to change the x
value on the mutable array:
let points2: array(mutablePoint('x, 'y)) = [|data1, data2, data3|];
let printCoords = coords => Array.iter(Js.log, coords);
printCoords(points2);
let rawData = [|[|1, 2|], [|3, 4|], [|9, 4|]|];
let update_x_on_point_i = (i, x) => points2[i].x = x;
let append = raw =>
Array.iteri(
(i, d) => {
let x: int = d[0];
update_x_on_point_i(i, x);
},
raw,
);
append(rawData);
Js.log2("points2 after: ", points2);
printCoords(points2);
// points2 after: [ { x: 1, y: 4 }, { x: 3, y: 4 }, { x: 9, y: 4 } ]
These two array happen to be the same size so there is no exception but there will easily be one once the array lengths are different so we should handle that in real life.
Belt.Array
Just because I happen to have done this today for the same reasons that led me to this post, here is the JS Analogue
version.
type point = {
x: int,
y: int,
};
let coords = [|{x: 9, y: 7}, {x: 2, y: 4}, {x: 3, y: 8}|];
Js.log("coords before");
Js.log("-------");
let append = raw =>
raw->Belt.Array.mapWithIndex(
_,
(i, r) => {
let new_point_i = {x: r[0], y: r[1]};
coords[i] = new_point_i;
},
);
let rawData = [|[|1, 2|], [|3, 4|], [|9, 4|]|];
append(rawData);
Js.log("coords after");
Js.log(coords);
coords before
[ { x: 9, y: 7 }, { x: 2, y: 4 }, { x: 3, y: 8 } ]
-------
coords after
[ { x: 1, y: 2 }, { x: 3, y: 4 }, { x: 9, y: 4 } ]
The following code is updated syntax for @yawar's code in the gist in the comments above. His explanation there is worth reading.
type data_item = {
symbol: string,
next: bool,
};
/* Convenience function for making data items. */
let make_data_item = (symbol, next) => {symbol, next};
let process = (data: list(data_item)) => {
/*
We track the current symbol as well as the result list in the folding function.
*/
let fold_func = ((current, result), {symbol, next}) =>
if (next) {
(symbol, [current, ...result]);
} else {
(current ++ symbol, result);
};
let (current, result) = List.fold_left(fold_func, ("", []), data);
/*
We need to reverse the result list because `[el, ...els]` above actually
builds it up in the _opposite_ order to the original input list.
*/
(current, List.rev(result));
};
let result =
process([
make_data_item("a", false),
make_data_item("b", false),
make_data_item("c", true),
make_data_item("d", false),
]);
/* result = ("cd", ["ab"]) */
Upvotes: 0
Reputation: 3640
Welcome to Reason!
In Reason/OCaml, lists are immutable. Under the hood they're simple singly-linked list. You create new ones each time you "modify" them. Here's an example:
let a = [1, 2, 3];
let b = [0, ...a];
This is akin to JavaScript's array "spread", except here you're taking the existing a
, linking a new node 0
at the front, and calling it b. a
still points to [1, 2, 3]
(thus "immutable"). b
is now [0, 1, 2, 3]
. This is efficient since the [1, 2, 3]
part is shared.
The advantage of this is that you don't have to worry about passing your list around and accidentally have an obscure function modify it. List's immutability allows you to reason about your code purely by looking at the value you're staring right now (since it'll never change!).
The drawback of list is that it's inefficient to add something at the end:
let c = a @ [4]
That operation's basically taking a list of one item, [4]
, and successively attaching each item of [1, 2, 3]
to it. So linear in terms of perf. But judging by the simplicity of the list implementation, it was historically deemed worth the tradeoff.
So 3. it's the wrong flow if you're trying to set a list item.
let newList = List.map (fun blabla => ...) raw
Array.of_list
and Array.to_list
if you're ever stuck.More on array: OCaml array is mutable, and its size is unchangeable. Think of it as a block of memory. You'd allocate a new array through Array.make newSize
, then populate it through Array.set
. This wouldn't make sense if you're resizing the array a lot, so choose the right data structure.
For JS compilation, BuckleScript compiles an ocaml array to a JS array. It's thus mutable and resizable. You'll find your familiar JS array operations under Js.Array
As a general heuristic, if you'd like to change the length: try filter
. If you'd like to change the length and the contained items, try fold_left
. Otherwise, map
.
More recently, we've started implementing some immutable, resizable, optionally mutable array. Stay tuned!
Upvotes: 8