Skip to content

Instantly share code, notes, and snippets.

@chexxor
Last active May 3, 2022 14:39
Show Gist options
  • Save chexxor/d4afd991645b36cf7da6fcb8486c7b10 to your computer and use it in GitHub Desktop.
Save chexxor/d4afd991645b36cf7da6fcb8486c7b10 to your computer and use it in GitHub Desktop.
PureScript Lazy-load Modules Idea

Lazy/Dynamic Loadable Modules in PureScript

Motivation

https://discourse.purescript.org/t/lazy-loading-routes-in-tea-style-app/141/2

Ideas

  • Here, we aren't first-class modules, because we are still using the normal module definition and import system. We believe that first-class modules refer to a special feature in OCaml.
  • Can not refer to type classes across a lazy-load boundary. Type-class instances and classes are not first-class values.
  • Presume the lazy-loaded module is bundled/includes its own dependencies. This resolves diamond dependency problem.
  • Backend can choose to ignore the lazy loading (e.g. Erlang). It simply enables a programmer to declare a dynamic loading boundary which they have prepared.

Syntax Ideas

Lazy-load Functions

module Lazy exports (trans) where

import LazyTransitive (trans :: String)

data LazyType = LazyType String

class LazyLoad where
  load :: { val1 :: LazyLoad => String }

val1 :: String
val1 = “lazy loaded ” <> (getA :: TypeA)

val2 :: LazyType
val2 = LazyType “there”
module Main where

type Lazy = { val1 :: String, LazyType :: String -> LazyType }
main = do
  — LazyLoad is available before lazyLoading the module.
  mod <- (lazyLoad :: Lazy)
  let x :: LazyType
       x = LazyType “here”
  let y :: LazyLoad
       y = mod.val2 :: LazyLoad

Lazy-load Modules

module Main where

lazy import Lazy as Lazy

-- Lazy :: LazyModule { val1 :: String, LazyType :: String -> LazyType }
-- type LazyModule a = OPAQUE -- Provided by Prim.

—- Eject LazyModule:
-- lazyLoadEff :: forall a. LazyModule a -> Eff a
-- e.g.
-- foreign import lazyLoadAff
-- exports.lazyLoadAff = function(moduleName, succCb, errCb) {
--   import(moduleName).then(succCb).catch(errCb); -- Use ECMAScript Dynamic Import syntax or alternative.
-- };
-- lazyLoadEff :: forall a. LazyModule a -> Eff a
-- exports.lazyLoadEff = function(moduleName, succCb, errCb) {
--   return require(moduleName); -- Use NodeJS require syntax.
-- };

main = do
  -- LazyType *type* is available before lazyLoading the module.
  mod :: Lazy <- (lazyLoadEff Lazy)
  let x :: LazyType
       x = LazyType “here”
  let y :: LazyLoad
       y = mod.val2 :: LazyLoad

Lazy-load Modules 2

module Main where

lazy import Lazy as Lazy

— Lazy.module :: LazyModule { val1 :: String, LazyType :: String -> LazyType }

main = do
  — LazyLoad *type* is available before lazyLoading the module.
  mod <- (lazyLoad Lazy.module)
  let x :: LazyType
       x = LazyType “here”
  let y :: LazyLoad
       y = mod.val2 :: LazyLoad
  let z :: String
       z = mod.val1

Lazy-load Diamond Dependency Handling

module Main where

lazy import Lazy as Lazy — refers to C
lazy import Lazy2 as Lazy2 — refers to C

— Duplicate definitions of C exist across in each of these two lazy-loaded modules.

Async Error Handling

module Lazy where

class LazyLoad where
  load :: { val1 :: LazyLoad => String }

val1 :: LazyLoad => String
val1 = “lazy loaded ” <> (getA :: TypeA)

Forbidden Type Class-use Example

module Lazy where

class HasA where
  getA :: String

data TypeA

instance HasA TypeA where
  getA = “typeclass instance”

val1 :: String
val1 = “lazy loaded ” <> (getA :: TypeA)
module Main where

Import Lazy (val1, getA, TypeA)

t = true

main =
  let x = if t then “here” else val1
  -- let y = if t then (“here” <> (getA :: TypeA)) else val1
  in log x
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment