12
$\begingroup$

In Mathematica 9 and earlier, one of the points under "Details"/"More Information" section for the function Normal was (this is gone after version 10):

enter image description here

I have defined my own "data types" and would like to define Normal for them to be converted to normal expressions. How do I make this definition? (undocumented functionality for an efficient answer is encouraged)

Example

Here is a "data type" called FractionSum, and it is a data structure containing a list of three items:

expr = FractionSum[{Item1 :> a, Item2 :> b, Item3 :> c}]

Then I would like the following behavior:

expr // Normal

1/a + 1/(a+b) + 1/(a+b+c)

$\endgroup$
5
  • 1
    $\begingroup$ Are you intending to self-answer? $\endgroup$
    – Mr.Wizard
    Dec 23, 2016 at 0:34
  • $\begingroup$ @Mr.Wizard No, I am asking sincerely. Do you have an answer? $\endgroup$
    – QuantumDot
    Dec 23, 2016 at 0:34
  • $\begingroup$ Would you please tell me the ways in which my present answer is lacking that are important to you, so I can try to improve it if i have time? $\endgroup$
    – Mr.Wizard
    Mar 18, 2017 at 11:23
  • 1
    $\begingroup$ @Mr.Wizard So sorry; by starting a bounty I didn't mean to snub your answer, which I think is very good. I am looking to see if there is a proper way to do it (as the older documentation suggests) without hacking or modifying built-in functions. $\endgroup$
    – QuantumDot
    Mar 18, 2017 at 17:11
  • $\begingroup$ okay, no problem $\endgroup$
    – Mr.Wizard
    Mar 18, 2017 at 17:14

2 Answers 2

8
$\begingroup$

I may be overlooking a deeper aspect of this question but a quick and dirty UpSetDelayed definition appears to work:

Normal[FractionSum[rules : {__}]] ^:= 
  1/Item1 + 1/(Item1 + Item2) + 1/(Item1 + Item2 + Item3) /. rules


expr = FractionSum[{Item1 :> a, Item2 :> b, Item3 :> c}]

expr // Normal
1/a + 1/(a + b) + 1/(a + b + c)

This doesn't work with foo[expr] // Normal however (it does not match) so maybe:

Unprotect[Normal]

Normal[x_ /; ! FreeQ[Unevaluated@x, _FractionSum]] := 
 Normal[
   x /. FractionSum[rules : {__}] :>
     (1/Item1 + 1/(Item1 + Item2) + 1/(Item1 + Item2 + Item3) /. rules)
 ]

Protect[Normal]

This still is not equivalent within held expression as described in the now-corrected Usage note section of Is there a way to control which special forms Normal converts? I shall continue to contemplate how this might be improved along with Leonid's admonition about modifying a System function in this way.

$\endgroup$
9
  • 2
    $\begingroup$ Why not TagSetDelayed? It is usually cleaner than UpSetDelayed (although in this particular case it doesn't matter). Also, why should it work on foo[expr]? I don't see any reason to request or expect that. And, I would really avoid redefining Normal like that (via DownValues) - it may have many undesired consequences. $\endgroup$ Dec 23, 2016 at 2:42
  • $\begingroup$ @LeonidShifrin To be clear, I want Normal to act on my "data type" just as it acts on built-in ones. This means it should work on foo[expr]. $\endgroup$
    – QuantumDot
    Dec 23, 2016 at 2:45
  • $\begingroup$ @Leonid (1) ^:= makes definitions for all heads at level one but there is only one head here so I would argue that ^:= is cleaner if a little less explicit. (2) foo[SparseArray[{1}]] // Normal (3) I acknowledge that redefining any System function may have undesired consequences but I don't see why this one would be especially dangerous. Could you be more specific? edit I see that the way I wrote my code could lead to an infinite loop but that's a somewhat separate issue. $\endgroup$
    – Mr.Wizard
    Dec 23, 2016 at 2:45
  • 1
    $\begingroup$ @Mr.Wizard (1) I view TagSetDelayed as a cleaner method precisely because it is more selective, so you always explicitly specify the symbol to which the rule is attached. I think this is good. (2) I didn't know about this, but actually I think this particular behavior / design choice was / is bad design. Allowing Normal to have non-local effects and going through expressions is just bad. (3) Normal is a pretty popular function. As such, it is called often enough in various parts of the system. As a rule, redefining such functions is bad, because you can't anticipate the consequences. $\endgroup$ Dec 23, 2016 at 10:16
  • $\begingroup$ @Leonid (1) I guess I'll just agree to disagree, mindful of the fact that at least 80% of the time I end up agreeing with you later. Do you presently use UpSet for anything? (2) Quite a surprise! I could certainly see the value of adding a function that only operates directly but having Normal operate within expressions is very useful and I make use of that with some regularity. But you have made me look at Normal again with new eyes and I see that some of my past beliefs were wrong! Quantity will disregard Hold attributes which I had convinced myself it respected. $\endgroup$
    – Mr.Wizard
    Dec 23, 2016 at 18:28
3
+25
$\begingroup$

using TagSetDelayed as pointed by @J.M. and @Leonid

FractionSum /: Normal[FractionSum[{patt : RuleDelayed[_, _Symbol] ..}]] := 
With[{sym = {patt}[[All, 2]]}, 
Plus @@ (Power[#, -1] &@Table[First@ListCorrelate[ConstantArray[1, i], sym], 
{i, Length@sym}])]

FractionSum[{Item1 :> a, Item2 :> b, Item3 :> c}] // Normal

(* 1/a + 1/(a + b) + 1/(a + b + c) *)
$\endgroup$
2
  • 1
    $\begingroup$ What I actually had in mind when using TagSetDelayed[] was something like FractionSum /: Normal[FractionSum[{patt : RuleDelayed[_, _Symbol] ..}]] := (* stuff *)... $\endgroup$ Mar 20, 2017 at 3:28
  • $\begingroup$ @J.M. thanks ! edited $\endgroup$
    – Ali Hashmi
    Mar 20, 2017 at 3:39

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.