44
$\begingroup$

Context

I'm writing a function that look something like:

triDiagonalQ[mat_] := MapIndexed[ #1 == 0 || Abs[#2[[1]]-#2[[2]]] <= 1 &, mat, {2}] //
     Flatten // And @@ # &

Now, things like #2[[1]] and #2[[2]] are somewhat hard to read. I'd prefer to do something like:

triDiagonalQ[mat_] := MapIndexed[ #1 == 0 || Abs[i-j] <= 1 &, mat, {2}] // 
    Flatten // And @@ # & (* with a {i, j} <- #2 somewhere *)

Question

Is there someway to do something like "destructuring" in Mathematica?


The following links convey what I mean by "destructuring":

(These have nothing to do with Mathematica; they're posted mainly to demonstrate what is meant by "destructuring")

$\endgroup$
1
  • 1
    $\begingroup$ Power Programming with Mathematica: The Kernel by David B. Wagner has a Chapter 6.1.2 Destructuring. "the technique of extracting parts of patterns using pattern variables is sometimes called destructuring". (I know I'm late to the party but thought it relevant givin the valuable dicussion resulting from this question). $\endgroup$
    – Sander
    May 3, 2016 at 4:05

6 Answers 6

22
$\begingroup$

You can use macros / code generation to be able to use the syntax you like. Here is one possibility:

ClearAll[withLiteralIndices];
SetAttributes[withLiteralIndices, HoldAll];
withLiteralIndices[code_, inds : {__Symbol}] :=
  Block[inds,
     Unevaluated[code] /.
       MapIndexed[
          Function[{i, pos}, pos /. {p_} :> (i :> #2[[p]])], 
          inds
       ]
  ]

Now, you can pretty much literally use the code you would like to use:

withLiteralIndices[
  triDiagonalQ[mat_] := 
     MapIndexed[#1 == 0 || Abs[i - j] <= 1 &, mat, {2}] // Flatten // And @@ # &,
  {i, j}
]

When you look at the resulting definition, you can see that this is entirely equivalent to hand-written code using slots:

?triDiagonalQ

(*
  Global`triDiagonalQ

  triDiagonalQ[mat_]:=
    (And@@#1&)[Flatten[MapIndexed[#1==0||Abs[#2[[1]]-#2[[2]]]<=1&,mat,{2}]]]
*)

EDIT

Here is a simpler and perhaps more elegant version of the macro, which uses the injector pattern more explicitly:

ClearAll[withLiteralIndices];
SetAttributes[withLiteralIndices, HoldAll];
withLiteralIndices[code_, inds : {__Symbol}] :=
  Block[inds,
     Unevaluated[code] /.
        Replace[
          Range[Length[inds]],
          p_ :> (inds[[p]] :> #2[[p]]),
          {1}
        ]
  ]
$\endgroup$
2
  • 1
    $\begingroup$ I don't understand this yet, but I will meditate on it. You should write a "On Mathematica", like the Mathematica version of "On Lisp". $\endgroup$
    – user1602
    Jul 17, 2012 at 0:06
  • $\begingroup$ @term-rewritica I should first read some more elementary Lisp books, like ANSI CL by the same PG, or Seibel's book. It has been several years already that I promise this to myself. Thanks for the accept. $\endgroup$ Jul 17, 2012 at 0:07
42
$\begingroup$

Leonid provides a nice method for doing this within "pure functions" but I think it should be pointed out that the common method for doing this is pattern matching.

I argue that destructuring is the foundational use of pattern matching in Mathematica.

Every replacement pattern, be it an explicit rule (:>, ->) or part of a definition (:=, =), that uses a named pattern on the left-hand side that does not match the entire expression or argument is doing destructuring.

Applied to your specific example:

f[a_, {i_, j_}] := a == 0 || Abs[i - j] <= 1

triDiagonalQ[mat_] := And @@ Flatten @ MapIndexed[f, mat, {2}]

Or:

triDiagonalQ[mat_] := And @@ Flatten @
   MapIndexed[#2 /. {i_, j_} :> # == 0 || Abs[i - j] <= 1 &, mat, {2}]

The second example is almost exactly what you asked for: "with a {i, j} <- #2 somewhere"
It's just turned around: #2 /. {i_, j_}.

This destructuring is common in Mathematica programming for experienced users.

Among many examples:

Here I use it to separate a + b + c:

(a + b + c) /. head_[body___] :> {head, body}  (* Out= {Plus, a, b, c} *)

Here Leonid uses it in a recursive function. ({x_, y_List})

Szabolcs uses it in iter, also recursive.

Heike uses it with /. in PerforatePolygons and with := in torn.

Here I used it simply in formula but also in MakeBoxes[defer[args__], fmt_] := where the parameter pattern defer[args__] serves to match the literal head defer while also destructuring.

In withOptions it is used both in the function definition and in the replacement rule.

The "injector pattern" is a form of destructing.

I also used it in inside, withTaggedMsg, pwSplit, dPcore etc.


Another, simpler form of destructuring exists in the form of Set and List ({}). A matching List structure on the left and right sides of = will assign values part-wise.

{{a, b}, c, {d}} = {{1, 2}, 3, {4}};

{a, b, c, d}
 {1, 2, 3, 4}

This is used e.g. in the first LUDecomposition example, and R.M uses it here.

$\endgroup$
4
  • 1
    $\begingroup$ This is a very cute use of pattern matching. Never thought of it. $\endgroup$
    – user1602
    Jul 17, 2012 at 9:11
  • $\begingroup$ (purely out of courisity) Can pattern matching be combined with anonymous functions (i.e. #1 #2 ... &), or is it only for named functions? $\endgroup$
    – user1602
    Jul 17, 2012 at 9:11
  • 3
    $\begingroup$ @term-rewritica it is not just a "cute" use, it is the foundational use. This is destructuring in Mathematica. My second example is a combination of pattern matching and anonymous functions. The second example is almost exactly what you asked for: "with a {i, j} <- #2 somewhere" -- it's just turned around (#2 /. {i_, j_}). $\endgroup$
    – Mr.Wizard
    Jul 17, 2012 at 18:02
  • $\begingroup$ you're absolute right, I missed the ingenuinity of the /. {i+, j_} :> in the second example. :-) I'm going to try to work this into my "default/habitual mathematica style" $\endgroup$
    – user1602
    Jul 18, 2012 at 1:00
21
+100
$\begingroup$

I think Mr. Wizard provided a very thorough answer to the question. I would however like to add a slight example of wrapping this up nicely in a format similar to Function[] but using destructuring:

SetAttributes[dFunction, HoldAll]
dFunction[pattern_, body_][arg___] /;MatchQ[{arg}, pattern] := {arg} /. pattern :> body

This then allows you to have nice syntax for an anonymous destructuring function using pattern matching:

triDiagonalQ3[mat_] := And @@ Flatten@ MapIndexed[
   dFunction[{v_, {i_, j_}}, v == 0 || Abs[i - j] <= 1], mat, {2}]

Syntax

As to the "clumsiness" of this syntax, I have to agree with Mr. Wizard that it's not as nicely looking as replacement rules, but then again I don't particularly like Function[x,x^2] either, and much prefer the aesthetics of the built-in shorthand which looks like x ↦ x^2(entered via EscfnEsc). The above code can be made to use a similar shorthand rather simply by setting RightArrowBar = dFunction; which allows the definition to look like :

{v_, {i_, j_}}  ⇥  (v == 0 || Abs[i - j] <= 1)

And if one is afraid of using the build-in undefined infix operators, you could of course define a custom symbol for it which doesn't lay claim on RightArrowBar. Personally I find this appearance of an anonymous replacement rule based function to be quite nice.

The value of pattern matching

As was discussed in comments, one of the interesting behaviors of this construct is that it closely mirrors the behavior of a function defined through pattern matching. if f[x_Integer]:=... is defined and called with arguments not matching the pattern the function remains unevaluated. For instance f[2.3] will return itself. The same behavior can be observed with dFunction where for instance dFunction[{x_Integer}, x][3.2] will return itself unevaluated.

Sadly due to the nature of SubValues, it's not possible to fully simulate pattern matching functions in an anonymous form using this method, since attributes such as HoldAll cannot be implemented. I do belive that this could be implimented using devious hacks similar to the method employed here https://mathematica.stackexchange.com/a/5458/1194 by Leonid Shifrin. However I fear that even if I did mange to implement it I would never use the result for fear of unexpected behavior, while I quite like the simplicity of my current dFunction.

$\endgroup$
7
  • $\begingroup$ I usually like your answers (in fact I know through ♦ magic that I've upvoted your posts more than anyone else), so please don't take offense, but honestly how is that in any way superior to the forms that I show? It looks very clumsy to me compared to triDiagonalQ[mat_] := And @@ Flatten @ MapIndexed[#2 /. {i_, j_} :> # == 0 || Abs[i - j] <= 1 &, mat, {2}] $\endgroup$
    – Mr.Wizard
    Aug 23, 2012 at 12:47
  • $\begingroup$ @Mr.Wizard I didn't claim that it was superior in any way. It's a stylistic choice I sometimes find I have a short function which doesn't do much, so I replace it with an anonymous function, however if the function relies on pattern matching, as is often the case for my MapIndexed functions, I can't easily do this. But with this definition it's no problem. So really it's a style choice. I decided to post it since Mike also commented that it was a shame Function didn't support destructuring. $\endgroup$
    – jVincent
    Aug 23, 2012 at 12:52
  • $\begingroup$ +1 Now it would be nice to support attributes $\endgroup$
    – Rojo
    Aug 23, 2012 at 12:53
  • 1
    $\begingroup$ @Mr.Wizard And another thing, if you call something like f[_Integer]:=code;f[2.3] You get the unresolved "call". If you try to emulate this using your method, you get the unchanged input to the replacement, instead of getting the unresolved "call". To be specific 0.3/._Integer:>code yields 0.3, while dFunction[{_Integer},code][0.3] yields itself, meaning that your output specifies that you have the unresolved call, rather then just passing on the input to higher functions. Speedwice my best bet is that you win by a factor of 2 due to my version having two pattern checks. $\endgroup$
    – jVincent
    Aug 23, 2012 at 12:58
  • 1
    $\begingroup$ I just wanted to say that in the time since I doubted the value of this construct I have come to really see the value in it. Critical for me is the behavior that the function does not evaluate until the pattern matches. Please consider placing notice of this behavior conspicuously in your answer. I'd +1 a second time if I could. $\endgroup$
    – Mr.Wizard
    Aug 9, 2013 at 2:57
6
$\begingroup$

You can use Subtract or Differences to get the differences in the index and use FreeQ to test if every element is True. This removes the need for the ugly #2[[1]]-#2[[2]] and also the Flatten and And@@ at the end.

triDiagonalQ[mat_?MatrixQ] :=FreeQ[MapIndexed[(#1 == 0 || Abs[Subtract @@ #2] <= 1) &, 
      mat, {2}], False]

The pattern test _?MatrixQ ensures that you only operate on matrices.

$\endgroup$
1
  • 1
    $\begingroup$ This solution is more elegant than more. However, I'd still like to know how to do destructuring (if possible) :-) $\endgroup$
    – user1602
    Jul 17, 2012 at 0:00
5
$\begingroup$

My first answer explains that Mathematica's replacement rules perform destructuring. This answer is intended to complement jVincent's method, which I see is appreciated.

My aim is to provide Attributes for the pattern-based function. This requires that the head evaluate therefore SubValues may not be used. Here are two separate approaches.

Module

A direct approach is to automate the creation of a new DownValue function using Module.

SetAttributes[pFunc1, HoldAll]

pFunc1[pattern_, body_, attrib_: {}] :=
  Module[{pf},
    SetAttributes[pf, attrib];
    pf[pattern] := body;
    pf
  ]

pFunc1[_[__, b_, c_], Binomial[b, c], HoldAllComplete][9 + 7 + 6 + 3]

pFunc1[PatternSequence[_[__, b_, c_], x_], Binomial[b, c]/x, HoldAllComplete][9 + 7 + 6 + 3, 4]
20

5

ReplaceAll

The first method creates a new symbol pf$xxx for each use which could be seen as undesirable. A method that does not is to simply embed a ReplaceAll operation in the body of Function like this:

SetAttributes[pFunc2, HoldAll]

pFunc2[pattern_, body_, attrib_: {}] :=
  Function[
    Null,
    Unevaluated@{##} /. pattern :> body,
    attrib
  ]

This method returns an actual Function expression rather than a pf$xxx symbol.
It also allows mixing Slot (#) and pattern references in body should that be desired.

It however does not return an unevaluated function expression when the arguments do not match the pattern. (Though one could add a {__} :> $Failed type of rule to /., if that is all that is desired.)

$\endgroup$
0
4
$\begingroup$

Perhaps you could just use With in a Function?

lst = ConstantArray[0, {3, 3}];
MapIndexed[
   Function[{value, pos}, 
      With[{i = pos[[1]], j = pos[[2]]}, 
         {value, i, j}
      ]
   ], 
lst, {2}]
$\endgroup$
1
  • $\begingroup$ It's a shame that neither With[] nor Function[] support destructuring. $\endgroup$
    – M.R.
    Jul 16, 2012 at 23:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.