Blocks and calls
A block is MaSH’s function: a named piece of code that you can call from elsewhere in the script, with values in and a value out.
Defining a block
Section titled “Defining a block”A block starts with block, a name, and a list of parameter names. The body runs to end, or sits in braces.
block greet name printline "Hello, {{name}}"endgreet("Ada")Hello, Adablock greet(name) { printline("Hello, {{name}}")}greet("Ada")Hello, AdaThe parameter list can be written three ways:
| Form | Example |
|---|---|
| in brackets | block add(a, b) |
| bare, separated by spaces | block add a b |
| bare, separated by commas | block add a, b |
| none | block hello |
block add a b return a + bendblock sub a b return a - bendprintline add(5, 3)printline sub(5, 3)82block add(a, b) { return a + b}block sub(a, b) { return a - b}printline(add(5, 3))printline(sub(5, 3))82A block can be defined anywhere in the script, and used before the line that defines it. The reader collects every block first.
printline later(2)block later n return n * 10end20printline(later(2))block later(n) { return n * 10}20Rules for definitions:
- A parameter has no type and no default.
block f(a = 1)is a syntax error. - Defining a name twice is allowed. The last definition wins.
- A block defined inside another block is not private to it. It is defined for the whole script once the outer block has run.
- A block name can be the same as a variable name. See Variables and scope.
Private blocks
Section titled “Private blocks”pblock name (or here block name) defines a private block. It works like any other block inside its own script. It cannot be called from another script that imports this one, and mash.scripts reports it as not found.
pblock secret return "private, but callable here"endprintline secret()private, but callable herepblock secret() { return "private, but callable here"}printline(secret())private, but callable hereCalling a block
Section titled “Calling a block”With brackets
Section titled “With brackets”name(arguments) calls the block. The ( must come straight after the name, with no space. Arguments are separated by commas, and can run across lines.
block pair a b return "{{a}} and {{b}}"endprintline pair( "left", "right")left and rightblock pair(a, b) { return "{{a}} and {{b}}"}printline(pair( "left", "right"))left and rightMissing arguments are null. Extra arguments are ignored. No error is reported for either.
block pair a b return "[{{a}}] [{{b}}]"endprintline pair(1)printline pair(1, 2, 3)[1] [][1] [2]block pair(a, b) { return "[{{a}}] [{{b}}]"}printline(pair(1))printline(pair(1, 2, 3))[1] [][1] [2]Arguments can also be named, with name: value. Named arguments are matched to the parameters by name, in any order. A name that matches no parameter is ignored.
block pair a b return "{{a}}-{{b}}"endprintline pair(b: "B", a: "A")printline pair("A", b: "B")printline pair(z: 1)A-BA-B-block pair(a, b) { return "{{a}}-{{b}}"}printline(pair(b: "B", a: "A"))printline(pair("A", b: "B"))printline(pair(z: 1))A-BA-B-Naming a block with no parameters
Section titled “Naming a block with no parameters”A block with no parameters is called when you use its name on its own, with or without (). A name that is used as a value calls the block and gives its result.
block today return "Monday"endset day to todayprintline dayprintline today()todayMondayMondayblock today() { return "Monday"}day = todayprintline(day)printline(today())today()MondayMondayWithout brackets
Section titled “Without brackets”When a statement is a block’s name followed by values, those values are the arguments. This is the natural call form, and it reads like a command:
block greet name greeting printline "{{greeting}}, {{name}}"endgreet "Ada", "Hello"greet "Grace" "Hi"Hello, AdaHi, GraceArguments can be separated by commas or by spaces. Each argument is a full expression: greet "a" ~ "b", "Hi" joins before the call.
A name is called without brackets inside an expression only when it is a block the script defines, or a built-in. How much it takes depends on where it stands:
| Position | What the call takes | Example |
|---|---|---|
| Start of a line or of an argument list | everything after it | printline double 4 + 1 prints 10 |
| Start of a condition | up to a comparison, and or or | if double n + 1 is 10 |
| After an operator | one value | 1 + double 4 + 1 is (1 + 8) + 1, which is 10 |
block double n return n * 2endset n to 4printline double 4 + 1if double n + 1 is 10 printline "ten"endprintline 1 + double 4 + 1printline double -3set x to 7printline double not x10ten10-60printline -x (or printline - x), printline !x, printline not x and printline [1, 2] all work when the callee is a block the script defines or a built-in. After a variable, or a block with no parameters, - x is a subtraction. return double 4 returns 8.
And /pattern/ straight after a block name is a division, not a pattern. See Lexical structure.
The built-in blocks
Section titled “The built-in blocks”These are available everywhere and take one argument: print, printline, send, sendline, uppercase, lowercase, sleep. sendheader takes two. The others are sendstatus and explain.
print writes without a line ending, printline with one. send and sendline do the same for a script that is answering a web request, and write at once. A built-in given more arguments than it takes records an error and does nothing: printline "a", "b" prints nothing and records “printline takes 1 argument, 2 given”. To print several values, join them with ~.
print "no line ending, "printline "then one"printline "a" ~ " and " ~ "b"no line ending, then onea and bprint("no line ending, ")printline("then one")printline("a" ~ " and " ~ "b")no line ending, then onea and bEvery method of the mash object also works without the mash. prefix. See Variables and scope.
Return values
Section titled “Return values”return value ends the block and gives the value back. A block that ends without return gives the value of its last statement. If that statement has no value, such as an if or a printline, the result is null or whatever that statement produced, so write return when the result matters.
block explicit n return n * 2endblock implicit n n * 2endblock nothing printline "side effect only"endprintline explicit(4)printline implicit(4)set r to nothing()printline mash.type(r)88side effect onlyNULLblock explicit(n) { return n * 2}block implicit(n) { n * 2}block nothing() { printline("side effect only")}printline(explicit(4))printline(implicit(4))r = nothing()printline(mash.type(r))88side effect onlyNULLA block can call itself.
block fact n return n is less than or equal to 1 ? 1 : n * fact(n - 1)endprintline fact(10)3628800block fact(n) { return n <= 1 ? 1 : n * fact(n - 1)}printline(fact(10))3628800Arguments are copies
Section titled “Arguments are copies”Passing an array or dictionary gives the block a copy. Changing it inside the block does not change the caller’s. Objects are shared. See Values and types.
Blocks as values
Section titled “Blocks as values”A named block as a callback
Section titled “A named block as a callback”A block can be passed where a callback is expected by writing its name with @, or with no brackets and no arguments. Methods such as a collection’s map and filter take it. A variable can hold the reference and call it.
block square n return n * nendprintline mash.collect([1, 2, 3]).map(square).toArray()Array [ 1, 4, 9]block square(n) { return n * n}printline(mash.collect([1, 2, 3]).map(square).toArray())Array [ 1, 4, 9]@name refers to a block without calling it, so it is the only way to pass a block that has no parameters.
block square n return n * nendblock hello return "hello"endprintline mash.collect([1, 2, 3]).map(@square).toArray()set f to @helloprintline f()set g to @squareprintline g(5)Array [ 1, 4, 9]hello25block square(n) { return n * n}block hello() { return "hello"}printline(mash.collect([1, 2, 3]).map(@square).toArray())f = @helloprintline(f())g = @squareprintline(g(5))Array [ 1, 4, 9]hello25Anonymous blocks
Section titled “Anonymous blocks”block(params) { body } is a block with no name. It is an expression, so it can be passed straight to a callback, or stored.
set big to mash.collect([1, 2, 3]).filter(block n { return n is more than 1 }).values().toArray()printline bigArray [ 2, 3]big = mash.collect([1, 2, 3]).filter(block(n) { return n > 1 }).values().toArray()printline(big)Array [ 2, 3]The parameter list can also be bare, as block n { ... }, and the body can use end.
A variable that holds an anonymous block can be called like a block.
set add to block a b { return a + b }printline add(1, 2)3add = block(a, b) { return a + b }printline(add(1, 2))3The statements break and next inside an anonymous block both end that one call. A yield is not allowed inside an anonymous block.
Generator blocks and yield
Section titled “Generator blocks and yield”A block that contains yield is a generator. It does not run when called. It gives back a lazy collection, and the body runs as you ask for each item.
yield value hands out one value. yield key: value hands out a key as well. return ends the sequence.
block evens limit each 1...limit as n if n % 2 is 0 yield n end endendeach evens(10) as e print "{{e}} "endprintline ""printline evens(10).first()2 4 6 8 102block evens(limit) { each (1...limit) as n { if (n % 2 == 0) { yield n } }}each (evens(10)) as e { print("{{e}} ")}printline("")printline(evens(10).first())2 4 6 8 102A generator can be endless, because it only works as far as you read. A lazy collection has take, map, filter, first and toList. Use toList(), not toArray(), to turn it into an array.
block counting here n = 0 while true increment n by 1 yield n endendprintline counting().map(block x { return x * 2 }).take(3).toList()Array [ 2, 4, 6]block counting() { here n = 0 while (true) { n += 1 yield n }}printline(counting().map(block(x) { return x * 2 }).take(3).toList())Array [ 2, 4, 6]With a key:
block pairs yield "a": 1 yield "b": 2endeach pairs() as k, v printline "{{k}}={{v}}"enda=1b=2block pairs() { yield "a": 1 yield "b": 2}each (pairs()) as k, v { printline("{{k}}={{v}}")}a=1b=2Each step of a loop and each call of a block counts towards the run’s time limit, so an endless loop is stopped at that limit.