Kotlin / kotlin-jupyter

Kotlin kernel for Jupyter/IPython
Apache License 2.0
1.12k stars 105 forks source link

Add notebook-wide implicit cell receivers #239

Open altavir opened 3 years ago

altavir commented 3 years ago

The use case Consider the code here: https://github.com/AndreiKingsley/kmath-tensors-example-jupyters/blob/master/kmath_tensors_examples.ipynb. KMath extensively uses context-oriented programming so each section on the code should be encapsulated with the appropriate receiver. Usually, we just do something like fun main() = DoubleTensorAlgebra{...} to avoid additional clutter. In jupyter one have to wrap each cell in it which looks ugly.

Additional use cases:

Proposed syntax

/**
 * Add an implicit receiver to all subsequent cells. The `typeOf<T>` is used as a key for the stored receiver
 */
public inline fun <reified T: Any> Notebook.withReceiver(obj: T)

/**
 * Remove receiver with the appropriate key from subsequent cells
 */
public inline fun <reified T: Any> Notebook.removeReceiver()

Functions are used on Notebook to improve discoverability and avoid accidentally calling them.

Risks Broad usage of implicit (including implicit receivers) could cause a lot of confusion in a large code, so this feature should not be used in large notebooks. The receiver should be declared in the notebook explicitly so it probably should not be used in plugins.

Adding multiple receivers which export the same functions and properties could produce unexpected behavior since the resolution could depend on the order. So there should be cell magic to list currently loaded receivers.

ileasile commented 3 years ago

Idea is cool, but there are two things I don't like in proposed API:

  1. Using types as keys. I think that value is more important than the type anyway. You may have different receivers with the same type, for example. I understand your concern here, but I'm not sure it's a good solution.
  2. Using Notebook as receiver. Notebook is an entity that describes notebook - its cells, Kotlin version and related things. I think that these methods should be the members of KotlinKernelHost interface (or extensions on this interface) which is responsible for execution itself.

What do you think?

altavir commented 3 years ago

No objections against the second one. My point was it should not be a top-level function. As for the first one, they should not be two receivers of the same type, the function of the lower receiver in the stack could not be ever called and it could cause bugs. So I think that adding a new receiver with the same type should either replace the current one or cause an exception. Removal could be done by value, but it actually complicates the procedure since you need to keep that value somwhere.