Skip to content

Commit 18a696c

Browse files
authored
Use a threading.Lock internally for multi-methods (#478)
1 parent bc88383 commit 18a696c

File tree

2 files changed

+24
-32
lines changed

2 files changed

+24
-32
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313

1414
### Changed
1515
* Change the default user namespace to `basilisp.user` (#466)
16+
* Changed multi-methods to use a `threading.Lock` internally rather than an Atom (#477)
1617

1718
### Fixed
1819
* Fixed a reader bug where no exception was being thrown splicing reader conditional forms appeared outside of valid splicing contexts (#470)

src/basilisp/lang/multifn.py

Lines changed: 23 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
import threading
12
from typing import Any, Callable, Generic, Optional, TypeVar
23

3-
import basilisp.lang.atom as atom
44
import basilisp.lang.map as lmap
55
import basilisp.lang.symbol as sym
6+
from basilisp.lang.interfaces import IPersistentMap
67
from basilisp.util import Maybe
78

89
T = TypeVar("T")
@@ -11,71 +12,61 @@
1112

1213

1314
class MultiFunction(Generic[T]):
14-
__slots__ = ("_name", "_default", "_dispatch", "_methods")
15+
__slots__ = ("_name", "_default", "_dispatch", "_lock", "_methods")
1516

17+
# pylint:disable=assigning-non-slot
1618
def __init__(
1719
self, name: sym.Symbol, dispatch: DispatchFunction, default: T
1820
) -> None:
19-
self._name = name # pylint:disable=assigning-non-slot
20-
self._default = default # pylint:disable=assigning-non-slot
21-
self._dispatch = dispatch # pylint:disable=assigning-non-slot
22-
self._methods: atom.Atom = atom.Atom( # pylint:disable=assigning-non-slot
23-
lmap.Map.empty()
24-
)
21+
self._name = name
22+
self._default = default
23+
self._dispatch = dispatch
24+
self._lock = threading.Lock()
25+
self._methods: IPersistentMap[T, Method] = lmap.Map.empty()
2526

2627
def __call__(self, *args, **kwargs):
2728
key = self._dispatch(*args, **kwargs)
28-
method_cache = self.methods
29-
method: Optional[Method] = Maybe(method_cache.val_at(key, None)).or_else(
30-
lambda: method_cache.val_at(self._default, None) # type: ignore
31-
)
32-
if method:
29+
method = self.get_method(key)
30+
if method is not None:
3331
return method(*args, **kwargs)
3432
raise NotImplementedError
3533

36-
@staticmethod
37-
def __add_method(m: lmap.Map, key: T, method: Method) -> lmap.Map:
38-
"""Swap the methods atom to include method with key."""
39-
return m.assoc(key, method)
40-
4134
def add_method(self, key: T, method: Method) -> None:
4235
"""Add a new method to this function which will respond for
4336
key returned from the dispatch function."""
44-
self._methods.swap(MultiFunction.__add_method, key, method)
37+
with self._lock:
38+
self._methods = self._methods.assoc(key, method)
4539

4640
def get_method(self, key: T) -> Optional[Method]:
4741
"""Return the method which would handle this dispatch key or
4842
None if no method defined for this key and no default."""
49-
method_cache = self.methods
43+
method_cache = self._methods
5044
# The 'type: ignore' comment below silences a spurious MyPy error
5145
# about having a return statement in a method which does not return.
5246
return Maybe(method_cache.val_at(key, None)).or_else(
5347
lambda: method_cache.val_at(self._default, None) # type: ignore
5448
)
5549

56-
@staticmethod
57-
def __remove_method(m: lmap.Map, key: T) -> lmap.Map:
58-
"""Swap the methods atom to remove method with key."""
59-
return m.dissoc(key)
60-
6150
def remove_method(self, key: T) -> Optional[Method]:
6251
"""Remove the method defined for this key and return it."""
63-
method = self.methods.val_at(key, None)
64-
if method:
65-
self._methods.swap(MultiFunction.__remove_method, key)
66-
return method
52+
with self._lock:
53+
method = self._methods.val_at(key, None)
54+
if method:
55+
self._methods = self._methods.dissoc(key)
56+
return method
6757

6858
def remove_all_methods(self) -> None:
6959
"""Remove all methods defined for this multi-function."""
70-
self._methods.reset(lmap.Map.empty())
60+
with self._lock:
61+
self._methods = lmap.Map.empty()
7162

7263
@property
7364
def default(self) -> T:
7465
return self._default
7566

7667
@property
77-
def methods(self) -> lmap.Map:
78-
return self._methods.deref()
68+
def methods(self) -> IPersistentMap[T, Method]:
69+
return self._methods
7970

8071

8172
def multifn(dispatch: DispatchFunction, default=None) -> MultiFunction[T]:

0 commit comments

Comments
 (0)