Relude is a ReasonML/OCaml standard library replacement ("prelude") written in ReasonML, targeting compilation to JavaScript via the Melange compiler.
Please visit the documentation site for more information!
FP-inspired prelude/standard library for ReasonML projects
Home Page: https://reazen.github.io/relude
License: MIT License
Relude is a ReasonML/OCaml standard library replacement ("prelude") written in ReasonML, targeting compilation to JavaScript via the Melange compiler.
Please visit the documentation site for more information!
...or maybe they do, idk. But these are some utility libraries that I'd like to have a "de facto" standard for.
relude-url
relude-parser
for parsing URLsrelude-color
relude-parser
IO
to a stable state firstfetch
or bs-fetch
Js.Date.t
I forget how close we actually got to this, but the following code kind of outlines how I'd like typeclasses to work:
Foldable.max((module List.Foldable), (module Int.Ord), [ 3, 1, 7, 2 ]);
Foldable.maxBy((module Array.Foldable), Int.compare, [| 4, 6, 1 |]);
List.max((module Float.Ord), [ 3.1, 3.6, 4.2 ]);
List.maxBy(Float.compare, [1.2, 1.1, 1.0]);
List.Int.max([1, 2, 3]);
With that same pattern extended to Functor/Applicative/Monad/Traversable and all of the implementation types.
To summarize: structure things as if modular implicits were a thing, then build specialized modules since the implicit resolution isn't real yet, and possibly more specific sub-modules so you don't have to pass modules anywhere if you don't want to.
Not saying this is a radical departure from what we've already been doing... just wanted to formalize it a bit more.
There are some annoyances in how flatMap
is currently defined as "value first."
I talked to @mlms13, and we decided that we could have a bind
function as (t('a), 'a => t('b)) => t('b))
with the usual infix of >>=
, and then settled on the convention that flatMap
is flip(bind)
: ('a => t('b), t('a)) => t('b)
.
This would make it easier to consistently use |>
for chaining with the named functions, and not have to resort to ->
for flatMap
.
It would be handy to add some helpers for dealing with tuples.
One thing I can think of off the top of my head is a set of applicative map2/map3/etc.
functions for a tuple. It will unfortunately have to use a module functor to get the applicative behavior, but we can add some specializations e.g. (pseudocode):
let fa: option(int) = Some(1);
let fb: option(int) = Some(2);
let fc: option(int) = (fa, fb) |> Relude.Tuple.Option.map2((a, b) => a + b));
Or maybe it should be Relude.Option.Tuple
?
This would be equivalent to Relude.Option.map2((a, b) => a + b, fa, fb)
but with better chainability and readability.
It would be handy to have a arbitrary precision numeric type, like BigDecimal from Java/Scala.
We could also consider adding a Numeric
typeclass that could be brought into scope for a specific numeric type to give access to basic infix operators.
A lazy Aff as an alternative to eager Future
Both refmt
and the OCaml compiler seem to do strange things with String.get
, even if you've shadowed the Pervasives String module locally. This could also happen for Array (and BigArray and other specific modules, I think).
The following function names are at risk of being handled incorrectly depending on the module name:
get
unsafe_get
set
unsafe_set
We should purge all of these names from Relude to avoid any potential issues.
It looks like jaredly/redoc is probably the fastest and easiest way to get API doc pages up.
I'm going to run through all of the option utilities I've used and written recently and make a list of all of the helper functions here:
(b, a => b, f(a)) => b
fold
in Scalamaybe
in PureScript and bs-abstractmapWithDefault
in Beltunwrap
in Elm (Maybe.Extra)map_default
in Batteriescata
in Thx (a, f(a)) => a
getOrElse
in Scala and ThxfromMaybe
in PureScriptwithDefault
in ElmgetWithDefault
in Beltdefault
in Batteries(|?)
in bs-abstract ((b, a) => b, b, f(a)) => b
(traditional fold left, is this even useful?)
foldLeft
in Scala (not sure if it's present for Option)foldl
in PureScript (and Elm, but not present for Maybe)fold_left
in bs-abstractreduce
in Belt and fantasy-land, but not present for Option (f(a), f(a)) => f(a)
alt
in PureScript, bs-abstract, fantasy-landor
in Elm (Maybe.Extra)orElse
in Elm (Maybe.Extra) (a => b, f(a)) => f(b)
map
pretty much everywhere (f(a => b), f(a)) => f(b)
ap
in PureScript and fantasy-landapply
in bs-abstractandMap
in Elm (Maybe.Extra) a => f(a)
pure
in PureScript and bs-abstractof
in fantasy-land (a => f(b), f(a)) => f(b)
flatMap
in Scala and Beltflat_map
(flipped) in bs-abstractbind
(flipped) in PureScript and BatteriesandThen
in Elmchain
in fantasy-land ((a, b) => c, f(a), f(b)) => f(c)
and friends
map2
in Elmlift2
in PureScript and bs-abstractliftA2
in Haskellap2
in Thx toList
toArray
lazy versions of maybe
and default
isSome
isNone
filter
join
infix functions
I originally wanted the default Option.fold
function to be lazy for the None case, to avoid constructing values that never get used. I added Option.foldStrict
to be the non-lazy, but more commonly-wanted version. However, in practice, people typically want to just use the signature of foldStrict
, with the option of having the lazy version available for certain cases.
This is in-line with how purescript handles this for Maybe
: https://github.com/purescript/purescript-maybe/blob/master/src/Data/Maybe.purs#L209-L232
I suggest we rename the current Option
fold
to foldLazy
and rename foldStrict
to fold
to make the non-lazy version the default, with the option of a lazy one that can be used if desired. I'm open to suggestions on better names for foldLazy
.
I know @mlms13 is on board with this!
@johnhaley81 reported that IO catchError
doesn't seem to work for Async
, and he's absolutely right - the implementation is missing it completely.
It's been awhile, so I'll need to revisit all the variants for catchError
to make sure all errors are handled.
namespace: true
is convenient in terms of having easy-to-reference modules in the project, but I'm running into more and more conflicts and name resolution issues with ocaml std lib modules like String
and List
, which have no base Stdlib
base module to use to resolve conflicts. The Stdlib
module was added in ocaml 4.07, but bucklescript is currently based on ocaml 4.02.3.
I'm proposing that we set namespace: false in bsconfig, rename our modules from List.re
to Relude_List.re
and create the base module Relude.re
which aliases all of our longer names like module List = Relude_List
Thoughts?
It would be cool if we could support both somehow, to satisfy everyone... but maybe it's best to just choose one style?
BuckleScript community seems to be pushing more for ->
while classic FP conventions and libs like bs-abstract
prefer the |>
style.
It would be nice to have our own Future type, so we have full control over the implementation, and can make it consistent with our other abstractions.
I'm having trouble with nixos and redoc stuff, and since we are planning to switch from redoc to something else anyway, I'm going to just remove redoc from the package.json, the docs folder, the current gh-pages site, etc.
(If we were planning on staying on redoc, I would have tried harder to fix my local setup 😃)
Consider bringing mlms13/bs-nonempty stuff into this project?
Or we could alias it, or just let it live outside on its own.
Result.fold
expects the Ok
handler to come first and the Error
handler to come second.Option.fold
expects the None
handler to come first and the Some
handler to come secondI think i like providing the "bad case" handler first, but I don't have a super strong opinion. Either way, this should probably change to be more consistent.
If you believe Purescript, scanl
and scanr
should come for free with anything that is Traversable, but I looked at their implementations and I'd be lying if I said it was immediately clear to me how we get there...
See what it would look like to have a single effect type IO.t('a, 'e)
. I want to try the bi-functor IO approach, because it is the latest craze in the Scala world (ZIO, cats-bio, etc.), and was hinted at in purescript-aff: purescript-contrib/purescript-aff#137
List.surround((module String.Semigroup), "-", []); // "-"
List.surround((module String.Semigroup), "a", ["h"]); // "aha"
List.surround((module String.Semigroup), "*", ["1", "2", "3"]); // "*1*2*3*"
This is a lot like Foldable.intercalate
except the "separator" is also added to the beginning and end. This also means that the inner type only needs to implement Semigroup (not Monoid) because the separator is used as the initial value.
Once we get a little farther along with our own Future
type, it would be interesting to try some monad transformers, like ResultT
to handle things like Future.t(Result.t(ok, err))
, which seems to be a common pattern.
ContT
ResultT
(aka ExceptT)ListT
(not sure if we want this one)OptionT
(aka MaybeT)ReaderT
WriterT
StateT
RWST
(reader/writer/state)String.replace
to String.replaceFirst
String.replaceAll
, possibly using Elude as a modelDoes this belong in this project, or is it best to leave it in a separate lib?
The benefit of having something like that in this project is consistency with our other conventions.
countBy
is free if your thing is Foldablemax
is free if your thing is Foldable and the inner thing has OrderingListF
thingsumInt
into List.Int.sum
This should be pretty quick, since Array is already a member of Foldable
, and a whole bunch of List functions were rewritten to come for free from Foldable.
file:///home/david/reasonlab/relude/docs/api/Relude.html
Works correctly on Chrome Version 73.0.3683.75 (Official Build) (64-bit)
CircleCI or Travis might be good options
Talking to @mlms13 - I think we decided on a basic release procedure. We're going to just do releases by hand for now, until we have a good procedure for doing automatic CI releases.
> ./release <bump>
where <bump> is major|minor|patch
This would perform the following operations:
...developer manually updates CHANGELOG.md to add summary of changes
...or maybe we can generate the changelog via PR metadata since the last tag?
...getting the new version # at this point might be tricky
# Update the version number in package.json and create the git tag
npm version $bump
npm publish
# Push the tags to the root repo
# Note: try to use an explicit reference to the root repo, rather than `upstream`
git push upstream --tags
# Create the github release
...Use github API to create a release for this new version
or
echo "Create github release now"
Not sure whether something like this exists in its more pragmatic form in bs-abstract
.
If you look at the Int
submodule in ListF
, you'll see that most of that duplicates the String
submodule in that same file, and when we add Float
, it will be even more duplication.
I think we can probably cut down on some of this by getting rid of the first-class-module functions in the Foldables
helper, and instead, we can add Eq
and Ord
submodules there. Then, ListF.Int
can look something like:
module Int = {
module FoldOrdFns = Foldable.Eq(List.Foldable, Int.Ord);
module FoldMonoidFns = Foldable.Eq(List.Foldable, Int.Additive.Monoid);
include FoldOrdFns;
let sum = FoldMonoidFns.fold;
};
Theoretically, we can get all of the Eq
functions for free from Ord
, and we can alias functions as needed. If this works, it should lead to a lot less duplication across ListF.String
, ListF.Int
, ListF.Float
, etc.
@fponticelli has proposed that Refreshing
should have it's own type parameter - but there are some other options to consider too:
type t('a, 'r, 'e) =
| Idle
| Loading
| Loaded('a)
| Refreshing('r)
| Failed('e)
;
The downside of this is that it becomes more of a bifunctor, so the basic applicative/functor/monad/etc. will only operate on the Loaded
value, rather than on Refreshing
too.
Another option would be to give Loading
a value of either type 'a
or another type:
| Idle
| Loading('a)
| Loaded('a)
| Failed('e)
You can choose your 'a
to represent whatever you want, including unit
for the first load, then something else when loaded/refreshing. the only semantic is whether the whole thing is Loading
or Loaded
.
Or even take it one more step:
| Idle('a)
| Loading('a)
| Loaded('a)
| Failed('e)
Or this:
| Idle('i)
| Loading('l)
| Loaded('a)
| Failed('e)
Thoughts?
When the outer thing is Foldable and the inner thing has Ordering, min
and max
should come for free. This is mostly useful for Int
and Float
.
Currently the signature of take
is (int, list('a)) => option(list('a))
, probably because that's what Belt does by default.
But "monoid of a monoid" is always awkward (is Some([])
different from None
? are we implying some kind of weird semantics here?), and in practice, I always use takeUpTo
and can't say I've ever touched take
.
I propose we change take
to (int, list('a)) => list('a)
(which is consistent with PureScript). We can keep the current take
behavior with a different name. @andywhite37 suggested takeExactly
, which I think is a pretty 👍 name.
I'm in the middle of a small refactor where I'm trying not to make interface-breaking changes, so I'm going to hold off on this for a bit. Consider it open-for-discussion-but-likely-to-change.
...and all of the other ...F
suffixed functions. This seems to be consistent with PureScript's maximumBy
where you pass in an a -> a -> Ordering
instead of a member of Ord.
Initially I was thinking that the function-based (as opposed to module-based) versions should be the "default" (e.g. eq
would be the function-based version instead of using an Eq module), but prioritizing modules feels "more correct", and I don't mind the ...By
suffix quite as much.
Relude.Option.Infix.(>>=)
appears to have its arguments in the wrong order, at least compared to what I'm used to.
$ ghci
Prelude> Just 5 >>= \n -> Just $ n + 1
Just 6
> Relude.Option.Infix.((n => Some(n + 1)) >>= Some(5));
Some(6)
We should discuss a versioning/release strategy.
Although it's not required, it's probably smart to push releases to npm, https://redex.github.io, or whatever other places people push ReasonML projects.
Idea for a basic process:
master
> npm version major|minor|patch
> npm publish
> submit a PR to redex (details TBD)
@mlms13 - thoughts?
bs-abstract uses a polymorphic variant for ordering: https://github.com/Risto-Stevcev/bs-abstract/blob/master/src/interfaces/Interface.re#L130
It would be useful to understand why a polymorphic variant was used, rather than a non-polymorphic variant like:
type ordering = | EQ | LT | GT
Since ordering
is not likely to be extended, I'm not sure what the reason was for using polymorphic.
We could deviate from bs-abstract if we wanted to, but it would be nice to keep a compatibility layer if we do.
This looks a lot like a bug to me:
relude/src/Relude_AsyncData.re
Line 16 in 9018b40
Is this the expected behavior, especially from drop
?
take(-1, [|100, 101, 102|]) == Some([| |]);
take(-2, [|100, 101, 102|]) == Some([| |]);
take(-3, [|100, 101, 102|]) == Some([| |]);
take(-4, [|100, 101, 102|]) == Some([| |]);
but...
drop(-1, [|100, 101, 102|]) == Some([|102|]);
drop(-2, [|100, 101, 102|]) == Some([|101, 102|]);
drop(-3, [|100, 101, 102|]) == Some([|100, 101, 102|]);
drop(-4, [|100, 101, 102|]) == Some([|100, 101, 102|]);
I would expect all of the above to return None
.
After looking at the code: it appears that take()
and drop()
call JavaScript’s Array.Prototype.slice()
, which gives these exact weird-seeming results.
AsyncData status as bool:
isInit
isBusy
-- true for Loading or ReloadingisLoading
-- true for Loading, false for ReloadingisReloading
isComplete
isSuccess
, isFailure
(or isCompleteOk
, isCompleteError
?) for AsyncResultAsyncData get option:
getComplete
getOk
, getError
for AsyncResultAdd some folding functions to help with the verbose pattern matches with AsyncResult.
Also add some helper functions to transition an AsyncData/Result from one state to another, carrying state over as appropriate (e.g. toLoading
, etc.)
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.