Mathematica is a sharp tool, but it can cut you with its somewhat untyped behaviour and avalanches of cryptic diagnostic messages. One way to deal with this is to define functions following this idiom:
ClearAll@zot
SetAttributes[zot, ...]
zot[a_] := ...
zot[b_ /; ...] := ...
zot[___] := (Message[zot::invalidArguments]; Abort[])
That is a lot of boilerplate, which I'm frequently tempted to skip. Especially when prototyping, which happens a lot in Mathematica. So, I use a macro called define
that allows me to stay disciplined, with much less boilerplate.
A basic usage of define
is like this:
define[
fact[0] = 1
; fact[n_ /; n > 0] := n * fact[n-1]
]
fact[5]
120
It doesn't look like much at first, but there are some hidden benefits. The first service that define
provides is that it automatically applies ClearAll
to the symbol being defined. This ensures that there are no leftover definitions -- a common occurrence during the initial development of a function.
The second service is that the function being defined is automatically "closed". By this I mean that the function will issue a message and abort if it is invoked with an argument list that is not matched by one of the definitions:
fact[-1]
define::badargs: There is no definition for 'fact' applicable to fact[-1].
$Aborted
This is the primary value of define
, which catches a very common class of error.
Another convenience is a concise way to specify attributes on the function being defined. Let's make the function Listable
:
define[
fact[0] = 1
; fact[n_ /; n > 0] := n * fact[n-1]
, Listable
]
fact[{3, 5, 8}]
{6, 120, 40320}
In addition to all of the normal attributes, define
accepts an additional attribute called Open
. This prevents define
from adding the catch-all error definition to the function:
define[
successor[x_ /; x > 0] := x + 1
, Open
]
successor /@ {1, "hi"}
{2, successor["hi"]}
Multiple attributes may be defined for a function:
define[
flatHold[x___] := Hold[x]
, {Flat, HoldAll}
]
flatHold[flatHold[1+1, flatHold[2+3]], 4+5]
Hold[1 + 1, 2 + 3, 4 + 5]
Without further ado, here is the definition of define
:
ClearAll@define
SetAttributes[define, HoldAll]
define[body_, attribute_Symbol] := define[body, {attribute}]
define[body:(_Set|_SetDelayed), attributes_List:{}] := define[CompoundExpression[body], attributes]
define[body:CompoundExpression[((Set|SetDelayed)[name_Symbol[___], _])..], attributes_List:{}] :=
( ClearAll@name
; SetAttributes[name, DeleteCases[attributes, Open]]
; If[!MemberQ[attributes, Open]
, def:name[___] := (Message[define::badargs, name, Defer@def]; Abort[])
]
; body
;
)
def:define[___] := (Message[define::malformed, Defer@def]; Abort[])
define::badargs = "There is no definition for '``' applicable to ``.";
define::malformed = "Malformed definition: ``";
The exhibited implementation supports neither up-values nor currying, nor patterns more general than simple function definition. It remains useful, however.