Cells
=====

A cell is an object with a value and a set of observers that react to
changes in its value. You'll see exactly what that means in a moment.

There are a number of ways to create cells. The simplest cell is the
constant cell, created with the :any:`live_cells.value` function,
which holds a constant value.

.. code-block:: python

   import live_cells as lc

   a = lc.value(1)
   b = lc.value('hello world')


=============
Mutable Cells
=============

Mutable cells, created with :any:`live_cells.mutable`, which takes
the initial value of the cell, have a `value` property that can be set
directly.

.. code-block:: python

   import live_cells as lc

   a = lc.mutable(0)

   print(a.value) # Prints 0

   # Set the value of a to 3
   a.value = 3
   print(a.value) # Prints 3

===============
Observing Cells
===============

When the value of a cell changes, its observers are notified of the
change. The simplest way to demonstrate this is to set up a *watch
function* using :any:`live_cells.watch`:

.. code-block:: python

   import live_cells as lc

   a = lc.mutable(0)
   b = lc.mutable(1)

   lc.watch(lambda: print(f'{a()}, {b()}'))

   a.value = 5  # Prints 5, 1
   b.value = 10 # Prints 5, 10

:any:`live_cells.watch` takes a watch function and registers it to be
called when the values of the cells referenced within it change. In
the example above, a watch function that prints the values of cells
``a`` and ``b`` is defined. This function is called automatically when the
value of either ``a`` or ``b`` changes.

There are a couple of points to keep in mind when using :any:`live_cells.watch`:

* The watch function is called once immediately when
  :any:`live_cells.watch` is called, to determine which cells are
  referenced by it.

* :any:`live_cells.watch` automatically tracks which cells are
  referenced within the watch function and calls it when their values
  change. This works even when the cells are referenced conditionally.

.. attention::

   Within a watch function, the values of cells are referenced using
   the function call syntax, e.g. ``a()``, rather than accessing the
   ``value`` property directly.

Every call to :any:`live_cells.watch` adds a new watch function:

.. code-block:: python

   import live_cells as lc

   a = lc.mutable(0)
   b = lc.mutable(1)

   lc.watch(lambda: print(f'{a()}, {b()}'))
   lc.watch(lambda: print(f'A = {a()}'))

   # Prints: 20, 1
   # Also prints: A = 20
   a.value = 20

   # Prints 20, 10
   b.value = 10

This results in the following being printed:

.. code-block:: text

  20, 1
  A = 20
  20, 20

In this example, the second watch function only observes the value of
``a``. Change the value of ``a`` results in both the first and second
watch function being called. Changing the value of ``b`` results in
only the first watch function being called, since the second watch
function does not reference ``b`` and hence is not observing it.

:any:`live_cells.watch` returns a watch *handle* (:any:`CellWatcher`),
which provides a ``stop`` method that *deregisters* the watch
function. When the ``stop`` method is called, the watch function is no
longer called when the values of the cells it is observing change.

.. code-block:: python

   import live_cells as lc

   a = lc.mutable(0)

   watcher = lc.watch(lambda: print(f'A = {a()}'))

   # Prints A = 1
   a.value = 1

   # Prints A = 2
   a.value = 2

   watcher.stop()

   # Doesn't print anything
   a.value = 3


.. tip::

   A watch function with more than one expression can be defined by
   using :any:`live_cells.watch` as a decorator:

   .. code-block:: python

      import live_cells as lc

      a = lc.mutable(0)

      @lc.watch
      def watcher():
          print(f'A = {a()}')
	  print(f'A + 1 = {a() + 1}')

      # Prints:
      #   A = 2
      #   A + 1 = 3
      
      a.value = 2

  The decorated function is registered as a watch function that
  observes the cells referenced within it. The watch handle can be
  accessed by the name of the decorated function. For example, the
  watch function in the previous example can be stopped with the
  following:

  .. code-block:: python

     watcher.stop()

:any:`live_cells.watch` also takes an optional ``schedule`` argument,
which if not *None* is a function that is called when the watch
function should be called, with the watch callback passed as an
argument. The ``schedule`` function should *schedule* the callback
function to it, to be called at a later stage. If ``schedule`` is
*None*, the watch function is called immediately when the cells
referenced within it change.

.. code-block:: python

   import gevent
   import live_cells as lc

   a = lc.mutable()

   @lc.watch(schedule=gevent.spawn)
   def watch():
       print(f'{a()}')

In this example a watch function observing cell ``a`` is defined. When
the value of ``a`` changes, the watch function is not called
immediately but is scheduled to run on a `gevent
<http://www.gevent.org/>`_ green thread, using ``gevent.spawn``.

.. important::

   The watch function sees the values of the cells, which are
   referenced within it, as they are at the time the ``schedule``
   function is called and not at the time when the watch function is
   actually run. This guarantees that the watch function will not
   "miss" updates if it runs after the values of the cells are changed
   again.

==============
Computed Cells
==============

A *computed cell* is a cell with a value that is defined as a function
of the values of one or more argument cells. Whenever the value of an
argument cell changes, the value of the computed cell is recomputed.

Computed cells are defined using :any:`live_cells.computed`, which
takes the value computation function of the cell:

.. code-block:: python

   import live_cells as lc

   a = lc.mutable(1)
   b = lc.mutable(2)

   sum = lc.computed(lambda: a() + b())

In this example, ``sum`` is a computed cell with the value defined as
the sum of cells ``a`` and ``b``. The value of ``sum`` is recomputed
whenever the value of either ``a`` or ``b`` changes. This is
demonstrated below:

.. code-block:: python

   lc.watch(lambda: print(f'The sum is {sum()}'))

   a.value = 3 # Prints: The sum is 5
   b.value = 4 # Prints: The sum is 7

In this example:

#. A watch function observing the ``sum`` cell is defined.
#. The value of ``a`` is set to ``3``, which:

   #. Causes the value of ``sum`` to be recomputed.
   #. Calls the watch function defined in 1.

#. The value of ``b`` is set to ``4``, which likewise also results in
   the value of ``sum`` being recomputed and the watch function being
   called.

.. tip::

   A computed cell with more than one expression can be defined by
   using :any:`live_cells.computed` as a decorator:


   .. code-block:: python

      import live_cells as lc

      n = lc.mutable(2)

      @lc.computed
      def factorial_n():
          result = 1

	  while n() > 1:
	    result *= n
	    n -= 1
	  
	  return result

      lc.watch(lambda: print(f'n! = {factorial_n()}'))

   The computed cell is defined with the decorated function as the
   compute function. The cell can then be referenced using the name of
   the decorated function.

The :any:`live_cells.computed` function takes a ``changes_only``
argument which allows you to control whether the computed cell
notifies its observers if its value hasn't changed after a
recomputation. By default this is ``False``, which means the computed
cell notifies its observers whenever it is recomputed. If
``changes_only`` is ``True``, the cell only notifies its observers if
the new value of the cell is not equal to its previous value.

This is demonstrated with the following example:

.. code-block:: python

   import live_cells as lc

   a = lc.mutable(0)
   b = lc.computed(lambda: a % 2, changes_only=True)

   lc.watch(lambda: print(f'{b()}'))

   a.value = 1
   a.value = 3
   a.value = 5
   a.value = 6
   a.value = 8

This results in the following being printed to standard output:

.. code-block:: text

   0
   1
   0

Notice that only three lines are printed to standard output, even
though the value of the computed cell argument ``a`` was changed five
times.

If ``changes_only=True`` is omitted from the definition of ``b``, the
following is printed to standard output:

.. code-block:: text

   0
   1
   1
   1
   0
   0

Notice that a new line is printed to standard output whenever the
value of ``a``, which is an argument of ``b`` is changed. This is
because ``b`` notifies its observers whenever the value of its
argument ``a`` has changed, even if ``b``\ 's new value is equal to
its previous value.
   
=============
Batch Updates
=============

The values of multiple mutable cells can be set simultaneously in a
*batch update*. The effect of this is that while the values of the
cells are changed on setting the ``value`` property, the observers of
the cells are only notified after all the cell values have been set.

Batch updates are performed using the :any:`live_cells.batch` context
manager:

.. code-block:: python

   import live_cells as lc

   a = lc.mutable(0)
   b = lc.mutable(1)

   lc.watch(lambda: print(f'a = {a()}, b = {b()}'))

   # This only prints: a = 15, b = 3
   with lc.batch():
       a.value = 15
       b.value = 3

In the example above, the values of ``a`` and ``b`` are set to ``15``
and ``3`` respectively, with a *batch update*. The watch function,
which observes both ``a`` and ``b`` is only called once when exiting
the context managed by :any:`live_cells.batch`.

As a result the following is printed:

.. code-block:: text

   a = 0, b = 1
   a = 15, b = 3

#. ``a = 0, b = 1`` is printed when the watch function is first defined.
#. ``a = 15, b = 3`` is printed when exiting the context managed by
   :any:`live_cells.batch`.