When is a symbol a Symbol? Is there an easy Mathematica way to test if an object is a symbol sort of like a SymbolQ?

14

1

Yes I know there is no built-in native function called SymbolQ (but JavaScript does). However, could one be simulated to work for most cases? I often rely on objectName[symbol] and makeRuleRow[symbol] to return the name of a defined variable and its value in a ready-to-use row for structured Grid layouts of results to computations. However, sometimes an error is returned if a variable is not a Symbol which leaves me asking, "When is a symbol a Symbol?"

I would like to catch such errors and return as much useful information as possible. That is why I ask if there is an easy hack for determining if a variable is a symbol.

Here is some working code where I might use such a function...

SetAttributes[symbolQ, HoldAllComplete];
symbolQ[x_] := ResourceFunction["SymbolQ"][x];

SetAttributes[{objectName}, HoldFirst];
objectName = Function[Null, SymbolName[Unevaluated[#]], {HoldFirst}];
objectName::usage = 
  "objectName@# returns Unevaluated shortened SymbolName.";

SetAttributes[{makeRuleRow}, HoldFirst];
makeRuleRow[symbol_, altname_ : Null, desc_ : Null] := 
 Module[{name = "", prepend = ""},
  If[ResourceFunction["SymbolQ"][symbol] === False && 
    altname === Null, 
   Return[Row[{Style["Argument ", Red], symbol, 
      Style[" is not a symbol. Use altname in makeRuleRow.", Red]}]
    ], False
   ];
  name = If[StringQ[altname], altname, objectName[symbol]];
  prepend = If[StringQ[desc], desc <> " ", ""];
  {Row[{Style[prepend, Brown], name, rule}], 
   TraditionalForm[symbol]}
  ]

The following is how it would be used for most cases (including an error) expected to be encountered when setting up name-value pairs for Grid row elements...

xxx = 123;

makeRuleRow[xxx] (* this outputs name and value *)
makeRuleRow[xxx, "alternate name"] (* this creates alternate name *)
makeRuleRow[xxx, "alternate name", "this is a symbol"] (* this prepends a description and creates alternate name *)
makeRuleRow[69] (* this generates an error message suggesting a fix *)
makeRuleRow[69, "XXX"] (* bypasses error by creating alternate name *)
makeRuleRow[69, "XXX", "not a symbol"] (* bypasses error by creating alternate name and prepend a description *)

The actual output when done correctly conveniently makes {name ->, value} rows ready to be inserted into two-column Grid layouts...

{xxx -> ,123}
{alternate name -> ,123}
{this is a symbol alternate name -> ,123}
Argument 69 is not a symbol. Use altname in makeRuleRow.
{XXX -> ,69}
{not a symbol XXX -> ,69}

Jules Manson

Posted 2020-07-12T20:21:26.220

Reputation: 1 075

3

You mean something like this? https://resources.wolframcloud.com/FunctionRepository/resources/SymbolQ

– Sjoerd Smit – 2020-07-12T20:24:32.097

@SjoerdSmit Yes that is exactly what I wanted. But I don't know how to access it. Am I supposed to download the definition to my pc and install it to my mathematica as an addon? Or copy the function SymbolQ and paste it to my open notebook? As you can tell I am sort of new to Mathematica. – Jules Manson – 2020-07-12T20:35:16.773

1Is it enough just to check that SymbolName works? Something like: symbolQ[x_]:=Quiet[Check[StringQ@SymbolName[x], False, General::sym], General::sym] – flinty – 2020-07-12T20:39:17.513

2@JulesManson You can just click on the code blocks on that page to copy code you can paste into your notebook. For example, clicking on the first block copies ResourceFunction["SymbolQ"][x] to the clipboard. The function will download automatically. You can also download the source notebook if you want to see how it works internally. – Sjoerd Smit – 2020-07-12T20:50:46.273

@SjoerdSmit yeah i figured it out. it's very straight forward. i noticed that to use the function for some x=123 you have to call it like this ResourceFunction["SymbolQ"][x] so i tried to make a short alias for it but it wouldn't work symbolQ = ResourceFunction["SymbolQ"][#] &;. do you have ideas of how i could do that? – Jules Manson – 2020-07-12T20:56:44.067

2@JulesManson - you can just do symbolQ = ResourceFunction["SymbolQ"]. My kernel init.m file is filled with definitions like that. – Jason B. – 2020-07-12T21:02:36.517

@JasonB. Actually, I just found out that that doesn't work for resource functions. It seems like the attributes don't propagate correctly for some reason. You need SetAttributes[symbolQ, HoldAllComplete]; symbolQ[x_] := ResourceFunction["SymbolQ"][x] to make it work. I think I'll report a bug about this. – Sjoerd Smit – 2020-07-12T21:07:41.410

Can you not use a pattern in makeRuleRow like foo[s_Symbol,..]:=...? – Michael E2 – 2020-07-12T21:53:01.547

@MichaelE2 I don't think you can because there is no Head called symbol. – Jules Manson – 2020-07-12T22:14:48.470

@SjoerdSmit What makes you think that this is fixable at all? The only way to make SubValues - based definitions hold their second group of arguments is to use the Stack, more or less along the lines I described here - and if you look at DownValues of ResourceFunction, you will see that they use exactly that (which is an ugly hack that actually also adds significant overhead to the function call, potentially changing its complexity).This is why your method worked, but I just don't see an easy way to make it work as initially requested.

– Leonid Shifrin – 2020-07-12T22:14:59.610

@Jules Try Head@Plot and MatchQ[Plot, _Symbol] for instance. – Michael E2 – 2020-07-12T22:52:33.603

@LeonidShifrin I appreciate that this is not easy to make work, but from a user perspective I expect the head symbolQ to evaluate to the resource function and then evaluate the same as ResourceFunction["SymbolQ"][x]. That's how it should behave and it doesn't. If it's fixable or not, I will leave up to whoever's in charge of that. – Sjoerd Smit – 2020-07-13T07:23:43.467

3@SjoerdSmit Sure thing, I don't dispute that your plan for filing a bug is the right thing to do. I was trying to hint that the design itself is problematic. Actually, this problem shows just one facet of it, it doesn't stop here. – Leonid Shifrin – 2020-07-13T08:27:37.940

@LeonidShifrin I just discovered you can do symbolQ = ResourceFunction["SymbolQ", "Function"] to avoid this problem. See the answer I posted. – Sjoerd Smit – 2020-07-28T08:29:47.293

Answers

12

I'd probably use x_Symbol in a function argument to control evaluation. Otherwise, one might do the following (thanks to @Leonid for pointing out an oversight).

If the argument is to be evaluated before testing:

SymbolQ = MatchQ[#, t_Symbol /; AtomQ[t]] &

If the argument is not to be evaluated:

SymbolQ = Function[s,
  MatchQ[Unevaluated@s, t_Symbol /; AtomQ[Unevaluated@t]], 
  HoldAllComplete];

Examples with the second definition:

SymbolQ@Plot
(*  True  *)
x = 1;
SymbolQ[x]
(*  True  *)
Clear[y];
SymbolQ@y[1]
(*  False  *)

Addendum

Here's what I had in mind for makeRuleRow:

ClearAll[makeRuleRow];
SetAttributes[makeRuleRow, HoldFirst];
makeRuleRow[symbol_Symbol, altname_ : "", desc_ : ""] := 
  "execute body of function";
makeRuleRow[symbol_, altname_ : "", desc_ : ""] := 
  Null /; (Message[makeRuleRow::sym, symbol, 1]; False);

makeRuleRow[123]

makeRuleRow::sym: Argument 123 at position 1 is expected to be a symbol.

(*  makeRuleRow[123]  *)
makeRuleRow[y]
(* "execute body of function"  *)

Addendum 2

1. You could use Replace or Switch to define name in either way below:

ClearAll[makeRuleRow];
SetAttributes[{makeRuleRow}, HoldFirst];
makeRuleRow[symbol_, altname_ : Null, desc_ : Null] := 
  Module[{name = "", prepend = ""},
   name = Replace[Unevaluated@symbol, {
      s_Symbol :> objectName[symbol]
      , s_ /; StringQ@altname :> altname
      , _ -> $Failed}
     ];
   prepend = If[StringQ[desc], desc <> " ", ""];
   {Row[{Style[prepend, desccolor], name, rule}], 
     TraditionalForm[symbol]} /; FreeQ[name, $Failed]];
makeRuleRow[symbol_, altname_ : Null, desc_ : Null] := Null /; (
    Message[makeRuleRow::args, makeRuleRow]; False);

2. Or:

ClearAll[makeRuleRow];
SetAttributes[{makeRuleRow}, HoldFirst];
makeRuleRow[symbol_, altname_ : Null, desc_ : Null] := 
  Module[{name = "", prepend = ""},
   Switch[Unevaluated@symbol
    , s_Symbol, name = objectName[symbol]
    , s_ /; StringQ@altname, name = altname
    , _, name = $Failed
    ];
   prepend = If[StringQ[desc], desc <> " ", ""];
   {Row[{Style[prepend, desccolor], name, rule}], 
     TraditionalForm[symbol]} /; FreeQ[name, $Failed]];
makeRuleRow[symbol_, altname_ : Null, desc_ : Null] := Null /; (
    Message[makeRuleRow::args, makeRuleRow]; False);

Some may prefer Switch because they know it from another language or just find it easier to read. Too many commas for me, and I find the Replace method easier.

3. There are a few ways to handle complicated argument checking. Another is to call an "internal" version which throws $Failed when there's is an error:

func[symbol_, altname_ : Null, desc_ : Null] := Module[{res},
   res = Catch[iFunc[symbol, altname, desc], func];
   res /; FreeQ[res, $Failed]
   ];
iFunc[symbol_, altname_, desc_] := Module[{ ...},
   If[error1,
    Message[func::err1, ...];
    Throw[$Failed, func]
    ];
   If[error2,
    Message[func::err2, ...];
    Throw[$Failed, func]
    ];
   ...
   res (* return result *)
   ];

4. Yet another way is to have the outer function process the arguments and call the internal function with canonicalized arguments (for example, iMakeRuleRow[name_, desc_]) or indicate an error. The internal function then can assume the arguments are valid.

Michael E2

Posted 2020-07-12T20:21:26.220

Reputation: 190 928

first off thank you for taking the time to answer this post. :) The first definition with symbol_Symbol works perfect in filtering out non-symbols. How could I modify it so that makeRuleRow still executes if symbol_Symbol is not a Symbol but altname_ is given as a string (not Null). BTW I modified my function a little. The updated version is shown at the top. The argument altname_ provides an alternative variable name to display if the first argument is not a Symbol. So to encapsulate I only want it to fail if both symbol_Symbol and altname_ do not pass. – Jules Manson – 2020-07-12T23:58:23.163

1@JulesManson I see, I did not appreciate the role of altname. See my update – Michael E2 – 2020-07-13T03:12:50.720

i want you to know that i really appreciate your help. I noticed a lot of advanced features (at least to me) in both solutions which I know I will love learning. I think I will go with your ReplaceAll version. I also hate all those commas from switch statements which I believe come from procedural languages. Mathematica is primarily a functional language and that is how I like it. Just one last question. Where do I put the argument checking functions func and ifunc? Should it go in my notebook where I am using the makeRuleRow? – Jules Manson – 2020-07-13T04:40:32.067

2+1. One tiny nitpick is that as written, your function will give True also for e.g. SymbolQ[Symbol[123]]. So I'd throw in e.g. AtomQ test after the pre-screening _Symbol pattern: SymbolQ = Function[s, MatchQ[Unevaluated@s, t_Symbol /; AtomQ[Unevaluated[t]]], HoldAllComplete]. – Leonid Shifrin – 2020-07-13T13:43:20.207

1@JulesManson I meant func to stand for makeRuleRow (and iFunc for iMakeRuleRow). I meant func[]/iFunc[] to illustrate a general strategy, and the internal details I presented won't necessarily match exactly the details of makeRuleRow[]. The last point I thought would be clearer if I called the function func by a name different from makeRuleRow. So if you're talking about the approach I've just numbered 3, I would put the argument checking in iMakeFunction (represented by the If[error1,...]... statements. – Michael E2 – 2020-07-13T14:27:47.783

8

I found my answer thanks to Sjoerd Smit who referenced me to the Mathematica Function Repository. And yes it is appropriately called SymbolQ which is used like the following...

xxx = 123
ResourceFunction["SymbolQ"][xxx] (* returns True *)

A little bit ugly and long but it works. But why not fix if it isn't broken? And that is what I tried to do...

SetAttributes[symbolQ, HoldAllComplete];
symbolQ = ResourceFunction["SymbolQ"][#] &;
symbolQ[xxx] (* returns False *)

However Sjord came up with a solution that looks eerily similar to mine which leaves me scratching my head, why doesn't my alias work?...

SetAttributes[symbolQ, HoldAllComplete];
symbolQ[x_] := ResourceFunction["SymbolQ"][x];
symbolQ[xxx] (* returns True *)

Jules Manson

Posted 2020-07-12T20:21:26.220

Reputation: 1 075

2Your function does not work correctly, because Mathematica is first replacing symbolQ with the ownvalue ResourceFunction["SymbolQ"][#] &, and then applying it to xxx, so the HoldAllComplete attribute never comes into play. You can see that when you run Trace@symbolQ[xxx] with your function. – Hausdorff – 2020-07-12T22:41:56.067

2

There is more detail here

– Hausdorff – 2020-07-12T22:51:19.927

1In the comment to your question I have hinted why your alias didn't work. It is due both to a specific approach being used in the design / implementation of ResourceFunction, and to the general fact that it is hard and non-trivial to maintain attributes for SubValues - based definitions. – Leonid Shifrin – 2020-07-13T13:45:37.070

1As a general rule: attributes don't affect OwnValues (like symbolQ = ...) because the transformation will happen before the attributes can take effect. That's why I used a definition based on DownValues (symbolQ[arg___] := ...). – Sjoerd Smit – 2020-07-13T14:30:54.257

1And to add: you could also use symbolQ = Function[x, ResourceFunction["SymbolQ"][x], HoldAllComplete];. In that case, you attach the attribute to the anonymous function instead. – Sjoerd Smit – 2020-07-13T14:33:34.533

8

How about:

SymbolQ[_Symbol] = True
SymbolQ[_] = False

?

John Doty

Posted 2020-07-12T20:21:26.220

Reputation: 11 607

1That's what I had in mind, tho' with the attribute HoldFirst. Actually, I thought the pattern could go directly in the def. of makeRuleRow and skip SymbolQ altogether... – Michael E2 – 2020-07-12T22:50:58.317

@MichaelE2 please bare with me as I am still new to mathematica. could you elaborate a little more on how i could use that for makeRuleRow[]? – Jules Manson – 2020-07-12T23:00:25.747

1@JulesManson I added a shortened paradigm to my answer. – Michael E2 – 2020-07-12T23:08:14.660

5

I want to add something to the discussion about the ResourceFunction SymbolQ. The OP observed that doing something like:

x = 1;
symbolQ = ResourceFunction["SymbolQ"];
ResourceFunction["SymbolQ"][x]
symbolQ[x]
(* True *)
(* False *)

does not work because the attributes of the resource function are not applied correctly. However, I just discovered that you can do the following instead:

x = 1;
symbolQ = ResourceFunction["SymbolQ", "Function"];
ResourceFunction["SymbolQ"][x]
symbolQ[x]
(* True *)
(* True *)

It seems like ResourceFunction["SymbolQ", "Function"] will give you direct access to the function without having to go through the ResourceFunction wrapper. This is also nice because it avoids some evaluation overhead from ResourceFunction.

Sjoerd Smit

Posted 2020-07-12T20:21:26.220

Reputation: 15 418

2+1. I am not a big fan of the ResourceFunction design in general, but within what it is, this is definitely a useful thing to know. – Leonid Shifrin – 2020-07-28T15:06:52.307

@SjoerdSmit

Thanks for the update. I also have a question about resource functions. Are they ever promoted to Mathematica native code in updates? If some have been promoted in the past (due to for example popularity or usefulness) I can see SymbolQ getting a bump up. – Jules Manson – 2020-08-04T17:48:33.300

1@JulesManson It's difficult to say; it's possible they might make it into the language. That said, SymbolQ is already a built-in function. It's just a wrapper for Developer`HoldSymbolQ. – Sjoerd Smit – 2020-08-04T18:14:48.873