diff --git a/README.md b/README.md index 829980d..6d03a14 100644 --- a/README.md +++ b/README.md @@ -227,12 +227,10 @@ may readily be added as required. Two simple class decorators for objects useful in hardware interfacing. Documented [here](./functor_singleton/README.md). -Singletons denote classes for which only a single instance will ever occur. -They are contentious in some circles, on the grounds that the single instance -guarantee may be violated in a specification change. They can be useful in -hardware contexts where a chip design is unlikely suddenly to change. -Singletons denoting hardware interfaces avoid globals and the need to pass -references around. +Singletons denote classes for which only a single instance can ever occur. +They can be useful for hardware interface classes. Their use avoids the need +for a global instance: the sole instance may be retrieved efficiently from +anywhere in the code. A functor is a class which is accessed via function call syntax. There is only one instance, like a singleton. Initial access calls the constructor, with diff --git a/functor_singleton/README.md b/functor_singleton/README.md index fedbe15..d801753 100644 --- a/functor_singleton/README.md +++ b/functor_singleton/README.md @@ -1,14 +1,23 @@ +# Singletons and Functors + +These closely related concepts describe classes which support only a single +instance. They share a common purpose of avoiding the need for global data by +providing a callable capable of retaining state and whose scope may be that of +the module. + +In both cases implementation is via very similar class decorators. + # Singleton class decorator -A singleton is a class which has only one instance. IT gurus debate whether -they should be used, mainly on the grounds that project aims can change: a need -for multiple instances may arise later. I would argue that singleton classes -have merit in defining interfaces to hardware objects. You can be sure that -(for example) a Pyboard D will only have one RTC instance. +A singleton is a class with only one instance. Some IT gurus argue against them +on the grounds that project aims can change: a need for multiple instances may +arise later. My view is that they have merit in defining interfaces to hardware +objects. You might be quite certain that your brometer will have only one +pressure sensor. The advantage of a singleton is that it removes the need for a global instance -or for passing an instance between functions. The sole instance is retrieved at -any point in the code using constructor call syntax. +or for passing an instance between functions. The sole instance is efficiently +retrieved at any point in the code using function call syntax. ```python def singleton(cls): @@ -33,8 +42,8 @@ ms = MySingleton(42) # prints 'In __init__ 42' x = MySingleton() # No output: assign existing instance to x x.foo(5) # prints 'In foo 47': original state + 5 ``` -The first instantiation sets the object's initial state. Thereafter -'instantiations' retrieve the original object. +The first call instantiates the object and sets its initial state. Subsequent +calls retrieve the original object. There are other ways of achieving singletons. One is to define a (notionally private) class in a module. The module API contains an access function. There @@ -73,7 +82,49 @@ class MyFunctor: MyFunctor(42) # prints 'In __init__ 42' MyFunctor(5) # 'In __call__ 47' ``` -A use case is in asynchronous programming. The constructor launches a task: -this will only occur once. Subsequent calls might alter the behaviour of that -task. An example may be found -[in the Latency class](https://github.com/peterhinch/micropython-async/blob/master/lowpower/rtc_time.py). +A use case is in asynchronous programming. The constructor launches a +continuously running task. Subsequent calls alter the behaviour of that task. +The following simple example has the task waiting for a period which can be +changed at runtime: + +```python +import uasyncio as asyncio +import pyb + +def functor(cls): + instance = None + def getinstance(*args, **kwargs): + nonlocal instance + if instance is None: + instance = cls(*args, **kwargs) + return instance + return instance(*args, **kwargs) + return getinstance + +@functor +class FooFunctor: + def __init__(self, led, interval): + self.led = led + self.interval = interval + asyncio.create_task(self._run()) + + def __call__(self, interval): + self.interval = interval + + async def _run(self): + while True: + await asyncio.sleep_ms(self.interval) + # Do something useful here + self.led.toggle() + +def go_fast(): # FooFunctor is available anywhere in this module + FooFunctor(100) + +async def main(): + FooFunctor(pyb.LED(1), 500) + await asyncio.sleep(3) + go_fast() + await asyncio.sleep(3) + +asyncio.run(main()) +```