5
$\begingroup$

The default behavior of the character . between two non-numeric characters is to be interpreted as the infix of the Mathematica function Dot.

Is there a command that can be placed in a package to get the front-end to parse . as an infix for another function -- say user-defined one, myDot[]?

That is,

  1. a.b should be interpreted as myDot[a, b]

  2. myDot[a.b] should map to a.b

  3. Dot[a,b] should not get mapped to a.b, but the functionality of the built-in function Dot should not be lost.

  4. 3.14 should remain a single number, where here the dot indicates a decimal separator.

$\endgroup$
2

3 Answers 3

7
$\begingroup$

Updated

First, let's stop Dot from creating these box structures.

MakeBoxes[Dot[x__], form_] := 
 RowBox[{"Dot", "[", RowBox@Riffle[MakeBoxes /@ {x}, ","], "]"}]

Next, let's specify that these structures should instead be interpreted as myDot:

MakeExpression[RowBox@(row : {PatternSequence[_, "."] .., _}), form_] := 
 MakeExpression[
  RowBox@{"myDot", "[", RowBox@Riffle[row[[1 ;; -1 ;; 2]], ","], "]"}, form]

The slicing and riffling is ugly... there is probably a cleaner way to do it. ReplacePart seemed to be doing weird things when I tried it but I didn't spend any time on it.

The last step is to specify that myDot should also be rendered into the same structure:

MakeBoxes[myDot[x__], form_] := RowBox@Riffle[MakeBoxes /@ {x}, "."]

Now,

Dot[a, b, c]

Dot[a, b, c]

Dot[{a, b, c}, {x, y, z}]

a x + b y + c z

a.b.c

a.b.c

a.b.c // FullForm

myDot[a, b, c]

{a, b, c}.{x, y, z} // InputForm

myDot[{a, b, c}, {x, y, z}]

a.(b^2).c

a.b2.c

% // InputForm

myDot[a, b^2, c]

In all cases you will have to use MakeExpression to override the interpretation of user input, but you may also find TagBox and InterpretationBox interesting alternatives to the MakeBoxes implementations above.

$\endgroup$
7
  • $\begingroup$ We posted at exactly the same time. I mean to the second. Look at the time-stamp. :O $\endgroup$
    – Mr.Wizard
    Oct 14, 2014 at 22:19
  • $\begingroup$ I think one may want to test the elements of RowBox to make sure they are Symbols, assuming that is what QuantumDot wants. This is the purpose of symbolQ in my answer. For example should 99 .bar (note the space) be read as Dot[99, bar] or myDot[99, bar]? $\endgroup$
    – Mr.Wizard
    Oct 14, 2014 at 22:22
  • $\begingroup$ @Mr.Wizard All the cool kids post at the same time. Good point about testing for symbols. I didn't think about that very hard -- it could get messy otherwise. $\endgroup$
    – mfvonh
    Oct 14, 2014 at 22:24
  • $\begingroup$ I just noticed your edit; that won't work. The pattern a_Symbol will not match as the Symbols are still in String form, e.g. "foo" rather than foo, hence the somewhat baroque symbolQ in my answer. I suggest you put it back to the way it was; perhaps the original is even the behavior that QuantumDot wants. $\endgroup$
    – Mr.Wizard
    Oct 14, 2014 at 22:46
  • $\begingroup$ Sigh, I always make that mistake. Thanks for the catch. $\endgroup$
    – mfvonh
    Oct 14, 2014 at 22:54
6
$\begingroup$

I am guessing that you intend for a transformation into myDot[a, b] rather than myDot[a.b] and I will answer accordingly. Different output formatting would remain possible.

There is not much that you can do to affect the parsing of code as that is handled by the Front End before even CellEvaluationFunction is called. To see how an expression is parsed you may use the method given by John Fultz in answer to my question:

For example:

parseString[s_String, prep : (True | False) : True] := 
  FrontEndExecute @ UndocumentedTestFEParserPacket[s, prep]

"3.14 + foo.bar / 22 - Dot[x,y]" // parseString
{BoxData[RowBox[{"3.14", "+", RowBox[{RowBox[{"foo", ".", "bar"}], "/", "22"}], "-", 
    RowBox[{"Dot", "[", RowBox[{"x", ",", "y"}], "]"}]}]], StandardForm}

We can see that foo.bar is already parsed differently from 3.14 and Dot[x,y] therefore, fortunately, we do not need to change parsing to effect your modification. Instead we can operate on this Box data.

Here is a basic implementation using $PreRead:

symbolQ[s_String] := MatchQ[ToHeldExpression@s, Hold[_Symbol]]

$PreRead = # /. 
    RowBox[{a_?symbolQ, ".", b_?symbolQ}] :> 
     RowBox[{"myDot", "[", RowBox[{a, ",", b}], "]"}] &;

Now:

3.14 + foo.bar / 22 - Dot[x, y]
3.14 - x.y + 1/22 myDot[foo, bar]

If you want to add output formatting for myDot please read:

Then ask for help as needed.

$\endgroup$
4
  • $\begingroup$ Very helpful! This is in the direction of precisely what I need. There is a strange effect that I need help understanding. Suppose (in v 9.0.1) I am now manipulating the expression in the following way: 3.14 + foo.bar/22 - Dot[x, y] /. myDot[x_, y_] :> x@y, I get the expected result. But look what happens when I surround the rule with braces: 3.14 + foo.bar/22 - Dot[x, y] /. {myDot[x_, y_] :> x@y}. I get a ToExpression::sntxi: error message, although the replacement is still successful. Do you know how to understand the origin of that error? $\endgroup$
    – QuantumDot
    Oct 15, 2014 at 14:31
  • $\begingroup$ @QuantumDot That appears to be the result of a poorly conceived symbolQ. (i.e. my mistake.) Let me start by asking if it symbolQ is even necessary: is it okay for non-Symbol terms in term1.term2 to be interpreted as myDot[term1, term2]? Number forms such as 3.14 will always be interpreted as numbers regardless. $\endgroup$
    – Mr.Wizard
    Oct 15, 2014 at 19:06
  • $\begingroup$ Hmm... now that I think about it, the answer is actually no; it is not necessary, and probably wiser to always map . to myDot. The original reason for requiring that the arguments be symbolic is that if the objects on both sides of . had head List, then it should proceed to operate as built-in Dot on the two lists. But I realize this can be confusing to the users, and users would anyway fall back on explicit use of Dot to sum the products of list elements. $\endgroup$
    – QuantumDot
    Oct 15, 2014 at 20:10
  • $\begingroup$ Next, I tried to format the output of myDot. Following the link at the end of your answer, I came up with MakeBoxes[myDot[arg1__, arg2__], _] ^:= InterpretationBox[RowBox[{arg1, ".", arg2}], myDot[arg1, arg2]]. The problem with this is that (1) myDot[a,b] leads to Global'a.Global'b. How do I get rid of the Global context? and (2) myDot[a,b^2] leads to error message "An unknown box name (Power) was sent as the BoxForm for the expression. Check the format rules for the expression." Do you know how I can allow more complicated structures inside RowBox? $\endgroup$
    – QuantumDot
    Oct 15, 2014 at 20:22
4
$\begingroup$

I guess this is too easy, but \[CenterDot], or Esc.Esc, is all set up for use as an infix operator, with no need to mess with box structures or confuse the user by changing the built-in meaning of ..

You can just set

CenterDot = myDot

and then call

a·b
(* myDot[a, b] *)
$\endgroup$
4
  • $\begingroup$ Nice alternative, +1, But esc-dot-esc is two too many key-strokes for me $\endgroup$
    – QuantumDot
    Oct 18, 2014 at 18:04
  • $\begingroup$ Fair enough. The other drawback is that unicode characters aren't friendly if you want to edit the code in a .m file. $\endgroup$ Oct 19, 2014 at 12:17
  • $\begingroup$ There's one other problem: the CenterDot character has too much space on either side. If the font designer had kept the spacing the same as that of the ordinary Dot, I think I would have opted for your alternative. $\endgroup$
    – QuantumDot
    Oct 19, 2014 at 13:46
  • 1
    $\begingroup$ @QuantumDot You could do: CurrentValue[EvaluationNotebook[], {InputAliases, "."}] = RowBox[{"\[InvisibleSpace]", "\[CenterDot]", "\[InvisibleSpace]"}] to shrink the space. $\endgroup$
    – Carl Woll
    Mar 19, 2019 at 3:25

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.