git.iko.soy/rowtype-yoga/purescript-lazy-joe
on main help sign in
261 lines 7.7 kB history raw
1# ๐ŸŽท purescript-lazy-joe ๐Ÿฆฅ
3Zero boilerplate ffi for purescript.
5For these days when you got the blues and are just too lazy to write ffi code.
7## ToC
8* [Installation](#installation)
9* [Quickstart](#quickstart)
10* [Usage](#usage)
11 * [Method chaining](#method-chaining)
12 * [Uncurried functions](#uncurried-functions)
13 * [Vararg functions](#vararg-functions)
14 * [Scoped functions](#scoped-functions)
15 * [Effectful functions](#effectful-functions)
16 * [`new` constructor](#new-constructor)
17* [Credits](#credits)
19## Installation
21```
22spago install lazy-joe
23```
25## Quickstart
27```
28main :: Effect Unit
29main = launchAff_ do
30 { red } <- fromDefault "chalk" -- import a module
31 log $ red "Red velvet ๐ŸŽ‚" -- use a function
32```
34## Usage
36Imagine we want to use the amazing [`chalk`](https://github.com/chalk/chalk) js library for terminal styling in our purescript code.
38We start by installing chalk.
39```
40npm install chalk
41```
43`chalk` defines a number of functions and combinators that allow you to write coloured text to your terminal. For instance the function `red` from `chalk` prints a red string. In js you would use it like this:
45```js
46import chalk from 'chalk';
48console.log(chalk.red("Red velvet ๐ŸŽ‚"));
49```
51Let's see how we can ffi this in our purescript code:
53```purescript
54main :: Effect Unit
55main = launchAff_ do
56 { red } <- fromDefault "chalk"
57 log $ red "Red velvet ๐ŸŽ‚"
58```
59Running it will print:
62![red velvet in red letters](./assets/red-velvet.png)
65You can easily import the `default` export from a module using `fromDefault`. This will type red as `String -> String`.
67### Method chaining
69In js method chaining is a common pattern. In purescript we can use function application to model this pattern.
72#### Example
74In chalk, you can combine different styles by chaining them:
76```js
77import chalk from 'chalk';
79console.log(chalk.underline.bold.green('Leaf ๐Ÿƒ'));
80```
82In purescript, we can use function application to model this method chaining:
83```purescript
84main :: Effect Unit
85main = launchAff_ do
86 { underline } <- fromDefault "chalk"
87 log $ underline # \{ bold } -> bold # \{ green } -> green "Leaf ๐Ÿƒ"
88```
90Running it will print:
92![green leaf printed in bold green underline](./assets/green-leaf.png).
94### Uncurried functions
96js typically uses uncurried functions (e.g. `f(a,b,c) `) instead of curried functions (e.g. `f(a)(b)(c)` ) like purescript. Use `curried` to use the uncurried js function as a curried purescript function:
98```
99result = curried myFunc arg1 arg2 arg3
100```
102#### Example
104E.g. we can use the `blue` function as a three-argument uncurried function:
106```js
107console.log(chalk.blue('blue', 'azul', 'blau'));
108```
110In purescript functions are curried, so we need to curry them using `curried`:
112```purescript
113main :: Effect Unit
114main = launchAff_ do
115 { blue } <- fromDefault "chalk"
117 log $ curried blue "blue" "azul" "blau"
118```
120Running it will print:
122![blue azul blau in blue colours](./assets/blau-azul.png)
124### Vararg functions
126js functions are sometimes designed to be variadic, i.e. to have a variable number of arguments. Use `variadic myModuleOrFunc func` for functions that accept varargs:
128```purescript
129let
130 example1 = variadic func arg1
131 example1 = variadic func arg1 arg2 arg3
132 example1 = variadic func [arg1, arg2, arg3, arg4]
133```
135#### Eample
137In fact, the colour methods in `chalk` are variadic methods (as you have seen in the previous example) and you can pass an arbitrary number of arguments:
138```js
139console.log(chalk.blue('Hello', 'world!', 'Hola', 'mundo!'));
140```
142If we need this variadic behaviour, we can use `variadic` to model this:
143```purescript
144main :: Effect Unit
145main = launchAff_ do
146 { blue } <- fromDefault "chalk"
148 log $ variadic blue "Hello"
149 log $ variadic blue "Hello" "world!"
150 log $ variadic blue [ "Hello", "world!", "Hola", "mundo!"]
151```
153### Scoped functions
155Sometimes js functions don't work in purescript, because they internally use `this` which fails to resolve in a curried contex. Use `scoped myModuleOrFunc func` you to make a function scoped:
157```purescript
158result <- scoped myModuleOrFunc func arg1 arg2
159```
161#### Example
163Simply using the `rgb` function from `chalk` as we did before will fail because it uses `this` internally. To make it work again we will need to set the scope for the function to the module:
165```purescript
166main :: Effect Unit
167main = launchAff_ do
168 m@{ rgb } <- fromDefault "chalk"
170 log $ scoped m (curried rgb) 129 37 218 # \{ bold } -> bold "PURPLE!!! ๐Ÿช"
171```
173Running it will print:
175![purple in bold purple with a kite](./assets/purple-kite.png)
177### Effectful functions
179js functions very often cause side-effects (e.g. like printing to the console). Use `effectful` you to catch these side-effects and return `Effect` instead:
181```purescript
182let
183 result :: Effect SomeResult
184 result = effectful func arg1 arg2
185```
187#### Example
189Let's try another example and install minimalistic http-client `got`:
191```console
192npm install got
193```
195With `got` we can create a simple http request using the `post` function and passing a url and a json body:
196```js
197import got from 'got';
199const {data} = await got.post('https://httpbin.org/anything', {
200 json: {
201 hello: '๐ŸŒŽ'
202 }
203}).json();
205console.log(data);
206```
208Clearly, `post` is an effectful function, as it triggers a promise. So in purescript `post` will have the signature `Effect (Promise json)` which we can then be converted to an `Aff` using the `toAffE`.
210```purescript
211main :: Effect Unit
212main = launchAff_ do
213 { post } <- fromDefault "got"
214 resp <- Promise.toAffE $ effectful (curried post) "https://httpbin.org/anything" { json: { hello: "๐ŸŒŽ" } } >>= \{ json } -> json
215 log resp.json
216```
218We import the `post` from `got`. The first thing we need to do is uncurry it, since it receives two arguments, the url and the json record. We then wrap it using `effectful` so that is run in an `Effect`. We can then call the effectful js function `json()` on the resulting `Promise` to get the body, so we can just `bind` (`>>=`) it.
220### `new` constructor
222Use `new` to create new objects:
224```purescript
225myModule <- fromDefault "my-module"
226let myObj = new myModule arg1 arg2
227```
229#### Example
231`fuse.js` is a library for fuzzy search. In js you use it by first creating a new `Fuse` object using `new` and passing the data as a list and some options:
232```js
233const fuse = new Fuse(list, options);
234```
235Then you can search on this object:
236```js
237const pattern = "my-search-pattern"
238fuse.search(pattern)
239```
241We first import the default export from `fuse.js` which gives us the class, which we can then pass to `new` to create the `Fuse` object:
243```purescript
244fuse <- fromDefault "fuse.js"
245 let
246 list =
247 [ { "title": "Old Man's War", "author": { "firstName": "John", "lastName": "Scalzi" } }
248 , { "title": "The Lock Artist", "author": { "firstName": "Steve", "lastName": "Hamilton" } }
249 ]
250 options =
251 { keys: [ "title", "author.firstName" ]
252 }
253 result :: Array { item :: { title :: String, author :: { firstName :: String, lastName :: String } } } <-
254 (new fuse list options) # \f@{ search } -> effectful (scoped f search) "eve"
256 logShow result
257```
259## Credits
261Kudos to @paluh for writing the [`purescript-js-object`](https://github.com/paluh/purescript-js-object) library (check it out!), which basically inspired me to write this library. @paluh's library is probably the (type-)safer option, but I thought if I am already too lazy to write ffi, then I really want to be lazy and not write anything at all. So this library follows a different implementation approach to basically not require any ffi code, at the expense of more type-safety.