4
$\begingroup$

I'm teaching myself Mathematica and came across tagset.

So I followed the example in the link, but seem to be missing the point. All of the below give me the same answer after assignment: that area[square] equals s^2.

area /: area[square] = s^2

square /: area[square] = s^2

area[square] = s^2

So why have 3 ways of doing things?

$\endgroup$
6

2 Answers 2

9
$\begingroup$

TagSet is useful for assigning UpValues to symbols. These are often used to make built-in functions do something different for specific symbols. For example, suppose I defined a symbolic square with the symbol square and I want to compute its area by doing this:

square[4]^2 

16

This is how you can do this with TagSet:

ClearAll[square]
square /: square[length_]^2 := length^2;
square[4]^2

Note that this does not work with a normal assignment:

In[85]:= ClearAll[square2]
square2[length_]^2 := length^2;
square2[4]^2

During evaluation of In[85]:= SetDelayed::write: Tag Power in square2[length_]^2 is Protected.

Out[87]= square2[4]^2

This is because the assignment square2[length_]^2 := ... tries to assign a DownValue to Power:

Hold[square2^2 := area[square]] // FullForm

Hold[SetDelayed[Power[square2, 2], area[square]]]

Since Power is a protected symbol, you can't assign values to it like this. By using TagSet, you assign an UpValue to square, which overrides the default behavior of Power, since upvalues have a higher priority than DownValues:

In[91]:= UpValues[square]

Out[91]= {HoldPattern[square[length_]^2] :> length^2}

edit

As pointed out in the link in the comments, there are some subtleties about the evaluation order you should be aware off if you really want to use UpValues in your code. It's good to be aware of those.

$\endgroup$
2
  • 3
    $\begingroup$ +1. One comment though. "UpValues have a higher priority than DownValues" - when written in this way, this statement is somewhat misleading, since it suggests comparison for these ...Values for the same symbol, while it is only true when we talk about UpValues attached to inner expression's head vs. DownValues of the outer head. I have devoted a few paragrhaphs to explain the meaning of this statement in more detail in this answer (section "DownValues vs UpValues: Order of evaluation"), specifically to avoid this confustion. $\endgroup$ Nov 11, 2019 at 14:38
  • 1
    $\begingroup$ @LeonidShifrin Thanks for linking that answer. I was aware that there are some subtleties involved with that, but I thought that for an answer like this it was probably not worth it to bury the main story in details. Still, it's very good to have a resource for further reading. I'll include the link in the answer. $\endgroup$ Nov 11, 2019 at 17:00
7
$\begingroup$

As an addendum to Sjoerd's great answer, I would simply like to show a use case for defining a custom data type and then using TagSet to create what is called an abstract data type.

In many cases we would like to be quite sure, that our data have a certain form (e.g. do some type checking). Mathematica nicely supports this by constraining patterns to those with a certain Head (see Specifying Types of Expressions in Patterns):

func[ input_myDataType ] :=  ...

Here, func will only accept expressions that have the form myDataType[ e1, e2, ... ]. Having constructed any myDataType "object" in a reliable way (e.g. from basic inputs with very concise and strict pattern matching restrictions) greatly helps to prevent errors down the road.

While Associations are really great for building composite data types, it will usually not suffice to simply check an expression for the head being Association - instead, we have to look at least at some key(s) as well to be certain.

But, we may (with a maybe negligible disadvantage regarding speed) combine data type filtering using an expression's Head with the flexibility of associations:

data = Association @@ Table[ "Key" <> ToString @ i -> i , {i,1000} ];    
myData = myDataType[ data ];
myDataType/: (key_String)[ data_myDataType ] := Lookup[ data[[1]], key ]

Now, we can elegantly write:

"Key500" @ myData
(* 500 *)

Note, that this is really quite close to object-oriented programming:

class/: method[ instance_class, args___ ] := "What to do"

Instead of overloading some method to work with a new class, we can simply tie the method to the definition of the class as an UpValue.

$\endgroup$
1

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