darklang / tablecloth Goto Github PK
View Code? Open in Web Editor NEWA standard library with the same API in F#, Rescript and OCaml
Home Page: https://www.tablecloth.dev
License: MIT License
A standard library with the same API in F#, Rescript and OCaml
Home Page: https://www.tablecloth.dev
License: MIT License
This should work just like the functionList
:
val splitAt: string -> index:int -> (string * string)
Currently Tablecloth sticks to Elm's API by naively using flip
in the implementation of fold for Lists / Arrays.
@j-m-hoffman showed this had some pretty disastrous consequences for performance in a benchmark so it looks like we have two options:
'b -> 'a -> 'b
type signatureIt's actually kinda a big deal, as making it ergonomic to use with the rest of the library means we have to ensure all of the other modules have API's that permit "non-flipped" usage
e.g. Having foldLeft
take f : 'a -> 'b -> 'b
means that we should also make sure that the signature for Set.add
is 'a -> 'a t -> 'a t
.
This means making a commitment to either:
t
then any non-labelled arguments then the labelled arguments aka the approach of Base / Core.I would be in favour of either 1 or 3, I think being able to consistently use |>
is important:
->
and |>
when using BeltI noticed Tablecloth API doesn't have Float.toString
/ Float.to_string
. Both backends have corresponding APIs Belt.Float.toString
and Base.Float.to_string
. I'll create PR for this if there is no reason to add this API. Please comment :)
This appears to be a known issue, but I'm creating an issue in this repository too so it doesn't get lost: ocaml/opam-repository#13670 (comment).
I got this error after installing and compiliing with the lastest bucklescript
Warning number 3
/home/liftit/code/ml/redemo/node_modules/tablecloth-bucklescript/src/tablecloth.ml 797:54-63
795 ┆ let regex s : Js.Re.t = Js.Re.fromStringWithFlags ~flags:"g" s
796 ┆
797 ┆ let contains ~(re : Js.Re.t) (s : string) : bool = Js.Re.test s re
798 ┆
799 ┆ let replace ~(re : Js.Re.t) ~(repl : string) (str : string) =
deprecated: Js.Re.test
Please use Js.Re.test_ instead
Warning number 3
/home/liftit/code/ml/redemo/node_modules/tablecloth-bucklescript/src/tablecloth.ml 804:5-14
802 │
803 │ let matches ~(re : Js.Re.t) (s : string) : Js.Re.result option =
804 │ Js.Re.exec s re
805 │ end
Looks like the definitions of these functions changed
I don't think one need to differentiate 'native' and 'js_of_ocaml'.
Just removing the dependency on Str
will make the lib fully usable with js_of_ocaml.
Str
is only used to split strings. One should be able to achieve the same thing with a bit of glue around String.Search_pattern.index_all
This should work the same way as Option.unwrap
but the default
value should be a 'a lazy
rather than a 'a
getLazy : 'a option -> default:('a Lazy.t) -> 'a
Dependabot couldn't find a package.json for this project.
Dependabot requires a package.json to evaluate your project's current JavaScript dependencies. It had expected to find one at the path: /bs/package.json
.
If this isn't a JavaScript project, or if it is a library, you may wish to disable updates for it from within Dependabot.
In the bucklescript code path, String.to_list
produces a list of something that is not quite an ocaml char
, so that a |> String.to_list |> String.from_list
does not give back a
, and attempting to do anything that expects an ocaml char with the contents of String.to_list a
gives unpredictable results (mostly i seem to get a bunch of 0x0
unless i pass it straight back to a Js
function).
npm install tablecloth-bucklescript
currently installs a version from over 100 commits ago - could you please make a new release?
(also if the project can be installed directly from github, could you add instructions to the readme?)
This is sort of a wishlist / half-baked idea. Is there a clever way for us to share the test cases between bs
and native
?
This may be over-engineered, but perhaps an S-expression DSL that could compile into test cases would be simple enough for us to seriously DRY up the testing code and ensure that our assertions are aligned between bs
and native
.
Or maybe there's a better way to design this that wouldn't involve writing a DSL compiler if it were just native OCaml.
Here's a sketch of a datastructure that I think could help dedup a lot of the assertions:
(List
(init
("returns None for an empty list" [] None)
("returns an empty list for a single item list" ['a'] (Some []))
("returns all but the last item for a list with two or more items" ['a';'b';'c';'d'] ['a';'b';'c'])))
Like I said, this is half-baked, and I think an OCaml solution would be preferable. Just wanted to raise the issue before forgetting.
Hi all! So I'm a college student who loves Reason/OCaml, and also have my college's annual Hackathon next weekend. What's cool about the University of Illinois' Hackathon is that it's fully open-source; this got me thinking, what better project to do then to contribute to a standard library!
If one of the maintainers could post several issues (new algorithms/methods, documentation fixes, etc.) they think I could tackle in a 36 hour period, that'd be awesome! If there are enough doable things I can contribute to, I definitely have a shot at winning 😄
Thanks in advance!
These should work in the same way as List.take
It looks as if foldrValues
is a helper function for values
. (What it really does is prepend an item to a list, so its name as a stand-alone function is not very meaningful.)
Suggestion: remove it from the .mli
file, and nest the function in the values
function in the .ml
file. This would also allow you to get rid of foldr_values
, which does not appear to be used anywhere else.
We have got flatten and concat which do the same.
Elm and Base use concat.
Remove flatten?
This should work the same as List.splitAt
Right now, in order to construct a Result
, you have to use the platform specific Belt.Result.Ok
or Jane Street's Result.return
and Result.fail
.
Would you be open to a PR adding these functions (and likewise for Option)
These should work the same way as their Array counterparts
Array has the sliding
and chunksOf
functions:
val sliding : ?step:int -> 'a t -> size:int -> 'a t t
(** Provides a sliding 'window' of sub-arrays over an array.
The first sub-array starts at index [0] of the array and takes the first [size] elements.
The sub-array then advances the index [step] (which defaults to 1) positions before taking the next [size] elements.
The sub-arrays are guaranteed to always be of length [size] and iteration stops once a sub-array would extend beyond the end of the array.
{[Array.sliding [|1;2;3;4;5|] ~size:1 = [|[|1|]; [|2|]; [|3|]; [|4|]; [|5|]|] ]}
{[Array.sliding [|1;2;3;4;5|] ~size:2 = [|[|1;2|]; [|2;3|]; [|3;4|]; [|4;5|]|] ]}
{[Array.sliding [|1;2;3;4;5|] ~size:3 = [|[|1;2;3|]; [|2;3;4|]; [|3;4;5|]|] ]}
{[Array.sliding [|1;2;3;4;5|] ~size:2 ~step:2 = [|[|1;2|]; [|3;4|]|] ]}
{[Array.sliding [|1;2;3;4;5|] ~size:1 ~step:3 = [|[|1|]; [|4|]|] ]}
*)
let chunksOf t ~size = sliding t ~step:size ~size
These would be good additions to the String module too
Elm uses indexedMap
both for List
and Array
. In Tablecloth, I was expecting to find Array.indexedMap
instead of Array.mapWithIndex
.
This surprised me:
insert_at ~index:(-1) ~value:999 [100;101;102;103] = [999]
insert_at ~index:5 ~value:999 [100;101;102;103] = [999]
Looking at the code, this happens because take
and drop
return the empty list when given values that are out of bounds.
However, I would expect the function to do one of these:
As soon as I install tablecloth-bucklescript (following the instructions in the README) to a project and open it in VS Code, the reason language server crashes and all the reason-specific IDE features disappear. My logs say
Fatal error: exception Failure("Invalid subdirs entry")
Raised at file "stdlib.ml", line 33, characters 17-33
Called from file "src/analyze/FindFiles.re", line 49, characters 44-76
Called from file "list.ml", line 88, characters 20-23
Called from file "src/analyze/FindFiles.re", line 35, characters 30-69
Called from file "util/Infix.re", line 17, characters 68-73
Called from file "src/analyze/FindFiles.re", line 55, characters 2-51
Called from file "src/analyze/FindFiles.re", line 313, characters 19-70
Called from file "util/Infix.re" (inlined), line 16, characters 62-67
Called from file "src/analyze/FindFiles.re", line 303, characters 11-1023
Called from file "list.ml", line 88, characters 20-23
Called from file "src/analyze/FindFiles.re", line 300, characters 4-1023
Called from file "src/analyze/State.re", line 160, characters 55-129
Called from file "src/analyze/State.re", line 583, characters 24-73
Called from file "src/lsp/MessageHandlers.re" (inlined), line 15, characters 17-92
Called from file "src/lsp/MessageHandlers.re", line 296, characters 11-35
Called from file "src/lsp/BasicServer.re", line 44, characters 17-39
Called from file "src/lsp/BasicServer.re", line 111, characters 61-123
Called from file "src/lsp/Main.re", line 130, characters 6-255
Called from file "bin/Bin.re", line 3, characters 9-24
[Info - 1:51:43 PM] Connection to server got closed. Server will restart.
It then restarts 4 times before giving up entirely.
I'm using Windows 10, and VS-Code v.1.33.0 (user setup), the latest extension "reason-vscode" v. 1.5.2 (by Jared Forsyth). And I'm writing my code in BuckleScript (OCaml syntax) not the Reason syntax.
I have a minimal repro here. If I do npm install
and npm run build
, and then open it in VS Code, the language server will crash. If I remove "tablecloth-bucklescript" from bsconfig.json and reopen VS Code, it'll open fine with all IDE features working.
Any ideas where to start looking to get to the bottom of this? I'm not even sure where to start.
We should ocamlformat using:
profile = sparse
break-string-literals = never
escape-strings = preserve
This would be a check in CI (once it's set up) to verify that everything is properly formatted.
This should work the same way as Result.unwrap but the default value should be a 'a lazy rather than a 'a
val getLazy : 'a Result. -> default:('a Lazy.t) -> 'a
val takeLeft : string -> count:int -> string
(* Partition into two lists, of potentially different type, using function
* `f`. Returns value in the first list for `Left` and second list for
* `Right`. *)
let partitionMap (items : 'c list) ~(f : 'c -> ('a, 'b) Either.t): ('a list * 'b list) =
TableclothList.foldRight
~initial:([], [])
~f:(fun (lefts, rights) item ->
match f item with
| Left a ->
(a :: lefts, rights)
| Right b ->
(lefts, b :: rights))
items
These produce different output:
let _ = Js.log(foldl ~f:(-) ~init:0 [1;2;3]) (* output is 2 *)
let _ = Js.log(List.fold_left (-) 0 [1;2;3]) (* output is -6 *)
It looks like the reducer function forList.fold_left
takes the accumulator and list item as its parameters (in that order); foldl
takes the list item and accumulator.
Possible performance issue: the code for foldl
in bs/src/tablecloth.ml
uses List.fold_right
, which is not tail recursive, according to http://caml.inria.fr/pub/docs/manual-ocaml/libref/List.html
The Js.Math.pow_int
function has been deprecated, we need an alternative implementation for Int.power
and Int.(**)
Its a false positive right now: https://circleci.com/gh/darklang/tablecloth/549
The following is printed on the make check-format
step
/bin/sh: 1: fd: not found
Find some way to DRY the comments, perhaps something which syncs them from a single source?
You can install it like:
npm i --save-dev bs-platform@next
Main one is:
[start-reason] deprecated: Js.Re.test
[start-reason] Please use Js.Re.test_ instead
There might be others
https://twitter.com/bobzhang1988/status/1107099244748394497
Changes: https://github.com/BuckleScript/bucklescript/blob/master/Changes.md#500
Making a note of the functions we've written in the darklang repo that I haven't upstreamed yet:
Here's the bucklescript functions:
module Array = struct
let iter ~(f : 'a -> unit) (arr : 'a array) : unit = Belt.Array.forEach arr f
end
module Option = struct
let exec ~(f : 'a -> unit) (v : 'a option) : unit =
match v with Some v -> f v | None -> ()
let orLazy (v : 'a option) (v2 : unit -> 'a option) : 'a option =
match v with Some v -> Some v | None -> v2 ()
let orElseLazy (v : unit -> 'a option) (v2 : 'a option) : 'a option =
match v2 with Some v2 -> Some v2 | None -> v ()
let pair (a : 'a option) (b : 'b option) : ('a * 'b) option =
match (a, b) with Some a, Some b -> Some (a, b) | _ -> None
let map2 (a : 'a option) (b : 'b option) ~(f : 'a -> 'b -> 'c) : 'c option =
match (a, b) with Some a, Some b -> Some (f a b) | _ -> None
let andThen2 (a : 'a option) (b : 'b option) ~(f : 'a -> 'b -> 'c option) :
'c option =
match (a, b) with Some a, Some b -> f a b | _ -> None
let isSomeEqualTo ~(value : 'a) (o : 'a option) : bool = Some value = o
(* If a is some, then apply fn to a, return both a and the result.
if either a or b is none, then return none
*)
let thenAlso (a : 'a option) ~(f : 'a -> 'b option) : ('a * 'b) option =
let b = andThen ~f a in
pair a b
let withDefaultLazy (a : 'a option) ~(default : unit -> 'a) : 'a =
match a with Some a -> a | None -> default ()
end
module Result = struct
let isOk (v : ('err, 'ok) t) : bool = match v with Ok _ -> true | _ -> false
end
module List = struct
(* From https://github.com/janestreet/base/blob/eaab227499b36bb90c2537bc6358a2d5caf75227/src/list.ml#L247 *)
let findMap t ~f =
let rec loop = function
| [] ->
None
| x :: l ->
(match f x with None -> loop l | Some _ as r -> r)
in
loop t
let findWithIndex ~(f : int -> 'a -> bool) (l : 'a list) : int option =
let rec findIndexHelper
~(i : int) ~(predicate : int -> 'a -> bool) (l : 'a list) : int option =
match l with
| [] ->
None
| x :: rest ->
if predicate i x
then Some i
else findIndexHelper ~i:(i + 1) ~predicate rest
in
findIndexHelper ~i:0 ~predicate:f l
let rec dropLeft ~(count : int) (l : 'a list) : 'a list =
if count <= 0
then l
else
match l with
| [] ->
[]
| [_] ->
[]
| _ :: rest ->
dropLeft ~count:(count - 1) rest
let dropRight ~(count : int) (l : 'a list) : 'a list =
l |> reverse |> dropLeft ~count |> reverse
let range (start : int) (end_ : int) : 'a list =
let length = end_ - start in
if length < 0 then [] else Belt.List.makeBy length (fun i -> i + start)
(* Takes everything before and after, but not including nexted element *)
let splitOn ~(index : int) (l : 'a list) : 'a list * 'a list =
(take ~count:index l, drop ~count:(index + 1) l)
(* Moves item in oldPos into the position at newPos, pushing the element already at newPos down. Ex:
l = [a b c d]
moveInto 3 1 l, takes d and moves it between a & b. => [a d b c]
NOTE: This is not swapping the elements in newPos & oldPos
*)
let moveInto ~(oldPos : int) ~(newPos : int) (l : 'a list) : 'a list =
match getAt ~index:oldPos l with
| Some value ->
let index =
(* Checks to see if we need to offset the newPos by -1, after removing the element at oldPos *)
if newPos > oldPos
then
let len = List.length l in
(* Clamp at list length to prevent overflow *)
if newPos > len then len - 1 else newPos - 1
else newPos
in
l |> removeAt ~index:oldPos |> insertAt ~index ~value
| None ->
l
(* Partition into two lists, of potentially different type, using function
* `f`. Returns value in the first list for `Left` and second list for
* `Right`. *)
let partitionMap ~(f : 'c -> ('a, 'b) Either.t) (items : 'c list) :
'a list * 'b list =
Tablecloth.List.foldr
~init:([], [])
~f:(fun item (lefts, rights) ->
match f item with
| Left a ->
(a :: lefts, rights)
| Right b ->
(lefts, b :: rights))
items
end
module Float = struct
let toString (f : float) : string = Js.Float.toString f
end
module Int = struct
let toString (i : int) : string = Js.Int.toString i
end
module String = struct
let splitAt ~(index : int) (s : string) : string * string =
(slice ~from:0 ~to_:index s, slice ~from:index ~to_:(length s) s)
let left ~(count : int) (s : string) : string = slice ~from:0 ~to_:count s
let rec segment ~(size : int) (s : string) : string list =
let front, back = splitAt ~index:size s in
if back = "" then [front] else front :: segment ~size back
let replaceChunk ~(from : int) ~(to_ : int) ~(replacement : string) s : string
=
slice ~from:0 ~to_:from s ^ replacement ^ slice ~from:to_ ~to_:(length s) s
(* returns the index of the last occurrence of character c in string s before position i+1 or None if c does not occur in s before position i+1. *)
let rindex_from_opt ~(pos : int) (s : string) (c : char) : int option =
String.rindex_from_opt s pos c
(* returns the index of the first occurrence of character c in string s after position i or None if c does not occur in s after position i.
*)
let index_from_opt ~(pos : int) (s : string) (c : char) : int option =
String.index_from_opt s pos c
end
module StrDict = struct
let values (dict : 'a t) : 'a list =
Belt.Map.String.valuesToArray dict |> Array.toList
let updateIfPresent ~(key : key) ~(f : 'v -> 'v) (dict : 'value t) : 'value t
=
update ~key ~f:(Option.map ~f) dict
let mergeLeft (dict1 : 'v t) (dict2 : 'v t) : 'v t =
Tablecloth.StrDict.merge
~f:(fun (_key : string) (v1 : 'v option) (v2 : 'v option) ->
match (v1, v2) with Some _, _ -> v1 | None, _ -> v2)
dict1
dict2
let mergeRight (dict1 : 'v t) (dict2 : 'v t) : 'v t =
Tablecloth.StrDict.merge
~f:(fun (_key : string) (v1 : 'v option) (v2 : 'v option) ->
match (v1, v2) with _, Some _ -> v2 | _, None -> v1)
dict1
dict2
let count (dict : 'v t) = Belt.Map.String.size dict
let mapValues (dict : 'v t) ~(f : 'v -> 'x) : 'x list =
dict |> values |> List.map ~f
let filterMapValues (dict : 'v t) ~(f : 'v -> 'x option) : 'x list =
dict |> values |> List.filterMap ~f
let mapWithKey (dict : 'v t) ~(f : key:string -> 'v -> 'x) : 'x t =
Belt.Map.String.mapWithKey dict (fun key v -> f ~key v)
let remove (dict : 'v t) ~(key : key) : 'v t = Belt.Map.String.remove dict key
let removeMany (dict : 'v t) ~(keys : key list) : 'v t =
Belt.Map.String.removeMany dict (Belt.List.toArray keys)
let singleton ~(key : key) ~(value : 'v) : 'v t = empty |> insert ~key ~value
let has ~key (dict : 'v t) : bool = Belt.Map.String.has dict key
end
module StrSet = struct
let removeMany ~(values : string list) (set : t) : t =
Belt.Set.String.removeMany set (Array.fromList values)
let eq = Belt.Set.String.eq
end
This should work the same way as Array.count and List.count
Option.map uses a labelled parameter for the function and therefore can be called either subject first or subject last (meaning it is interchangeable with ->
and |>
). However, Result.map
does not use a labelled parameter for the function meaning it can only be called subject-last, meaning it only works with |>
. Is there a reason for this inconsistency? Would you accept a PR updating Result to work like Option?
Warning number 3 (configured as error)
node_modules/tablecloth-bucklescript/src/tablecloth.ml 806:47-63
804 ┆ let capitalize (s : string) : string = String.capitalize s
805 ┆
806 ┆ let isCapitalized (s : string) : bool = s = String.capitalize s
807 ┆
808 ┆ let is_capitalized = isCapitalized
deprecated: String.capitalize
Use String.capitalize_ascii instead.
We've found a bug for you!
node_modules/tablecloth-bucklescript/src/tablecloth.ml
The implementation node_modules/tablecloth-bucklescript/src/tablecloth.ml
does not match the interface src/tablecloth.cmi:
...
In module Array:
Values do not match:
val empty : '_a array
is not included in
val empty : 'a array
File "node_modules/tablecloth-bucklescript/src/tablecloth.mli", line 77, characters 2-22:
Expected declaration
File "node_modules/tablecloth-bucklescript/src/tablecloth.ml", line 12, characters 6-11:
Actual declaration
rescript-lang/rescript-compiler#4087
After this change empty array has concrete type, current interface is expecting it to be polymorphic
Is any documentation generated? Or is the recommendation to read the .mli
files?
Every native build on CI reinstalls the opam components, see for example https://circleci.com/gh/darklang/tablecloth/21.
The cache should be storing them. Not sure why it isn't - perhaps opam is storing things in a different directory than we're caching?
Since Float.degrees
converts degrees to radians, Float.radians
should do the complementary operation of converting radians to degrees. This would be useful for any user-facing program involving angles, as non-mathematicians think of angles in degrees and expect to see both input and output in those units. (Either that or implement functions with names to_radians
and to_degrees
.)
Sample implemenetation
(* If a is some, then apply fn to a, return both a and the result.
if either a or b is none, then return none
*)
let thenAlso (a : 'a option) ~(f : 'a -> 'b option) : ('a * 'b) option =
let b = andThen ~f a in
pair a b
Possibly this is a Reason issue, not a Tablecloth one, but I'm too new to distinguish, so apologies:
open Tablecloth;
let x = Array.get(~index=0, xs);
gets rewritten by refmt to
open Tablecloth;
let x = 0[xs];
which is surprising to me at least. (I'm new to both Ocaml and Reason, so maybe I have different expectations.) Note that I originally wrote xs[0]
but that's a type error...
Another related issue is that 0[xs]
, even if it is intended syntax, triggers a warning about omitted labels.
My workaround is to write
open Tablecloth;
let x = Tablecloth.Array.get(~index=0, xs);
This function should have the following signature:
val insertNoOverride : 'key 'value 'id Map.t -> key:'key -> 'value -> ( 'key 'value 'id Map.t) 'value Result.t
It should return an Ok
containing the newly update map *if key
has not already been set.
If it has it should return an Error
containing the existing value for the key.
Open to suggestions for the name. Some inspiration:
This should work the same way as Array.findIndex
This would allow you to transform a list, returning either a Left
or a Right
to signal which component of the tuple the result should end up in
let partitionMap : 'a list -> f:('a -> ('b, 'c) Either.t) -> 'b list * 'c list
This should work the same way as its Array counterpart
in List, list parameter is named l, ls or xs.
think we should stick to one.
i often renamed ls to l and chose a in the Array module,
but xs would make it more uniform.
Suggestions?
Looks like sometime today (or yesterday evening) the MLI link on the readme was broken. It pointed to here: https://github.com/darklang/tablecloth/blob/master/bs/src/tablecloth.mli
It looks like the bs
folder got renamed to bucklescript
, which caused that path to change. Seems important to fix, since it's probably the first thing people try to access when they reach the repository.
The new link should point here: https://github.com/darklang/tablecloth/blob/master/bucklescript/src/tablecloth.mli
Hey all, thanks for the hard work on Tablecloth (and Standard) and for deciding to merge the two together, I think it's the right move even though compromises like that can be very tough!
Would the two following operations make sense to add? No preference for naming.
String.get
exists in Standard
which is handy since the Stdlib-version throws.
String.indexOf
exists in Stdlib
as index_opt
EDIT: Semi-related and I realise this may be personal preference, but I also kind of miss utility-functions like String.isEmpty(foo)
vs doing foo == ""
I hooked up tablecloth to CircleCI, but I didn't make any effort to make it work.
There's two test scripts that could be run. It might be necessary to use esy
or similar to have a repeatable install for opam packages.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.