optimist

When building user interfaces, we are often faced with the question of what to do while waiting for a response from a server. One common pattern is to update the ui with an optimistic value - one we expect to get back if an operation is successful - and then update the ui again once we get an actual response.

The Optimistic type is a simple way to model this pattern. A value can either be fully resolved or it can be an optimistic update pending resolution. The functions in this module help you manage and query the state of an optimistic update.

Types

A value that is either fully resolved or an optimistic update pending resolution. You can access the value of an Optimistic by using the unwrap function.

pub opaque type Optimistic(a)

Functions

pub fn force(optimistic: Optimistic(a)) -> Optimistic(a)

Take an Optimistic update and force it to be resolved. This will erase the fallback value and commit to whatever value is currently stored.

Forcing an optimistic update

import gleeunit/should
import optimist

pub fn example() {
  let optimistic =
    optimist.from(1)
    |> optimist.update(2)
    |> optimist.force
    |> optimist.unwrap

  optimistic |> should.equal(2)
}
pub fn from(value: a) -> Optimistic(a)

Construct an Optimistic update from a fully resolved value.

pub fn is_pending(optimistic: Optimistic(a)) -> Bool

Determine if an Optimistic update is still pending resolution.

Note: it is uncommon to need this function. The optimistic ui pattern typically means we are pretending an operation succeeded before we know for sure. If you want to know if some request or other async job is still in progress, you probably have that information in a more direct form elsewhere in your state!

pub fn is_resolved(optimistic: Optimistic(a)) -> Bool

Determine if an Optimistic update is fully resolved.

Note: it is uncommon to need this function. The optimistic ui pattern typically means we are pretending an operation succeeded before we know for sure. If you want to know if some request or other async job is still in progress, you probably have that information in a more direct form elsewhere in your state!

pub fn push(optimistic: Optimistic(a), value: a) -> Optimistic(a)

Push an optimistic update. If the current value is already resolved, this becomes a new optimistic update and the current value becomes the fallback. If the current value is already an optimistic update, the value is updated but the fallback remains unchanged.

Optimistic update of a resolved value

import gleeunit/should
import optimist

pub fn example() {
  let optimistic =
    optimist.from(1)
    |> optimist.push(2)
    |> optimist.unwrap

  optimistic |> should.equal(2)
}

Multiple optimistic updates

import gleeunit/should
import optimist

pub fn example() {
  let optimistic =
    optimist.from(1)
    |> optimist.push(2)
    |> optimist.push(3)
    |> optimist.unwrap

  optimistic |> should.equal(3)
}

Rejecting multiple optimistic updates

import gleeunit/should
import optimist

pub fn example() {
  let optimistic =
    optimist.from(1)
    |> optimist.push(2)
    |> optimist.push(3)
    |> optimist.revert
    |> optimist.unwrap

  optimistic |> should.equal(1)
}
pub fn resolve(
  optimistic: Optimistic(a),
  result: Result(a, b),
) -> Optimistic(a)

Take a Result and use it to resolve an optimistic update. If the result is Ok that value becomes a new resolved value. If the result is an Error and an optimistic update is pending, the fallback value is used to resolve the update.

case result {
  Ok(value) -> optimist.from(value)
  Error(_) -> optimist.revert(optimistic)
}

Resolving a successful optimistic update

import gleeunit/should
import optimist

pub fn example() {
  let result = Ok(2)
  let optimstic =
    optimist.from(1)
    |> optimistic.update(2)
    |> optimist.resolve(result)
    |> optimist.unwrap

  optimistic |> should.equal(2)
}

Rejecting a failed optimistic update

import gleeunit/should
import optimist

pub fn example() {
  let result = Error("failed")
  let optimistic =
    optimist.from(1)
    |> optimistic.update(2)
    |> optimist.resolve(result)
    |> optimist.unwrap

  optimistic |> should.equal(1)
}
pub fn revert(optimistic: Optimistic(a)) -> Optimistic(a)

Take an Optimistic update and revert it back to its initial value if it is still pending.

Reverting an optimistic update

import gleeunit/should
import optimist

pub fn example() {
  let optimistic =
    optimist.from(1)
    |> optimist.update(2)
    |> optimist.revert
    |> optimist.unwrap

  optimistic |> should.equal(1)
}
pub fn try(
  optimistic: Optimistic(a),
  result: Result(b, c),
  f: fn(a, b) -> a,
) -> Optimistic(a)

Take a Result and use it to resolve an optimistic update. If the result is Ok that provided callback is run using either the value of an already-resolved optimistic update, or the fallback value of a pending update.

Resolving a successful optimistic update

import gleeunit/should
import optimist

pub fn example() {
  let history = ["hey", "hi"]
  let result = Ok("how are you?")
  let optimistic =
    optimist.from(history)
    |> optimist.update(list.prepend(_, "how are you?"))
    |> optimist.try(result, list.prepend)
    |> optimist.unwrap

  optimistic |> should.equal(["how are you?", "hey", "hi"]
}

Resolving an unsuccessful optimistic update

import gleeunit/should
import optimist

pub fn example() {
  let history = ["hey", "hi"]
  let result = Error(Nil)
  let optimistic =
    optimist.from(history)
    |> optimist.update(list.prepend(_, "how are you?"))
    |> optimist.try(result, list.prepend)
    |> optimist.unwrap

  optimistic |> should.equal(["hey", "hi"]
}
pub fn unwrap(optimistic: Optimistic(a)) -> a

Unwrap an Optimistic value and return the underlying value.

pub fn update(
  optimistic: Optimistic(a),
  f: fn(a) -> a,
) -> Optimistic(a)

Perform an optimistic update. If the current value is already resolved, this becomes a new optimistic update and the current value becomes the fallback. If the current value is already an optimistic update, the value is updated but the fallback remains unchanged.

Optimistic update of a resolved value

import gleeunit/should
import optimist

pub fn example() {
  let optimistic =
    optimist.from(1)
    |> optimist.update(int.add(_, 1))
    |> optimist.unwrap

  optimistic |> should.equal(2)
}

Multiple optimistic updates

import gleeunit/should
import optimist

pub fn example() {
  let optimistic =
    optimist.from(1)
    |> optimist.update(int.add(_, 1))
    |> optimist.update(int.add(_, 2))
    |> optimist.unwrap

  optimistic |> should.equal(3)
}

Rejecting multiple optimistic updates

import gleeunit/should
import optimist

pub fn example() {
  let optimistic =
    optimist.from(1)
    |> optimist.update(int.add(_, 1))
    |> optimist.update(int.add(_, 2))
    |> optimist.revert
    |> optimist.unwrap

  optimistic |> should.equal(1)
}
Search Document