This page contains the abstracts of the talks held during the latest Venerdì Protetto on June 14, 2024.
+ This page contains the abstracts of the talks held during the latest Venerdì Protetto on May 17, 2024.
+
Semantically, we can describe a Signal
as a container for a value that’s going to be there at a certain point in time, and it’s going to update itself indefinitely.
What follows is the entire implementation of the Signal
class:
-public enum SignalContinuation
-{
- case Continue
- case Stop
-}
-
-public class Signal <Subtype>
-{
- private var observers: [(Subtype -> SignalContinuation)] = []
-
- public init () {}
-
- public func observe (observeFunction: Subtype -> SignalContinuation)
- {
- observers.append(observeFunction)
- }
-
- public func send (value: Subtype)
- {
- var continuations: [(Subtype -> SignalContinuation)] = []
- while observers.count > 0
- {
- let observer = observers.removeFirst()
- let continuation = observer(value)
- switch continuation
- {
- case .Continue:
- continuations.append(observer)
- case .Stop: break
- }
- }
- observers = continuations
- }
-
- public func forwardTo (otherSignal: Signal<Subtype>) -> Signal
- {
- observe { action in
- otherSignal.send(action)
- return .Continue
- }
- return self
- }
-
- public func forwardTo <OtherSubtype> (
- otherSignal: Signal<OtherSubtype>,
- mappingFunction: Subtype -> OtherSubtype) -> Signal
- {
- observe { action in
- otherSignal.send(mappingFunction(action))
- return .Continue
- }
- return self
- }
-
- public func map <OtherSubtype> (transform: Subtype -> OtherSubtype) -> Signal<OtherSubtype>
- {
- let mappedSignal = Signal<OtherSubtype>()
- forwardTo(mappedSignal, mappingFunction: transform)
- return mappedSignal
- }
-}
-
This is really simple, but still, really powerful. A problem that’s frequently found when implementing the observer pattern is how to manage unsubscriptions; it’s a responsibility of the observer object to stop observing something, and in this Signal
implementation this is easily managed within the closure passed to the observe
method: the closure must return a SignalContinuation
value, that can be simply .Continue
(that is, keep observing updates) or .Stop
. Another problem is memory management: we need to make sure that when an observer’s memory is released, it will also stop observing, or a message will be sent to a dangling pointer, resulting in the app crashing. Swift’s weak
memory semantics actually makes this really easy to do: we’ll put a guard
clause at the beginning of the closure passed to the observe
method; if the object has become nil
, we’ll simply return .Stop
. The following example shows a simple use of the Signal
class, including the stop-observing-on-nil mechanism:
-class Sender
-{
- let signal = Signal<Int>()
-
- func sendNew (value: Int)
- {
- signal.send(value)
- }
-}
-
-class Receiver <Type : CustomStringConvertible>
-{
- func startObserving (signal: Signal<Type >)
- {
- signal.observe { [weak self ] value in
- guard let this = self else { return .Stop }
- this.printNewValue(value)
- return .Continue
- }
- }
-
- func printNewValue (value: Type )
- {
- print(value)
- }
-}
-
-let sender = Sender()
-let receiver = Receiver<Int>()
-
-receiver.startObserving(sender.signal)
-
-sender.signal.send(3 )
-sender.signal.send(5 )
-sender.signal.send(10 )
-sender.signal.send(20 )
-/// this will print 3, 5, 10, 20 on console
-
In the example we can see an application of the concepts we talked about at the beginning of this article: instead of creating yet another interface for the same behavior, we are directly using and reusing the Signal
object. A more complex example would be the addition of something like a resonator , that is, an object with a Signal
that resonates with another, like in the following example:
-class DoublingResonator
-{
- let signal = Signal<Int>()
-
- func resonateWith (otherSignal: Signal<Int>)
- {
- otherSignal.forwardTo(signal) { $0* 2 }
- }
-}
-
-let resonator = DoublingResonator()
-resonator.resonateWith(sender.signal)
-
-let receiver = Receiver<Int>()
-
-receiver.startObserving(resonator.signal)
-
-sender.signal.send(3 )
-sender.signal.send(5 )
-sender.signal.send(10 )
-sender.signal.send(20 )
-/// this will print 6, 10, 20, 40 on console
-
There are many other options for composition of signals, but as long as we don’t need them, it’s better to keep the class simple : then, gradually over time, we can start adding features to the class, and as long as they’re properly generic and tested, we will be able to use them in all of our projects.
+public enum SignalContinuation
+{
+ case Continue
+ case Stop
+ }
+
+ public class Signal <Subtype>
+{
+ private var observers: [(Subtype -> SignalContinuation)] = []
+
+ public init () {}
+
+ public func observe (observeFunction: Subtype -> SignalContinuation)
+ {
+ observers.append(observeFunction)
+ }
+
+ public func send (value: Subtype)
+ {
+ var continuations: [(Subtype -> SignalContinuation)] = []
+ while observers.count > 0
+ {
+ let observer = observers.removeFirst()
+ let continuation = observer(value)
+ switch continuation
+ {
+ case .Continue:
+ continuations.append(observer)
+ case .Stop: break
+ }
+ }
+ observers = continuations
+ }
+
+ public func forwardTo (otherSignal: Signal<Subtype>) -> Signal
+ {
+ observe { action in
+ otherSignal.send(action)
+ return .Continue
+ }
+ return self
+ }
+
+ public func forwardTo <OtherSubtype> (
+ otherSignal: Signal<OtherSubtype>,
+ mappingFunction: Subtype -> OtherSubtype) -> Signal
+ {
+ observe { action in
+ otherSignal.send(mappingFunction(action))
+ return .Continue
+ }
+ return self
+ }
+
+ public func map <OtherSubtype> (transform: Subtype -> OtherSubtype) -> Signal<OtherSubtype>
+ {
+ let mappedSignal = Signal<OtherSubtype>()
+ forwardTo(mappedSignal, mappingFunction: transform)
+ return mappedSignal
+ }
+ }
+
This is really simple, but still, really powerful. A problem that’s frequently found when implementing the observer pattern is how to manage unsubscriptions; it’s a responsibility of the observer object to stop observing something, and in this Signal
implementation this is easily managed within the closure passed to the observe
method: the closure must return a SignalContinuation
value, that can be simply .Continue
(that is, keep observing updates) or .Stop
. Another problem is memory management: we need to make sure that when an observer’s memory is released, it will also stop observing, or a message will be sent to a dangling pointer, resulting in the app crashing. Swift’s weak
memory semantics actually makes this really easy to do: we’ll put a guard
clause at the beginning of the closure passed to the observe
method; if the object has become nil
, we’ll simply return .Stop
. The following example shows a simple use of the Signal
class, including the stop-observing-on-nil mechanism:
+class Sender
+{
+ let signal = Signal<Int>()
+
+ func sendNew (value: Int)
+ {
+ signal.send(value)
+ }
+ }
+
+ class Receiver <Type : CustomStringConvertible>
+{
+ func startObserving (signal: Signal<Type >)
+ {
+ signal.observe { [weak self ] value in
+ guard let this = self else { return .Stop }
+ this.printNewValue(value)
+ return .Continue
+ }
+ }
+
+ func printNewValue (value: Type )
+ {
+ print(value)
+ }
+ }
+
+ let sender = Sender()
+let receiver = Receiver<Int>()
+
+ receiver.startObserving(sender.signal)
+
+ sender.signal.send(3 )
+ sender.signal.send(5 )
+ sender.signal.send(10 )
+ sender.signal.send(20 )
+ /// this will print 3, 5, 10, 20 on console
+
In the example we can see an application of the concepts we talked about at the beginning of this article: instead of creating yet another interface for the same behavior, we are directly using and reusing the Signal
object. A more complex example would be the addition of something like a resonator , that is, an object with a Signal
that resonates with another, like in the following example:
+class DoublingResonator
+{
+ let signal = Signal<Int>()
+
+ func resonateWith (otherSignal: Signal<Int>)
+ {
+ otherSignal.forwardTo(signal) { $0* 2 }
+ }
+ }
+
+ let resonator = DoublingResonator()
+resonator.resonateWith(sender.signal)
+
+ let receiver = Receiver<Int>()
+
+ receiver.startObserving(resonator.signal)
+
+ sender.signal.send(3 )
+ sender.signal.send(5 )
+ sender.signal.send(10 )
+ sender.signal.send(20 )
+ /// this will print 6, 10, 20, 40 on console
+
There are many other options for composition of signals, but as long as we don’t need them, it’s better to keep the class simple : then, gradually over time, we can start adding features to the class, and as long as they’re properly generic and tested, we will be able to use them in all of our projects.
To conclude, finding the right abstractions for reusability is of course a problem, and the solution is not an easy one: plenty of academic papers address the problem in several ways (classic Charles W. Krueger’s paper Software Reuse contains a good overview of the used techniques), and the reason why category theory has many applications in functional programming is because it offers an excellent set of abstractions for tackling several classes of problems. But still, I think the advantages of code reuse are many, and that achieving a compositional design through atomic, reusable components is a worthy goal to pursue.
@@ -642,29 +629,6 @@ Code reuse: a primer