Open n-riesco opened 7 years ago
So there might be quite a lot to though about if we want something completely async, one other is the cancellation of task without affecting subsequently submitted tasks.
You don't really want to handle that case. This would mean that frontends would have to provide means for users to declare task dependencies. Do you have an use case in mind for this functionality?
The biggest use case right now is cluster computing where you'll typically be running N jobs + stages of jobs -- spark, dask, etc. Same applies for node developers using Eclair for Spark.
@gnestor
OK, if we go with $$.display(id?)
, this is how the display_id
vs no display_id
cases would look like:
// No `display_id` case
// (only `display_data` messages)
// In[1]
// append a display output to cell 1
var display1 = $$.display();
display1.text("tic"); // sends a `display_data` with no `display_id` from cell 1
// In[2]
// append a display output to cell 1 from code run in cell 2 but pretending to be from cell 1
display1.text("tac"); // sends a `display_data` with no `display_id` pretending to come from cell 1
// append a display output to cell 2
var display2 = $$.display();
display2.text("toe"); // sends a `display_data` with no `display_id` from cell 2
// `display_id` case
// (`$$.display(id) sends an empty `display_data` that registers the `display_id` with the frontend)
// (`display.text(data)` sends `update_display_data` messages)
// (in this way, IJavascript doesn't need to keep a table of `display_id`'s)
// In[1]
// append a display output to cell 1
var tic1 = $$.display("tic");
tic1.text("tic"); // sends a `display_data` with `display_id: "tic"` from cell 1
// append a display output to cell 1
var tac1 = $$.display("tac");
tac1.text("tac"); // sends a `display_data` with `display_id: "tac"` from cell 1
// In[2]
// replace display output in cell 1 from code run in cell 2 pretending to be from cell 1
tic1.text("TIC"); // sends a `update_display_data` with `display_id: "tic"` pretending to come from cell 1
// replace display output in cell 1 from code run in cell 2
var tac2 = $$.display("tac");
tac2.text("TAC"); // sends a `update_display_data` with `display_id: "tac"` from cell 2
@rgbkrk
The biggest use case right now is cluster computing where you'll typically be running N jobs + stages of jobs -- spark, dask, etc. Same applies for node developers using Eclair for Spark.
But in Spark's case, this is hidden from the frontend, isn't it? The user runs a command and the command doesn't complete until all the jobs complete or an exception is triggered.
I probably misunderstood Matthias (I thought by execution tasks he meant the execution of a bunch of cells).
@gnestor
Here's a preliminary TODO list of what needs doing to implement display_id
:
In the NEL
module:
nel_server.js
into smaller files (this file implements IJavascript's Node session as an IPC server; this file was fairly small in the beginning, but after the introduction of $$
has grown to a point that it'll be easier for others to understand if I split it into smaller files).$$.display(id?)
in nel_server.js
.Session#execute
to accept an onDisplay
handler.$$.display(id?)
.In the jp-kernel
module:
execute_request
handler to register an onDisplay
handler.onDisplay
handler (this is the code that will send display_data
and update_display_data
messages).In the jp-Babel
kernel:
jp-kernel
(I updated jp-CoffeeScript
last week, so I want to do jp-Babel
while it's still fresh in my mind)Implement module ijavascript-ipython-display
to replicate the API in IPython.display
$$.display(id?)
under the hood.I hope this gives us a good plan of action.
I'll ping you back once I'm done splitting nel_server.js
(I really think that this file split will make things easier; I should've already done it).
@n-riesco Great! Sounds good to me. I can start playing around with jp-kernel
part in the meantime.
@gnestor I'm not sure how far you can go playing with jp-kernel
without knowing the details of the onDisplay
handler.
I have a suggestion, why don't you start with the implementation of $$.display(id?)
:
npm
module,$$.display(id?)
should replicate the interface here,display_data
and update_display_data
, the mock would call console.log
with the MIME of the data to be sent,msg_id
(but this is OK; it'll have it once we replace console.log
for the actual call for sending display_data
and update_display_data
).Here's an example of how this mock'd look like:
// In[1]:
var display = require("nel-display-mock");
// In[2]:
var displayInCell2 = display();
displayInCell2.text("tic"); // in mock this could result in `console.log('display_data:', id, mime)`
// In[3]:
var displayInCell3 = display("cell3");
displayInCell3.text("tic"); // in mock this could result in `console.log('display_data:', id, mime)`
// In[4]:
displayInCell3.text("tac"); // in mock this could result in `console.log('display_data:', id, mime)`
Please, have a look at the coding guidelines. To those, I'd add:
Promise
's).@n-riesco Am I on the right track?
// In[1]
function display(id) {
this.id = id || '';
// Mock for `this.send`
this.send = console.log;
return {
text: function sendText(text, keepAlive) {
this.send({
displayId: this.id,
mime: {
"text/plain": text,
},
end: !keepAlive,
});
}.bind(this)
};
}
// In[2]
var displayInCell2 = display();
displayInCell2.text("tic");
// { displayId: '', mime: { 'text/plain': 'tic' }, end: true }
// In[3]
var displayInCell3 = display("cell3");
displayInCell3.text("tic");
// { displayId: 'cell3', mime: { 'text/plain': 'tic' }, end: true }
// In[4]
displayInCell3.text("tac");
// { displayId: 'cell3', mime: { 'text/plain': 'tac' }, end: true }
This looks an awful lot like the Context
function in nel... I guess that's sorta the point.
I hope you won't mind me teasing you a little bit. ;)
I don't think that code does what you expect. Try the following after running it:
// In[5]
id
// Out[5]
'cell3'
We really want display
to return a new instance (amongst other reasons so that we can use the operator instaceof
). This is what I mean in pseudo-code:
function display(id) {
return new Display(id);
}
function Display(id) {
this.id = id;
}
Display.prototype.text = function text(text) {
// ...
}
I'm not much of a OOPer 😔 But I see what you mean and your example makes sense:
function Display(id) {
this.id = id || '';
this.send = console.log;
}
Display.prototype.text = function text(text) {
this.send({
displayId: this.id,
mime: {
"text/plain": text,
}
});
}
Display.prototype.mime = function mime(bundle) {
this.send({
displayId: this.id,
mime: bundle
});
}
Display.prototype.text = function text(text) {
this.send({
displayId: this.id,
mime: {
"text/plain": text,
}
});
}
Display.prototype.html = function html(html) {
this.send({
displayId: this.id,
mime: {
"text/html": html,
}
});
}
Display.prototype.svg = function svg(svg) {
this.send({
displayId: this.id,
mime: {
"image/svg+xml": svg,
}
});
}
Display.prototype.svg = function png(png) {
this.send({
displayId: this.id,
mime: {
"image/png": png,
}
});
}
Display.prototype.jpeg = function jpeg(jpeg) {
this.send({
displayId: this.id,
mime: {
"image/jpg": jpeg,
}
});
}
function display(id) {
return new Display(id);
}
That's the idea. That code would go into nel_server.js (probably in its own file after I split nel_serve.js
).
nel_server.js
and nel.js
communicate via IPC. Here's a list of the current message types. We need to add new message type for Display
. How about this?
/**
* Message received from the session server
*
* @typedef Message {
* module:nel~LogMessage |
* module:nel~DisplayMessage |
* module:nel~StatusMessage |
* module:nel~StdoutMessage |
* module:nel~StderrMessage |
* module:nel~ErrorMessage |
* module:nel~SuccessMessage
* }
*/
/**
* Display message received from the session server
*
* @typedef DisplayMessage
*
* @property {number} [id] Execution context id (deleted before the message reaches the API user)
* @property display
* @property {string} [display.id] Display id
* @property display.mime
* @property {string} [display.mime."text/plain"] Result in plain text
* @property {string} [display.mime."text/html"] Result in HTML format
* @property {string} [display.mime."image/svg+xml"] Result in SVG format
* @property {string} [display.mime."image/png"] Result as PNG in a base64 string
* @property {string} [display.mime."image/jpeg"] Result as JPEG in a base64 string
*
* @private
*/
In this way the code you wrote now would look like:
function Display(id, context) {
this.id = id || '';
this.context = context;
}
Display.prototype.text = function text(text) {
this.context.send({
display: {
id: this.id,
mime: {
"text/plain": text,
}
}
});
}
// ...
One of the benefits of $$
and Display
having the same API is that wrappers like ijavascrip-plotly can accept either a cell or a display output.
Looks good to me :+1:
Moved from https://github.com/n-riesco/nel/issues/4