Skip to content

Commit c6f3cf2

Browse files
committed
improve docstrings of package visitors.substitution
1 parent 6766e5e commit c6f3cf2

File tree

6 files changed

+226
-40
lines changed

6 files changed

+226
-40
lines changed

lambda_calculus/visitors/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ def defer_application(self, application: terms.Application[V]) -> tuple[T, Defer
167167
"""
168168
Visit an Application term.
169169
170-
:param abstraction: application term to visit
170+
:param application: application term to visit
171171
:return: tuple containing a value as required by its type variable
172172
and visitors to be used for visiting its abstraction and argument
173173
"""

lambda_calculus/visitors/substitution/__init__.py

Lines changed: 63 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,45 +20,92 @@
2020

2121

2222
class Substitution(Visitor["terms.Term[V]", V]):
23-
"""ABC for Visitors which replace a free Variable with another term"""
23+
"""
24+
ABC for Visitors which replace a free Variable with another term.
25+
26+
Type Variables:
27+
28+
V: represents the type of variables used in terms
29+
"""
2430

2531
__slots__ = ()
2632

2733
@abstractmethod
2834
def visit_abstraction(self, abstraction: terms.Abstraction[V]) -> terms.Abstraction[V]:
29-
"""visit an Abstraction term"""
35+
"""
36+
Visit an Abstraction term
37+
38+
The body is not automatically visited.
39+
40+
:param abstraction: abstraction term to visit
41+
:return: new term with substitutions performed
42+
"""
3043
raise NotImplementedError()
3144

3245
@abstractmethod
3346
def visit_application(self, application: terms.Application[V]) -> terms.Application[V]:
34-
"""visit an Application term"""
47+
"""
48+
Visit an Application term
49+
50+
The abstraction and argument are not automatically visited.
51+
52+
:param appliation: application term to visit
53+
:return: new term with substitutions performed
54+
"""
3555
raise NotImplementedError()
3656

3757
@classmethod
3858
@abstractmethod
3959
def from_substitution(cls: Type[T], variable: V, value: terms.Term[V]) -> T:
40-
"""create an instance from the substitution it should perform"""
60+
"""
61+
Create an instance from the substitution it should perform
62+
63+
:param variable: variable to substitute
64+
:param value: value which should be substituted
65+
:return: new instance
66+
"""
4167
raise NotImplementedError()
4268

4369

4470
class DeferrableSubstitution(DeferrableVisitor["terms.Term[V]", V], Substitution[V]):
45-
"""ABC for Substitutions which can be performed lazyly"""
71+
"""
72+
ABC for Substitutions which can be performed lazyly.
73+
"""
4674

4775
__slots__ = ()
4876

4977
@abstractmethod
5078
def defer_abstraction(self, abstraction: terms.Abstraction[V]) -> tuple[terms.Abstraction[V], DeferrableSubstitution[V] | None]:
51-
"""visit an Abstraction term and return the visitor used to visit its body"""
79+
"""
80+
Visit an Abstraction term.
81+
82+
:param abstraction: abstraction term to visit
83+
:return: tuple containing a new term instance with substitutions performed
84+
and a visitor to be used for visiting its body
85+
"""
5286
raise NotImplementedError()
5387

5488
@abstractmethod
5589
def defer_application(self, application: terms.Application[V]) -> tuple[terms.Application[V], DeferrableSubstitution[V] | None, DeferrableSubstitution[V] | None]:
56-
"""visit an Application term and return the visitors used to visit its abstraction and argument"""
90+
"""
91+
Visit an Application term.
92+
93+
:param application: application term to visit
94+
:return: tuple containing a new term instance with substitutions performed
95+
and visitors to be used for visiting its abstraction and argument
96+
"""
5797
raise NotImplementedError()
5898

5999
@final
60100
def visit_abstraction(self, abstraction: terms.Abstraction[V]) -> terms.Abstraction[V]:
61-
"""visit an Abstraction term"""
101+
"""
102+
Visit an Abstraction term
103+
104+
The body is visited after performing substitution.
105+
106+
:param abstraction: abstraction term to visit
107+
:return: new term instance with substitutions performed
108+
"""
62109
substituted, body_visitor = self.defer_abstraction(abstraction)
63110
if body_visitor is None:
64111
return substituted
@@ -69,7 +116,14 @@ def visit_abstraction(self, abstraction: terms.Abstraction[V]) -> terms.Abstract
69116

70117
@final
71118
def visit_application(self, application: terms.Application[V]) -> terms.Application[V]:
72-
"""visit an Application term"""
119+
"""
120+
Visit an Application term
121+
122+
The abstraction and argument are visited after performing substitution.
123+
124+
:param appliation: application term to visit
125+
:return: new term instance with substitutions performed
126+
"""
73127
substituted, abstraction_visitor, argument_visitor = self.defer_application(application)
74128
if abstraction_visitor is None:
75129
abstraction = substituted.abstraction

lambda_calculus/visitors/substitution/checked.py

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,12 @@
1919
@final
2020
class CheckedSubstitution(Substitution[V]):
2121
"""
22-
Visitor which replaces a free Variable with another term
23-
Raises a CollisionError if a free variable gets bound
22+
Substitution which checks if a free variable gets bound.
23+
24+
:param variable: variable to substitute
25+
:param value: value which should be substituted
26+
:param free_variables: free variables which should not be bound
27+
:raise errors.CollisionError: If a free variable gets bound
2428
"""
2529

2630
variable: V
@@ -47,23 +51,50 @@ def __init__(self, variable: V, value: terms.Term[V], free_variables: Set[V]) ->
4751

4852
@classmethod
4953
def from_substitution(cls, variable: V, value: terms.Term[V]) -> CheckedSubstitution[V]:
50-
"""create an instance from the substitution it should perform"""
54+
"""
55+
Create an instance from the substitution it should perform
56+
57+
:param variable: variable to substitute
58+
:param value: value which should be substituted
59+
:return: new instance with free_variables set to the free variables of value
60+
"""
5161
return cls(variable, value, value.free_variables())
5262

5363
def bind_variable(self, name: V) -> None:
54-
"""mark a variable as bound"""
64+
"""
65+
Mark a variable as bound.
66+
67+
Bound variables are not automatically unbound
68+
and can be bound multiple times.
69+
70+
:param name: name of the variable
71+
"""
5572
self.bound_variables[name] = self.bound_variables.get(name, 0) + 1
5673

5774
def unbind_variable(self, name: V) -> None:
58-
"""mark a variable as not bound"""
75+
"""
76+
Mark a variable as not bound.
77+
78+
A variable needs to be unbound multiple times
79+
if it was bound multiple times.
80+
81+
:param name: name of the variable
82+
:raise KeyError: If the variable is not bound
83+
"""
5984
number = self.bound_variables[name]
6085
if number == 1:
6186
del self.bound_variables[name]
6287
else:
6388
self.bound_variables[name] = number - 1
6489

6590
def visit_variable(self, variable: terms.Variable[V]) -> terms.Term[V]:
66-
"""visit a Variable term"""
91+
"""
92+
Visit a Variable term.
93+
94+
:param variable: variable term to visit
95+
:raise errors.CollisionError: If the substitution whould bind free variables
96+
:return: variable term or value which should be substituted
97+
"""
6798
if variable.name != self.variable:
6899
return variable
69100
collisions = self.free_variables & self.bound_variables.keys()
@@ -72,7 +103,14 @@ def visit_variable(self, variable: terms.Variable[V]) -> terms.Term[V]:
72103
return self.value
73104

74105
def visit_abstraction(self, abstraction: terms.Abstraction[V]) -> terms.Abstraction[V]:
75-
"""visit an Abstraction term"""
106+
"""
107+
Visit an Abstraction term.
108+
109+
:param abstraction: abstraction term to visit
110+
:raise errors.CollisionError: If a substitution in the body
111+
whould bind free variables
112+
:return: abstraction term or new term with substitutions performed
113+
"""
76114
if abstraction.bound == self.variable:
77115
return abstraction
78116
self.bind_variable(abstraction.bound)
@@ -86,7 +124,14 @@ def visit_abstraction(self, abstraction: terms.Abstraction[V]) -> terms.Abstract
86124
self.unbind_variable(abstraction.bound)
87125

88126
def visit_application(self, application: terms.Application[V]) -> terms.Application[V]:
89-
"""visit an Application term"""
127+
"""
128+
Visit an Application term.
129+
130+
:param appliation: application term to visit
131+
:raise errors.CollisionError: If a substitution in the abstraction
132+
or argument whould bind free variables
133+
:return: new term with substitutions performed
134+
"""
90135
return terms.Application(
91136
application.abstraction.accept(self),
92137
application.argument.accept(self)

lambda_calculus/visitors/substitution/renaming.py

Lines changed: 77 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@
2323

2424
class RenamingSubstitution(DeferrableSubstitution[V]):
2525
"""
26-
Visitor which replaces a free Variable with another term
27-
Renames bound variables if a free variable gets bound
26+
ABC for Substitutions which rename
27+
bound variables if a free variable gets bound.
2828
"""
2929

3030
variable: V
@@ -38,39 +38,67 @@ class RenamingSubstitution(DeferrableSubstitution[V]):
3838

3939
@abstractmethod
4040
def prevent_collision(self, abstraction: terms.Abstraction[V]) -> terms.Abstraction[V]:
41-
"""prevent collisions by renaming bound variables"""
41+
"""
42+
Prevent collisions by renaming bound variables.
43+
44+
:param abstraction: abstraction term which could bind free variables
45+
:return: abstraction term which does not bind free variables
46+
"""
4247
raise NotImplementedError()
4348

4449
@final
4550
def trace(self) -> TracingDecorator[V]:
46-
"""return a new visitor which yields when an alpha conversion occurs"""
51+
"""
52+
Create a new visitor which yields when an alpha conversion occurs.
53+
54+
:return: new visitor wrapping this instance
55+
"""
4756
return TracingDecorator(self)
4857

4958
@final
5059
def visit_variable(self, variable: terms.Variable[V]) -> terms.Term[V]:
51-
"""visit a Variable term"""
60+
"""
61+
Visit a Variable term.
62+
63+
:param variable: variable term to visit
64+
:return: variable term or value which should be substituted
65+
"""
5266
if variable.name != self.variable:
5367
return variable
5468
return self.value
5569

5670
@final
5771
def defer_abstraction(self, abstraction: terms.Abstraction[V]) -> tuple[terms.Abstraction[V], RenamingSubstitution[V] | None]:
58-
"""visit an Abstraction term and return the visitor used to visit its body"""
72+
"""
73+
Visit an Abstraction term.
74+
75+
:param abstraction: abstraction term to visit
76+
:return: tuple containing an abstraction term not binding free variables and
77+
this visitor to be used for visiting its body if variable is not bound
78+
"""
5979
if abstraction.bound == self.variable:
6080
return abstraction, None
6181
return self.prevent_collision(abstraction), self
6282

6383
@final
6484
def defer_application(self, application: terms.Application[V]) -> tuple[terms.Application[V], RenamingSubstitution[V], RenamingSubstitution[V]]:
65-
"""visit an Application term and return the visitors used to visit its abstraction and argument"""
85+
"""
86+
Visit an Application term.
87+
88+
:param application: application term to visit
89+
:return: tuple containing the application term and this visitor
90+
to be used for visiting its abstraction and argument
91+
"""
6692
return application, self, self
6793

6894

6995
@final
7096
class TracingDecorator(Visitor[Generator["terms.Term[V]", None, "terms.Term[V]"], V]):
7197
"""
72-
Visitor which transforms a RenamingSubstitution into an Generator
73-
which yields after performing an alpha conversion
98+
Visitor which transforms a :class:`RenamingSubstitution` into an Generator
99+
which yields after performing an alpha conversion and returns the term with substitutions.
100+
101+
:param substitution: instance to wrap
74102
"""
75103

76104
substitution: RenamingSubstitution[V]
@@ -81,13 +109,25 @@ def __init__(self, substitution: RenamingSubstitution[V]) -> None:
81109
self.substitution = substitution
82110

83111
def visit_variable(self, variable: terms.Variable[V]) -> Generator[terms.Variable[V], None, terms.Term[V]]:
84-
"""visit a Variable term"""
112+
"""
113+
Visit a Variable term.
114+
115+
:param variable: variable term to visit
116+
:return: empty Generator returning the result
117+
of :meth:`RenamingSubstitution.visit_variable`
118+
"""
85119
return self.substitution.visit_variable(variable)
86120
# to create a generator
87121
yield variable # type: ignore[unreachable]
88122

89123
def visit_abstraction(self, abstraction: terms.Abstraction[V]) -> Generator[terms.Abstraction[V], None, terms.Abstraction[V]]:
90-
"""visit an Abstraction term"""
124+
"""
125+
Visit an Abstraction term
126+
127+
:param abstraction: abstraction term to visit
128+
:return: Generator yielding alpha conversions and
129+
returning the term with substitutions
130+
"""
91131
substituted, visitor = self.substitution.defer_abstraction(abstraction)
92132
if visitor is None:
93133
return substituted
@@ -98,7 +138,13 @@ def visit_abstraction(self, abstraction: terms.Abstraction[V]) -> Generator[term
98138
return terms.Abstraction(substituted.bound, body)
99139

100140
def visit_application(self, application: terms.Application[V]) -> Generator[terms.Application[V], None, terms.Application[V]]:
101-
"""visit an Application term"""
141+
"""
142+
Visit an Application term
143+
144+
:param appliation: application term to visit
145+
:return: Generator yielding alpha conversions and
146+
returning the term with substitutions
147+
"""
102148
# distinguish between last alpha conversion (step) and
103149
# substituted abstraction (abstraction)
104150
abstraction = step = application.abstraction
@@ -119,8 +165,12 @@ def visit_application(self, application: terms.Application[V]) -> Generator[term
119165
@final
120166
class CountingSubstitution(RenamingSubstitution[str]):
121167
"""
122-
Visitor which replaces a free Variable with another term
123-
Renames bound variables if a free variable gets bound by appending a number
168+
Substitution which renames bound variables
169+
if a free variable gets bound by appending a number.
170+
171+
:param variable: variable to substitute
172+
:param value: value which should be substituted
173+
:param free_variables: free variables which should not be bound
124174
"""
125175

126176
free_variables: Set[str]
@@ -134,11 +184,22 @@ def __init__(self, variable: str, value: terms.Term[str], free_variables: Set[st
134184

135185
@classmethod
136186
def from_substitution(cls, variable: str, value: terms.Term[str]) -> CountingSubstitution:
137-
"""create an instance from the substitution it should perform"""
187+
"""
188+
Create an instance from the substitution it should perform
189+
190+
:param variable: variable to substitute
191+
:param value: value which should be substituted
192+
:return: new instance with free_variables set to the free variables of value
193+
"""
138194
return cls(variable, value, value.free_variables())
139195

140196
def prevent_collision(self, abstraction: terms.Abstraction[str]) -> terms.Abstraction[str]:
141-
"""prevent collisions by renaming bound variables"""
197+
"""
198+
Prevent collisions by appending a number.
199+
200+
:param abstraction: abstraction term which could bind free variables
201+
:return: abstraction term which does not bind free variables
202+
"""
142203
if abstraction.bound in self.free_variables:
143204
used_variables = abstraction.body.bound_variables() \
144205
| abstraction.free_variables() \

0 commit comments

Comments
 (0)