Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Ramda inspired library of helper functions for ReasonML

License

NotificationsYou must be signed in to change notification settings

jonlaing/rationale

Repository files navigation

Rationale is inspired byRamdaJS. It is a collection of helper utility functions that are absent in the OCaml/ReasonML standard library.

Note that not all of Ramda was ported over, as many of Ramda's utilities are making up for deficits in Javascript, which Reason doesn't have. Furthermore, many of the functions that operate on objects, simply don't make sense in Reason.

Installation

Runnpm install --save rationale and addrationale tobs-dependencies inbsconfig.json.

Features

Exception-free List operations

In the OCaml/ReasonML standard library, many of the common List operations throw exceptions if there's a problem. Rationale's utilities do not throw exceptions, and instead returnoptions.

  • head
  • tail
  • init
  • last
  • nth
  • etc

Monadic Options and Belt.Results

Rationale includes monadic and functor operations ala Haskell for theoption andBelt.Result types.

openRationale.Option.Infix;openRationale.Function;RList.init(a)>>= ((x)=>RList.last(a)<$> f<$> flip(RList.append, x))<$>RList.concat(b)|>Option.default(xs);

Additional ADT-s

Reader

Writer

IO, KIO

The main idea of the IO monad is to isolate our effects as much possible. Some languages like Haskell don't evenallow the user to manually start an effect, which is not the case for Reason, but with a little bit of self-discipline we can handle side effects in a monadic fashion as well.

KIO is a monadic structure with the type signature:io('a, 'env) = IO(Lazy.t('env => 'a))which makes it posible to store some effect with its config environment. ( dependency injection )

We can usereturn orlift methods to wrap our unsafe mutations.The difference is that withreturn we throw away the environment, while withlift we are using it.

As you can see in our example we usedreturn to wrapJs.log

letlog= (x:string)=>KIO.return({Js.log(x)  });

In the followingsaveFile example we use lift which makes it possible to use an injectedenv config( the injection happens when we callKIO.runIO )

letsaveFile= str=>KIO.lift(env=> {Node.Fs.writeFileSync(env.target, str,`ascii);});

To run our effects we need to call: KIO.runIO(main, env);Ideally in your program this method will be called once on the bottom of your index file.Example:

exceptionReadError(string);typeenvT= {    path:string,    dir:string,    target:string  };letenv= {path:"./input.txt", dir:"/", target:"./out.txt"};letreadFile=KIO.lift(env=> {try (Node.Fs.readFileSync(env.path,`ascii)) {|ReadError(msg)=>raise@@ReadError("File read failed: "++ msg)      };    });letsaveFile= str=>KIO.lift(env=> {Node.Fs.writeFileSync(env.target, str,`ascii);  });letlog= (x:string)=>KIO.return({Js.log(x)    });letparseFile= input=> {letl=Js.String.split("\n", input);Array.map(x=> x++"100", l);  }letjoinArray= (xs:array(string))=>Js.Array.join(xs)letmain=KIO.(    readFile<$> parseFile<$> joinArray>>= saveFile  )KIO.runIO(main, env);

Support for Point-free style

Rationale hascompose andpipe functions, as well as supporting infix operators:<|| and||> respectively.

Infix Lens composition

Rationale also allows for fluid lens composition via infix operators:-<< and>>-.

Lens.view(aLens>>- bLens>>- optional(0), { a: { b:Some(3) } });

Function signatures for composition

Like in Ramda, functions always keep their data at the end making piping and composing a breeze:

list|> take(9)|> drop(3)|> splitAt(4);

Usage

Using Optional Returns in RList and Dict

Returningoption('a) from functions is generally preferred to throwing an exception.It protects you from runtime errors, and forces you to deal with potential errors atcompile time. However, if you're not used to doing it, things can get a littleconfusing.

Pattern matching for errors all the time would be extremely cumbersome. Fortunately,we provide a host of useful methods to working with optional returns. Hopefully,this doc will show you that you don't need to use excessive pattern matching towork with optional returns.

Default

The most straight forward way to get out of anoption is by callingdefault.

Option.default(0,Some(1));/* 1*/Option.default(0,None);/* 0*/

Monads

Callingdefault will definitely get you out of theoption, but what if you wantto do some things to it first? What if you need other funtions that also returnoption?

the Option module includes monadic operations foroption, so you can take arailway orientedapproach to working with them.

First, let's check if the last item of a list is equal to a certain value:

letlastEquals= (a, xs)=>Option.fmap(Util.eq(a),RList.last(xs))|>Option.default(false);lastEquals(3,[1,2,3]);/* true*/lastEquals(3,[4,5,6]);/* false*/lastEquals(3,[]);/* false*//* Or, with infix operators*/openOption.Infix;letlastEquals= (a, xs)=>RList.last(xs)<$>Util.eq(a)|>Option.default(false);

Here we usedfmap and its infix variation<$> to apply a function to the valueinside the option.Note that,Util.eq returns abool not anoption. So what if the next function also returns anoption? Well you'd get nested options, which doesn't really help anyone. So, instead, we wouldusebind.

Now let's replace the last item of one list with the last item of another. Note that bothlast andinit returnoption:

letswapLast= (xs, ys)=>Option.(bind(RList.last(xs), ((x)=> fmap(RList.append(x),RList.init(ys))))|> default(ys));swapLast([1,2,3],[4,5,6]);/* [4,5,3]*/swapLast([],[4,5,6]);/* [4,5,6]*//* Or, with infix operators*/openOption.Infix;letswapLast= (xs, ys)=>RList.last(xs)>>= ((x)=>RList.init(ys)<$>RList.append(x))|>Option.default(ys);

Here we usedbind and its infix variation>>= to apply a function that also returned anoption.

Applicatives

Let's try checking if the last elements of two lists are equal. We could accomplish this usingbind,but that can be a little awkward.

letlastEqual= (xs, ys)=>Option.(apply(apply(Some(Util.eq),RList.last(xs),RList.last(ys)))|> default(false));lastEqual([1,2,3],[4,5,3]);/* true*/lastEqual([1,2,3],[4,5,6]);/* false*/lastEqual([],[4,5,6]);/* false*/lastEqual([1,2,3],[]);/* false*//* Or, with infix operators*/openOption.Infix;letlastEqual= (xs, ys)=>Some(Util.eq)<*>RList.last(xs)<*>RList.last(ys)|>Option.default(false);

Alternative

With alternative you can implement simple but powerful fallback mechanism your ADT-s;

Example:

openOption;letsomeData= some("Hello");letguard=fun|true=> pure()|_=> empty();letstartWith: (string,string) =>option(unit)= (str, char)=> guard(Js.String.startsWith(char, str))letdataWithFallback=    someData>>= val_=> startWith(val_,"T")>>= (_=> {Js.log(val_); some(Js.String.toUpperCase(val_))})<|> some("Not started with T")

Translating JS Idioms

Or chains

Take the following example in #"auto" data-snippet-clipboard-copy-content="let x = a || b || c || d;">

letx=a||b||c||d;

We can't translate that directly to Reason, because there is nonull orundefined in Reason.The closest approximation would beoption, in which we can string togetherSome andNoneto get the first one that isSome.

There is a helper function calledfirstSome and its infix variation|? that do exactly this.

/* a, b, and c are all options, but d is not*/letx= a|? b|? c|> default(d);

Reference

Infix Operators

  • >>=: Monadic Bind
  • <$>: Functor Fmap
  • <*>: Applicative Apply
  • <||: Point-free Function Compose
  • ||>: Point-free Function Pipe
  • -<<: Lens Compose
  • >>-: Lens Pipe
  • |?: Optional Or

About

Ramda inspired library of helper functions for ReasonML

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors8

Languages


[8]ページ先頭

©2009-2025 Movatter.jp