/purescript-call-by-name

Syntactically light-weight call-by-name arguments in PureScript. No guarantees. Completely gratuitous.

Primary LanguagePureScriptMIT LicenseMIT

purescript-call-by-name

Latest release Build status

Syntactically light-weight call-by-name arguments in PureScript. No guarantees. Completely gratuitous.

What is this library?

This library takes advantage of term-abstraction incurred by type class dictionaries to approximate call-by-name evaluation for arguments. We simulate call-by-name by taking a Unit -> a argument which defers evaluation of a until invoked. We then provide operators to coerce these type-class-abstracted terms into the more reliable call-by-name form.

Take the when function from Prelude:

when :: forall m. Applicative m => Boolean -> m Unit -> m Unit

It is strict in both arguments, so it will allocate the provided effect even though it may be discarded. This can be problematic, especially if there are let bound values:

example =
  when that do
    let
      a = somethingExpensive 42
      b = somethingElseExpensive a
    this a b

Both a and b will be evaluated, which we don't want. The alternative is to use a call-by-name approximation:

when :: forall m. Applicative m => Boolean -> (Unit -> m Unit) -> m Unit

example =
  when that \_ -> do
    let
      a = somethingExpensive 42
      b = somethingElseExpensive a
    this a b

But wow that's irritating. This library exports an application operator \\ that makes this look better.

import CallByName.Applicative (when)
import CallByName.Syntax ((\\))

example =
  when that \\do
    let
      a = somethingExpensive 42
      b = somethingElseExpensive a
    this a b

We've saved four arduous characters.

There's also the ~ operator which lifts terms into Lazy.

somethingLazy :: Lazy Int -> Int

example =
  somethingLazy ~(evaluate expensive int)

Versus the old:

example =
  somethingLazy (Lazy.defer \_ -> evaluate expensive int)

Somewhat actually useful things

This library also exports some call-by-name variants of functions that don't exist in current Prelude or ecosystem.

CallByName.Applicative.when ::
  forall m. Applicative m => Boolean -> (Unit -> m Unit) -> m Unit

CallByName.Applicative.unless ::
  forall m. Applicative m => Boolean -> (Unit -> m Unit) -> m Unit

CallByName.Monoid.guard ::
  forall m. Monoid m => Boolean -> (Unit -> m) -> m

And also exports a version of the Alt type class which defers it's second argument.

class Strict.Alt f <= Alt f where
  alt :: forall a. f a -> (Unit -> f a) -> f a

This can be combined with a magic, right-associated version of <|> which does not strictly evaluate it's second argument.

example =
  Just 42 <|> unsafeThrow "Too strict!"