processing / p5.js

p5.js is a client-side JS platform that empowers artists, designers, students, and anyone to learn to code and express themselves creatively on the web. It is based on the core principles of Processing. http://twitter.com/p5xjs —
http://p5js.org/
GNU Lesser General Public License v2.1
21.6k stars 3.31k forks source link

PI is not defined before setup()! #903

Closed golanlevin closed 9 years ago

golanlevin commented 9 years ago

An error occurs when a variable with global scope is initialized to PI or TWO_PI. It appears that PI does not work as a constant until after setup is called. The error is: Uncaught ReferenceError: PI is not defined

Here's a working program that produces the error:

var halfCircle = PI;
function setup() {
  createCanvas (100,100);
}

Interestingly, this error will allow the app to run when launched from a local file, but NOT when it is run from a server (e.g. when embedded in Wordpress).

lmccart commented 9 years ago

thanks @golanlevin. this is actually the correct behavior. the reason for this is that p5 functions are not added to the global namespace by default, it is not until the sketch is run and we check whether we are running in global or instance mode that the functions get bound to global.

so there isn't really a way to have these p5 names recognized outside of setup() and draw() and support the two different modes without requiring the user to add some data- attribute to the <script> tag including p5. which we discussed at length but felt wasn't ideal or appropriate. (much more on this in issue #113 and #78)

maybe what we need are some notes in the documentation warning people of this. any suggestions on the best place to put this?

golanlevin commented 9 years ago

Actually, @lmccart I was just about to contact you about this, unfortunately to elevate the issue. The bugs caused by this turn out to be killing my intro students in practice. Another example:

var foo = random(0,100);
function setup() {
  createCanvas (100,100);
  line (0,0, foo,foo);
}

The REAL problem is that the resulting application will run in a browser when launched from a local file, but will fail to run (leaving a big blank space) when embedded in a wordpress page with the plugin.

So there are two brutal inconsistencies:

Please help! Hopefully, Golan

lmccart commented 9 years ago

I'm not seeing this run successfully in the browser, I get an error and it doesn't draw to the screen (this is the expected behavior for p5).

screen shot 2015-09-12 at 8 15 07 pm

The best way to write your example code above is as follows:

var foo;
function setup() {
  createCanvas (100,100);
  foo = random(0,100);
  line (0,0, foo,foo);
}

As explained above, I unfortunately don't know if there's really a way around this while still supporting all the other modes we need to. The best solution I can think of is making this very clear in the documentation that p5 functions do not exist before setup() is called. So the variable declaration step can go outside, but the variable assignment step needs to happen inside setup(). I'd welcome any suggestion about the best places to put this note where students would be most likely to see it.

If others have thoughts about any possible workarounds I'd love to hear.

golanlevin commented 9 years ago

Whoops -- augh, sorry -- I meant the following program:

var foo = random(100);
function setup() {
  createCanvas (100,100);
}
function draw(){
  background(200);
  foo = random(100);
  line (mouseX, mouseY, foo, foo);
}

As you can see, this throws an error (random is not defined), but still runs. But it doesn't run in the wordpress. What do you suggest?

lmccart commented 9 years ago

I think my best suggestion would be to get students used to checking the console for errors before submitting homework. They may find it easier to use the p5 editor as the errors pop up in the built-in console instead of having to open any extra console area manually.

In general, I feel the wordpress plugin is really not ideal, for this reason and others we have discussed on that repo's issues. Luckily @therewasaguy and team is hard at work on an online editor which will hopefully remove the need for the plugin, allowing students to directly grab embed code they can confirm works and paste into their posts.

We could try to force some behavior where the sketch will not run locally if a p5 function is called outside causing an error. However, this really feels like it goes against how JS works. I think this is just one of the sometimes helpful and sometimes annoying differences between Java and JS--there can be errors and your code may still run, albeit with unpredictable behavior. As opposed to Java or other stricter languages where they will throw up an error and not compile. (Helpful in the case where you may have a non-crucial error on the page, maybe even in unrelated JS code elsewhere, and it doesn't stop the whole page in it's tracks.)

Again, I'll be sure to add more documentation around this point, and if others have thoughts about a better way to handle this technically, I'd be happy to implement anything we can come up with.

kadamwhite commented 9 years ago

This is probably a terrible idea, but what if there was a global-by-default build of p5 that could be used for learning projects?

kadamwhite commented 9 years ago

Or, possibly, a method that could be called that would initialize global behavior when called; something like

p5.global(); // bad name

console.log( PI ); // works

This method could be called automatically by editors or plugins, and it might be easier for students to learn that this is some boilerplate they need to put that at the top of their sketch than it is to learn that variables don't exist until setup

golanlevin commented 9 years ago

I do appreciate this kind of thinking. Thanks for your replies.

It's pretty challenging to explain to introductory students why PI is not defined until setup. One student said, "But why isn't PI defined? It's a universal constant. And I thought it would at least be defined so long as I included p5.js in the HTML page."

indefinit commented 9 years ago

This might be opening a can of worms but just my 2 cents: this is likely already something you're familiar with, but in case not, both PI and random exist in the JavaScript Math object (which you get "for free"). I agree with Lauren's point regarding variable scope in p5/JavaScript, but if the point of contention is that a student thinks of PI as a universal constant then you could just declare a global variable and assign the value of Math.PI; when it's outside of the setup or draw functions.

I often flip between Math.PI and PI as well as console.log() and println() in my teaching examples so maybe it's a simpler solution for students to know both exist and the nuances between the two have to do with limitations of variable scope.

Sent from my mobile.

On Sep 13, 2015, at 4:47 PM, Golan Levin notifications@github.com wrote:

I do appreciate this kind of thinking. Thanks for your replies.

It's pretty challenging to explain to introductory students why PI is not defined until setup. One student said, "But why isn't PI defined? It's a universal constant. And I thought it would at least be defined so long as I included p5.js in the HTML page."

— Reply to this email directly or view it on GitHub.

lmccart commented 9 years ago

I sometimes find this goes well with the explanation of how all of this fits into the web / what order things are happening in, and a little about window.onload. The order is something like this:

  1. Scripts in <head> are loaded.
  2. <body> of HTML page loads (when this is complete, the onload event fires, which then triggers step 3).
  3. p5 is started, all functions are added to the global namespace.

So the issue is that the scripts are loaded and evaluated before p5 is started, when it's not yet aware of the p5 variables. If we try to call them here, they will cause an error. However, when we use p5 function calls inside setup() and draw() this is ok, because the browser doesn't look inside functions when the scripts are first loaded. This is because the setup() and draw() functions are not called in the user code, they are only defined, so the stuff inside of them isn't run or evaluated yet.

It's not until p5 is started up that the setup() function is actually run (p5 calls it for you), and at this point, the p5 functions exist in the global namespace.

shiffman commented 9 years ago

This is a great discussion.

As much as it might be nice if there were a magical, elegant solution to this, after reading through this thread I think I falling towards the this-is-just-something-we-have-to-teach-about-web-p5-js-etc direction. I'll share one reference to Processing -- this is often a mistake beginner Processing users make:

float x = width/2;

void setup() {
  size(400, 400);
}

This also can fail for a variety of reasons:

PImage img = loadImage("file.jpg");

void setup() {
  size(400, 400);
}

While the situation is much more extreme with p5 given that nothing is available, not PI, random(), etc. I think this can hopefully be covered with good documentation and examples? Is there a way we can catch these with the "friendly error messages" and say something like:

PI and other global p5 constants are not available outside of setup(), try initializing your variable there.

random() and other p5 functions are not available outside of setup(), try initializing your variable there.

brysonian commented 9 years ago

I normally teach to only declare, not assign, globals outside of setup and draw, in processing and p5. I find it just confuses people that somethings work and some don't—random() is fine but loadImage() fails. It also potentially dovetails nicely with things like C++ where you have a header to declare vars but you assign them elsewhere. Between this and lauren's discussion of order of scope and loading it seems that most bases would be covered?

lmccart commented 9 years ago

I've added documentation about this to these pages:

If anyone can think of other places it would be helpful to include this, please let me know (or feel free to make PR).