Skip to content

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.

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}}"
end
greet("Ada")
Hello, Ada

The parameter list can be written three ways:

FormExample
in bracketsblock add(a, b)
bare, separated by spacesblock add a b
bare, separated by commasblock add a, b
noneblock hello
block add a b
return a + b
end
block sub a b
return a - b
end
printline add(5, 3)
printline sub(5, 3)
8
2

A 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 * 10
end
20

Rules 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.

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"
end
printline secret()
private, but callable here

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}}"
end
printline pair(
"left",
"right"
)
left and right

Missing arguments are null. Extra arguments are ignored. No error is reported for either.

block pair a b
return "[{{a}}] [{{b}}]"
end
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}}"
end
printline pair(b: "B", a: "A")
printline pair("A", b: "B")
printline pair(z: 1)
A-B
A-B
-

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"
end
set day to today
printline day
printline today()
today
Monday
Monday

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}}"
end
greet "Ada", "Hello"
greet "Grace" "Hi"
Hello, Ada
Hi, Grace

Arguments 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:

PositionWhat the call takesExample
Start of a line or of an argument listeverything after itprintline double 4 + 1 prints 10
Start of a conditionup to a comparison, and or orif double n + 1 is 10
After an operatorone value1 + double 4 + 1 is (1 + 8) + 1, which is 10
block double n
return n * 2
end
set n to 4
printline double 4 + 1
if double n + 1 is 10
printline "ten"
end
printline 1 + double 4 + 1
printline double -3
set x to 7
printline double not x
10
ten
10
-6
0

printline -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.

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 one
a and b

Every method of the mash object also works without the mash. prefix. See Variables and scope.

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 * 2
end
block implicit n
n * 2
end
block nothing
printline "side effect only"
end
printline explicit(4)
printline implicit(4)
set r to nothing()
printline mash.type(r)
8
8
side effect only
NULL

A block can call itself.

block fact n
return n is less than or equal to 1 ? 1 : n * fact(n - 1)
end
printline fact(10)
3628800

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.

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 * n
end
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 * n
end
block hello
return "hello"
end
printline mash.collect([1, 2, 3]).map(@square).toArray()
set f to @hello
printline f()
set g to @square
printline g(5)
Array [
1,
4,
9
]
hello
25

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 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)
3

The statements break and next inside an anonymous block both end that one call. A yield is not allowed inside an anonymous block.

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
end
end
each evens(10) as e
print "{{e}} "
end
printline ""
printline evens(10).first()
2 4 6 8 10
2

A 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
end
end
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": 2
end
each pairs() as k, v
printline "{{k}}={{v}}"
end
a=1
b=2

Each 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.