Purescript ffi for the lazy joe 🦥
Go to file
2022-06-18 11:23:59 +01:00
assets Initial commit 2022-06-16 22:20:33 +01:00
src/Lazy Improve documentation and switch curried 2022-06-18 11:20:37 +01:00
test Improve documentation and switch curried 2022-06-18 11:20:37 +01:00
.gitignore Initial commit 2022-06-15 22:58:13 +01:00
bower.json Add bower.json 2022-06-18 11:23:59 +01:00
LICENSE Initial commit 2022-06-16 22:20:33 +01:00
package-lock.json Initial commit 2022-06-16 22:20:33 +01:00
package.json Initial commit 2022-06-16 22:20:33 +01:00
packages.dhall Remove dhall percentage from github page 2022-06-16 22:32:02 +01:00
README.md Improve documentation and switch curried 2022-06-18 11:20:37 +01:00
spago.dhall Initial commit 2022-06-16 22:20:33 +01:00
test.dhall Improve documentation and switch curried 2022-06-18 11:20:37 +01:00

🎷 purescript-lazy-joe 🦥

Zero boilerplate ffi for purescript.

For these days when you got the blues and are just too lazy to write ffi code.

ToC

Installation

spago install lazy-joe

Quickstart

main :: Effect Unit
main = launchAff_ do
  { red } <- fromDefault "chalk" -- import a module
  log $ red "Red velvet 🎂" -- use a function

Usage

Imagine we want to use the amazing chalk js library for terminal styling in our purescript code.

We start by installing chalk.

npm install chalk

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:

import chalk from 'chalk';

console.log(chalk.red("Red velvet 🎂"));

Let's see how we can ffi this in our purescript code:

main :: Effect Unit
main = launchAff_ do
  { red } <- fromDefault "chalk"
  log $ red "Red velvet 🎂"

Running it will print:

red velvet in red letters

You can easily import the default export from a module using fromDefault. This will type red as String -> String.

Method chaining

In js method chaining is often used.

Example

In chalk, you can combine different styles by chaining:

import chalk from 'chalk';

console.log(chalk.underline.bold.green('Leaf 🍃'));

In purescript, we can use function application to model this method chaining:

main :: Effect Unit
main = launchAff_ do
  { underline } <- fromDefault "chalk"
  log $ underline # \{ bold } -> bold # \{ green } -> green "Leaf 🍃"

Running it will print:

green leaf printed in bold green underline.

Uncurried functions

js typically uses uncurried functions (e.g. f(a,b,c) ) instead of uncurried functions (e.g. f(a)(b)(c) ) like purescript. Use curried to use the uncurried js function as a curried purescript function:

result = curried myFunc arg1 arg2 arg3

Example

E.g. we can use the blue function as a three-argument uncurried function:

console.log(chalk.blue('blue', 'azul', 'blau'));

In purescript functions are curried, so we need to uncurry them using curried:

main :: Effect Unit
main = launchAff_ do
  { blue } <- fromDefault "chalk"
  
  log $ curried blue "blue" "azul" "blau"

Running it will print:

blue azul blau in blue colours

Vararg functions

js 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:

let 
  example1 = variadic func arg1
  example1 = variadic func arg1 arg2 arg3
  example1 = variadic func [arg1, arg2, arg3, arg4]

Eample

In 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:

console.log(chalk.blue('Hello', 'world!', 'Hola', 'mundo!'));

If we need this variadic behaviour, we can use variadic to model this:

main :: Effect Unit
main = launchAff_ do
  { blue } <- fromDefault "chalk"
  
  log $ variadic blue "Hello"
  log $ variadic blue "Hello" "world!"
  log $ variadic blue [ "Hello", "world!", "Hola", "mundo!"]

Scoped functions

Sometimes 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:

result <- scoped myModuleOrFunc func arg1 arg2

Example

Simply 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:

main :: Effect Unit
main = launchAff_ do
  m@{ rgb } <- fromDefault "chalk"
  
  log $ scoped m (curried rgb) 129 37 218 # \{ bold } -> bold "PURPLE!!! 🪁"

Running it will print:

purple in bold purple with a kite

Effectful functions

js 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:

let
  result :: Effect SomeResult 
  result = effectful func arg1 arg2

Example

Let's try another example and install minimalistic http-client got:

npm install got

With got we can create a simple http request using the post function and passing a url and a json body:

import got from 'got';

const {data} = await got.post('https://httpbin.org/anything', {
	json: {
		hello: '🌎'
	}
}).json();

console.log(data);

Clearly, 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.

main :: Effect Unit
main = launchAff_ do
  { post } <- fromDefault "got"
  resp <- Promise.toAffE $ effectful (uncurried post) "https://httpbin.org/anything" { json: { hello: "🌎" } } >>= \{ json } -> json
  log resp.json

We 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.

new constructor

Use new to create new objects:

myModule <- fromDefault "my-module"
let myObj = new myModule arg1 arg2

Example

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:

const fuse = new Fuse(list, options);

Then you can search on this object:

const pattern = "my-search-pattern"
fuse.search(pattern)

We 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:

fuse <- fromDefault "fuse.js"
  let
    list =
      [ { "title": "Old Man's War", "author": { "firstName": "John", "lastName": "Scalzi" } }
      , { "title": "The Lock Artist", "author": { "firstName": "Steve", "lastName": "Hamilton" } }
      ]
    options =
      { keys: [ "title", "author.firstName" ]
      }
  result :: Array { item :: { title :: String, author :: { firstName :: String, lastName :: String } } } <-
    (new fuse list options) # \f@{ search } -> effectful (scoped f search) "eve"

  logShow result

Credits

Kudos to @paluh for writing the 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.