@@ -372,6 +372,126 @@ Well, nothing is **really** immutable in python, but you were warned.
372372We also provide :class: `returns.primitives.types.Immutable ` mixin
373373that users can use to quickly make their classes immutable.
374374
375+ Creating Modified Copies of Containers
376+ --------------------------------------
377+
378+ While containers are immutable, sometimes you need to create a modified copy
379+ of a container with different inner values. Since Python 3.13, ``returns ``
380+ containers support the ``copy.replace() `` function via the ``__replace__ ``
381+ magic method.
382+
383+ .. code :: python
384+
385+ >> > from returns.result import Success, Failure
386+ >> > import copy, sys
387+ >> >
388+ >> > # Only run this example on Python 3.13+
389+ >> > if sys.version_info >= (3 , 13 ):
390+ ... # Replace the inner value of a Success container
391+ ... original = Success(1 )
392+ ... modified = copy.replace(original, _inner_value = 2 )
393+ ... assert modified == Success(2 )
394+ ... assert original is not modified # Creates a new instance
395+ ...
396+ ... # Works with Failure too
397+ ... error = Failure(" original error" )
398+ ... new_error = copy.replace(error, _inner_value = " new error message" )
399+ ... assert new_error == Failure(" new error message" )
400+ ...
401+ ... # No changes returns the original object (due to immutability)
402+ ... assert copy.replace(original) is original
403+ ... else :
404+ ... # For Python versions before 3.13, the tests would be skipped
405+ ... pass
406+
407+ .. note ::
408+ The parameter name ``_inner_value `` is used because it directly maps to the
409+ internal attribute of the same name in ``BaseContainer ``. In the ``__replace__ ``
410+ implementation, this parameter name is specifically recognized to create a new
411+ container instance with a modified inner value.
412+
413+ .. warning ::
414+ While ``copy.replace() `` works at runtime, it has limitations with static
415+ type checking. If you replace an inner value with a value of a different
416+ type, type checkers won't automatically infer the new type:
417+
418+ .. code :: python
419+
420+ # Example that would work in Python 3.13+:
421+ # >>> num_container = Success(123)
422+ # >>> str_container = copy.replace(num_container, _inner_value="string")
423+ # >>> # Type checkers may still think this is Success[int] not Success[str]
424+ >> > # The above is skipped in doctest as copy.replace requires Python 3.13+
425+
426+ Using ``copy.replace() `` with Custom Containers
427+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
428+
429+ If you create your own container by extending ``BaseContainer ``, it will automatically
430+ inherit the ``__replace__ `` implementation for free. This means your custom containers
431+ will work with ``copy.replace() `` just like the built-in ones.
432+
433+ .. code :: python
434+
435+ >> > from returns.primitives.container import BaseContainer
436+ >> > from typing import TypeVar, Generic
437+ >> > import copy, sys # Requires Python 3.13+ for copy.replace
438+
439+ >> > T = TypeVar(' T' )
440+ >> > class MyBox (BaseContainer , Generic[T]):
441+ ... """ A custom container that wraps a value."""
442+ ... def __init__ (self , inner_value : T) -> None :
443+ ... super ().__init__ (inner_value)
444+ ...
445+ ... def __eq__ (self , other : object ) -> bool :
446+ ... if not isinstance (other, MyBox):
447+ ... return False
448+ ... return self ._inner_value == other._inner_value
449+ ...
450+ ... def __repr__ (self ) -> str :
451+ ... return f " MyBox( { self ._inner_value!r } ) "
452+
453+ >> > # Create a basic container
454+ >> > box = MyBox(" hello" )
455+ >> >
456+ >> > # Test works with copy.replace only on Python 3.13+
457+ >> > if sys.version_info >= (3 , 13 ):
458+ ... new_box = copy.replace(box, _inner_value = " world" )
459+ ... assert new_box == MyBox(" world" )
460+ ... assert box is not new_box
461+ ... else :
462+ ... # For Python versions before 3.13
463+ ... pass
464+
465+ By inheriting from ``BaseContainer ``, your custom container will automatically support:
466+
467+ 1. The basic container operations like ``__eq__ ``, ``__hash__ ``, ``__repr__ ``
468+ 2. Pickling via ``__getstate__ `` and ``__setstate__ ``
469+ 3. The ``copy.replace() `` functionality via ``__replace__ ``
470+ 4. Immutability via the ``Immutable `` mixin
471+
472+ Before Python 3.13, you can use container-specific methods to create modified copies:
473+
474+ .. code :: python
475+
476+ >> > from returns.result import Success, Failure, Result
477+ >> > from typing import Any
478+
479+ >> > # For Success containers, we can use .map to transform the inner value
480+ >> > original = Success(1 )
481+ >> > modified = original.map(lambda _ : 2 )
482+ >> > assert modified == Success(2 )
483+
484+ >> > # For Failure containers, we can use .alt to transform the inner value
485+ >> > error = Failure(" error" )
486+ >> > new_error = error.alt(lambda _ : " new error" )
487+ >> > assert new_error == Failure(" new error" )
488+
489+ >> > # For general containers without knowing success/failure state:
490+ >> > def replace_inner_value (container : Result[Any, Any], new_value : Any) -> Result[Any, Any]:
491+ ... """ Create a new container with the same state but different inner value."""
492+ ... if container.is_success():
493+ ... return Success(new_value)
494+ ... return Failure(new_value)
375495
376496 .. _type-safety :
377497
0 commit comments