@@ -329,57 +329,78 @@ def replace_slots(self, slots, evaluation) -> "Atom":
329329
330330
331331class Symbol (Atom , NumericOperators , EvalMixin ):
332- """
333- Note: Symbol is right now used in a couple of ways which in the
334- future may be separated.
332+ """A Symbol is a kind of Atom that acts as a symbolic variable.
335333
336- A Symbol is a kind of Atom that acts as a symbolic variable or
337- symbolic constant.
334+ All Symbols have a name that can be converted to string.
338335
339- All Symbols have a name that can be converted to string form.
336+ A Variable Symbol is a ``Symbol`` that is associated with a
337+ ``Definition`` that has an ``OwnValue`` that determines its
338+ evaluation value.
340339
341- Inside a session, a Symbol can be associated with a ``Definition``
342- that determines its evaluation value.
340+ A Function Symbol, like a Variable Symbol, is a ``Symbol`` that is
341+ also associated with a ``Definition``. But it has a ``DownValue``
342+ that is used in its evaluation.
343343
344- We also have Symbols which are immutable or constant; here the
345- definitions are fixed. The predefined Symbols `` True``, `` False``,
346- and ``Null`` are like this.
344+ We also have Symbols which in contrast to Variables Symbols have
345+ a constant value that cannot change. System` True and System` False
346+ are like this.
347347
348- Also there are situations where the Symbol acts like Python's
349- intern() built-in function or Lisp's Symbol without its modifyable
350- property list. Here, the only attribute we care about is the name
351- which is unique across all mentions and uses, and therefore
352- needs it only to be stored as a single object in the system.
348+ These however are in class SymbolConstant. See that class for
349+ more information.
353350
354- Note that the mathics.core.parser.Symbol works exactly this way.
351+ Symbol acts like Python's intern() built-in function or Lisp's
352+ Symbol without its modifyable property list. Here, the only
353+ attribute we care about is the value which is unique across all
354+ mentions and uses, and therefore needs it only to be stored as a
355+ single object in the system.
355356
356- This aspect may or may not be true for the Symbolic Variable use case too .
357+ Note that the mathics.core.parser.Symbol works exactly this way .
357358 """
358359
359360 name : str
360361 hash : str
361362 sympy_dummy : Any
362- defined_symbols = {}
363+
364+ # Dictionary of Symbols defined so far.
365+ # We use this for object uniqueness.
366+ # The key is the Symbol object's string name, and the
367+ # diectionary's value is the Mathics object for the Symbol.
368+ _symbols = {}
369+
363370 class_head_name = "System`Symbol"
364371
365372 # __new__ instead of __init__ is used here because we want
366373 # to return the same object for a given "name" value.
367- def __new__ (cls , name : str , sympy_dummy = None , value = None ):
374+ def __new__ (cls , name : str , sympy_dummy = None ):
368375 """
369- Allocate an object ensuring that for a given `name` we get back the same object.
376+ Allocate an object ensuring that for a given ``name`` and ``cls`` we get back the same object,
377+ id(object) is the same and its object.__hash__() is the same.
378+
379+ SymbolConstant's like System`True and System`False set
380+ ``value`` to something other than ``None``.
381+
370382 """
371383 name = ensure_context (name )
372- self = cls .defined_symbols .get (name , None )
384+
385+ # A lot of the below code is similar to
386+ # the corresponding for numeric constants like Integer, Real.
387+ self = cls ._symbols .get (name )
388+
373389 if self is None :
374- self = super (Symbol , cls ).__new__ (cls )
390+ self = super ().__new__ (cls )
375391 self .name = name
376392
393+ # Cache object so we don't allocate again.
394+ cls ._symbols [name ] = self
395+
377396 # Set a value for self.__hash__() once so that every time
378- # it is used this is fast.
379- # This tuple with "Symbol" is used to give a different hash
380- # than the hash that would be returned if just string name were
381- # used.
382- self .hash = hash (("Symbol" , name ))
397+ # it is used this is fast. Note that in contrast to the
398+ # cached object key, the hash key needs to be unique across *all*
399+ # Python objects, so we include the class in the
400+ # event that different objects have the same Python value.
401+ # For example, this can happen with String constants.
402+
403+ self .hash = hash ((cls , name ))
383404
384405 # TODO: revise how we convert sympy.Dummy
385406 # symbols.
@@ -392,25 +413,8 @@ def __new__(cls, name: str, sympy_dummy=None, value=None):
392413 # value attribute.
393414 self .sympy_dummy = sympy_dummy
394415
395- # This is something that still I do not undestand:
396- # here we are adding another attribute to this class,
397- # which is not clear where is it going to be used, but
398- # which can be different to None just three specific instances:
399- # * ``System`True`` -> True
400- # * ``System`False`` -> False
401- # * ``System`Null`` -> None
402- #
403- # My guess is that this property should be set for
404- # ``PredefinedSymbol`` but not for general symbols.
405- #
406- # Like it is now, it looks so misterious as
407- # self.sympy_dummy, for which I have to dig into the
408- # code to see even what type of value should be expected
409- # for it.
410- self ._value = value
411416 self ._short_name = strip_context (name )
412417
413- cls .defined_symbols [name ] = self
414418 return self
415419
416420 def __eq__ (self , other ) -> bool :
@@ -631,24 +635,59 @@ def to_sympy(self, **kwargs):
631635 return sympy .Symbol (sympy_symbol_prefix + self .name )
632636 return builtin .to_sympy (self , ** kwargs )
633637
634- @property
635- def value (self ) -> Any :
636- return self ._value
637-
638638
639- class PredefinedSymbol (Symbol ):
639+ class SymbolConstant (Symbol ):
640640 """
641- A Predefined Symbol of the Mathics system.
641+ A Symbol Constant is Symbol of the Mathics system whose value can't
642+ be changed and has a corresponding Python representation.
642643
643- A Symbol which is defined because it is used somewhere in the
644- Mathics system as a built-in name, Attribute, Property, Option,
645- or a Symbolic Constant.
644+ Therefore, like an ``Integer`` constant such as ``Integer0``, we don't
645+ need to go through ``Definitions`` to get its Python-equivalent value.
646646
647- In contrast to Symbol where the name might not have been added to
648- a list of known Symbol names or where the name might get deleted,
649- this never occurs here.
647+ For example for the ``SymbolConstant`` ``System`True``, has its
648+ value set to the Python ``True`` value.
649+
650+ Note this is not the same thing as a Symbolic Constant like ``Pi``,
651+ which doesn't have an (exact) Python equivalent representation.
652+ Also, Pi *can* be Unprotected and changed, while True, cannot.
653+
654+ Also note that ``SymbolConstant`` differs from ``Symbol`` in that
655+ Symbol has no value field (even when its value happens to be
656+ representable in Python. Symbols need to go through Definitions
657+ get a Symbol's current value, based on the current context and the
658+ state of prior operations on that Symbol/Definition binding.
659+
660+ In sum, SymbolConstant is partly like Symbol, and partly like
661+ Numeric constants.
650662 """
651663
664+ # Dictionary of SymbolConstants defined so far.
665+ # We use this for object uniqueness.
666+ # The key is the SymbolConstant's value, and the
667+ # diectionary's value is the Mathics object representing that Python value.
668+ _symbol_constants = {}
669+
670+ # We use __new__ here to unsure that two Integer's that have the same value
671+ # return the same object.
672+ def __new__ (cls , name , value ):
673+
674+ name = ensure_context (name )
675+ self = cls ._symbol_constants .get (name )
676+ if self is None :
677+ self = super ().__new__ (cls , name )
678+ self ._value = value
679+
680+ # Cache object so we don't allocate again.
681+ self ._symbol_constants [name ] = self
682+
683+ # Set a value for self.__hash__() once so that every time
684+ # it is used this is fast. Note that in contrast to the
685+ # cached object key, the hash key needs to be unique across all
686+ # Python objects, so we include the class in the
687+ # event that different objects have the same Python value
688+ self .hash = hash ((cls , name ))
689+ return self
690+
652691 @property
653692 def is_literal (self ) -> bool :
654693 """
@@ -676,6 +715,10 @@ def is_uncertain_final_definitions(self, definitions) -> bool:
676715 """
677716 return False
678717
718+ @property
719+ def value (self ):
720+ return self ._value
721+
679722
680723def symbol_set (* symbols : Tuple [Symbol ]) -> FrozenSet [Symbol ]:
681724 """
@@ -689,10 +732,10 @@ def symbol_set(*symbols: Tuple[Symbol]) -> FrozenSet[Symbol]:
689732
690733# Symbols used in this module.
691734
692- # Note, below we are only setting PredefinedSymbol for Symbols which
735+ # Note, below we are only setting SymbolConstant for Symbols which
693736# are both predefined and have the Locked attribute.
694737
695- # An experiment using PredefinedSymbol ("Pi") in the Python code and
738+ # An experiment using SymbolConstant ("Pi") in the Python code and
696739# running:
697740# {Pi, Unprotect[Pi];Pi=4; Pi, Pi=.; Pi }
698741# show that this does not change the output in any way.
@@ -702,9 +745,9 @@ def symbol_set(*symbols: Tuple[Symbol]) -> FrozenSet[Symbol]:
702745# more of the below and in systemsymbols
703746# PredefineSymbol.
704747
705- SymbolFalse = PredefinedSymbol ("System`False" , value = False )
706- SymbolList = PredefinedSymbol ("System`List" )
707- SymbolTrue = PredefinedSymbol ("System`True" , value = True )
748+ SymbolFalse = SymbolConstant ("System`False" , value = False )
749+ SymbolList = SymbolConstant ("System`List" , value = list )
750+ SymbolTrue = SymbolConstant ("System`True" , value = True )
708751
709752SymbolAbs = Symbol ("Abs" )
710753SymbolDivide = Symbol ("Divide" )
0 commit comments