Closed rgbkrk closed 8 years ago
+1
I need to experiment further with this idea. The main "con" is that:
> Object.getOwnPropertyNames(util.inspect)
[ 'length',
'name',
'arguments',
'caller',
'prototype',
'colors',
'styles' ]
In the meantime, similar, experimental functionality is already available through $$mimer$$. See:
inspect
is something you define on your own objects though that works in Chrome too, you don't have to use Node's util.inspect
.
I must be missing what the con is.
> var obj = { inspect: function(){}}
undefined
> Object.getOwnPropertyNames(obj.inspect)
[ 'length',
'name',
'arguments',
'caller',
'prototype' ]
My first example of a con was a poor one. A function defines the properties:
[ 'length', 'name', 'arguments', 'caller', 'prototype' ]
This is not a terrible con. A quick solution would be to change the convention, instead of defining obj.inspect["text/plain"]
, we could use obj.inspect.mime["text/plain"]
.
Ah, ok, that makes sense. How do you define these in a way that is natural for a JS programmer? I'm assuming we want these to be bound by the caller and I'm not sure how you do it for nested functions.
function X() {}
X.prototype.inspect.mime = {
'text/html': function html() {
// How does this get bound to the future `this`?
}
}
> X.prototype.inspect.mime['text/html'] = function html(){ return `<b>${this.x}</b>`}
[Function: html]
> new X(12)
ITS AN X: 12
> y = new X(12)
ITS AN X: 12
> y.inspect.mime['text/html']
[Function: html]
> y.inspect.mime['text/html']()
'<b>undefined</b>'
@rgbkrk How urgent is this issue for you? I was working on #58 , but if we agree on what to do for #59 , I could work on it right away.
This is not urgent, working on #58 is great. @jdfreder was interested in the JavaScript kernel and since I saw the thread going along I wanted to experiment.
Currently there's a compare and contrast with tonic dev and IJavascript in Plotly's plotly-notebook-js and I'd like to make IJavascript take off in the same way that IPython did via libraries like pandas
.
Here's an alternative using a getter:
function X(x) {
this.x = x;
}
X.prototype.inspect = function inspect() {
return "X = " + this.x;
};
X.prototype.inspect.mime = {
get "text/html"() {
return "<b>X = " + this.x + "</b>";
},
};
var x = new X(1);
x.inspect.mime["text/html"]; // output: '<b>X = undefined</b>'
The main con of using a getter is that we would give up passing arguments. For example, obj.inspect.mime["image/jpg"]("lossless")
(admittedly, not a terribly good example).
In Python at least, we never pass arguments to the _repr_html_
. It's supposed to be a function that takes nothing and returns a string that we can send over the wire.
Uhhhh, appears that getter doesn't work for us since it comes out undefined
. Tested locally as well.
Oh well... using X.prototype.inspect.mime
has an undesired side-effect. this
gets assigned to X.prototype.inspect.mime
instead of the X instance:
function X(x) {
this.x = x;
}
X.prototype.inspect = function inspect() {
return "X = " + this.x;
}
X.prototype.inspect.mime = {};
Object.defineProperties(X.prototype.inspect.mime, {
"text/html": { get: function html() {
return this === X.prototype.inspect.mime;
},
},
});
var x = new X(1);
x; // output: X = 1
Object.getOwnPropertyNames(x.inspect.mime); // output: [ 'text/html' ]
x.inspect.mime["text/html"]; // output: true
Something like the following would be more in the spirit of the global $$html$$
:
function X(x) {
this.x = x;
}
X.prototype.inspect = function inspect() {
return "X = " + this.x;
}
Object.defineProperty(X.prototype, "$$html$$", {
get: function html() {
return "<b>" + this.inspect() + "</b>";
},
});
var x = new X(1);
x; // output: X = 1
x.$$html$$; // output:<b>X = 1</b>
Considering how you have to send these across the wire, what if the interface is just:
X.prototype.inspectAsMimeBundle = function inspectAsMimeBundle() {
return {
'text/html': `<b>${this.x}</b>`,
'text/plain': this.inspect(),
}
}
Alternatively, or in addition to (since these are for your own use in calling from ijavascript) they can implement inspectAsMimetype
:
X.prototype.inspectAsMimetype = function inspectAsMimetype(mimetype) {
switch(mimetype) {
case 'text/html':
return `<b>X = ${this.x}</b>`
case 'text/plain':
default:
return this.inspect();
}
}
var x = new X(1);
console.log(x.inspectAsMimetype('text/html'));
In IJavascript, we also have the global $$mime$$
:
Object.defineProperty(X.prototype, "$$mime$$", {
get: function mime() {
return {
"text/plain": this.inspect(),
"text/html": this.$$html$$,
};
},
});
x.$$mime$$; //output: { 'text/plain': 'X = 1', 'text/html': '<b>X = 1</b>' }
The main reason I'm aiming for something similar to inspect is that it's a built in. My big repulsion to the global $$html$$ is that it's on the user to run (or be detected by a library) and I wouldn't know how to use it in an asynchronous context.
Alternatively we could model the API closer to IPython's. The way this is done in IPython isvia reprs. Python itself uses x.__repr__
for the string representation of an object. IPython extends this by also looking for x._repr_*_
, i.e. x._repr_html_
...
In Javascript the equivalent of __repr__
is toString
, right? So why not toHTML
, toJavascript
etc. Or toMimeBundle
?
function A() {}
A.prototype.toString = function() { return 'b'; }
a = new A();
String(a)
"b"
At least in node and v8 (which are what this kernel is), inspect
is the equivalent to __repr__
(the analogue to toString
is __str__
). inspect
has been around at least as far as 0.8 (was easy to launch a docker image with it):
$ docker run -it node:0.8
> X = function(x) { this.x = x; }
[Function]
> X.prototype.inspect = function inspect(){ return 'x = ' + this.x; }
[Function: inspect]
> trial = new X(23)
x = 23
> trial
x = 23
I've put together a document with all the display conventions mentioned here. I think it'll help understand the current conventions in IJavascript:
Display Conventions
1. Javascript `toString()`
2. Node.js `inspect()`
3. Python `__repr__()`
4. IPython `__repr_*__()`
5. IJavascript
5.1. Using `util.inspect()` (except for functions)
5.2. Using globals: `$$mime$$`, `$$html$$`, ...
5.3. Extensions:
5.3.1. Using global `$$mimer$$()`
5.3.2. Using Node.js `inspect()`
5.3.3. Using IPython's `__repr_*__()`
5.3.4. Using getters: `$$mime$$`, `$$html$$`, ...
toString()
In Javascript, a class can define its string representation by defining the method toString()
.
$ node
> function F() {}
undefined
> F.prototype.toString = function toString() { return "World!"; };
[Function: toString]
> "Hello, " + new F()
'Hello, World!'
See documentation here.
inspect()
Node.js introduced another convention. The string representation of an object in a Node.js shell is determined by the method inspect([depth])
.
$ node
> function F() {}
undefined
> F.prototype.inspect = function inspect() { return "Hello, World!"; };
[Function: inspect]
> new F()
Hello, World!
See documentation here.
__repr__()
Similarly to Javascript, Python uses the method __repr__()
to determine the string representation of an object.
See documentation here.
__repr_*__()
And similarly to Node.js, the IPython shell introduces the methods __repr_*__()
to define rich representations of an object.
See documentation here.
The sections below describe how IJavascript displays objects, and discuss potential extensions.
util.inspect()
(except for functions)With the exception of functions, IJavascript displays objects like Node.js does; i.e. by invoking util.inspect()
, which, in turn, invokes the method inspect()
of the object.
Functions, however, are displayed using the method toString()
. See the difference:
$ node
> function f() { return "Hello, World!"; }
undefined
> f.toString()
'function f() { return "Hello, World!"; }'
> util.inspect(f)
'[Function: f]'
Thus, in IJavascript, it is possible to define an alternative string representation of an object by defining the method inspect()
(or the method toString()
in case of functions).
$$mime$$
, $$html$$
, ...The Jupyter notebook accepts object representations in a number of MIME formats. To produce these MIME representations, IJavascript defines a convention in terms of the global variables: $$html$$
, $$svg$$
, $$png$$
, $$jpeg$$
and $$mime$$
.
See documentation here.
The choice of names was guided by:
$$
In this thread, we're discussing new conventions so that it is possible to customise an object representation based on its class.
$$mimer$$
The global $$mimer$$
was experimentally introduced in NEL@0.1.1.
$$mimer$$
is the function that IJavascript uses to generate the MIME representation of an object. By making this function accessible, a library can redefine $$mimer$$
to produce a custom representation.
See here for some examples of use.
I want to deprecate this convention because of its cons:
$$mimer$$
can break the custom $$mimer$$
of other packages$$mimer$$
, each checking whether an object is an instance of a given class.inspect()
All the ideas we've discussed so far that extend the inspect()
convention have serious drawbacks:
X.prototype.inspect["text/html"]
:
this
is set to X.prototype.inspect
X.prototype.inspect
defines property names other than "text/html"X.prototype.mime["text/html"]
this
is set to X.prototype.inspect
$$mime$$
, $$html$$
, ...From the experience with X.prototype.inspect["text/html"]
and X.prototype.mime["text/html"]
, it is clear that for this
to be properly defined a method or a getter must be used to generate a rich representation of an object. Once this is agreed, the issue becomes a naming issue. The first choice that came to my mind was to use the names already used in the global variables. The pros are:
__repr_*__()
Jonathan suggested an alternative naming convention, inspired by IPython. The advantage I see is that:
The cons are:
Thank you so much for putting this together @n-riesco, this helped me understand the whole picture quite a bit more.
The major con to me of using _repr_*_
is that it should appeal to and be idiomatic for the kernel's language.
It's worth pointing out that the global variables do have an analogue to IPython as well, IPython.display.display(X)
and helpers like IPython.display.HTML('<b>test</b>')
.
This may be a silly question, but what's the issue with just using a prototype method that takes an argument?
$ node
> function X(n){
this.y = n; return this;
}
> X.prototype.inspectMime = function(t){
var types = {
'text/html': '<h1>' + this.y + '</h1>'
};
return types[t];
}
> var x = new X(100);
undefined
> x
X { y: 100 }
> x.inspectMime('text/html')
<h1>100</h1>
I like inspectMime
@mdtusz Kyle suggested something very similar here.
I feel wary of using unmangled names like inspectAs*()
. inspect()
is fine because it's a Node.js convention.
I think it's easier for IJavascript to introduce a convention with mangled names, that are unlikely to clash with other naming conventions.
I'm preparing a new release in the async-stdio
branch, here and here.
Apart from improving the handling of asynchronous output, it also includes an initial implementation of a convention to fix this issue: Class.prototype.inspect
, Class.prototype._toMime
, Class.prototype._toHtml
, Class.prototype._toSvg
, Class.prototype._toPng
and Class.prototype._toJpg
.
Here's an example:
In[1]:
function F(f) {
this.f = f;
}
F.prototype.inspect = function inspect() {
return "F { f: " + this.f + " }";
}
F.prototype._toHtml = function toHtml() {
return "<div style='background-color:yellow'>" + this.inspect() + "</div>";
}
new F("Hello, World!");
Out[1]:
{ mime:
{ 'text/plain': 'F { f: Hello, World! }',
'text/html': '<div style=\'background-color:yellow\'>F { f: Hello, World! }</div>' } }
Nice!
So to clarify, one of the mime
properties will be displayed as output then - presumably defaulting to text/html
if provided?
@mdtusz My understanding is that the frontend chooses amongst all the available. For example, the notebook prefers text/html
over text/plain
.
The frontend chooses once it gets the display_data
or execute_reply
message.
Example from Pandas:
The raw messages that the kernel (JavaScript in this case) sends over look like this:
{
content:
{ data:
{ 'text/plain': ' A B C D\n2000-01-01 1.153881 -0.462802 -1.304602 -0.112967\n2000-01-02 2.041548 0.193145 -2.154538 -0.026075\n2000-01-03 2.277393 -0.990363 -1.676727 0.463335\n2000-01-04 2.472088 0.376239 -2.691538 -0.329923\n2000-01-05 3.426450 -0.052412 -3.275203 -1.422521',
'text/html': '<div style="max-height:1000px;max-width:1500px;overflow:auto;">\n<table border="1" class="dataframe">\n <thead>\n <tr style="text-align: right;">\n <th></th>\n <th>A</th>\n <th>B</th>\n <th>C</th>\n <th>D</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <th>2000-01-01</th>\n <td> 1.153881</td>\n <td>-0.462802</td>\n <td>-1.304602</td>\n <td>-0.112967</td>\n </tr>\n <tr>\n <th>2000-01-02</th>\n <td> 2.041548</td>\n <td> 0.193145</td>\n <td>-2.154538</td>\n <td>-0.026075</td>\n </tr>\n <tr>\n <th>2000-01-03</th>\n <td> 2.277393</td>\n <td>-0.990363</td>\n <td>-1.676727</td>\n <td> 0.463335</td>\n </tr>\n <tr>\n <th>2000-01-04</th>\n <td> 2.472088</td>\n <td> 0.376239</td>\n <td>-2.691538</td>\n <td>-0.329923</td>\n </tr>\n <tr>\n <th>2000-01-05</th>\n <td> 3.426450</td>\n <td>-0.052412</td>\n <td>-3.275203</td>\n <td>-1.422521</td>\n </tr>\n </tbody>\n</table>\n</div>' },
execution_count: 21,
metadata: {} },
header:
{ username: 'kyle6475',
msg_id: 'e697b50a-9aa0-4d7a-a825-27ac99479679',
msg_type: 'execute_result',
version: '5.0',
date: '2015-07-26T15:52:59.473133',
session: 'c297c1c0-89bb-4ded-9720-d298c33b2595' },
parentHeader:
{ username: 'kyle6475',
msg_id: '841e2314-0af6-46c8-8083-d4b544f91e0e',
version: '5.0',
session: 'b4e75019-83e8-4bd4-99f5-e462d5203886',
date: '2015-07-26T15:52:59.468333',
msg_type: 'execute_request' },
metadata: {},
signature: '6c41e0ecbbb50010c26d8e04e8ff2c634816761f610b30ab1765539c216be591',
blobs: []
}
and a frontend will choose how to wrap those up accordingly
I've made a pre-release with the changes in the async-stdio
branch.
I will keep this issue open until I make the release.
If you have any more feedback that I can address before the release, please, keep posting here.
I've just released IJavascript@5.0.11, which uses NEL@0.4.0, and fixes this issue.
Awesome, I look forward to installing and using this.
I'd love to see something similar to IPython's expectation of a _reprhtml, that IJavascript could recognize (for libraries to implement).
This would come out more like this (for frontends that can handle it):
Cool thing then is that you can extend common objects to produce rich representations as well.
We could make a function that returns single mimetypes (though the msg spec expects an entire mime bundle):
Implementors can of course write their own functions for html or other mimetypes that they use in these two methods, I kept this encapsulated as a proposal.