A block’s syntax is a list of statements:
Blocks serve two roles in Pyret:
Sequencing of operations
Units of lexical scope
Blocks evaluate each of their statements in order, and evaluate to the value of the final statement in the block.
The ‹user-block-expr› form additionally creates a scope for any names bound inside it. That is, definitions within such a block are visible only within that block:
x = 10 ans = block: y = 5 + x # x is visible here 42 # value result of the block end z = y + ans # error: y is not in scope here
Many expressions in Pyret include one or more blocks within them. For example, the body of a function is defined as a block. Technically, this means the following program is legal:
fun weather-reaction(forecast, temp): ask: | forecast == "sunny" then: "sunglasses" | forecast == "rainy" then: "umbrella" | otherwise: "" end ask: | temp > 85 then: "shorts" | temp > 50 then: "jeans" | temp > 0 then: "parka" | otherwise: "stay inside!" end end
However, the program probably won’t behave as expected: rather than returning some combination of "sunglasses" and "shorts" for a warm, sunny day, it will evaluate the first ask expression, discard the result, and then evaluate the second ask expression and return its result.
Pyret will warn the programmer if it encounters programs like these,
and complain that the block contains multiple expressions. Often
as in this case, it signals a real mistake, and the programmer ought
to revise the code to comprise a single expression —
if some-condition(): temp = some-complicated-expression() print(temp) # make sure we got it right! do-something-with(temp) else: do-something-else() end
To tell Pyret that these multiple statements are intentional, we could write an explicit block form:
if some-condition(): block: temp = some-complicated-expression() print(temp) # make sure we got it right! do-something-with(temp) end else: do-something-else() end
...but that is syntactically annoying for a straightforward situation! Instead, Pyret allows for block shorthands: writing block before the opening colon of a blocky expression signals that the expression is deliberate.
if some-condition() block: temp = some-complicated-expression() print(temp) # make sure we got it right! do-something-with(temp) else: do-something-else() end
However, even this marker is sometimes too much. Suppose we eliminated the print call in the example above:
if some-condition() block: temp = some-complicated-expression() do-something-with(temp) else: do-something-else() end
Why should this expression be penalized, but the equivalent one, where we inline the definition of temp, not be? After all, this one is clearer to read! In fact, Pyret will not complain about this block containing multiple expressions. Instead, Pyret will consider the following to be valid "non-blocky" blocks:
Any sequence of let-bindings followed by exactly one expression is fine, as is any block containing even a single template-expression, or (obviously) an explicit block expression. All other blocks will trigger the multiple-expressions warning and require either an explicit block or a block-shorthand to fix.