polar

Hosts

A host is a place your code runs, like Node or Browser. Polar uses each function's effects to work out which hosts it can run on, and builds a separate bundle for each.

Declaring hosts

A module lists the hosts it targets in the hosts zone. Every effect then says which host it lives on:

hosts
  Node

effects
  Clock in Node {
    now() -> Int
  }

The standard library declares the two common hosts with their effects. Std.Dom provides Browser and its Dom effect, and Std.Process provides Node and its Process effect.

Where code can run

You never mark a function with a host yourself. Polar works it out from its effects:

  • A pure function runs on every host.
  • A function that uses Clock runs only where Clock exists: Node.
  • A function that uses two effects runs only where both exist.

You can ask Polar what it decided with --emit=hosts. Here's the program from Effects:

$ polar build clock.px --emit=hosts
double : every host
main   : {Node}
stamp  : {Node}

Code that can't run anywhere

This function adds text to the page using Dom, which only exists in the browser. It also reads a count using Visits, which lives in Node:

start() -> {} / {Dom, Visits} {
  Dom.append_text("p", "#{Visits.count()} visits so far")
}

No host has both, and Polar says so before you ship it:

error[POLAR0811]: `start` can't run anywhere
  --> hosts_bad.px:23:3
   |
23 |   start() -> {} / {Dom, Visits} {
   |   ^^^^^ no host provides both `Visits` and `Dom`
24 |     Dom.append_text("p", "#{Visits.count()} visits so far")
   |     --------------- `Dom` runs on {Browser}
   |                             ------------ `Visits` runs on {Node}
   |
   = help: split the work: call a function that uses `Visits` from one host and one that uses `Dom` from the other

One way to fix this is a bridge, which makes Visits callable from the browser over HTTP. That has its own page: Bridges.

Building for several hosts

List the hosts in your project's polar.toml:

[project]
name = "visits"
hosts = ["Browser", "Node"]

polar build then writes one complete bundle per host. Each one contains only that host's binds, so browser code never ships your server's database code:

$ polar build
built visits (1 file) for Browser into dist/Browser in 118ms
built visits (1 file) for Node into dist/Node in 191ms

To build or run just one, pass --host:

$ polar build --host Node
$ polar run --host Node

Native effects and force

Some effects truly belong to one host. Mark those native:

effects
  native Canvas in Browser {
    draw(shape: String) -> {}
  }

You can still bind a native effect on another host, for example to draw into an SVG buffer on the server. But that's an emulation, not the real thing, so Polar asks you to say so with force:

binds
  force Canvas in Node {
    draw(shape) {
      Log.info("svg buffer: #{shape}")
    }
  }

Without force, Polar reports that Canvas is native to Browser. With force where it isn't needed, you get a warning to remove it.

You've now seen every main concept. Let's put them together in a real program: Thinking in Polar.