Smoothly Navigating between Functional Reactive Programming and Actors
aa r X i v : . [ c s . P L ] A ug Smoothly Navigating between FunctionalReactive Programming and Actors
Nick Webster and Marco Servetto Victoria University of Wellington, Kelburn, Wellington, 6012, New Zealand { [email protected], [email protected] } { https://nick.geek.nz,http://ecs.victoria.ac.nz/Main/MarcoServetto } Abstract.
We formally define an elegant multi-paradigm unification ofFunctional Reactive Programming, Actor Systems, and Object-OrientedProgramming. This enables an intuitive form of declarative program-ming, harvesting the power of concurrency while maintaining safety.We use object and reference capabilities to highlight and tame imperativefeatures: reference capabilities track aliasing and mutability, and objectcapabilities track I/O. Formally, our type system limits the scope, impactand interactions of impure code. – Scope: Expressions whose input is pure will behave deterministically. – Impact: Data-races and synchronisation issues are avoided. The onlyway for an actor to behave nondeterministically, is by mutating itsstate based on message delivery order. – Interactions: Signals provide a functional boundary between imper-ative and functional code, preventing impure code from invalidatingfunctional assumptions.
Parallel programming promises great performance improvements, but it is also asource of undesired nondeterministic behaviour. Actor systems and FRP (Func-tional Reactive Programming) tame nondeterminism in different ways: each ac-tor sees the world sequentially, and processes a single message at a time. However,messages can be delivered in an unpredictable order. Instead, pure FRP guar-antees complete determinism; signals may be processed in various orders and inparallel, but immutability shields us from observing any parallelism.In 2019, Lohstroh et al. [12] proposed a new actor system that uses reactors .Reactors declare their inputs and outputs, react to messages, and are connectedwith a composite main function that builds the graph. Effectively, the systemuses reactive programming techniques to build actor systems. The ‘reactor net-work’ can offer stronger guarantees for message delivery/processing order thantraditional actor models. However the system does not enforce any propertieson behaviour, like determinism or the absence of data races.In this paper we propose
Featherweight Reactive Java (FRJ), a way to blendFRP with actor systems in a minimal subset of Java, inspired by Featherweight
Nick Webster and Marco Servetto
Java (FJ) [9]. FRJ achieves this by using functional reactive programming tech-niques, which offers all the benefits of the reactor model and control over I/Oand state mutation. FRJ fulfils the promise of Lohstroh et al.’s work by allowingthe declarative creation of truly deterministic actor systems.In actor systems, actors have a list of messages that they process one at atime. For simplicity, in our language every object can function as an actor, andthus in memory there is a list of messages near every object record. FRJ’s FRP isinspired by E-FRP’s discrete signals, which are signals that update upon eventsoccurring [4, 20]. FRJ’s signals are a possibly infinite sequence of messages. Themessages contain values that have either been computed or are being computed.The head is the most recent message; the tail is an expression that returns a newsignal for the next message. Any usages of a signal with a message that has yetto be computed, will wait for the computation to finish. We implement signalsvia lists of expressions. The computation of each expression is deferred and theresult of the computation is a ‘message’. The head and tail of the signal can beaccessed using the conventional head(_) and tail(_) syntax. Finally, we have aspecial syntax for lifted method calls: a.@m(b,c) , where b and c are signals. Thissyntax sends to the actor a a message causing the (asynchronous) computationof a.m(head(b),head(c)) and then triggers a.@m(tail(b),tail(c)) ; until either b or c terminates.We can connect real world input output with our signals by using objectcapabilities. We have an expressive type system based on reference capabilities,supporting two fundamental properties: expressions that only use imm referencesare deterministic, and parallelism can only induce nondeterminism if a mutableactor relies on the delivery order of messages. Consider the following class: class Person { method Int age() {return 24;} method Str name() {return "Bob";} method Str format(Str name,Int age) { return name+":"+age; }} Using it, we could write the conventional method call p.format(p.name(),p.age()) , to compute a string once. Using FRJ’s lifted method calls, we can writethe following in an FRP style: @Int ages = p.@age(); p.@format(p.@name(),ages); This creates a signal with messages containing formatted names and ages.If we connected the behaviour of age() to the real world, we would see theformatted names change as Bob grows older. We can also write code in the actorstyle, by sending individual events to p ; @[] is the empty signal and the syntax @[ ; ] builds a signal manually. @Int ages=@[p.age();@[]] moothly Navigating between FRP and Actors 32 a.@format(@[p.name();@[]],ages) This code sends the messages, age and name , to the actor p one time. p replieswith an Int and a
Str message. When both messages are handled, p receives asingle message asking to produce the formatted string, parameterised over thename and the age. The creation of messages inside signals is computed in paralleland execution is deferred. Thus, implementing a fork–join is trivial in FRJ: @Int part1=@[x.computePart1();@[]] Int part2=x.computePart2(); return head(part1)+part2; The fork-join works because the creation of part1 does not block because itshead is being evaluated in parallel. The head(part1) call would block until theexpression had been computed and the message was ready. cap ::= capability | ∅ CD ::= cap class C implements C { F K M } | interface C extends C { MH ; . . . MH n ; } K ::= C ( T x . . . T n x n ) {this. x = x ; . . . this. x n = x n ; } F ::= T f ; T ::= mdf C | @ TMH ::= mdf method
T m ( T x . . . T n x n ); M ::= MH {return e ; } e ::= x | e . m ( e ) | e . f | e . f = e | new C ( e ) | e .@ m ( e ) | @[ e ; e ′ ] | @[ ] | head( e ) | tail( e ) v ::= L | S | [ v ; S ] E ::= (cid:3) | E . m ( e ) | v . m ( v E e ) | E .@ m ( e ) | v .@ m ( v E e ) | E . f | E . f = e | v . f = E| new C ( v E e ) | head( E ) | tail( E ) mdf ::= imm | mut | capsule | read B ::= S [ e ; e ] µ ::= ρ . . . ρ n ρ ::= L C ( v ) B FRJ is a minimal OO language, where the class table contains classes or in-terfaces. Classes have methods, M , fields, F , and a conventional constructor, K initialising all of the fields. Interfaces provide conventional nominal subtyping,and for simplicity we do not offer any kind of subclassing. The language makesuse of modifiers to implement reference capabilities. The modifiers will be dis-cussed alongside the typing rules because they are transparent to the reduction.FRJ builds over the conventional small step reduction model where a pair µ | e is reduced into a new memory and a new expression. The E nonterminal is theevaluation context for the reduction. The memory is a map from object loca-tions L to conventional object records. Additionally, every record also maintainsa list of pending messages ( B ). Values are conventional object locations, futuresignal values S , and completed signal values [ v | S ] . Types are class or inter-face names annotated with a capability modifier mdf . The default modifier imm Nick Webster and Marco Servetto can be omitted for convenience. Types for signals are annotated with @ . We alsosupport higher-order signals, as @@T .In addition to conventional variables, method calls, field accesses, field up-dates, and constructor calls, FRJ offers lifted method calls e .@ m ( e ) , explicitsignal construction @[ e ; e ′ ] , and the conventional head( e ) and tail( e ) notation.FRJ also offers the empty signal @[ ] , which is a special signal that will not haveany more messages in it. The special variable this is implicitly provided as anargument to methods. Using the auxiliary notation, our well-formedness rules are as follows: – @[ ] is not in domS ( µ ) (defined below). – All classes and interfaces are uniquely named. – All methods in a given class are uniquely named. – All fields in a given class are uniquely named. – All parameters in a given method are uniquely named and are not called this . – A capsule method parameter can be used zero or one times in the methodbody – All S labelling a B inside the memory are unique. – Fields can only have the type modifiers: imm or mut . – Types containing @ must have the imm modifier. – Classes can only implement interfaces. – Interfaces can only extend other interfaces. – µ | e is well formed if all L in e are in dom ( µ ) (defined below) and all usedS ( e ) ∪ usedS ( µ ) are in domS ( µ ). dom ( µ ) is the conventionally defined set of all keys ( L ) in the map ( µ ). domS ( µ ) is the set of all S labelling a B inside the memory, and usedS ( µ ) isdefined as follows: – usedS ( µ ) = usedS ( µ, ρ ) ∪ . . . ∪ usedS ( µ, ρ n ),with µ = ρ . . . ρ n – usedS ( L C ( v . . . v k ) B . . . B n ) = usedS ( v ) ∪ . . . ∪ usedS ( v k ) ∪ usedS ( B ) ∪ . . . ∪ usedS ( B n ) – S ∈ usedS ( S ′ [ e ; e ] ) = usedS ( e ) ∪ usedS ( e ) – S ∈ usedS ( e ) if S is a sub-expression of e . The shape of the reduction is: µ | e → µ ′ | e ′ . We use class ( C ) to denote theclass declaration ( CD ) for the class C and f ields ( C ) to denote the list of thefields for the class C . Additionally, ‘ ’ is used as a placeholder in the rules andcan match any syntactic term. moothly Navigating between FRP and Actors 5 L C ( v . . . v n ) in µ T f . . . T n f n = f ields ( C )(fAccess) µ | L.f i → µ | v i T f . . . T n f n = f ields ( C ) ρ = L C ( v v . . . v n ) B ρ = L C ( v v v . . . v n ) B (fUpdate) µ , ρ | L . f = v → µ , ρ | v (new) µ | new C ( v . . . v n ) → µ, L C ( v . . . v n ) B | LL C ( v ) B in µ method m ( x . . . x n ) {return e ; } in class ( C )(mCall) µ | L . m ( v . . . v n ) → µ | e [ this = L , x = v . . . x n = v n ] µ | e → µ | e ′ ( E ) µ | E [ e ] → µ | E [ e ′ ] µ | e → µ | e ′ ( E Head) µ, ρ S [ E [ e ] ; e ] | e → µ, ρ S [ E [ e ′ ] ; e ] | e µ | e → µ | e ′ ( E Tail) µ, ρ S [ v ; E [ e ] ] | e → µ, ρ S [ v ; E [ e ′ ] ] | e Field updates, field access, object construction and method call are standard.Contextual rules ( E , E Head, and E Tail) guide the parallel reduction: ( E ) allowsus to reduce the main expression, while ( E Head) reduces the last message of anobject. When the value is produced, rule ( E Tail) executes the expression creatingthe next stream node. Note that since the memory µ is a set, the rules can workon any ρ in µ . The ρ non-terminal has a list of B at its end; thus by writing µ, ρ S [ e ; e ′ ] we are selecting the last message of an arbitrary object in memory.(head) µ | head([ v ; S ]) → µ | v (tail) µ | tail([ v ; S ]) → µ | S (tailEmpty) µ | tail(@[ ]) → µ | @[ ] (msgComplete) µ, ρ S [ v ; S ′ ] | e → ( µ, ρ | e )[ S = [ v ; S ′ ] ]either e = E [ head(@[ ]) ]or e = v and e = E [ head(@[ ]) ] (Empty) µ, ρ S [ e ; e ] | e → ( µ, ρ | e )[ S = @[ ] ] Nick Webster and Marco Servetto
The rule (head) is conventional and simply reduces to the current value ofa completed signal. When the expression is well typed, the tail of a message isexpected to be a signal that will eventually contain the next value, rule (tail)can be used to get that continuation signal.When a message has been completely computed, rule (msgComplete) removesthe message from the memory, and replaces all of the references to S with [ v ; S ] .So, while head( S ) will cause the reduction to get stuck, head([ v ; S ]) can reduce.Therefore, the rule (msgComplete) enables a form of synchronisation betweenthe messages and their consumers.When the message execution tries to access the head of the empty signal,rule (Empty) terminates the signal, removing the message S and by replacingall occurrences of S with @[ ] . (explicitS) µ | @[ e ; e ] → µ , L Object() S [ e ; e ] | Se = L .@ m ( v . . . v n ) e = L . m (head( v ) . . . head( v n )) e = L .@ m (tail( v ) . . . tail( v n )) (liftS) µ, L C ( v ) B | e → µ, L C ( v ) S [ e ; e ] B | S This group of two rules (explicitS, and liftS) deals with the creation of signals.For the creation of signals, the rule (explicitS) reduces signal constructorsinto a message ( B ) and places it on a new empty actor. The signal constructorexpression is then replaced with the fresh signal ( S ) that was just associatedwith the message.The alternative way to create signals in FRJ is through lifting methods. liftSreduces lifted method calls by creating a B that gets placed onto the receivercontaining a head of the traditional method call with arguments of the head ofall of its inputs. The tail of this new B will be the same lifted method call, butwith the tail of all of the inputs as the inputs for the new lifted call. Effectively,the method now reacts to its inputs. (garbage) µ, µ ′ | e → µ | e Finally, the rule (garbage) gets rid of the part of memory that is unreachablestarting from the main expression. Note that we cannot arbitrarily split thememory. We can only split it in such a way that the resulting µ | e is wellformed. An important consequence of our garbage collection rule is that messagescan be collected too, even during their computation. However, due to our well-formedness rules, messages can only be collected if the receiver actor object iscollected, and an object can only be collected if there are no other references toits address and to any of the S in its mailbox. moothly Navigating between FRP and Actors 7 Parallel computation is inherently part of FRP and actor systems. FRJ usesreference capabilities to tame the nondeterminism that would otherwise arisefrom aliasing and mutability. FRJ supports the three traditional reference ca-pabilities: imm , deeply immutable (the default); mut mutable and read , the com-mon supertype of both imm and mut . In addition, FRJ supports capsule ; a refer-ence that dominates its ROG mut (reachable object graph) [7]. In OO languages,ROG( L ) = L is all the locations transitively reachable from the fields of L . Withreference capabilities, mutable ROG mut ( L ) = L is all the locations transitivelyreachable from L only following mutable fields.Assuming a traditional Person class, the following is an example of referencecapabilities: mut Person mP=new Person("Bob",24); imm Person iP=new Person("Bob",24); read Person rP=mP; mP.setAge(25);//ok, now rP.getAge()==25 iP.setAge(25);//type error rP.setAge(25);//type error rP=iP;//ok, read is supertype of imm/mut Note how the same object may be pointed at the same time by multiplereferences with different modifiers. Capsule references can be obtained when thealiasing is under control, and can be used to create immutable references. Cap-sule references can be used to create immutable references from non-immutableobjects. Capsule references can only exist in expressions, and the whole mutableobject graph reachable from a capsule reference can only be reached from thatspecific capsule reference. In this way, the capsule reference is the sole accesspoint to a group of mutable objects. Reference capabilities have the followingsubtype relation: – capsule ≤ mdf – mdf ≤ read Thus, all of the reference capabilities are subtypes of capsule and supertypesof read . mut and imm are not comparable to each other.The main advantage of reference capability over older forms of aliasing control[1, 8], is that references can be promoted/recovered to a subtype when the rightconditions arise. In this work we rely only on multiple method types :If mdf method T m ( T x . . . T n x n ) ∈ class ( C ), T = mdf ′ C and mdf ′ ≤ mdf then methTypes ( T , m ) = { T . . . T n T , ( T . . . T n T )[ mut = capsule ] , ( T . . . T n T )[ mut = capsule , read = imm ] } Where notation [ mut = capsule ] , replaces all of the mut with capsule . Forexample, the following code is correct: class Box { mut F f; Box(mut F f) { this.f=f; } read method read F f() { return this.f; } } Nick Webster and Marco Servetto3 class MakeBox{ method mut Box of(mut F f){ return new Box(f); } } capsule F f=..//we have a capsule f capsule Box b=new MakeBox().of(f); imm Box immB=b; imm F immF=immB.f(); On line 4, method of(f) was declared taking an imm receiver and a mut parameter,and returning a mut , but when called with a capsule parameter (line 6), we canpromote the result to capsule . On line 2, method f() was declared taking a read receiver and returning a read , but when called with an imm receiver (line 7) wecan promote the result to imm . An object capability is an object whose methods can do privileged operations.While reference capabilities keep mutability and aliasing under control, we relyon object capability [14] to tame I/O. Our reduction rules do not model I/Odirectly, but we assume predefined capability classes containing mut methodsdoing all of the desired I/O interactions. Since only mut methods of capability classes can do nondeterministic I/O, we keep I/O under control by allowing onlythe main and mut methods of capability classes to create instances of capabilityclasses [6]. In this way, any method that only takes immutable objects as inputis guaranteed to be deterministic.In FRJ, the default reference capability is carefully designed to require ex-plicit syntax to introduce any impurity and non-determinism. Because imm isthe default reference capability, imperative features are controlled by default.The values of signals are always imm , so every other reference being imm by de-fault makes using signals easier. Additionally, outside of the main expression, allclasses may not perform any I/O or other side effects without being declared asa capability or taking an object capability as input.
FRJ’s typing environment has three components: Γ , the mapping between vari-ables and types; Σ , the mapping between a memory address and object locations;and cap , a flag identifying if the expression is allowed to instantiate capabilityclasses.We will use notation capOf ( C ) and capOf ( T ) to denote the capability mod-ifier of a given class. ( x ) cap ; Σ ; Γ ⊢ x : Γ ( x ) cap ; Σ ; Γ ⊢ e : T ′ ⊢ T ′ ≤ T (sub) cap ; Σ ; Γ ⊢ e : T ( L ) cap ; Σ ; Γ ⊢ L : mdf Σ ( L ) moothly Navigating between FRP and Actors 9 Variable typing and subsumption are standard.The rule (L) types memory references as the class of the object it points toand the modifier of the reference . cap ; Σ ; Γ ⊢ e : mdf C T f . . . T n f n = f ields ( C )(fAccess) cap ; Σ ; Γ ⊢ e . f i : T i + mdfcap ; Σ ; Γ ⊢ e : mut C T f . . . T n f n = f ields ( C ) cap ; Σ ; Γ ⊢ e : T i (fUpdate) cap ; Σ ; Γ ⊢ e . f i = e : T i Field access and field update are conventional with the exception of modifiersbeing applied to the result of a field access and the added requirement that thereceiver of a field update must be mut . The rules for the composition for thereference capabilities of the result of a field access are: – @ mdf C + imm = @ imm C – @ mdf C + mut = @ mdf C – @ mdf C + capsule = @ mdf C – @ mut C + read = @ read C – @ imm C + read = @ imm C For example, with a field access, if the receiver had the read modifier and thefield had the imm modifier, result would be imm . Alternatively, if the receiver was read and the field was mut , the result would be read . T f . . . T n f n = f ields ( C ) cap ; Σ ; Γ ⊢ e i : T i either capOf ( C ) = ∅ or cap = capability (new) cap ; Σ ; Γ ⊢ new C ( e . . . e n ) : mut C Object instantiation is also mostly conventional. The major difference is thatif the class is marked as capability , then the object can only be created in themain method or in a mut method of another capability class ; see rule (method)on page 11. T f . . . T n f n = f ields ( C ) cap ; Σ ; Γ ⊢ e i : T i [ mdf = imm ] (newImm) cap ; Σ ; Γ ⊢ new C ( e . . . e n ) : imm C If the constructor arguments are all imm , then the object created can betyped with the imm modifier; also capability classes can be instantiated by thisrule, since only the mut methods can do privileged operations. T . . . T n T in methTypes ( T , m ) cap ; Σ ; Γ ⊢ e i : T i (mCall) cap ; Σ ; Γ ⊢ e . m ( e . . . e n ) : T To complete a proof of soundness, we would likely need to instrument the reductionto keep track of the pair L : mdf .0 Nick Webster and Marco Servetto Our method call type rule is mostly conventional but relies on methTypes ,and thus is more flexible than the conventional one. cap ; Σ ; Γ ⊢ e : T T . . . T n T in methTypes ( T , m ) cap ; Σ ; Γ ⊢ e i : @ T i ∀ i ∈ ..n validActor ( T ) (mCall@) cap ; Σ ; Γ ⊢ e .@ m ( e . . . e n ) : @ T The major difference between rule (mCall) and rule (mCall@) is that all of theargument types are lifted ( @ T ) and the receiver must be a validActor ( T ): eitherthe receiver is immutable ( T = imm ) or the receiver is a capability instanceand have only imm fields ( capOf ( T ) = capability and mut C f f ields ( T )).Actors may receive messages in any order; while immutable actors cannot beinfluenced by such order, a mutable actor may use the messages to update thevalue of a field . validActor ( T ) prevents this issue, but it requires mutable actors to be in-stances of capability classes. Note that there is no need for all of the actors tobe created in main ; it is sufficient to create a single capability ActorSystem objectthat creates new actors using some mut method. cap ; Σ ; Γ [only imm , capsule ] ⊢ e : T cap ; Σ ; Γ [only imm , capsule ] ⊢ e : @ T (fullSignal) cap ; Σ ; Γ ⊢ @[ e ; e ] : @ T The rule (fullSignal) is for a signal constructor with both a head and a tail.The rule enforces that only imm and capsule variables can be captured by thedeferred executed expressions inside the signal.(emptySignal) cap ; Σ ; Γ ⊢ @[ ] : @ T The rule (emptySignal) is similar to the conventional rule for typing emptylists, as the empty signal can assume any signal type; not unlike how [] in Haskellis generic and valid for any list type. cap ; Σ ; Γ ⊢ e : @ T (head) cap ; Σ ; Γ ⊢ head( e ) : Tcap ; Σ ; Γ ⊢ e : @ T (tail) cap ; Σ ; Γ ⊢ tail( e ) : @ T Rule (head) extracts the type of the value in the head and rule (tail) preservesthe type of the expression. cap ; C ⊢ M i overrideOk ( C ′ , M i ) ∀ C ′ ∈ C dom( C ′ ) ⊆ dom ( C ) ∀ C ′ ∈ C (class) ⊢ cap class C implements C { F K M . . . M n } OK If such an actor could be freely created, then we could use it to forge a no-argsmethod with a nondeterministic result.moothly Navigating between FRP and Actors 11 overrideOk ( C ′ , MH i ) ∀ C ′ ∈ C (interface) ⊢ interface C extends C { MH . . . MH n } OK cap ′ ; this : mdf C , x : T . . . x n : T n ⊢ e : Tcap ′ = ∅ iff cap = ∅ or mdf = mut (method) cap , C ⊢ mdf method T m ( T x . . . T n x n ) {return e ; } The last three type rules (class, interface, and method) are standard withthe exception of rule (method), where every mut method in a capability classis typed as a capability method. We omit the trivial but tedious definition for overrideOk ( C ′ , MH i ), checking if a method signature can override a potentialmethod with the same name defined in the super interface: if another methodwith the same name exists, the two method types must be identical. The scenario used in the proposal of the first-order purely FRP language,
Emfrp [21], is an air conditioning unit’s controller. The inputs are temperature , humidity ,and the current power state of the unit. The output is what the power state ofthe unit should be. To show how FRJ works, the same scenario can be done withour system. For this example we are taking the liberty of using number/booleanliterals and postfix operators for simplicity’s sake.We assume the existence of two capability classes: Sensors , which containsmethods to read the physical sensors on the AC unit and a clock; and AC , whichinteracts directly with the hardware to change power states.A discomfort index is calculated based on the temperature and the humidityto determine how uncomfortable the room is. We represent that with this actor: class ComfortComputer { method Float discomfort(Float temp,Float hum) { return 0.81 * temp +0.01 * hum * (0.99 * temp - 14.3) + 46.3; } } The actor
ACController computes if the unit should be on or off, dependingon the discomfort index and its current power state. The current power stateof the unit is needed to apply hysteresis, so that the unit does not constantlychange power state. The first implementation is in the traditional actor stylewith mutable state: capability class ACController{ Bool isOn;//can be updated ACController(Bool isOn) {this.isOn=isOn;} read method Float hysteresis() { return this.isOn?-0.5:0.5;} mut method Bool powerSwitch(Float d) { this.isOn=d>=(75.0+this.hysteresis()); return this.isOn; }} Note how
ACController is a valid mutable actor: It is a capability class wherethe only field is of type imm Bool . Note how the field can still be updated (line7); FRJ only requires the referred object (the
Bool ) to be deeply immutable.We now have an actor that will generate current discomfort values and an-other actor that will determine the current power state. In main , we can connectthem to the sensors to make our program react to the real world: // Get sensor input mut Sensors s=new Sensors();//capability @Bool tick=s.clock(); // emits every second @Float temps=s.@temp(tick); @Float humidities=s.@humidity(tick); // Decide power state @Float discomfort=new ComfortComputer().@discomfort(temps,humidities); @Bool powerState=new ACController(false).@powerSwitch(discomfort); // Apply power state mut AC ac=new AC();//capability ac.@setPower(powerState); One nice feature of our system is that, because inputs are waited for, the tick input coordinates the system to update once a second at maximum. The tick dependency is similar to using Rx’s
Interval operator as a source [17]. Thatfeature is important: it avoids mailbox overflow when one sensor is faster thanthe other.We now reimplement
ACController in a more traditional FRP approach,where state is kept via recursion with signals, much like Elm’s foldp pattern(before Elm removed FRP from their language ) foldp is short for “fold over thepast” [5] and is typed (a -> b -> b) -> b -> @a -> @b . We can define FoldP inFRJ, extended with some modern Java features: interface FoldP{method O apply(I v,O old); static method @O of(FoldP f,O initial,@I signal){ O out=f.apply(head(signal),initial); return @[out;FoldP.of(f,out,tail(signal))]; }} class ACController{//functional using FoldP method Float hysteresis(Bool isOn){return isOn?-0.5:0.5;} method @Bool powerSwitch(@Float discomfort){ return FoldP.of( (d,isOn)->d>=(75.0+this.hysteresis(isOn)), false,discomfort);}} To switch to the FRP style while keeping the same behaviour, on line 7 ofthe previous main we can use new ACController().powerSwitch(discomfort) .Both programming styles are highly parallelisable, with clear dependencychains, and a fairly compact code footprint. FRJ allows for smooth transitionsbetween the FRP and Actor model approaches to concurrent programming. https://elm-lang.org/news/farewell-to-frpmoothly Navigating between FRP and Actors 13 The potential for connection between reactive programming and the actor modelhas been a subject of active research over the past 4 years. The reactor model’sattempt to create deterministic actors using reactive programming [12] offersguarantees on deterministic message delivery and processing order to ensurethat all nodes requesting an input get it and process it before the next messageis sent. However, by abstracting message passing with FRP’s signal primitive,FRJ enables immutable and pure actors that do not need to be bound by thereactor model’s strict ordering rules.Work has been done by Van den Vonder et al. [19] on the ‘actor-reactor model’(ARM), which instead of replacing actors with reactors, creates a joint model,where actors can be nondeterministic. The reactors in the ARM should not beconfused with the Lohstroh et al.’s reactors; ARM’s reactors are always pureand deterministic. The ARM approach is novel and sensible, but FRJ takes adifferent path. FRJ does not have the distinction between ‘actors’ and ‘reactors’.Instead, we attempted to unify the two systems. FRJ’s unified approach doesstill make a distinction between deterministic and nondeterministic actors usingobject capabilities, but a pure FRJ actor is able to perform more complex tasksthan an ARM reactor. Ultimately, the ARM is very compelling, but we thinkthat a unified approach results in simpler systems.XFRP [18] offers an interesting model for executing pure FRP on an actor-based runtime. Using XFRP would be a similar experience to using FRJ withoutany object or reference capabilities. The language has fewer sources of non-determinism to control because it delegates side effects to components that areexternal to the program. Shibani et al. note their main source of nondetermin-ism as the @last operator, which is essentially syntactic sugar for foldp . Glitchis a common issue with systems inspired by FRP. Single source glitch freedommeans that all nodes (lifted functions for FRJ) that have one signal as an input,will get updates at the same time [15]. If a system does not have glitch freedom,then parts of the application that depend on the same signals could be in aninconsistent state until they get the latest message. If multiple stateful inputsare given to a signal function, glitch freedom can be violated in XFRP. XFRPmanages to get around the issue by adding an option to change the semanticsof their language to the same as FRJ’s lifted method call, with a feature theycall source unification . FRJ effectively treats all arguments to a lifted functionas a single input. FRJ’s behaviour has some interesting implications for glitchfreedom. Because FRJ’s evaluation model provides for single source glitch free-dom in the same way as XFRP [18], and all arguments to a lifted function canbe considered a single source; FRJ has complete glitch freedom [13].As a possible implementation technique, FRJ actors can be implementedwith a variety of actor frameworks, including Akka [11]. Alternatively, there isno reason why a simpler technique, like Emfrp’s actor implementation [21] couldnot be used. Similarly to
Pony [3], our actor system uses shared-memory messagepassing guarded by reference and object capabilities.
In this foundational work, we defined FRJ, a core OO calculus modelling bothFRP and actor systems. FRJ supports traditional imperative field updates andI/O, but it keeps control of side effects using reference and object capabilities.The work on FRJ is far from complete, we plan to formally model generics andlambdas, and to study possible efficient implementation strategies. Garbage col-lection may require particular attention since it can stop running computations.We plan to relax the restriction on the state of mutable actors, and to developsome case study, to explore useful programming patterns mixing Actors andFRP, and potentially to look into applying more performant and newer formsof FRP such as
Yampa ’s version of arrowized FRP [2, 16]. FRJ can model mostActor, FRP, and RP patterns. For example, a signal supplier can model hotor cold signals [10] by either returning a reference to an existing signal or byreturning a newly created one. FRJ’s signals can be finite or infinite, and theycan either be connected with real world devices or just manipulate objects inmemory. FRJ streams can be dynamically created and wired while preservingequational reasoning for all expressions that only take in immutable values asinput. Although a proof is still future work, we believe FRJ preserves two formalproperties: – If the reduction of an expression e is nondeterministic, then e refers to apre-existing mutable value. – There are no data races, that is: for all well-typed expressions, two differentnondeterministic reduction steps will not execute a field update on the samereceiver object.
References
1. Boyland, J.: Checking interference with fractional permissions. In: InternationalStatic Analysis Symposium. pp. 55–72. Springer (2003)2. Chupin, G., Nilsson, H.: Functional reactive programming, restated. In: Proceed-ings of the 21st International Symposium on Principles and Practice of Program-ming Languages 2019. pp. 1–14 (2019)3. Clebsch, S., Drossopoulou, S., Blessing, S., McNeil, A.: Deny capabilities for safe,fast actors. In: Proceedings of the 5th International Workshop on ProgrammingBased on Actors, Agents, and Decentralized Control. pp. 1–12 (2015)4. Czaplicki, E.: Elm: Concurrent frp for functional guis. Senior thesis, Harvard Uni-versity (2012)5. Czaplicki, E., Chong, S.: Asynchronous functional reactive programming for guis.ACM SIGPLAN Notices (6), 411–422 (2013)6. Finifter, M., Mettler, A., Sastry, N., Wagner, D.: Verifiable functional purity injava. In: Proceedings of the 15th ACM conference on Computer and communica-tions security. pp. 161–174 (2008)7. Giannini, P., Servetto, M., Zucca, E., Cone, J.: Flexible recovery of uniqueness andimmutability. Theoretical Computer Science , 145–172 (2019)moothly Navigating between FRP and Actors 158. Hogg, J.: Islands: Aliasing protection in object-oriented languages. In: Conferenceproceedings on Object-oriented programming systems, languages, and applications.pp. 271–285 (1991)9. Igarashi, A., Pierce, B.C., Wadler, P.: Featherweight java: a minimal core calcu-lus for java and gj. ACM Transactions on Programming Languages and Systems(TOPLAS) (3), 396–450 (2001)10. Liberty, J., Betts, P., Turalski, S.: Programming Reactive Extensions and LINQ.Springer (2011)11. Lightbend, I.: Akka: build concurrent, distributed, and resilient message-drivenapplications for java and scala, https://akka.io/12. Lohstroh, M., Lee, E.A.: Deterministic actors. In: 2019 Forum for Specificationand Design Languages (FDL). pp. 1–8 (2019)13. Margara, A., Salvaneschi, G.: On the semantics of distributed reactive program-ming: the cost of consistency. IEEE Transactions on Software Engineering (7),689–711 (2018)14. Melicher, D., Shi, Y., Potanin, A., Aldrich, J.: A Capability-BasedModule System for Authority Control. In: M¨uller, P. (ed.) 31st Eu-ropean Conference on Object-Oriented Programming (ECOOP 2017).Leibniz International Proceedings in Informatics (LIPIcs), vol. 74,pp. 20:1–20:27. Schloss Dagstuhl–Leibniz-Zentrum fuer Informatik,Dagstuhl, Germany (2017). https://doi.org/10.4230/LIPIcs.ECOOP.2017.20,http://drops.dagstuhl.de/opus/volltexte/2017/727015. Myter, F., Scholliers, C., De Meuter, W.: Distributed reactive programming forreactive distributed systems. arXiv pp. arXiv–1902 (2019)16. Nilsson, H., Courtney, A., Peterson, J.: Functional reactive programming, contin-ued. In: Proceedings of the 2002 ACM SIGPLAN workshop on Haskell. pp. 51–64(2002)17. ReactiveX: Reactivex - interval operator, http://reactivex.io/documentation/operators/interval.html18. Shibanai, K., Watanabe, T.: Distributed functional reactive programming on actor-based runtime. In: Proceedings of the 8th ACM SIGPLAN International Workshopon Programming Based on Actors, Agents, and Decentralized Control. pp. 13–22(2018)19. Van den Vonder, S., De Koster, J., Myter, F., De Meuter, W.: Tackling the awkwardsquad for reactive programming: the actor-reactor model. In: Proceedings of the 4thACM SIGPLAN International Workshop on Reactive and Event-Based Languagesand Systems. pp. 27–33 (2017)20. Wan, Z., Taha, W., Hudak, P.: Event-driven frp. In: International Symposium onPractical Aspects of Declarative Languages. pp. 155–172. Springer (2002)21. Watanabe, T., Sawada, K.: Towards an actor-based execution model of an frp lan-guage for small-scale embedded systems. Information Processing Society of JapanSIG Technical Report2016-EMB-43