towards type safe composition of actors
play

Towards Type-safe Composition of Actors Dominik Charousset, January - PowerPoint PPT Presentation

Towards Type-safe Composition of Actors Dominik Charousset, January 2016 1 Problem Statement 1. Actors lack (message) interfaces 2. Actors hard-code receivers 3. Actors do not compose 2 Actors lack Interfaces Erlang and Akka use dynamic


  1. Towards Type-safe Composition of Actors Dominik Charousset, January 2016 1

  2. Problem Statement 1. Actors lack (message) interfaces 2. Actors hard-code receivers 3. Actors do not compose 2

  3. Actors lack Interfaces • Erlang and Akka use dynamic typing for actors • No type checking of messages at sender • Burdens developer with correctness checking • SALSA, Pony, Charm++, etc. use an OO design • Member function names identify message types • Static type checking but inflexible, tight coupling 3

  4. Actors Hard-code Receivers • Erlang-like implementations use explicit sends only • Request/response modeled via two sends • Programmers implement pipelining manually • OO-inspired designs have call semantics • Request/response modeled via return values • Caller always receives results 4

  5. Actors do not Compose • No system offers configurable message flows • Users cannot define actors in terms of other actors 5

  6. Composability* • A type is composable if instances of it can be combined to produce the same or a similar type • Abstraction that enables gluing solutions together • Reuse existing components without modification • Modularize architecture * Loosely based on the definition in "Why Functional Programming Matters" 6

  7. Composability in FP • Functions are first-class citizens • Can be stored in variables / bound to names • Can be passed to (higher order) functions • Functions use parametric polymorphism* • Generic while maintaining full static type-safety • Transformation of data types and other functions * Allow deducing theorems for polymorphic functions, see "Theorems for free!" 7

  8. Dot Operator in Haskell • f.g composition pipes the output of g to f • Output type of g is the input type of f • f.g has input type of g and output type of f • Definition in the Haskell standard library: 
 (.) :: (b -> c) -> (a -> b) -> a -> c 
 f . g = \x -> f (g x) 8

  9. Dot Operator for Actors? • Composition requires reasoning about types: • What input types have actors F and G ? * • What output types generate F and G ? • Reasoning about types requires: • Unambiguously define input and output types • Force input -> output messaging style upon actors * We denote actors using uppercase and functions using lowercase letters 9

  10. State of the Art • Traditionally: dynamic typing, messages are tuples • Erlang, Scala Actors*, Akka • Alternative approach: OO-inspired design • SALSA, Pony, Charm++ * Deprecated since Scala 2.11 in favor of Akka 10

  11. Traditional Design • No correlation of input and output messages • Hard-coded message receivers • Atoms or case classes identify operations • Akka also offers non-messaging future-based API 11

  12. TAkka* • Attempts to add type-safety to Akka • Restricts input types for actors, but not outputs • Embeds manifests for run-time type checks • Keeps hard-coding of receivers as found in Akka * See "Typecasting actors: from Akka to TAkka" 12

  13. OO-inspired Design • Hides messages using named methods • Defines both input and output types of methods • Tightly couples caller and callee via type system • Caller needs to know type or supertype or callee • Structural types can prevent overly tight coupling 13

  14. Structural Types • Enable type-safe duck typing • Hide actual type of callee but still bind to names • Allow compiler to check compliance of interfaces • Require user-defined definition of method • Unsuitable for "dot operator"-style composition 14

  15. Conceptual Idea 1. Correlate inputs and outputs without OO design 2. Enable the runtime to manipulate message flows 3. Offer minimal set of composition primitives 4. Compose actors from other actors 15

  16. Expose Message Flow • Actors define input and output tuples as interface • Interfaces are sets of (a, b) -> (c, d) rules • Uniquely typed atoms allow to identify operations • Example interface for an associative container: 
 ('get', string) -> (int) 
 ('set', string, int) -> () 16

  17. Manipulate Message Flow • Prohibit actors from sending results manually • Generate responses from message handler results • Store messaging path in a message header • Build pipelines by redirecting responses 17

  18. Find Composition Primitives • Allow users to alter actor interfaces (unary) • Pre-define or re-order input and output types • Create adapter interface for further composition • Enable user to compose two actors (binary) • Compose result further with a third actor, etc. • Structure compositions like a tree 18

  19. Compose Unary and Binary • Unary composition: partial applications / bindings • Binding inputs: H(x) = F( δ $ x) * • Binding outputs: H(x) = δ $ F(x) • Binary composition: sequential or parallel • Pipelines: H(x) = F(G(x)) • Joins: H(x) = (F(x), G(x)) * $ is the apply operator, δ is a user-defined bind operation 19

  20. Define Composed Actors • Have their own identity • Are never scheduled and have no mailbox • Manipulate messages and message paths • Can spawn actors for stateful operations (e.g. join) • Cease to exist if any of their constituents dies 20

  21. Implementation in CAF • Messaging interfaces based on tuples only • Abstract: (a, b) -> (c, d) • C++: replies_to<a, b>::with<c, d> • Composition is implemented with 4 decorators • 2 for unary compositions • 2 for binary compositions 21

  22. adapter H = F.bind( δ ) x δ $ x y H F h = λ (x) -> f( δ $ x) FP equivalent: 22

  23. result adapter H = F.rbind( δ ) x x y δ $ y H F H’ h = λ (x) -> δ $ f(x) FP equivalent: 23

  24. sequencer H = F * G x x y z H G F h = f · g FP equivalent: 24

  25. splitter H = splice(F, G) F y x x (y, z) H H’ x z G h = λ (x) -> (f(x), g(x)) FP equivalent: 25

  26. Types of Composed Actors • Unary composition: • Remove types for partial applications • Select all possible clauses for reordering via bind • Binary composition: • Sequencer accept in(G) , return F(G(x)) • Splitter accept in(F) ∩ in(G) , return (F(x), G(x)) * in(F) denotes all accepted input tuples for F 26

  27. Currying calculator adder (‘add’, int, int) -> (int) (‘sub’, int, int) -> (int) = .bind(‘add’, _1, _2) (int, int) -> (int) (‘mul’, int, int) -> (int) (‘div’, int, int) -> (int) 27

  28. Binding calculator doubler (‘add’, int, int) -> (int) (‘sub’, int, int) -> (int) = .bind(‘mul’, _1, 2) (int) -> (int) (‘mul’, int, int) -> (int) (‘div’, int, int) -> (int) 28

  29. Prefixing Results calculator adder (int) -> (int) (int) -> (‘res’, int) = .rbind(‘res’, _1) (int, int) -> (int, int) (int, int) -> (‘res’, int) 29

  30. Pipelining F G H (int, string) -> (int) (int) -> (double) (int, string) -> (double) ○ = (int, double) -> (string, int) (string, string) -> (string) (string) -> (string) (string) -> (string, string) 30

  31. Joining F G H (int) -> (int, int) (int) -> (double) (int) -> (double, int, int) ++ = (string) -> (int) (string) -> (string) (string) -> (string, int) (string, string) -> (bool) 31

  32. Error Handling • Escalate errors always to original sender • Indicate at what stage error occurred • Trivial for sequencers: either F or G sends error • Splitters have 3 possible error states 32

  33. Error States of Splitters 1. F failed, but not G 2. G failed, but not F 3. Both F and G failed ➡ Transmit result of F and G regardless of error 33

  34. splitter H = splice(F, G) F ERR x x H H’ x z G ((F , ERR), (G, z)) 34

  35. Re-using Inputs • Consider • H(x) = F(x, G(x)) • H(x) = F(x, F(x, x)) • How to multiply inputs for later stages? 35

  36. H = F * splice(?, G) ? x x x (x, G(x)) H H’ F x G(x) G h = λ (x) -> f(x, g(x)) FP equivalent: 36

  37. H = F.bind(_1, _3) * splice(?, F) * splice(?, ?) ? ? x (x, x) x (x, x) x (x, x, F(x, x)) (x, F(x, x)) H H’ H’' H''' F x (x, x) x F(x, x) ? F h = λ (x) -> f(x, f(x, x)) FP equivalent: 37

  38. Idea: Pseudo Actors • Enable more complex composition • From the examples: ? denotes the "identity actor" • Have polymorphic interface (depends on the input) 38

  39. Efficiency • Not the goal of this work, but a side effect • Concurrent setting: fewer scheduler cycles • Distributed setting: fewer inter-node messages • Composed actors are considered as values • Never referenced across nodes, always copied 39

  40. Reactive Programming RP • Type-safe composable actors overlap with RP • Accumulated updates are glitch-free by design 40

  41. Example RP Case Study * from "Distributed REScala: An Update Algorithm for Distributed Reactive Programming" 41

  42. Conclusion • We have a more functional take on actors • Composability increases expressive power of CAF • New API has overlap to distr. reactive programming • More robust and efficient than user-generated code 42

  43. Future Work • Pseudo actors with polymorphic interfaces • Higher-level buildings blocks based on primitives • Explore connections and tradeoffs to RP • Compare generated code / "ease of use" • Evaluate differences in performance/networking 43

Recommend


More recommend