18
$\begingroup$

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}
$\endgroup$
14
  • 3
    $\begingroup$ You mean something like this? resources.wolframcloud.com/FunctionRepository/resources/SymbolQ $\endgroup$ Commented Jul 12, 2020 at 20:24
  • 1
    $\begingroup$ Is it enough just to check that SymbolName works? Something like: symbolQ[x_]:=Quiet[Check[StringQ@SymbolName[x], False, General::sym], General::sym] $\endgroup$
    – flinty
    Commented Jul 12, 2020 at 20:39
  • 2
    $\begingroup$ @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. $\endgroup$ Commented Jul 12, 2020 at 20:50
  • 2
    $\begingroup$ @JulesManson - you can just do symbolQ = ResourceFunction["SymbolQ"]. My kernel init.m file is filled with definitions like that. $\endgroup$
    – Jason B.
    Commented Jul 12, 2020 at 21:02
  • 3
    $\begingroup$ @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. $\endgroup$ Commented Jul 13, 2020 at 8:27

4 Answers 4

16
$\begingroup$

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.

$\endgroup$
7
  • $\begingroup$ 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. $\endgroup$ Commented Jul 12, 2020 at 23:58
  • 1
    $\begingroup$ @JulesManson I see, I did not appreciate the role of altname. See my update $\endgroup$
    – Michael E2
    Commented Jul 13, 2020 at 3:12
  • 3
    $\begingroup$ +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]. $\endgroup$ Commented Jul 13, 2020 at 13:43
  • 1
    $\begingroup$ @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. $\endgroup$
    – Michael E2
    Commented Jul 13, 2020 at 14:27
  • 1
    $\begingroup$ @JulesManson If I understand what you’re asking, the order the rules are tried are determined by the specificity of the pattern. The more specific ones are tried first and the more general ones last. When two rules have the same specificity, then the order is determined by the order in which the rules are defined, the first one going before the second, etc. See mathematica.stackexchange.com/q/8619/4999 for discussion of how specificity is determined. DownValues[func] and ? func show the order of the definitions. $\endgroup$
    – Michael E2
    Commented May 14, 2021 at 0:48
12
$\begingroup$

How about:

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

?

$\endgroup$
3
  • 1
    $\begingroup$ That'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... $\endgroup$
    – Michael E2
    Commented Jul 12, 2020 at 22:50
  • $\begingroup$ @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[]? $\endgroup$ Commented Jul 12, 2020 at 23:00
  • 1
    $\begingroup$ @JulesManson I added a shortened paradigm to my answer. $\endgroup$
    – Michael E2
    Commented Jul 12, 2020 at 23:08
8
$\begingroup$

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 *)
$\endgroup$
5
  • 2
    $\begingroup$ Your 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. $\endgroup$
    – Hausdorff
    Commented Jul 12, 2020 at 22:41
  • 2
    $\begingroup$ There is more detail here $\endgroup$
    – Hausdorff
    Commented Jul 12, 2020 at 22:51
  • 1
    $\begingroup$ In 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. $\endgroup$ Commented Jul 13, 2020 at 13:45
  • 1
    $\begingroup$ As 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___] := ...). $\endgroup$ Commented Jul 13, 2020 at 14:30
  • 1
    $\begingroup$ And 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. $\endgroup$ Commented Jul 13, 2020 at 14:33
7
$\begingroup$

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.

$\endgroup$
3
  • 2
    $\begingroup$ +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. $\endgroup$ Commented Jul 28, 2020 at 15:06
  • $\begingroup$ @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. $\endgroup$ Commented Aug 4, 2020 at 17:48
  • 2
    $\begingroup$ @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. $\endgroup$ Commented Aug 4, 2020 at 18:14

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.