Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Write What Not How

write what not how

💁‍♂️ https://git.io/write-what-not-how

hello

👋 Thank you!

jorgebucaran

const me = () => ({
  id: "jorgebucaran",
  age: 478452600,
  name: "ジョージ",
  alias: ["jorge", "george"],
  colors: ["pure red", "crimson glory"],
  animals: ["dogs", "frogs", "lizards"],
  japanese: true,
  favoriteEmoji: "📛" // Tofu on fire
})

work

const worksAt = Mercari.engineers.find(
  eng => eng.alias === "jorgebucaran"
)

loves

deepEqual(jorgebucaran.loves, {
  hazelNuts: true,
  hobbies: ["karaoke", "programming", "chess"],
  languages: ["japanese", "italian", "hindi"],
  programming: ["javascript", "haskell"]
})

open-source

app({
  init: {
    url: "github.com/jorgebucaran",
    projects: [
      "hyperapp",
      "fisher",
      // ...
    ]
  },
  view: state =>
    state.projects.map(name =>
      h("li", {}, `https://${state.url}/${name}`)
    )
})

learn more

💁‍♂️ https://github.com/jorgebucaran

this talk

declarative vs imperative

I'm nice Be nice(!)

declarative

SELECT * FROM Users WHERE Country='Japan';
<form action="/" method="post">
  <section>
    <label for="mail">E-mail:</label>
    <input type="email" id="mail" name="user_mail" />
  </section>
  <section>
    <label for="msg">Message:</label>
    <textarea id="msg" name="user_message"></textarea>
  </section>
</form>
{
  "first name": "ジョージ",
  "last name": "ブカラン",
  "address": {
    "street address": "1-12-1 cool bldg",
    "city": "Tokyo",
    "postal code": "106-0045"
  },
  "phone numbers": [
    {
      "type": "mobile",
      "number": "090-4444-2222"
    }
  ]
}
const add = numbers => numbers.reduce((a, z) => a + z, 0)

sum([1, 2, 3, 4]) === 10 //=> true

imperative programming

select users from database

let users = data.getUsers()
let usersFromJapan = []
for (let i = 0; i < users.length; i++) {
  let user = users[i]
  let country = user.getField("Country")
  if (country !== undefined && country === "Japan") {
    usersFromJapan.push(user)
  }
}

simple html form

const createSection = (id, name, text, isTextArea) => {
  const section = document.createElement("section")
  const label = document.createElement("label")
  label.setAttribute("for", id)
  label.appendChild(document.createTextElement(text))

  const area = isTextArea
    ? document.createElement("textarea")
    : document.createElement("input")
  if (!isTextArea) input.setAttribute("type", id)
  input.setAttribute("id", id)
  input.setAttribute("name", name)

  section.appendChild(label)
  section.appendChild(input)
}
const createForm = (action, method) => {
  const form = document.createElement("form")
  form.appendChild(
    createSection("email", "user_email", "E-mail")
  )
  form.appendChild(
    createSection("msg", "user_message", "Message:", true)
  )
  return form
}

document.body.appendChild(createForm("/", "post"))

creating an obj

const getMeAsJSON = () => {
  const me = {}

  me["first name"] = "ジョージ"
  me["last name"] = "ブカラン"
  me.address = {}
  me.address["street address"] = "1-12-1 cool bldg"
  me.address["city"] = "Tokyo"
  me.address["postal code"] = "106-0045"
  me["phone numbers"] = []
  const mobileNumber = {}
  mobileNumber.type = "mobile"
  mobileNumber.number = "090-4444-2222"
  me["phone numbers"].push(mobileNumber)

  return me
}

thoughts

  • declarative is a spin on FP
    • FP is basically math
    • people discuss FP, IP
    • no one says math is
  • jQuery is imperative
  • React is partly declarative
  • Hyperapp is (completely) declarative
  • Elm is purely functional, declarative, and strongly typed
  • imperative tells how to achieve a result
    • it's literally every step to get from A to B
  • declarative tells what the result is
    • leaving getting from A to B to the language, runtime, etc

UIs

imperative UIs

old school windows

import * as windows from "windows"

export const main = () => {
  const window = windows.createWindow({
    title: "Old-school Window",
    icon: Icons.MyIcon,
    cursor: Cursors.MyCursor,
    style: Styles.H_REDRAW | Styles.V_REDRAW
    background: Colors.LightGray,
    width: 1000,
    height: 600
  })

  for (const message of windows.GetMessage(window)) {
    await windows.dispatchMessage(message);
  }

  return window.returnValue
}

browser hello world

const createTitle = text => {
  const h1 = document.createElement("h1")
  h1.appendChild(document.createTextElement(text))
  return h1
}

document.body.appendChild(createTitle("hello"))

some app using the next coolest framework

import { Page, Button, Label } from "old-app"

export const main = args => {
  const page = new Page({ title: "example" })
  const button = new Button({ text: "increment" })
  const label = new Label(0)

  page.appendChild(label)
  page.appendChild(button)

  button.addEventListener("onclick", event => {
    page.label.value = parseInt(page.label.value) + 1
  })
}

declarative UIs

import { page, span, button } from "future-app"

export const view = state =>
  page("example", [
    span(state),
    button("increment", { onclick: state => state + 1 })
  ])
import { runtime } from "future-app"
import { view } from "."

runtime({ state: 0, view, node: document.body })
export const view = state => (
  <page title="example">
    <span>{state}</span>
    <button onclick={state => state + 1}>
      increment
    </button>
  </page>
)
const Increment = state => state + 1

export const view = state => (
  <page title="example">
    <span>{state}</span>
    <button onclick={Increment}>increment</button>
  </page>
)
const Decrement = state => state - 1
const Increment = state => state - 1

app({
  init: 0,
  view: state =>
    h("main", {}, [
      h("h1", {}, state),
      h("button", { onclick: Decrement }, "-"),
      h("button", { onclick: Increment }, "+")
    ]),
  node: document.getElementById("root")
})
const Increment = state => {
  state - 1 // what?
}

const Increment = state => {
  this.state-- // Worse than a paper cut! 🩸
}

const Increment = state => state - 1 // ✅

thoughts

  • hyperscript
    • the concept is not new see Dominic Tarr's hyperscript
      • or see why the lucky stiff's markaby for the original inspiration
  • "declarative" is a spin on FP
    • unfortunately, people still discuss whether FP is good or not
      • or whether it's "good enough"
      • or whether it's "scalable"
    • FP is basically math
      • no reasonable person asks whether math is "good enough"
  • declarative UIs: you can "see" the UI
  • most people are still wired to choose imperative code anyway
  • let's not fight over this
    • the most important things in life won't change whether you choose declarative over imperative

(side) effects

  • impossible to get anything useful without effects
    • creating HTTP requests
    • creating time delays
    • setting or removing the focus on a DOM element
    • generating random numbers
    • changing the browser history
    • writing and reading to/from local/session storage
    • opening/closing a database connection
      • using IndexedDB
    • requesting/exiting fullscreen
    • enqueing data over a WebSocket
    • etc
  • dealing with async control flow is hard and prone to debug hell
    • control flow is already hard!

imperative effects

imperative galore

doThat()
if (possibly) {
  thenDoThis()
} else {
  thatInstead()
}

console

console.log("hello")
const hello = () => console.log("hello")
//=> undefined

giving focus

<body>
  <input id="name" type="text" value="" />
  <button>Set Focus</button>
</body>
document.getElementById("name").focus()

self updating increasing counter

<body>
  <span id="count"></span>
</body>
let count = 0

const updateCounter = count => {
  document.getElementById("count").innerText = count
}

updateCounter(count)

setTimeout(() => {
  updateCounter(++count)
}, 1000)

talking to servers

const getMovies = async () => {
  const response = await fetch(
    "http://example.com/movies.json"
  )
  const myJson = await response.json()
  console.log(JSON.stringify(myJson))
}

declarative effects

{
  type, args
}

or

{
  effect, args
}

console naive

const consoleEffect = {
  code: "console.log",
  args: "hello"
}

// Somewhere 🤮
const run = fx => {
  return eval(`${fx.code}(${fx.args})`)
}

console better

const consoleEffect = {
  type: Effects.ConsoleLog,
  args: "hello"
}

// Somewhere 🤔
const run = fx => {
  switch (fx.type) {
    case Effects.ConsoleLog:
      console.log(fx.args)

    // ...
  }
}

console lot better

const consoleLogFx = props => {
  console.log(props)
}
const consoleEffect = [consoleLogFx, "hello"]

// Somewhere 🤓
const run = effect => {
  const [fx, props] = effect
  fx(props)
}

giving focus to elements

import { focus } from "./fx/focus"

export const view = state => (
  <body>
    <input id="name" type="text" value={state} />
    <button onclick={focus("name")}>Set Focus</button>
  </body>
)
const focusFx = props => {
  document.getElementById(id).focus()
}
export const focus = id => [focusFx, id]

self updating increasing counter

// app.js

import { timeout } from "./fx/timeout"

const initialState = 0
const Increment = state => state + 1

export const init = [
  initialState,
  timeout(Increment, { delay: 1000 })
]

export const view = state => (
  <body>
    <span>{state}</span>
  </body>
)
// bootstrap.js

import { app } from "hyperapp"
import { init, view } from "./app"

app({
  init,
  view,
  node: document.getElementById("root")
})

talking to servers

import { get } from "./fx/http"

const NewMovies = (state, data) => ({
  ...state,
  movies: data.movies
})

export const getMovies = genre =>
  get({
    url: `http://example.com/movies.json?genre=${genre}`,
    action: [NewMovies, json => JSON.stringify(json)]
  })
import { getMovies } from "./movies"

const LoadMoreMovies = state => [
  state,
  getMovies("biopics")
]

const initialState = {
  movies: []
}
app({
  init: [initialState, getMovies("slice of life")],
  view: state => (
    <main>
      <ul>
        {state.movies.map(movie => (
          <li>{movie}</li>
        ))}
      </ul>
      <button onclick={LoadMoreMovies}>
        Load More Movies
      </button>
    </main>
  )
})

thoughts

  • this is just effects 101
    • batch/parallelize effects => [state, [getFoo, getBar, getBaz]]
    • create effect series/chains

events / streams / subscriptions

  • event emitters require complex resource management

    • adding/removing event listeners, closing connections, clearing intervals

    • testing asynchronous code is hard

    • what happens when the source you are subscribed to shuts down?

    • how do you cancel or restart a stream easily?

  • (global) window events

    • addEventListener
    • removeEventListener
  • custom event streams

    • CustomEvent
    • dispatchEvent
  • clock ticks

  • geolocation changes

  • push notifications

  • WebSockets

imperative subs

let count = 0
setTimeout(function increment() {
  if (count >= 10) return
  count++
  setTimeout(increment, 1000)
}, 1000)

declarative subs

async increasing counter

const Increment = state => state + 1

app({
  state: 0,
  subscriptions: state =>
    state <= 10 && interval(Increment, { delay: 1000 })
})

track mouse coordinates

const NewCoordinates = (state, { x, y }) => ({
  ...state,
  x,
  y
})

const subscriptions = state =>
  state.isTracking && onMouseMove(NewCoordinates)

export const view = state => (
  <main>
    {state.x}:{state.y}
    <button onclick={ToggleTrack}>Track</button>
  </main>
)

const ToggleTrack = state => ({
  ...state,
  isTracking: !state.isTracking
})

app({
  state: {
    x: 0,
    y: 0,
    isTracking: true
  },
  view,
  subscriptions
})

arbitrarily bogus-looking code example

export const subscriptions = state => [
  state.isIntro || state.isLost || state.isWon
    ? onMouseClick(StartGame)
    : state.isInPlay && [
        onAnimationFrame(Tick),
        onKeyDown(KeyPressed),
        onKeyUp(MoveCharacter)
      ]
]

how do they work btw?

const interval = (dispatch, props) => {
  let id = setInterval(
    () => dispatch(props.action),
    props.delay
  )
  return () => clearInterval(id)
}

export const interval = (action, { delay }) => [
  intervalFx,
  { action, delay }
]

thoughts

  • so much more!
    • subs are nestable => [sub1, state.toggleSet && [sub2, sub3]]

hyperapp

+\ https://hyperapp.dev

summary

  • declarative is a spin on FP

    • FP is basically math
    • people discuss FP, IP
    • no one says math is

  • hyperapp === declarative UIs, effects, and streams
  • build your own library!
    • it's fun
    • less is more
    • it's okay to build to learn
      • learn as you go
    • people like drafts
      • making mistakes is good
        • sometimes bare bones is all you need
      • OSS is great for trying out ideas
        • specially when you don't know what you're doing

thank you! 👋

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.