Take the 2-minute tour ×
Mathematica Stack Exchange is a question and answer site for users of Mathematica. It's 100% free, no registration required.

It is common in many packages, as well as system symbols, that the same message can be issued by several functions. Here's an example:

Mathematica graphics

One mechanism that can be used for this is to define the message only once, and attach it to the General symbol. This is how the infy messages works as well. infy can appear to come from any symbol:

Mathematica graphics

When writing a custom package, it might not be the best idea to do this though because of the risk of message conflicts. Two packages might attach the same message identifier to General, so the one that's loaded last will overwrite the previous one. In fact, looking at the some standard packages (e.g. CUDALink), they do not attach messages to General even when the same message can be issued by several functions. Looking at the source code of the package, CUDALink uses a helper function to attach the same message text to several symbols in one go.

The question: What is the best way to proceed when several functions need to be able to issue the same message, and indicate that it is coming from them? What do you use in your own packages?

What's important for me is minimizing the risk of conflict with other packages and the simplicity of implementation.

share|improve this question
    
Once I saw it done by cascading $NewMessage functions, but I can't remember where. If I remember well, it wasn't too difficult. –  belisarius Feb 27 '13 at 17:30

2 Answers 2

up vote 6 down vote accepted

How about doing something as simple as

sym1::msg = "I am feeling grumpy.";
sym2::msg = sym3::msg = sym1::msg; 

Then

Do[With[{ff = f}, 
  Message[MessageName[ff, "msg"]]], {f, {sym1, sym2, sym3}}]

Outputs

sym1::msg: I am feeling grumpy.

sym2::msg: I am feeling grumpy.

sym3::msg: I am feeling grumpy.
share|improve this answer

Messages defined for a symbol are stored in list of rules Messages[sym] similar to DownValues, UpValues etc. We can use this fact to create general message inheritance mechanism.

Let's start with defining custom message on our own general symbol:

ClearAll[general]
general::myMessage = "text of general myMessage"

As expected this message is stored in a list as a delayed rule:

Messages[general]
(* {HoldPattern[general::myMessage] :> "text of general myMessage"} *)

Now let's try to define a symbol which will inherit messages from our general symbol. First attempt would be:

ClearAll[testSym]
MessageName[testSym, name_] := MessageName[testSym, name]
(* During evaluation of In[4]:= Message::name: Message name testSym::name_ is not of the form symbol::name or symbol::name::language. >>
$Failed *)

It fails since MessageName is protected from passing non-string as second argument. Also attempt of assigning anything else than explicit string fails:

testSym::myMessage := general::myMessage
(* During evaluation of In[6]:= MessageName::messg: testSym::myMessage cannot be set to general::myMessage. It must be set to a string. >>
$Failed *)

But nothing prevents us from adding hand crafted rule to Messages list:

AppendTo[Messages[testSym],
    HoldPattern[MessageName[testSym, name_]] :> MessageName[general, name]
]
(* {HoldPattern[testSym::name_] :> general::name} *)

There were no Message::name warnings since none of MessageName expressions was evaluated. The one on left hand side is wrapped in HoldPattern and the one on the right is protected from evaluation by RuleDelayed. They will be evaluated only when called:

MessageName[testSym, "myMessage"]
(* "text of general myMessage" *)

Message[testSym::myMessage]
(* During evaluation of In[9]:= testSym::myMessage: text of general myMessage *)

Problem arises when we want to call message from "real" General symbol on our inheriting symbol:

Message[testSym::argctu, testSym]
(* A lot of StringSplit::strse, StringJoin::string, StringForm::string errors *)

It fails since, where Message was expecting a string, it go MessageName[general, argctu], which does not evaluate to a string. We can easily prevent such situations by adding a Condition to our "inheritance" rule, such that rule matches only when MessageName[general, name] evaluates to a string.

ClearAll[testSym]
AppendTo[Messages[testSym],
    HoldPattern[MessageName[testSym, name_]] :>
        MessageName[general, name] /; StringQ[MessageName[general, name]]
]
(* {HoldPattern[testSym::name_] :> general::name /; StringQ[general::name]} *)

Now everything works as expected. If message is not defined on testSym, but is defined on general, message from general is used:

Message[testSym::myMessage]
(* During evaluation of In[13]:= testSym::myMessage: text of general myMessage *)

If message is not defined neither on testSym nor on general, ordinary delegation to "real" General is used:

Message[testSym::argctu, testSym]
(* During evaluation of In[14]:= testSym::argctu: testSym called with 1 argument. >> *)

If we explicitly define a message on testSym, normal pattern specificity applies and message from testSym is used:

testSym::myMessage = "text of testSym myMessage";
Message[testSym::myMessage]
(* During evaluation of In[15]:= testSym::myMessage: text of testSym myMessage *)

Usage in a package

BeginPackage["ToyPackage`"]

ToyFunc1::usage = ""
ToyFunc2::usage = ""
$ToyVariable::usage = ""
$ToyVariableWithMessages::usage = ""

Begin["`Private`"]
general::myMessage = "general myMessage"
general::myMessageWithArg = "general myMessageWithArg: `1`"
ToyFunc1::myMessage = "ToyFunc1 myMessage"
ToyFunc2::myMessageWithArg = "ToyFunc2 myMessageWithArg: `1`"
$ToyVariable = "$ToyVariable value"
$ToyVariableWithMessages = "$ToyVariableWithMessages value"

Function[
    sym
    ,
    Quiet[MessageName[sym, name_] =., Unset::norep];

    AppendTo[Messages[sym],
        HoldPattern[MessageName[sym, name_]] :>
            MessageName[general, name] /;
                StringQ[MessageName[general, name]]
    ]
    ,
    HoldFirst
] @@@
    (ToExpression[#, InputForm, Hold]&) /@
        DeleteCases[Names["ToyPackage`*"], "$ToyVariable"]

End[]
EndPackage[]

ToyFunc1 inherits from general and overrides myMessage:

Message[ToyFunc1::myMessage]
(* During evaluation of In[1]:= ToyFunc1::myMessage: ToyFunc1 myMessage *)
Message[ToyFunc1::myMessageWithArg, "arg"]
(* During evaluation of In[2]:= ToyFunc1::myMessageWithArg: general myMessageWithArg: arg *)
Message[ToyFunc1::argctu, ToyFunc1]
(* During evaluation of In[3]:= ToyFunc1::argctu: ToyFunc1 called with 1 argument. >> *)

ToyFunc2 inherits from general and overrides myMessageWithArg:

Message[ToyFunc2::myMessage]
(* During evaluation of In[4]:= ToyFunc2::myMessage: general myMessage *)
Message[ToyFunc2::myMessageWithArg, "arg"]
(* During evaluation of In[5]:= ToyFunc2::myMessageWithArg: ToyFunc2 myMessageWithArg: arg *)
Message[ToyFunc2::argctu, ToyFunc1]
(* During evaluation of In[6]:= ToyFunc2::argctu: ToyFunc1 called with 1 argument. >> *)

$ToyVariable was explicitly excluded from inheritance:

Message[$ToyVariable::myMessage]
(* During evaluation of In[7]:= $ToyVariable::myMessage: -- Message text not found -- *)
Message[$ToyVariable::myMessageWithArg, "arg"]
(* During evaluation of In[8]:= $ToyVariable::myMessageWithArg: -- Message text not found -- (arg) *)
Message[$ToyVariable::argctu, $ToyVariable]
(* During evaluation of In[9]:= $ToyVariable::argctu: $ToyVariable value called with 1 argument. >> *)

$ToyVariableWithMessages inherits from general and doesn't override anything:

Message[$ToyVariableWithMessages::myMessage]
(* During evaluation of In[10]:= $ToyVariableWithMessages::myMessage: general myMessage *)
Message[$ToyVariableWithMessages::myMessageWithArg, "arg"]
(* During evaluation of In[11]:= $ToyVariableWithMessages::myMessageWithArg: general myMessageWithArg: arg *)
Message[$ToyVariableWithMessages::argctu, $ToyVariableWithMessages]
(* During evaluation of In[12]:= $ToyVariableWithMessages::argctu: $ToyVariableWithMessages value called with 1 argument. >> *)
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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