Function Repository Resource:

CatchEnclose

Wrapper that combines the functionalities of Catch and Enclose

Contributed by: Sjoerd Smit

ResourceFunction["CatchEnclose"][expr]

acts like Enclose, but also catches lexically scoped untagged instances of Throw inside expr.

ResourceFunction["CatchEnclose"][expr,f]

applies f to any value thrown with an untagged Throw. Any failure in f generated by Confirm-like constructs will still be caught inside ResourceFunction["CatchEnclose"].

ResourceFunction["CatchEnclose"][expr,f,handler]

uses handler in the enclosing Enclose to handle generated failures.

Details

ResourceFunction["CatchEnclose"] lexically replaces all untagged instances of Throw with a uniquely tagged one. This means all instances of Throw[] inside of ResourceFunction["CatchEnclose"] will be invisible to other Catch statements, guaranteeing that untagged Throws return to the enclosing ResourceFunction["CatchEnclose"].
The idea behind ResourceFunction["CatchEnclose"] is that is treats untagged Throws and Confirms in the same way and scopes their range lexically, compared to the dynamic scoping mechanism that Throw normally uses.

Examples

Basic Examples (3) 

CatchEnclose is used just like Enclose:

In[1]:=
ResourceFunction["CatchEnclose"][2 + 2]
Out[1]=
In[2]:=
ResourceFunction["CatchEnclose"][2 + 2; Confirm[$Failed]; 4 + 4]
Out[2]=

However, any instance of a single-argument Throw will be caught as well:

In[3]:=
ResourceFunction["CatchEnclose"][2 + 2; Throw["Catch!"]; 4 + 4]
Out[3]=

Untagged Throws inside CatchEnclose will NOT be caught by other instances of Catch:

In[4]:=
ResourceFunction["CatchEnclose"][
 Catch[
  2 + 2;
  Throw["Catch!"]
  ];
 4 + 4
 ]
Out[4]=

Scope (3) 

Tagged Throws still need their custom Catch statements to be caught:

In[5]:=
ResourceFunction["CatchEnclose"][2 + 2; Throw["Catch!", "myTag"]; 4 + 4]
Out[5]=

Throws with custom tags will be invisible to CatchEnclose:

In[6]:=
Catch[
 ResourceFunction["CatchEnclose"][2 + 2; Throw["Catch!", "myTag"]; 4 + 4],
 "myTag"
 ]
Out[6]=
In[7]:=
ResourceFunction["CatchEnclose"][
 Catch[
  2 + 2;
  Throw["Catch!", "myTag"]
  ,
  "myTag"
  ];
 4 + 4
 ]
Out[7]=

The function in the second argument can still use Confirm and related constructs to validate the thrown value:

In[8]:=
ResourceFunction["CatchEnclose"][2 + 2; Throw[3]; 4 + 4,
 Function[ConfirmBy[#, IntegerQ] + 1]
 ]
Out[8]=
In[9]:=
ResourceFunction["CatchEnclose"][2 + 2; Throw[3]; 4 + 4,
 Function[ConfirmBy[#, StringQ] <> "1"]
 ]
Out[9]=

The third argument function will be applied to any error generated by Confirm-like constructs:

In[10]:=
ResourceFunction["CatchEnclose"][2 + 2; ConfirmBy["Hello", IntegerQ]; 4 + 4,
 Function[#],
 "Expression"
 ]
Out[10]=

Possible Issues (5) 

Just like the lexical version of Enclose, CatchEnclose can be slow if you insert large amounts of data into the body. This happens frequently with functions that take data. For example, here is a function that checks that a lists starts with integers until it hits an even number:

In[11]:=
firstEvenInteger[list_] := ResourceFunction["CatchEnclose"] @ Module[{
     fst,
     rest = ConfirmBy[list, ListQ]
     },
    While[True,
     ConfirmAssert[Length[rest] > 0];
     {{fst}, rest} = TakeDrop[rest, UpTo[1]];
     ConfirmBy[fst, IntegerQ];
     If[EvenQ[fst], Throw[fst]]
     ]
    ];

Test the function:

In[12]:=
firstEvenInteger[{1, 2, 3}]
Out[12]=
In[13]:=
firstEvenInteger[{1, 3, 5}]
Out[13]=
In[14]:=
firstEvenInteger[{"String", 1, 3, 5}]
Out[14]=

The function is quite slow for large lists because of the way that the body of CatchEnclose needs to be scanned for lexical instances of Throw and Confirm:

In[15]:=
data = Prepend[RandomChoice[Range[5000], 10^6], "String"];
firstEvenInteger[data] // AbsoluteTiming
Out[16]=

In most cases, this problem can be easily avoided by making sure that the data inserted from the left-hand-side of the assignment is not directly inside of CatchEnclose, but instead copied into a Module variable outside of CatchEnclose:

In[17]:=
firstEvenInteger2[list_] := Module[{
   fst,
   rest = list
   },
  ResourceFunction["CatchEnclose"][
   ConfirmBy[rest, ListQ];
   While[True,
    ConfirmAssert[Length[rest] > 0];
    {{fst}, rest} = TakeDrop[rest, UpTo[1]];
    ConfirmBy[fst, IntegerQ];
    If[EvenQ[fst], Throw[fst]]
    ]
   ]
  ]

This implementation is significantly faster:

In[18]:=
firstEvenInteger2[data] // AbsoluteTiming
Out[18]=

Publisher

Sjoerd Smit

Version History

  • 1.0.0 – 24 October 2022

Related Resources

License Information