-
Notifications
You must be signed in to change notification settings - Fork 97
The CODE Tab
NetLogo code has multiple purposes. The code must be "efficient" and functional, but it should also be readable to someone who wants to know what the exact rules of the model are.
The Models Library lives by a slightly stricter Code (pun intended) in that each model should also be a good code example since users use our models both to learn how to program and as templates for their own new models. Ideally, all of the code in every model in the Models Library should be technically, stylistically, and visually exemplary, because looking at our models is one of the main ways that NetLogo users learn how to code and learn what good code looks like.
Below are a lot of specific dos and don'ts for NetLogo code, but it's not an exhaustive list.
Name variables like this: leopard-spot-count
, and not like leopardspotcount
or leopardSpotCount
or leopard_spot_count
.
We only allow use of capital letters if it is accepted abbreviations. For instance, in the library model Climate Change
, infrared rays and carbon dioxide are abbreviated respectively IRs
, and CO2s
. So if it is an accepted abbreviation, it's fine. If it's not, don't use capital letters.
Breed declarations require both a plural form and a singular form. If there is not a clear singular form of the breed name, prefix it with "a-
".
breed [ wolves wolf ]
breed [ sheep a-sheep ]
Every Boolean (true/false) variable and reporter (and switch) should have a name ending in a question mark, like this: grow-grass?
.
Try to name your procedures so that they "read" easily in your code. A lot of the time, your procedures are "actions performed by agents," so ideally you'd like to ask sheep [ do-the-thing ]
instead of ask sheep [ foo ]
.
Put your procedures in a logical order: setup
, then go
, then supporting procedures, etc..
It also makes sense to group turtle procedures together by which breeds use those procedures (if they are indeed "breed-ed" procedures.
Big headers intended to group procedures together (like ;;;;; SETUP PROCEDURES ;;;;;
) are not required and tend not to be very useful unless you have a lot of procedures that you think need to be grouped in order for the code to be more readable
These are the only OK abbreviations: fd
, bk
, lt
, rt
. And even then, you should probably use the full version (i.e., forward
, back
, left
and right
).
Don't abbreviate: ca
, cro
, crt
, pu
, pd
, ppu
, ppd
, bf
, bl
, cd
, cp
, ct
, ht
, st
, se
.
The idea is that for readability, we should only use a few of the most common abbreviations. In their own code, users are free to save themselves typing using abbreviations if they want, but in sample code, we should favor readability.
Again, it's probably best to NOT abbreviate UNLESS, that abbreviation is very standard in the field which you are modeling (e.g. "carbon dioxide" -> "CO2"). Even then, you should make sure to preface and explain that abbreviation in the INFO tab.
For example,rewrite set heading heading + 10
as right 10
.
But also use your judgement. For example, set heading random 360
is better than right random 360
, since it is obvious that the resulting angle is not affected by the turtle's current heading.
Does the model have a natural stopping point, for example, when all the turtles are dead? If so, the "go" button should "unpress" itself if the model reaches that point, using the stop
command.
If the "go" button uses stop
to stop itself, the stopping check should be at the top of the go
procedure, not the bottom.
This is wrong:
to go
ask turtles [ ... ]
if not any? turtles [ stop ]
end
Do this instead:
to go
if not any? turtles [ stop ]
ask turtles [ ... ]
end
That way even if the user tries to press "go" again, nothing will happen. They shouldn't be able to force the model to step forward by repeatedly pressing "go" (especially since in some models that might cause bad behavior, such as a runtime error).
BehaviorSpace also expects this "stop check" to be at the top of the go
procedure to be if you want to still have the final state of the model included in the results.
Not all models have natural stopping conditions (such as all the turtles being dead) — many models just run forever and therefore have no need for added code like this.
Global variables are an endless source of errors and they can make a model much harder to understand.
In a nutshell: the main problem with global variables is that they can be modified from anywhere in your program (including plot or widget code) so it's hard to reason about the values that they can possibly hold at any particular time. Here is a more detailed explanation if you are interested: Why you should probably avoid global variables in NetLogo.
If you are thinking a global value is necessary, consider making it a NetLogo widget (e.g. slider, chooser, etc.) so that users can tinker with it.
If, for example, you wanted to use tau instead of pi, you could do:
globals [ tau ]
to setup
clear-all
set tau pi * 2
end
But this has two problems:
-
Until
setup
is called, the value of your uninitializedtau
variable is0
, meaning that any code usingtau
will give wrong results. -
You have no formal guarantee that the value always stays the same (you could, for example, have a forgotten, and less precise,
set tau 6.2831853
lying around somewhere in the code).
Don't hesitate to use reporters instead of globals to simulate constants:
to-report tau
report pi * 2
end
This ensures that the value of tau
will always be the same. And don't worry about the use of a reporter making your code slower until it becomes a demonstrable problem (which will probably never happen).
It's not uncommon to see code like:
ifelse any? out-link-neighbors
[ set has-neighbors? true ]
[ set has-neighbors? false ]
This can be replaced by:
set has-neighbors? any? out-link-neighbors
Writing if condition? = true
is unnecessary. It can be replaced by if condition?
.
Similarly, if condition? = false
can be replaced by if not condition?
.
The only exception to that rule is when checking the value of a global variable that could possibly still have its "uninitialized" value of 0
. But you should probably try to avoid that situation anyway.
If those were used for debugging, they should not be in the final version of the model.
If you want to communicate with the user, use an output widget and output-print
, output-show
, output-type
or output-write
instead.
For example, if you've used the profiler
extension (always a good idea!), don't leave the profiling code in your model.
Don't use if count turtles > 0
(or >= 1
) when you could use if any? turtles
. And don't use if count turtles = 0
when you could use if not any? turtles
.
NetLogo initializes all variables to 0
. That's fine for numeric variables, but variables of other types should be initialized to a value that makes sense, e.g.: false
for a boolean, ""
for a string, []
for a list, no-turtles
for an agentset, nobody
for an agent reference, etc. Even if it doesn't make a difference when your model runs, it could make one for somebody who is trying to analyse your model from the Command Center or from BehaviorSpace. For example, in the Flocking models, an expression like mean [ count flockmates ] of turtles
would fail right after setup
if flockmates
was not initialized to no-turtles
.
The nice thing about set-default-shape
is that if you create turtles from, e.g., the command center, they automatically get the right shape.
In the documentation of ask-concurrent
, it says:
This primitive exists only for backwards compatibility. We don't recommend using it new models.
...and we don't want new models, particularly in the Library, to use it!
While there are some legitimate uses for who
numbers, those are few and far between. If you're thinking of using who
numbers for something, think again: there is most probably a better way. The reason being is that often times, code dependent on who
has a tendency to be easily broken by edge cases.
As former NetLogo developer Seth says:
99%, if not 100%, of uses in the past were to compensate for the nonexistence of tick-based updates in older NetLogo versions, or were just because people didn't understand they should be using tick-based updates instead.
There are rare cases where hiding updates is either desired or necessary. But in general, you shouldn't be hiding things from the user.
If the model needs to tell the user that something has gone wrong, use user-message
. If you use show or print, the user might not see it because they might have hidden the command center.
If the model wants to print messages from time to time as it runs, it should use output-print
(and/or the other output-
commands). Add an Output area to the interface for the output to show up in. Doing so has several advantages:
-
The output will be visible even if the user has hidden the command center.
-
You can give the output a fairly narrow area of the screen if you want, whereas the command center is always the full width of the window.
It may look good on your own computer, but there is a very high chance that it will not look good on someone else's computer and that the HTML codes will leak out in the message text. This is because NetLogo splits your message in multiple JLabel
objects (https://github.com/NetLogo/NetLogo/blob/5.x/src/main/org/nlogo/swing/UserDialog.java) and that each JLabel
must have complete, HTML code independent from the other ones. Even if you achieve this on your machine, you have no guarantee that the split will be the same on another machine. It varies from OS to OS (in rare cases, it can also vary with the user's JVM font settings).
The code should be as easy to read as possible. Look at models in the Library to see what easy to read code looks like. There is no "uniform style" (and hence no linter) for spaces, bracket usage, line breaks, etc.. That being said, it is important to pick a style and to apply it consistently.
Try to apply the following guidelines:
As of NetLogo 6.0, there's a built in auto-indenter that will enforce 2 space indenting. Just highlight your code and hit TAB and the auto-indenter will take care of the rest!
In the Models Library, we limit line length to 85 characters. Why 85? Because that was the limit for the IABM textbook and the Library should be consistent. Having shorter lines has lots of advantages anyway:
- It encourages good practices like breaking up a long expression into a few local variables and/or reporters.
- It makes it easier to quote a piece of code in a blog post, a paper, or something like that.
- It works better for people that have older screens with lower resolution.
- You can zoom in the code (using Ctrl-+) and still see complete lines (very useful when teaching, amongst other things).
- You can keep your NetLogo window small if you want to.
- It makes it easier to view side-by-side diffs on GitHub or in your local diff tool.
- It just generally reads better.
Do you want a good trick to make it easier to follow that rule? Use a ruler! Paste this into your code:
;-----------------------------------------------------------------------------------;
That's 85 characters long.
Code like:
set fly? false set run? false if breed = frogs [ set jump? true ]
reads much better as:
set fly? false
set run? false
if breed = frogs [ set jump? true ]
No more, no less. One is enough.
Remove any extra blank lines from the end.
They can be useful to clarify the order of operations when it is not immediately obvious but can be omitted most of the time.
In particular, avoid if (condition?) [ ... ]
; it's just noise. Parentheses are known to be points of confusion for novice programmers. If you don't need them, don't use them.
Use 0.2
instead of .2
. The leading dot, by itself, is just too easy to miss.
E.g.:
print [ size ] of turtles
E.g.:
print (size + 2) * 3
Make sure code is adequately commented. Don't assume that users are expert programmers. It's particularly important to comment anything unusual, tricky, or obscure in the code. On the other hand, it is not necessary to comment every local variable or every line of code. This is because when you're writing code, you should try to do so that it is "readable." One of the design aspects of NetLogo that makes the language so unique is that you can write code that reads fairly close to a regular sentence. Use that power!
Use just one semicolon (;
) to introduce comments. Some NetLogo models adhere to a "double semicolon" intro, but we think that the source that is an old LISP convention. Consistency is key.
Every global variable and every turtle or patch variable should have a short comment (usually just one line) saying what it is for. The comment should give some indication of whether the variable holds a number or a string or an agentset or what. (Note that saying e.g. "how many" is sufficient to convey that it's a number.) Here are some examples:
globals [
%infected ;; what % of the population is infectious
%immune ;; what % of the population is immune
lifespan ;; the lifespan of a turtle
chance-reproduce ;; the probability of a turtle generating an offspring each tick
carrying-capacity ;; the number of turtles that can be in the world at one time
immunity-duration ;; mean number of weeks immunity lasts
]
turtles-own [
status ;; equals one of "susceptible" (healthy but not immune), "infected" (sick and infectious), or "immune" (healthy and immune)
age ;; how many years old the turtle is
]
Any turtle or patch procedure should be commented as follows:
to move ; turtle procedure
forward 1
end
If it's an observer procedure, it's not necessary to say so explicitly:
to go
ask turtles [ move ]
end
Don't leave commented-out code lying around. It's not doing anyone any good.
hubnet-reset
should only be called in startup
not in setup
. setup
should not destroy any information needed to communicate with clients. If the teacher needs to kick out all the client s/he can use the RESET-INTERFACE button in the Control Center which you may want to point out in the info tab.
Remember to verify any included BehaviorSpace experiments. If you change the names of anything, old experiments might not run anymore.
Models Library Editing
- Changes Since Previous Release
- Reviewing a Model for the Library
- Template Letter for Requesting a Review of a Model
- Automated Tests
- Resaving Models in the Newest Release
- Copyright and Citation Info
- Models Cross Referencing
- Models Library Statistics
- What is a Code Example?
- What is a Curricular Model?