13
$\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$
  • 3
    $\begingroup$ You mean something like this? resources.wolframcloud.com/FunctionRepository/resources/SymbolQ $\endgroup$ – Sjoerd Smit Jul 12 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 Jul 12 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$ – Sjoerd Smit Jul 12 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. Jul 12 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$ – Leonid Shifrin Jul 13 at 8:27
11
$\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.

| improve this answer | |
$\endgroup$
  • $\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$ – Jules Manson Jul 12 at 23:58
  • 1
    $\begingroup$ @JulesManson I see, I did not appreciate the role of altname. See my update $\endgroup$ – Michael E2 Jul 13 at 3:12
  • $\begingroup$ 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? $\endgroup$ – Jules Manson Jul 13 at 4:40
  • 2
    $\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$ – Leonid Shifrin Jul 13 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 Jul 13 at 14:27
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 *)
| improve this answer | |
$\endgroup$
  • 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 Jul 12 at 22:41
  • 2
    $\begingroup$ There is more detail here $\endgroup$ – Hausdorff Jul 12 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$ – Leonid Shifrin Jul 13 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$ – Sjoerd Smit Jul 13 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$ – Sjoerd Smit Jul 13 at 14:33
8
$\begingroup$

How about:

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

?

| improve this answer | |
$\endgroup$
  • 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 Jul 12 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$ – Jules Manson Jul 12 at 23:00
  • 1
    $\begingroup$ @JulesManson I added a shortened paradigm to my answer. $\endgroup$ – Michael E2 Jul 12 at 23:08
5
$\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.

| improve this answer | |
$\endgroup$
  • 1
    $\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$ – Leonid Shifrin Jul 28 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$ – Jules Manson Aug 4 at 17:48
  • 1
    $\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$ – Sjoerd Smit Aug 4 at 18:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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