On this page:
8.1 References
8.2 General
8.2.1 Indentation
8.2.2 Line Length
8.2.3 Variable Naming
8.2.3.1 Naming Constants
8.2.3.2 Reusing Variable Names
8.2.4 File Naming
8.2.5 Test Blocks
8.3 Functions
8.3.1 Naming
8.3.2 Documentation Strings
8.3.3 Annotations
8.3.4 Testing
8.4 Data
8.4.1 Definitions
8.4.2 Cases

8 Pyret Style Guide

Frank Goodman and Shriram Krishnamurthi

Ahoy matey! Here be the style guide for Pyret. Follow me rules to find the hidden treasure, or walk the plank!

    8.1 References

    8.2 General

      8.2.1 Indentation

      8.2.2 Line Length

      8.2.3 Variable Naming

        8.2.3.1 Naming Constants

        8.2.3.2 Reusing Variable Names

      8.2.4 File Naming

      8.2.5 Test Blocks

    8.3 Functions

      8.3.1 Naming

      8.3.2 Documentation Strings

      8.3.3 Annotations

      8.3.4 Testing

    8.4 Data

      8.4.1 Definitions

      8.4.2 Cases

8.1 References

You can consult the nascent Pyret Reference Guide, and also peruse the language introduction. Note that the introduction sometimes falls behind slightly; if you find an inconsistency between it and the current implementation, please let us know!

8.2 General

8.2.1 Indentation

You should indent your code blocks using two spaces (not tabs).

8.2.2 Line Length

Try to keep the total length of your lines under 80 characters.

8.2.3 Variable Naming

The programming language world rages about the use of camelCase versus under_scores in variable names. Pyret’s syntax supports both, but we can do better. In Pyret, you can use dashes (-) inside variable names. Thus, you would write camel-case and under-scores. Unlike underscores, dashes don’t need a shift key (or disappear when text is underlined by an environment). Unlike camelcase, dashes don’t create ambiguities (what if one of the words is in all-caps?). Dashes are also humanizing: they make your program look that little bit more like human prose.

Most languages can’t support dashes because the dash also stands for infix subtraction. In Pyret, subtraction must be surrounded by space. Therefore, camel-case is a name whereas camel - case is subtraction.

8.2.3.1 Naming Constants

You should name constants in all-capital letters unless an external convention would dictate using some other capitalization for that particular name. For example,

MY-COUNT = 100

e = 2.7182

8.2.3.2 Reusing Variable Names

Pyret is picky about letting you reuse variable names. This is to help you avoid confusing two different variables that have the same name and accidentally using the wrong one. Specifically, an inner scope can’t use a name that is already bound in an outer scope; but two different inner scopes can each use the same name. For instance,

fun f(x): x + 1 end

fun g(x): x + 2 end

is legal, but

fun h(x):

  x = 4

  x + 1

end

is not.

8.2.4 File Naming

Use .arr as the extension for Pyret files.

8.2.5 Test Blocks

Outside function definitions, use check blocks for testing:

check:

  1 + 1 is 2

  1 + 1 is-not 0

end

8.3 Functions

8.3.1 Naming

Give functions descriptive names. Do the same for arguments. That way, a quick scan of a function’s header will tell you what it does and what the arguments are supposed to do.

8.3.2 Documentation Strings

Unless the name is self-evident, write a brief documentation string for your function. For instance:

fun insert(x, l):

  doc: "consumes sorted list l; returns it with x in the right place"

  ...

end

Try to write your documentation in functional form, i.e., describing what the function consumes and what it returns after computation.

8.3.3 Annotations

Wherever possible, annotate both argument and return values. For instance,

fun str-len(str :: String) -> Number:

  # ...

end

Even though Pyret does not currently check parametric annotations, you should still write them for their value as user documentation. Thus:

fun length(lst :: List<Any>) -> Number:

  # ...

end

You can even write an arbitrary predicate when a built-in annotation isn’t expressive enough. For instance, suppose we want to write a function that consumes only non-negative numbers. We can define the predicate—

fun non-negative(n :: Number) -> Bool:

  n >= 0

end

and then use it as follows:

fun sqrt(n :: Number%(non-negative)) -> Number:

  # ...

end

8.3.4 Testing

You should test every function you write for both general cases and edge cases. To test functions, use where blocks. For example:

fun double(n :: Number) -> Number:

  n * 2

where:

  double(0) is 0

  double(5) is 10

  double(-5) is -10

  double(100) is 200

  double(-100) is -200

end

8.4 Data

8.4.1 Definitions

Wherever possible, provide annotations in Data definitions:

data Animal:

  | snake(name :: String)

  | dillo(weight :: Number, living :: Bool)

end

8.4.2 Cases

To branch on the variants of a datum, use cases:

cases (Animal) a:

  | snake(s) => s == "Dewey"

  | dillo(w, l) => (w < 10) and l

end

Sometimes, you won’t use all the parts of a datum. You can still name an unused part, but it is suggestive to use _ instead; this indicates to the reader that that field won’t be used in this computation, so they can ignore it:

cases (Animal) a:

  | snake(s) => ...

  | dillo(w, _) => ...

end

Note that _ is different from identifier names like dummy because you can’t write

cases (Animal) a:

  | snake(s) => ...

  | dillo(dummy, dummy) => ...

end

(you’ll get an error because you’re trying to bind dummy twice), but you can write

cases (Animal) a:

  | snake(s) => ...

  | dillo(_, _) => ...

end

and thus ignore multiple fields.

Finally, if your conditional is not designed to handle a particular kind of datum, signal an error:

cases (Animal) a:

  | snake(s) => ...

  | dillo(_, _) => raise("Serpents only, please!")

end