Repository: sympy/sympy
File: sympy/combinatorics/free_groups.py (Python)

Code before:
    is_assoc_word = True

    def _array_form_from_letter_form(self, letter_form):
        if not letter_form:
            return ()
        array_form = []
        for letter in letter_form:
            if letter.is_Symbol:
                array_form.append((letter, 1))
            else:
                array_form.append((-letter, -1))
        return _reduce_array_form(array_form)

    def __new__(cls, init=()):
        return cls._new(init)

    @classmethod
    def _new(cls, init=(), *, reduced=False, boundary_index=None):
        if not init:
            return tuple.__new__(cls, ())
        if boundary_index is not None:
            init = _reduce_array_form(init, boundary_index=boundary_index)
            return tuple.__new__(cls, init)
        if reduced:
            return tuple.__new__(cls, tuple(init))
        return tuple.__new__(cls, _reduce_array_form(init))

    @classmethod
    def new(cls, init):
        if isinstance(init, FreeGroupElement):
            return cls._new(init, reduced=True)
        return cls._new(init)

    _hash = None

    def __hash__(self):
        _hash = self._hash
        if _hash is None:
            self._hash = _hash = hash((self.group, frozenset(tuple(self))))
        return _hash

    def copy(self):
        return self.new(self)

    @property
    def is_identity(self):
        return not self.array_form

    @property
    def array_form(self):
        """

Review comment (suggestion):
This is a misunderstanding of the purpose of having `__new__` and `_new`.

The `_new` method should be a dead-simple constructor. It takes the already fully processed argument of known type in a canonical form and simply creates the new element. In this case it would be
```python
   @classmethod
    def _new(cls, init):
            return tuple.__new__(cls, init)
```
There should be no optional arguments, no checking types etc. This is the method that actually just makes a new element. You want `_new` for the internal code in the class that always passes in prepared and valid arguments of the correct type.

The `__new__` method is the public constructor. It can offer multiple types of input and default arguments etc. It is a bit unclear what `__new__` would be needed for here but maybe that is why it did not already have `__new__`. If you want to pass different types to `_new` then make a different method like `_new_from_list` or something.

Code after:
    is_assoc_word = True

    def _array_form_from_letter_form(self, letter_form):
        if not letter_form:
            return ()
        array_form = []
        for letter in letter_form:
            if letter.is_Symbol:
                array_form.append((letter, 1))
            else:
                array_form.append((-letter, -1))
        return _reduce_array_form(array_form)

    def __new__(cls, init=()):
        if isinstance(init, FreeGroupElement):
            return cls._new(tuple(init))
        return cls._new(_reduce_array_form(init))

    @classmethod
    def _new(cls, array_form=()):
        return tuple.__new__(cls, array_form)

    @classmethod
    def _new_reduce_at_boundary(cls, array_form, boundary_index):
        array_form = _reduce_array_form(array_form,
                                        boundary_index=boundary_index)
        return cls._new(array_form)

    _hash = None

    def __hash__(self):
        _hash = self._hash
        if _hash is None:
            self._hash = _hash = hash((self.group, frozenset(tuple(self))))
        return _hash

    def copy(self):
        return self._new(tuple(self))

    @property
    def is_identity(self):
        return not self.array_form

    @property
    def array_form(self):
        """
        SymPy provides two different internal kinds of representation
        of associative words. The first one is called the `array_form`
        which is a tuple containing `tuples` as its elements, where the
        size of each tuple is two. At the first position the tuple
        contains the `symbol-generator`, while at the second position