Presentation is loading. Please wait.

Presentation is loading. Please wait.

Matthew Heaney1 Implementing Design Patterns in Ada95 Tips, Tricks,and Idioms by Matthew Heaney.

Similar presentations


Presentation on theme: "Matthew Heaney1 Implementing Design Patterns in Ada95 Tips, Tricks,and Idioms by Matthew Heaney."— Presentation transcript:

1 Matthew Heaney1 Implementing Design Patterns in Ada95 Tips, Tricks,and Idioms by Matthew Heaney

2 Matthew Heaney2 Send a message with the body subscribe patterns to the ACM mailing list server: Search the archives for pattern implementations: Join the Ada95 patterns list!

3 Matthew Heaney3 Interpreter

4 Matthew Heaney4 Whats An Interpreter? Interpret sentences in a language specified by a grammar. Each production in the grammar is implemented as a type. As a lexical expression is parsed, an object is created for each production in the sentence.

5 Matthew Heaney5 Boolean Expression Grammar ::= | | | | ::= and ::= or ::= not ::= ::= true | false

6 Matthew Heaney6 package Bool_Exps is type Bool_Exp (<>) is abstract tagged limited private; type Bool_Exp_Access is access all Bool_Exp'Class; function Eval (Exp : access Bool_Exp; Context : in Exp_Context) return Boolean is abstract; function Copy (Exp : access Bool_Exp) return Bool_Exp_Access is abstract;...

7 Matthew Heaney7... procedure Free (Exp : in out Bool_Exp_Access); private type Bool_Exp is abstract tagged limited null record; procedure Do_Free (Exp : access Bool_Exp);... end Bool_Exps;

8 Matthew Heaney8 The Need For Indirection Some expressions (,, ) contain other expressions. We dont know the size of the expression component (of type Bool_ExpClass, which is indefinite), so we must refer to it indirectly. Containment by reference instead of containment by value.

9 Matthew Heaney9 package Bool_Exps.And_Exps is type And_Exp is new Bool_Exp with private; function New_And (L, R : access Bool_Exp'Class) return Bool_Exp_Access; function Eval (Exp : access And_Exp; Context : in Exp_Context) return Boolean;...

10 Matthew Heaney10... private type And_Exp is new Bool_Exp with record L, R : Bool_Exp_Access; end record; procedure Do_Free (Exp : access And_Exp); end Bool_Exps.And_Exps;

11 Matthew Heaney11 Desiderata We want prevent the client from creating or destroying instances directly, to allow each type to define and enforce its own storage management policy, and hide the details. We want to minimize syntactic overhead (dont want to have to explicitly dereference a pointer).

12 Matthew Heaney12 Implementation Designated type is limited and indefinite. Limited-ness prevents (shallow) copies, and indefinite-ness prevents direct allocation. Each specific type declares a constructor, so a client can create instances of type. (A client never calls allocator new directly.)

13 Matthew Heaney13 Primitive operations take access parameters. Therefore, no explicit dereferencing is necessary. Each type declares its own private deconstructor, which performs type-specific clean-up prior to actual deallocation. The client reclaims storage for an object by explicitly calling a public, class-wide deconstructor, which is implemented by calling the types private deconstructor.

14 Matthew Heaney14 ((True and X) or (Y and (not X))) declare Exp : Bool_Exp_Access := New_Or (New_And (New_Const (True), New_Var ('X')), New_And (New_Var ('Y'), New_Not (New_Var ('X')))); Exp_Value : constant Boolean := Eval (Exp, Context); begin Free (Exp); end;

15 Matthew Heaney15 package body Bool_Exps.And_Exps is... function Eval (Exp : access And_Exp; Context : in Exp_Context) return Boolean is begin return Eval (Exp.L, Context) and Eval (Exp.R, Context); end Eval;

16 Matthew Heaney16 package body Bool_Exps.And_Exps is type And_Exp_Access is access all And_Exp; function New_And (L, R : access Bool_Exp'Class) return Bool_Exp_Access is Exp : constant And_Exp_Access := new And_Exp; begin Exp.L := Bool_Exp_Access (L); Exp.R := Bool_Exp_Access (R); return Bool_Exp_Access (Exp); end New_And;

17 Matthew Heaney17 Deallocation Client manually calls a class-wide Free operation to deallocate an expression object. Free cant deallocate the (class-wide) object directly, because type-specific clean-up may be required. Free internally calls the private operation Do_Free, which dispatches according to the objects tag. The type itself does the clean- up and actual deallocation. Free is an example of a template method.

18 Matthew Heaney18 body Bool_Exps is... procedure Free (Exp : in out Bool_Exp_Access) is begin if Exp /= null then Do_Free (Exp); -- dispatches Exp := null; end if; end Free; end Bool_Exps;

19 Matthew Heaney19 package body Bool_Exps.And_Exps is... procedure Do_Free (Exp : access And_Exp) is procedure Deallocate is new Ada.Unchecked_Deallocation (And_Exp, And_Exp_Access); EA : And_Exp_Access := And_Exp_Access (Exp); begin Do_Free (Exp.L); Do_Free (Exp.R); Deallocate (EA); end Do_Free; end Bool_Exps.And_Exps;

20 Matthew Heaney20 Constant Expressions The type Const_Exp has only two values: True and False. Because objects are referred to indirectly, multiple clients can share the same object, thus avoiding allocation of duplicate values. This is an example of the Flyweight pattern.

21 Matthew Heaney21 package Bool_Exps.Const_Exps is pragma Elaborate_Body; type Const_Exp is new Bool_Exp with private;... function New_Const (Value : Boolean) return Bool_Exp_Access;...

22 Matthew Heaney22 package body Bool_Exps.Const_Exps is type Const_Exp_Array is array (Boolean) of aliased Const_Exp; Const_Exps : Const_Exp_Array; function New_Const (Value : Boolean) return Bool_Exp_Access is begin return Const_Exps (Value)'Access; end;...

23 Matthew Heaney23... begin for Value in Const_Exps'Range loop Const_Exps (Value).Value := Value; end loop; end Bool_Exps.Const_Exps;

24 Matthew Heaney24 Smart Pointers

25 Matthew Heaney25 Motivation One issue with the Interpreter example is that deallocation of expression objects must be done manually by the client, by explicitly calling Free. This is an obvious source of memory leaks and dangling references. Besides its being prone to error, explicit deallocation also carries a fair amount of syntactic overhead.

26 Matthew Heaney26 Perform_Mental_Gymnastics: declare Replacement : Bool_Exp_Access := New_Not (New_Var ('Z')); Rep_Exp : constant Bool_Exp_Access := Replace (Exp, 'Y', Replacement); begin Free (Replacement); Free (Exp); Exp := Rep_Exp; end Perform_Mental_Gymnastics;

27 Matthew Heaney27 Desiderata Low syntactic overhead. Manipulation of smart pointers should be similar to regular access objects. By-reference semantics implies a reference- counting scheme. No explicit deallocation is ever required. Implies use of a Controlled type.

28 Matthew Heaney28 package Bool_Exps is type Bool_Exp (<>) is abstract tagged limited private; type Bool_Exp_Access is access all Bool_Exp'Class; type Exp_Handle is private; function "+" (Handle : Exp_Handle) return Bool_Exp_Access; function Null_Handle return Exp_Handle;...

29 Matthew Heaney29 private type Bool_Exp is abstract tagged limited record Count : Natural; end record; type Exp_Handle_Rep is new Controlled with record Exp : Bool_Exp_Access; end record; procedure Adjust (Handle :...) procedure Finalize (Handle :...); type Exp_Handle is record Rep : Exp_Handle_Rep; end record;

30 Matthew Heaney30 ((True and X) or (Y and (not X))) declare Exp : constant Exp_Handle := New_Or (New_And (New_Const (True), New_Var ('X')), New_And (New_Var ('Y'), New_Not (New_Var ('X')))); Exp_Value : constant Boolean := Eval (+Exp, Context); begin null; end;

31 Matthew Heaney31 declare Replacement : Bool_Exp_Access := New_Not (New_Var ('Z')); Rep_Exp : constant Bool_Exp_Access := Replace (Exp, 'Y', Replacement); begin Free (Replacement); Free (Exp); Exp := Rep_Exp; end; Without Smart Pointers

32 Matthew Heaney32 declare Replacement : constant Exp_Handle := New_Not (New_Var ('Z')); begin Exp := Replace (+Exp, 'Y', Replacement); end; With Smart Pointers

33 Matthew Heaney33 function Eval (Exp : access And_Exp; Context : in Exp_Context) return Boolean is begin return Eval (+Exp.L, Context) and Eval (+Exp.R, Context); end Eval;

34 Matthew Heaney34 Implementation A smart pointer is a non-limited type that privately derives from Controlled, and has an access object as its only component. It uses unary plus + to return the value of the internal access object, which is used immediately as the actual parameter in subprogram calls.

35 Matthew Heaney35 The type designated by the access type has a Count component to store the number of references. When the reference count drops to zero (meaning there are no more references to the object), the designated object is automatically returned to storage.

36 Matthew Heaney36 Consequences Constructors now return Exp_Handle instead of Bool_Exp_Access. Expression components (of,, and expressions) are now of type Exp_Handle. Small syntactic penalty necessary to dereference handle object.

37 Matthew Heaney37 package Bool_Exps.And_Exps is type And_Exp is new Bool_Exp with private; function New_And (L, R : Exp_Handle) return Exp_Handle; function Eval (Exp : access And_Exp; Context : in Exp_Context) return Boolean;...

38 Matthew Heaney38... private type And_Exp is new Bool_Exp with record L, R : Exp_Handle; end record; procedure Do_Free (Exp : access And_Exp); end Bool_Exps.And_Exps;

39 Matthew Heaney39 function New_And (L, R : Exp_Handle) return Exp_Handle is Exp : constant And_Exp_Access := new And_Exp; Handle_Rep : constant Exp_Handle_Rep := (Controlled with Exp => Exp.allAccess); begin Exp.Count := 1; Exp.L := L; Exp.R := R; return (Rep => Handle_Rep); end New_And; Allocation

40 Matthew Heaney40 Assignment During assignment, the private operation Adjust is called to increment the reference count of the object designated by the pointer.

41 Matthew Heaney41 package body Bool_Exps is... procedure Adjust (Handle : in out Exp_Handle_Rep) is begin if Handle.Exp /= null then Handle.Exp.Count := Handle.Exp.Count + 1; end if; end Adjust;...

42 Matthew Heaney42 Deallocation When a smart pointer is assigned a new value, or goes out of scope, then the private operation Finalize is called. Finalize decrements the reference count of the designated object. If the reference count is zero, then Finalize also returns the object to storage, by calling (dispatching operation) Do_Free.

43 Matthew Heaney43 package body Bool_Exps is... procedure Finalize (Handle : in out Exp_Handle_Rep) is begin if Handle.Exp /= null then Handle.Exp.Count := Handle.Exp.Count - 1; if Handle.Exp.Count = 0 then Do_Free (Handle.Exp); end if; end Finalize;

44 Matthew Heaney44 package body Bool_Exps.And_Exps is... procedure Do_Free (Exp : access And_Exp) is EA : And_Exp_Access := And_Exp_Access (Exp); procedure Deallocate is new Ada.Unchecked_Deallocation (And_Exp, And_Exp_Access); begin pragma Assert (Exp.Count = 0); Exp.L := Null_Handle; Exp.R := Null_Handle; Deallocate (EA); end; end Bool_Exps.And_Exps;

45 Matthew Heaney45 Dereferencing The deference operator (+) for the smart pointer has a trivial implementation: it simply returns the internal access value. A weakness of the whole approach is that it depends on clients never making a copy or otherwise manipulating the access value. Limited access types or garbage-collecting storage pools would be a helpful addition to the language.

46 Matthew Heaney46 package body Bool_Exps is... function "+" (Handle : Exp_Handle) return Bool_Exp_Access is begin return Handle.Rep.Exp; end; function Null_Handle return Exp_Handle is begin return (Rep => Controlled with null); end;... end Bool_Exps;

47 Matthew Heaney47 Observer

48 Matthew Heaney48 Motivation Its often the case that when the state changes in one object, another object needs to be notified of the change. There are a couple of ways of implementing this. The subject can know who its observers are by name, and tell them directly about the state change; or,

49 Matthew Heaney49 The subject only knows that its being observed. It tells its observer that its state has changed, and then the observer queries the subject for the new state. A consequence of the former approach is that every time a new observer is added to the system, the subject must be modified to update yet another observer. The latter approach doesnt suffer from this, because the observer just inserts itself into a list of anonymous observers.

50 Matthew Heaney50 package Subjects_And_Observers is type Subject is tagged limited private; procedure Notify (Sub : in out Subject'Class); type Observer is abstract tagged limited private; procedure Update (Obs : access Observer) is abstract;...

51 Matthew Heaney51... procedure Attach (Obs : access Observer'Class; To : in out Subject); procedure Detach (Obs : access Observer'Class; From : in out Subject); private... end Subjects_And_Observers;

52 Matthew Heaney52 Subjects_And_Observers (public) Abstractions that wish to be observed derive from Subject. When the state changes, the abstraction calls Notify to let observers know about the change. Abstractions that wish to observe a subject derive from Observer, and Attach themselves to a subject. They must override Update, which is called by the subject during the notification.

53 Matthew Heaney53 private type Observer_Access is access all ObserverClass; type Subject is tagged limited record Head : Observer_Access; end record; type Observer is abstract tagged limited record Next : Observer_Access; end record; end Subjects_And_Observers;

54 Matthew Heaney54 Subjects_And_Obsrvrs (private) The subject type is implemented as a linked list of observers. When an observer wants to be notified of a state change in the subject, it places itself on the subjects list of observers. During a notification, the subject traverses the list, updating each observer in turn.

55 Matthew Heaney55 package body Subjects_And_Observers is procedure Notify (Sub : in out Subject'Class) is Obs : Observer_Access := Sub.Head; begin while Obs /= null loop Update (Obs); Obs := Obs.Next; end loop; end Notify;...

56 Matthew Heaney56 package Clock_Timers is type Clock_Timer is new Subject with private; procedure Tick (Timer : in out Clock_Timer); subtype Hour_Number is Natural range 0.. 23; function Get_Hour (Timer : Clock_Timer) return Hour_Number;... end Clock_Timers;

57 Matthew Heaney57 Clock_Timer Subject The subject Clock_Timer publicly derives from Subject, which allows it to be observed. Tick is the operation that updates the state of the clock timer, and then notifies any observers. Selector operations Get_Hour, Get_Minute, etc allow an observer to query the state.

58 Matthew Heaney58 Alternate Technique Derive from Subject privately, and provide public operations to attach an observer.

59 Matthew Heaney59 package Clock_Timers is type Clock_Timer is private; procedure Attach (Obs : access ObserverClass; To : in out Clock_Timer); … private type Clock_Timer is new Subject with record...

60 Matthew Heaney60 package body Clock_Timers is procedure Tick (Timer : in out Clock_Timer) is begin Notify (Timer); -- Update observers end Tick;... end Clock_Timers;

61 Matthew Heaney61 package Digital_Clocks is type Digital_Clock (Timer : access Clock_Timer'Class) is new Observer with null record; procedure Update (Clock : access Digital_Clock); end Digital_Clocks;

62 Matthew Heaney62 Digital_Clock Observer The observer Digital_Clock derives from Observer type. The clock observer binds to its timer subject via an access discriminant. This guarantees that the (clock) subject lives at least as long as the observer, and therefore ensures that no dangling references from observer to subject can occur.

63 Matthew Heaney63 Update is called by Notify, which is called by the timer subject just after it (the subject) has changed its state. The clock observer can see its timer subject through its access discriminant. During the Update, the clock queries the state of the timer, and then displays the time in a format specific to that observer.

64 Matthew Heaney64 package body Digital_Clocks is procedure Update (Clock : access Digital_Clock) is Hour : constant Hour_Number := Get_Hour (Clock.Timer.all); Hour_Image : constant String := Integer'Image (Hour);... Clock_Image : constant String := Hour_Image &...; begin Put_Line (Clock_Image); end Update;

65 Matthew Heaney65 declare Timer : aliased Clock_Timer; Clock : aliased Digital_Clock (Timer'Access); begin Attach (ClockAccess, To => Timer); Tick (Timer); end;

66 Matthew Heaney66 Dynamic Observers You may have an application in which observers of a subject are added and removed dynamically. We need to automate calls to Attach and Detach, to ensure no dangling reference from subject to observer occurs.

67 Matthew Heaney67 declare Timer : aliased Clock_Timer; begin … declare Clock : aliased Digital_Clock(TimerAccess); begin Attach (ClockAccess, To => Timer); Tick (Timer); end; -- Oops! Forget to Detach... Tick (Timer); -- Notify non-existent observer! end;

68 Matthew Heaney68 package Digital_Clocks is type Digital_Clock (Timer : access Clock_Timer'Class) is limited private; private … end Digital_Clocks;

69 Matthew Heaney69 private type Control_Type (Clock : access Digital_Clock) is new Limited_Controlled with null record; procedure Initialize (Control : in out Control_Type); procedure Finalize (Control : in out Control_Type); type Digital_Clock (Timer : access Clock_Timer'Class) is new Observer with record Control : Control_Type (Digital_Clock'Access); end record; procedure Update (Clock : access Digital_Clock); end Digital_Clocks;

70 Matthew Heaney70 Adding Controlled-ness We dont really need to advertise that Digital_Clock derives from Observer, so we declare the partial view of the type as limited private, and implement the full view as a derivation. Controlled-ness is added as a component of the extension, because Ada doesnt have multiple inheritance (and doesnt need it).

71 Matthew Heaney71 During its initialization, the observer inserts itself on its subjects observer list. During its finalization, the observer removes itself from its subjects observer list. This guarantees that no dangling reference from subject to observer can occur, because removal is automatic when the lifetime of the observer ends.

72 Matthew Heaney72 package body Digital_Clocks is procedure Initialize (Control : in out Control_Type) is Clock : Digital_Clock renames Control.Clock.all; Timer : Clock_Timer renames Clock.Timer.all; begin Attach (ClockAccess, To => Timer); end; procedure Finalize (Control : in out Control_Type) is Clock : Digital_Clock renames Control.Clock.all; Timer : Clock_Timer renames Clock.Timer.all; begin Detach (Clock'Access, From => Timer); end; …

73 Matthew Heaney73 declare Timer : aliased Clock_Timer; begin … declare Clock : Digital_Clock (TimerAccess); -- automatically Attach begin Tick (Timer); end; -- automatically Detach Tick (Timer); -- OK end;

74 Matthew Heaney74 Dynamic Observer Note This example was rather contrived, and was really designed to illustrate how to add Controlled-ness to an existing type hierarchy. Realistically, a dynamic observer would be declared on the heap. In that case, you could simply Attach in the constructor, and Detach in the deconstructor. A Controlled observer wouldnt be necessary.

75 Matthew Heaney75 package Digital_Clocks is type Digital_Clock(<>) is limited private; function New_Clock (Timer : access Clock_Timer) return Digital_Clock_Access;... private type Digital_Clock (Timer : access Clock_Timer) is new Observer with record...;

76 Matthew Heaney76 package body Digital_Clocks is function New_Clock (Timer : access Clock_Timer) return Digital_Clock_Access is Clock : constant Digital_Clock_Access := new Digital_Clock(Timer); begin Attach(Clock, To => Timer); return Clock; end;

77 Matthew Heaney77 Subject Displays Itself You might argue that a subject should be able to display itself, and that providing public selector operations to query the state is actually exposing implementation details about the abstraction. In that case, you may decide to make the observer more closely related to the subject, so that it can privately get the state it needs.

78 Matthew Heaney78 We can do this very simply in Ada95, by making the observer a child of the subject. This gives the observer access to the private part of the subject, obviating the need for the subject to provide any public query functions.

79 Matthew Heaney79 package Clock_Timers is type Clock_Timer is limited private; procedure Tick (Timer : in out Clock_Timer); private... type Clock_Timer is new Subject with record Hour : Hour_Number; Minute : Minute_Number; Second : Second_Number; end record; end Clock_Timers;

80 Matthew Heaney80 Clock_Timer Subject Since the Digital_Clock observers are going to be children, we can privately derive the Clock_Timer subject from Subject. Since non-observer clients dont care that its a subject, the public view of the Clock_Timer type is just (non-tagged) limited private.

81 Matthew Heaney81 package Clock_Timers.Digital_Clocks is type Digital_Clock (Timer : access Clock_Timer) is limited private; private... type Digital_Clock (Timer : access Clock_Timer) is new Observer with null record Control : Control_Type (D_ClockAccess); end record; procedure Update (Clock : access Digital_Clock); end Clock_Timers.Digital_Clocks;

82 Matthew Heaney82 Digital_Clock Observer The package Digital_Clocks is now a (public) child of Clock_Timers. As before, the Digital_Clock type privately derives from Observer. Update now queries the state of the timer directly, without using a query function. The clock has visibility to its subjects representation because the clock is a child.

83 Matthew Heaney83 package body Clock_Timers.Digital_Clocks is procedure Update (Clock : access Digital_Clock) is Hour_Image : constant String := Integer'Image (Clock.Timer.Hour + 100);... Clock_Image : constant String :=...; begin Put_Line (Clock_Image); end Update; end Clock_Timers.Digital_Clocks;

84 Matthew Heaney84 Observers Observed We now introduce another variation of our original example, which allows an observer itself to be observed, by another observer. As before, a Digital_Clock observes a Clock_Timer. Here, we add another observer, a Clock_Watcher, to observe the Digital_Clock.

85 Matthew Heaney85 package Digital_Clocks is type Digital_Clock (Timer : access Clock_Timer'Class) is new Subject with private; type Meridian_Type is (AM, PM); function Get_Meridian (Clock : Digital_Clock) return Meridian_Type; private …

86 Matthew Heaney86 Digital_Clock (public) The Digital_Clock must announce the fact that it can be observed, so it publicly derives from Subject. But its also an observer, so it binds to its Clock_Timer subject via an access discriminant. Like any subject, the Digital_Clock provides selector operations to allow its state to be queried by observers.

87 Matthew Heaney87 type Timer_Obs_Type (Clock : access Digital_Clock) is new Observer with null record; procedure Update (Timer_Obs : access Timer_Obs_Type); type Control_Type (Clock : access Digital_Clock) is new Limited_Controlled with record Timer_Obs : aliased Timer_Obs_Type (Clock); end record; procedure Initialize (Control : in out Control_Type); procedure Finalize (Control : in out Control_Type); type Digital_Clock (Timer : access Clock_Timer'Class) is new Subject with record Control : Control_Type (Digital_Clock'Access); Meridian : Meridian_Type; end record; end Digital_Clocks;

88 Matthew Heaney88 Digital_Clock (private) The Digital_Clock already derives from Subject, so in order to be an observer too it will have to have an Observer component. A helper type, Timer_Obs_Type, which derives from Observer, is used as the component. Here we also use a Controlled type to automatically Attach and Detach the observer. This wouldnt be necessary if you were to manually Attach to the subject.

89 Matthew Heaney89 package body Digital_Clocks is procedure Initialize (Control : in out Control_Type) is begin Attach (Obs => Control.Timer_Obs'Access, To => Control.Clock.Timer.all); end; procedure Finalize (Control : in out Control_Type) is begin Detach (Obs => Control.Timer_Obs'Access, From => Control.Clock.Timer.all); end;

90 Matthew Heaney90 The Control_Type can see its enclosing record (Digital_Clock) via its access discriminant. The Digital_Clock observer can see its Clock_Timer subject via its access discriminant. Together, these allow the Control_Type to Attach its Timer_Obs component to the Timer subject during Initialize, and Detach it during Finalize.

91 Matthew Heaney91 procedure Update (Timer_Obs : access Timer_Obs_Type) is begin if Hour < 12 then Timer_Obs.Clock.Meridian := AM; else Timer_Obs.Clock.Meridian := PM; end if; Notify (Timer_Obs.Clock.all); end Update; end Digital_Clocks;

92 Matthew Heaney92 As an observer, the Clock_Timer (really, the Timer_Obs_Type) must provide an implementation of Update. Update displays the new time (plays its observer role), then updates its own state and Notifys its own observers (plays its subject role). This organization has the effect of propagating a signal all the way back from the ultimate subject to the ultimate observer.

93 Matthew Heaney93 Clock_Watcher A very simple observer that observes a Digital_Clock. Per the idiom, it binds to its subject via an access discriminant. Here we manually Attach and Detach to the subject, instead of using Controlled-ness to do it automatically.

94 Matthew Heaney94 package Clock_Watchers is type Clock_Watcher (Clock : access Digital_Clock'Class) is limited private; procedure Start_Watching_Clock (Watcher : access Clock_Watcher); procedure Stop_Watching_Clock (Watcher : access Clock_Watcher); private type Clock_Watcher (Clock : access Digital_Clock'Class) is new Observer with null record; procedure Update (Watcher : access Clock_Watcher); end Clock_Watchers;

95 Matthew Heaney95 package body Clock_Watchers is procedure Start_Watching_Clock (Watcher : access Clock_Watcher) is begin Attach (Watcher, To => Watcher.Clock.all); end; procedure Stop_Watching_Clock (Watcher : access Clock_Watcher) is begin Detach (Watcher, From => Watcher.Clock.all); end;...

96 Matthew Heaney96... procedure Update (Watcher : access Clock_Watcher) is begin case Get_Meridian (Watcher.Clock.all) is when AM => Put_Line ("It's still morning."); when PM => Put_Line ("It's afternoon."); end case; end Update; end Clock_Watchers;

97 Matthew Heaney97 declare Timer : aliased Clock_Timer; Clock : aliased Digital_Clock (Timer'Access); Watcher : aliased Clock_Watcher (Clock'Access); begin Start_Watching_Clock (Watcher); Tick (Timer); end;

98 Matthew Heaney98 Observable-Observer Note There is another way to allow an observer to be both an observer and a subject. Simply change the declaration of Observer type in package Subjects_And_Observers so that it derives from Subject. Implementing the observing subject with an observer component isnt necessary, because the type is already an observer.

99 Matthew Heaney99 package Subjects_And_Observers is type Subject is tagged limited private;... type Observer is abstract new Subject with private;... end Subjects_And_Observers;

100 Matthew Heaney100 package Digital_Clocks is type Digital_Clock (Timer : access C_Timer'Class) is new Subject with private;... private type Control_Type (Clock : access Digital_Clock) is new Limited_Controlled with null record;... type Digital_Clock (Timer : access C_Timer'Class) is new Observer with record Control : Control_Type (D_Clock'Access); Meridian : Meridian_Type; end record; procedure Update (Clock : access Digital_Clock); end Digital_Clocks;

101 Matthew Heaney101 Observing Multiple Subjects We introduce yet another variation of the observer pattern, this time allowing an observer to observe multiple subjects. Now the digital clock simultaneously observes both a timer and a battery. The battery subject notifies its observer when it is drained or charged.

102 Matthew Heaney102 package Batteries is type Battery_Type is new Subject with private; procedure Charge (...); procedure Drain (...); function Is_Low (...) return Boolean; private type Battery_Type is new Subject with record State : Positive := 1; end record;

103 Matthew Heaney103 Battery Subject (spec) The battery is observable, asserting this by publicly deriving from Subject. Modifier operations Charge and Drain adjust the available energy, and then Notify any observers. A selector operation, Is_Low, queries whether there is any energy remaining.

104 Matthew Heaney104 package body Batteries is procedure Charge (Battery : in out Battery_Type) is begin Battery.State := 1; Notify (Battery); end; procedure Drain (Battery : in out Battery_Type) is begin Battery.State := Battery.State + 1; Notify (Battery); end; function Is_Low (Battery : in Battery_Type) return Boolean is begin return Battery.State > 3; end;

105 Matthew Heaney105 package Digital_Clocks is type Digital_Clock (Timer : access Clock_Timer'Class; Battery : access Battery_Type'Class) is limited private; private... end Digital_Clocks;

106 Matthew Heaney106 Digital_Clock (public) The public part of the observer type Digital_Clock has been modified to accept two access discriminants, one for each subject it observes.

107 Matthew Heaney107 private type Timer_Obs_Type (Clock : access Digital_Clock) is new Observer with null record; procedure Update (Observer : access Timer_Obs_Type); type Battery_Obs_Type (Clock : access Digital_Clock) is new Observer with null record; procedure Update (Observer : access Battery_Obs_Type);...

108 Matthew Heaney108... type Digital_Clock (Timer : access Clock_Timer'Class; Battery : access Battery_Type'Class) is new Limited_Controlled with record Timer_Obs : aliased Timer_Obs_Type (D_Clock'Access); Battery_Obs : aliased Battery_Obs_Type (D_Clock'Access); end record; procedure Initialize (Clock : in out Digital_Clock); procedure Finalize (Clock : in out Digital_Clock); end Digital_Clocks;

109 Matthew Heaney109 Digital_Clock (private) There has to be some type that derives from Observer and overrides Update to process Clock_Timer notifications. There has to be some type that derives from Observer and overrides Update to process Battery_Type notifications. The same type cant do both, because we dont have multiple inheritance in Ada95. No problem, we just use the multiple views idiom.

110 Matthew Heaney110 An internal type, Timer_Obs_Type, observes just the Clock_Timer. Another internal type, Battery_Obs_Type, observes just the Battery_Type. Each type is bound to its enclosing record, the Digital_Clock, via an access discriminant. These internal types will be used to declare the observer components of the Digital_Clock type, which itself already derives from Limited_Controlled.

111 Matthew Heaney111 package body Digital_Clocks is procedure Update (Observer : access Timer_Obs_Type) is …; procedure Update (Observer : access Battery_Obs_Type) is Clock : Digital_Clock renames Observer.Clock.all; Battery : Battery_Type'Class renames Clock.Battery.all; begin if Is_Low (Battery) then... end Update;

112 Matthew Heaney112 procedure Initialize (Clock : in out Digital_Clock) is begin Attach (Obs => Clock.Timer_Obs'Access, To => Clock.Timer.all); Attach (Obs => Clock.Battery_Obs'Access, To => Clock.Battery.all); end Initialize;

113 Matthew Heaney113 Observing Multiple Attributes One issue is that when a subject notifies an observer that a state change has occurred, the observer has no way of knowing which specific attribute has changed. This may require the observer to redo all her processing (say, redraw a window), which may be inefficient.

114 Matthew Heaney114 One solution is to make observation more fine-grained; that is, to be able to observe individual attributes of a object, instead of just one monolithic object. When an object being observed changes the value of an attribute, he can notify the observers of that one attribute. Its analogous to observing multiple subjects, but here, all the subjects are part of a single object.

115 Matthew Heaney115 package Clock_Timers is type Clock_Timer is limited private;... subtype Hour_Number is Natural range 0.. 23; function Get_Hour (Timer : access Clock_Timer) return Hour_Number; function Get_Hour_Subject (Timer : access Clock_Timer) return Subject_Access;

116 Matthew Heaney116... private type Clock_Timer is limited record Hour : Integer := -1; Hour_Subject : aliased Subject; Minute : Integer := -1; Minute_Subject : aliased Subject; Second : Integer := -1; Second_Subject : aliased Subject; end record; end Clock_Timers;

117 Matthew Heaney117 package body Clock_Timers is procedure Tick (Timer : in out Clock_Timer) is begin if Timer.Hour /= Hour then Timer.Hour := Hour; Notify (Timer.Hour_Subject); end if;... end Tick;

118 Matthew Heaney118 function Get_Hour_Subject (Timer : access Clock_Timer) return Subject_Access is begin return Timer.Hour_Subject'Access; end;

119 Matthew Heaney119 package Digital_Clocks is type Digital_Clock (Timer : access Clock_Timer) is limited private; private type H_Obs_Type (Timer : access Clock_Timer) is new Observer with null record; procedure Update (H_Obs : access H_Obs_Type);... type Digital_Clock (Timer : access Clock_Timer) is new Limited_Controlled with record H_Obs : aliased H_Obs_Type (Timer);... end record;...

120 Matthew Heaney120 package body Digital_Clocks is procedure Update (H_Obs : access H_Obs_Type) is Image : constant String := Integer'Image (Get_Hour (H_Obs.Timer) + 100); begin end; procedure Initialize (Clock : in out Digital_Clock) is begin Attach (Obs => Clock.H_Obs'Access, To => Get_Hour_Subject (Clock.Timer));

121 Matthew Heaney121 Factory Method

122 Matthew Heaney122 Motivation Suppose we have a family of stack types, and we want to provide a class-wide operation to print a stack. We plan on using an active iterator to implement the operation. Each type in the class has its own iterator. Heres the problem: if the stack parameter has a class-wide type, then how do we get an iterator that works for this stack object?

123 Matthew Heaney123 procedure Stacks.Put (Stack : in Root_Stack_TypeClass) is Iterator : := begin while not Is_Done (Iterator) loop...

124 Matthew Heaney124 What's A Factory Method? If you need an iterator for this type of stack, then just ask the stack for one. A factory method is a constructor that dispatches on one type, and returns a value of some other type. In Ada95, the return type has to be class– wide, since an operation can only be primitive for one type.

125 Matthew Heaney125 generic type Item_Type is private; package Stacks is type Root_Stack_Type is abstract tagged limited null record; type Root_Iterator_Type is abstract tagged null record; -- Heres the factory method: -- function Start_At_Top (Stack : Root_Stack_Type) return Root_Iterator_Type'Class is abstract;

126 Matthew Heaney126 procedure Stacks.Put (Stack : in Root_Stack_TypeClass) is Iterator : Root_Iterator_TypeClass := Start_At_Top (Stack); begin while not Is_Done (Iterator) loop … Get_Item (Iterator) … Advance (Iterator); end loop; New_Line; end Stacks.Put;

127 Matthew Heaney127 generic Max_Depth : in Positive; package Stacks.Bounded_G is type Stack_Type is new Root_Stack_Type with private; type Iterator_Type is new Root_Iterator_Type with private; function Start_At_Top (Stack : Stack_Type) return Root_Iterator_Type'Class;

128 Matthew Heaney128 Copying A Stack Requires care, because its easy to populate the target stack in reverse order. You can either (1) traverse the items in the source stack in bottom-to-top order, and populate the target stack in the normal way (using Push); or, You can (2) traverse the items in the source stack in top-to-bottom order, and populate the target stack in reverse order, using a special operation (like Copy).

129 Matthew Heaney129 procedure Copy_That_Does_Not_Work (From : in Root_Stack_TypeClass; To : in out Root_Stack_TypeClass) is Iter : Root_Iterator_TypeClass := Start_At_Top (From); begin if then return; end if; Clear (To); while not Is_Done (Iter) loop Push (Get_Item (Iter), On => To); Advance (Iter); end loop; end Copy_That_Does_Not_Work;

130 Matthew Heaney130 procedure Stacks.Copy -- technique (1) (From : in Root_Stack_TypeClass; To : in out Root_Stack_TypeClass) is Iterator : Root_Iterator_TypeClass := Start_At_Bottom (From); begin if FromAddress = ToAddress then -- per RM95 3.10 (9) and 13.3 (16) return; end if; Clear (To); while not Is_Done (Iterator) loop Push (Get_Item (Iterator), On => To); Backup (Iterator); end loop; end Stacks.Copy;

131 Matthew Heaney131 package body Stacks.Bounded_G is procedure Copy -- technique (2) (From : in Root_Stack_Type'Class; To : in out Stack_Type) is Depth : constant Natural := Get_Depth (From); Iterator : Root_Iterator_Type'Class := Start_At_Top (From); use type System.Address; begin...

132 Matthew Heaney132... if From'Address = To'Address then return; end if; if Depth > Max_Depth then raise Storage_Error; end if; To.Top := Depth; for I in reverse 1.. Depth loop To.Items (I) := Get_Item (Iterator); Advance (Iterator); end loop; end Copy;

133 Matthew Heaney133 Summary of Stack Copying Technique (1) requires that you be able to traverse stacks in reverse order. Technique (1) can be implemented as a class-wide operation, or as a primitive operation with a default implementation. Technique (2) must be implemented as a primitive operation, for each type, because it needs to know the types representation.

134 Matthew Heaney134 Singleton

135 Matthew Heaney135 Using a State Machine Package The package itself is the object – an instance of an anonymous type. State data is declared in the package body and manipulated via public operations. Popular in Ada because static-ness is the default for objects and operations.

136 Matthew Heaney136 with Ownship_Types; use Ownship_Types; package Ownship is procedure Update; function Get_Speed_In_Knots return Speed_In_Knots_Type; function Get_Heading_In_Deg return Heading_In_Deg_Type; procedure Set_Heading (Heading : in Heading_In_Deg_Type);... end Ownship;

137 Matthew Heaney137 with Ownship; package body P is … procedure Op is Ownship_Speed : constant Speed_In_Knots_Type := Ownship.Get_Speed_In_Knots; begin end Op;

138 Matthew Heaney138 Using a Named Type Instance creation can be controlled by the abstraction by declaring the type as limited and indefinite. The (single) instance of the type is declared in the package body. A public operation returns an access object designating the singleton instance. All operations of the type take access parameters (so no explicit deref is reqd).

139 Matthew Heaney139 package Ownships is type Ownship_Type (<>) is limited private; function Get_Speed_In_Knots (Ownship : access Ownship_Type) return Speed_In_Knots_Type;... type Ownship_Access is access all Ownship_Type; function Ownship return Ownship_Access; private type Ownship_Type is …; end Ownships;

140 Matthew Heaney140 with Ownships; use Ownships; package body P is … procedure Op is Ownship_Speed : constant Speed_In_Knots_Type := Get_Speed_In_Knots (Ownship); begin end Op;

141 Matthew Heaney141 package body Ownships is function Get_Speed_In_Knots (Ownship : access Ownship_Type) return Speed_In_Knots_Type is begin return Ownship.Speed; end;... Singleton : aliased Ownship_Type; function Ownship return Ownship_Access is begin return Singleton'Access; end; end Ownships;

142 Matthew Heaney142 Well-Known Objects Well-known objects are global abstractions that have a defined cardinality. They tend to be passive holders of system– wide state. A singleton is a well-known object whose cardinality happens to be 1. Use a discrete identifier to refer to a specific instance.

143 Matthew Heaney143 with Rodmeter_Types; use Rodmeter_Types; package Rodmeters is type Rodmeter_Id is range 1.. 2; procedure Update (Rodmeter : in Rodmeter_Id); function Get_Speed_In_Knots (Rodmeter : Rodmeter_Id) return Speed_In_Knots_Type; procedure Set_Bias (Rodmeter : in Rodmeter_Id; Bias : in Bias_In_Knots_Type); function Get_Bias (Rodmeter : Rodmeter_Id) return Bias_In_Knots_Type; end Rodmeters;

144 Matthew Heaney144 with Ownships, Ownship_Types; package body Rodmeters is type Speed_Array_Type is array (Rodmeter_Id) of Speed_In_Knots_Type; Speed_Array : Speed_Array_Type; type Bias_Array_Type is array (Rodmeter_Id) of Bias_In_Knots_Type; Bias_Array : Bias_Array_Type;...

145 Matthew Heaney145 function Get_Speed_In_Knots (Rodmeter : Rodmeter_Id) return Speed_In_Knots_Type is begin return Speed_Array (Rodmeter); end; procedure Set_Bias (Rodmeter : in Rodmeter_Id; Bias : in Bias_In_Knots_Type) is begin Bias_Array (Rodmeter) := Bias; end; function Get_Bias (Rodmeter : Rodmeter_Id) return Bias_In_Knots_Type is begin return Bias_Array (Rodmeter); end;

146 Matthew Heaney146 procedure Update (Rodmeter : in Rodmeter_Id) is OS_Speed : constant OS_Types.Speed_In_Knots_Type := Get_Speed_In_Knots (Ownship); Speed : Speed_In_Knots_Type'Base := Speed_In_Knots_Type'Base (OS_Speed) + Speed_In_Knots_Type'Base (Bias_Array (Rodmeter)); begin if Speed < 0.0 then Speed := 0.0; elsif Speed > Speed_In_Knots_Type'Last then Speed := Speed_In_Knots_Type'Last; end if; Speed_Array (Rodmeter) := Speed; end Update;

147 Matthew Heaney147 package TCP.States is type Root_State_Type (<>) is abstract tagged limited private; type State_Access is access all Root_State_Type'Class; type Root_Connection_Type is abstract tagged limited null record; procedure Set_State (Connection : in out Root_Connection_Type; State : in State_Access) is abstract; procedure Transmit (State : access Root_State_Type; Connection : in out Root_Connection_Type'Class; Item : in Stream_Element_Array);...

148 Matthew Heaney148 package TCP.States.Listen is type Listen_State_Type is new Root_State_Type with private; procedure Send (State : access Listen_State_Type; Connection : in out Root_Connection_Type'Class); function State return State_Access; private type Listen_State_Type is new Root_State_Type with null record; end TCP.States.Listen;

149 Matthew Heaney149 with TCP.States.Established; package body TCP.States.Listen is Singleton : aliased Listen_State_Type; procedure Send (State : access Listen_State_Type; Connection : in out Root_Connection_Type'Class) is begin... Set_State (Connection, Established.State); end Send; function State return State_Access is begin return Singleton'Access; end; end TCP.States.Listen;

150 Matthew Heaney150 package TCP.Connections is type Connection_Type is limited private;... private function Get_Default return State_Access; type Connection_Type is new Root_Connection_Type with record State : State_Access := Get_Default; File : Streams.File_Type; end record; procedure Set_State (Connection : in out Connection_Type; State : in State_Access); end TCP.Connections;

151 Matthew Heaney151 Strategy

152 Matthew Heaney152 Whats A Strategy? The simple answer: a fancy name for a generic formal subprogram. Its a way to parameterize a component. You effect different behavior by plugging in a different algorithm (the strategy).

153 Matthew Heaney153 generic type Item_Type is limited private; package Storage_Nodes is type Storage_Node; type Storage_Node_Access is access all Storage_Node; type Storage_Node is limited record Item : aliased Item_Type; Next : Storage_Node_Access; end record; procedure Do_Nothing (Node : in out Storage_Node); end Storage_Nodes;

154 Matthew Heaney154 with Storage_Nodes; generic with package Nodes is new Storage_Nodes (<>); use Nodes; with procedure Finalize (Node : in out Storage_Node) is Do_Nothing; package Storage is function New_Node return Storage_Node_Access; procedure Free (Node : in out Storage_Node_Access); end Storage;

155 Matthew Heaney155 package body Storage is Free_List : Storage_Node_Access; function New_Node return Storage_Node_Access is... procedure Free (Node : in out Storage_Node_Access) is begin if Node = null then return; end if; Finalize (Node.all); Node.Next := Free_List; Free_List := Node; Node := null; end Free; end Storage;

156 Matthew Heaney156 with Storage_Nodes; generic type Item_Type is private; package Unbounded_Stacks is type Stack_Type is limited private;... private package Nodes is new Storage_Nodes (Item_Type); use Nodes; type Stack_Type is limited record Top : Storage_Node_Access; end record; end Unbounded_Stacks;

157 Matthew Heaney157 with Storage; package body Unbounded_Stacks is package Stack_Storage is new Storage (Nodes); use Stack_Storage; procedure Pop (Stack : in out Stack_Type) is Node : Storage_Node_Access := Stack.Top; begin Stack.Top := Stack.Top.Next; Free (Node); end Pop; … end Unbounded_Stacks;

158 Matthew Heaney158 with Storage_Nodes; generic type Item_Type is private; package Lists is type List_Type is private; … procedure Clear (List : in out List_Type); private package Nodes is new Storage_Nodes (Item_Type); use Nodes; type List_Type is record Head : Storage_Node_Access; end record; end Lists;

159 Matthew Heaney159 with Storage; package body Lists is procedure Finalize (Node : in out Storage_Node); package List_Storage is new Storage (Nodes, Finalize); use List_Storage; procedure Finalize (Node : in out Storage_Node) is begin Free (Node.Next); end;... procedure Clear (List : in out List_Type) is begin Free (List.Head); end; end Lists;

160 Matthew Heaney160 Generic Dispatching

161 Matthew Heaney161 Motivation Suppose we want to import, as generic formal parameters, a tagged type and one of its primitive operations, and we want dynamically dispatch the operation inside the generic. The problem is that you cant dispatch on a formal subprogram, because a formal subprogram isnt primitive for a formal type.

162 Matthew Heaney162 package P is type T is tagged limited private; procedure Op (O : in out T); … end P; package P.C is type NT is new T with private; procedure Op (O : in out NT); … end P.C;

163 Matthew Heaney163 generic type T (<>) is abstract tagged limited private; with procedure Op (O : in out T) is <>; package GQ is procedure Do_Something (O : in out T'Class); end GQ;

164 Matthew Heaney164 Heres what we want to do: with P, GQ; package Q is new GQ (T => P.T, Op => P.Op);

165 Matthew Heaney165 package body GQ is procedure Do_Something (O : in out T'Class) is begin Op (O); --heres the offending line end; end GQ; gq.adb:6:10: class-wide argument not allowed here gq.adb:6:10: "Op" is not a primitive operation of "T"

166 Matthew Heaney166 The problem is that the compiler has no way of knowing (at the time of compilation of the generic) that formal procedure Op is really primitive for type T. So it assumes the worst, and doesnt allow you to dispatch on a formal operation. The actual operation we import must be statically bound to T. What we can do is import the class-wide type, TClass, and import a class-wide operation (that takes an object of type TClass) that calls the (primitive) dispatching operation.

167 Matthew Heaney167 Changes to Client Implement a new, class-wide operation (as a child, if you dont have it already): procedure P.Call_Op (O : in out T'Class) is begin Op (O); -- dispatches end;

168 Matthew Heaney168 Changes to Server Declare the formal type as non-tagged and indefinite. This allows a class-wide type to be used as the generic actual. In the generic operations, declare the formal parameters to be of type T instead of TClass. (This is required anyway, because the formal type isnt tagged anymore.)

169 Matthew Heaney169 generic type T (<>) is limited private; with procedure Op (O : in out T) is <>; package GQ is procedure Do_Something (O : in out T); end GQ;

170 Matthew Heaney170 package body GQ is procedure Do_Something (O : in out T) is begin... Op (O); -- legal (static call)... end; end GQ;

171 Matthew Heaney171 Changes to Instantiation Now lets instantiate the new version of the generic using TClass as the actual type, and our special class-wide operation as the actual operation: with GQ, P.Call_Op; package Q is new GQ (P.T'Class, P.Call_Op);

172 Matthew Heaney172 with P.C, Q; procedure Test_Q is OT : P.T; -- T is root of class ONT : P.C.NT; -- NT derives from T begin Q.Do_Something (OT); -- call Ts Op Q.Do_Something (ONT); -- call NTs Op end Test_Q;

173 Matthew Heaney173 The Rosen Trick

174 Matthew Heaney174 Ada I/O Model A communication path, such as disk I/O, socket I/O, etc, is modeled as a file. You open the file to establish communication with a device. You close the file to sever the connection to the device.

175 Matthew Heaney175 The communication path to a device is represented as a handle, which designates connection state. The state may change, but the handle itself does not.

176 Matthew Heaney176 package Files is type File_Type is limited private; procedure Open(File : in out File_Type; Name : in String); procedure Close(File : in out File_Type); procedure Read(File : in File_Type; Item : out Item_Type); procedure Write(File : in File_Type; Item : in Item_Type);

177 Matthew Heaney177 Issue Read and Write are state-changing operations, yet the File object (the handle) is passed as an in-mode parameter. How to we implement File_Type in order to implement this model?

178 Matthew Heaney178 Use the Heap private type Connection_State; type File_Type is access all Connection_State; end Files;

179 Matthew Heaney179 Use the Heap (contd) procedure Open (File : in out File_Type; Name : in String) is begin File := new Connection_State;... end Open; procedure Write (File : in File_Type...) is begin File.all :=... end;

180 Matthew Heaney180 Use the Heap: Consequences Well, requires heap use. In general, if given a choice, wed rather use the stack. The declaration of a named access type means you cant declare the package using pragma Pure. In order to prevent memory leaks, you have to implement File_Type as controlled. This adds a certain amount of heaviness.

181 Matthew Heaney181 Use Static Allocation private type File_Type is limited record Index : Natural := 0; end record; end Files;

182 Matthew Heaney182 Use Static Allocation (contd) package body Files is type Descriptor_Type is... Descriptors : array (1.. 20) of Descriptor_Type; procedure Open (File : in out File_Type;...) is begin File.Index := Get_Descriptor_Index;

183 Matthew Heaney183 Static Allocation: Consequences Limits number of file objects (although theres probably a system-defined limit anyway). Having package state means you wont be able to declare the package using pragma Pure.

184 Matthew Heaney184 Use Chapter 13 Tricks private type File_Type is limited record end record; end Files;

185 Matthew Heaney185 Chapter 13 (contd) package body Files is package Address_To_Access_Conversions is new System.Addr_To_Acc_Conversions... procedure Write (File : in File_Type;...) is FA : const Object_Pointer := To_Pointer(FileAddress); F : File_Type renames FA.all; begin

186 Matthew Heaney186 Chapter 13: Consequences Using Address turns off type-checking.

187 Matthew Heaney187 The Rosen Trick A clean way to modify a limited (by– reference) in-mode subprogram parameter, that doesnt require any Chap 13 tricks. Allocate memory for the handle directly adjacent to the connection state, on the stack. No package state is necessary.

188 Matthew Heaney188 generic type Result_Subtype is (<>); package Ada.Numerics.Discrete_Random is type Generator is limited private; function Random (Gen : Generator) return Result_Subtype;... private type Handle_Type (Gen : access Generator) is limited null record; type Generator is limited record Handle : Handle_Type (GeneratorAccess); Gen_State : State; end record;

189 Matthew Heaney189 function Random (Gen : Generator) return Result_Subtype is Gen_State : State renames Gen.Handle.Gen.Gen_State; begin return ; end Random;

190 Matthew Heaney190 type File_Type is limited private;... procedure Read (File : in File_Type; Key : in Key_Type; Item : out Item_Type); procedure Write (File : in File_Type; Item : in Item_Type); procedure Remove (File : in File_Type; Key : in Key_Type);

191 Matthew Heaney191 private... type Handle_Type (File : access File_Type) is limited null record; type File_Type is limited record Handle : Handle_Type (File_Type'Access); File : Stream_IO.File_Type; File_Index : Stream_IO.Positive_Count; Root_Page : Page_Type; end record;

192 Matthew Heaney192 procedure Read (File : in File_Type; Key : in Key_Type; Item : out Item_Type) is Index : Natural; Found : Boolean; File_Index : Stream_IO.Count; Page : Page_Type; begin Set_Mode (File.Handle.File.File, In_File);...

193 Matthew Heaney193 procedure Write (File : in out File_Type; Item : in Item_Type) is... begin Set_Mode (File.Handle.File.File, In_File);... Reset (File.Handle.File.File); Count'Write (Stream (File.File), File.File_Index); Flush (File.Handle.File.File); end Write;

194 Matthew Heaney194 Collections Of Limited Items

195 Matthew Heaney195 Generic data structures typically declare the generic formal item type as non-limited: generic type Item_Type is private; Max_Depth : in Positive; package Stacks is …;

196 Matthew Heaney196 This gives you assignment, allowing you to implement the insertion operation by copying the formal parameter: procedure Push (Item : in Item_Type; On : in out Stack_Type) is begin On.Top := On.Top + 1; On.Items (On.Top) := Item; -- copy end Push;

197 Matthew Heaney197 Other modifier operations are implemented the same way, by copying the formal parameter: procedure Set_Top (Stack : in out Stack_Type; Item : in Item_Type) is begin Stack.Items (Stack.Top) := Item; -- copy end;

198 Matthew Heaney198 But suppose you need a collection of items whose type is limited? with Stacks; with Ada.Text_IO; use Ada.Text_IO; package File_Stacks is new Stacks (Item_Type => File_Type, -- illegal Max_Depth => 10);

199 Matthew Heaney199 We can solve the problem by rewriting modifier operations as functions that return a reference to the object. This works because assignment isnt necessary for the implementation of these special modifiers. Its the client who does the modify, by manipulating the actual item (instead of the supplier internally replacing it with a copy).

200 Matthew Heaney200 Stack : aliased Stack_Type;... declare File : File_Type renames Push (StackAccess).all; -- -- Theres now a new File object on -- the top of the Stack. begin Open (File, Out_File, myfile.dat);... Reset (File);... Close (File); end; Pop (Stack);

201 Matthew Heaney201 Now lets see what Set_Top will look like: Stack : aliased Stack_Type; … Reset (File => Set_Top (StackAccess).all); … Close (File => Set_Top (StackAccess).all);

202 Matthew Heaney202 Strictly speaking, Push doesnt have to be implemented by returning an access object, if you have Set_Top too: Push (Stack); -- note: no Item parameter … Open (File => Set_Top (StackAccess).all, Mode => Out_File, Name => myfile.dat);

203 Matthew Heaney203 How do we do it? (public part) Declare the generic formal item type as limited private. Declare a general access type that designates the item type. Declare the modifier operations as functions that return the access type, and take the stack as an access parameter.

204 Matthew Heaney204 generic type Item_Type is limited private; Max_Depth : in Positive; package Stacks is type Stack_Type is limited private; type Item_Access is access all Item_Type; function Push (Stack : access Stack_Type) return Item_Access; function Set_Top (Stack : access Stack_Type) return Item_Access;...

205 Matthew Heaney205 How do we do it? (private part) Declare the item array with aliased components.

206 Matthew Heaney206 private type Item_Array is array (1.. Max_Depth) of aliased Item_Type; subtype Top_Range is Natural range 0.. Max_Depth; type Stack_Type is limited record Items : Item_Array; Top : Top_Range := 0; end record; end Stacks;

207 Matthew Heaney207 How do we do it? (body) Implement the modifier operations by returning an access object designating the item on the top of the stack.

208 Matthew Heaney208 package body Stacks is function Push (Stack : access Stack_Type) return Item_Access is begin Stack.Top := Stack.Top + 1; return Stack.Items (Stack.Top)Access; end; function Set_Top (Stack : access Stack_Type) return Item_Access is begin return Stack.Items (Stack.Top)Access; end;...

209 Matthew Heaney209 How do we do it? (body) The alternate version of Push only needs to increment the Top index (you actually modify the item later, using Set_Top): procedure Push (Stack : in out Stack_Type) is begin Stack.Top := Stack.Top + 1; end;

210 Matthew Heaney210 For non-limited items too This technique isnt specific to collections of limited items. You could use it for non- limited items too, if you prefer to modify the actual item in place, instead of (internally) replacing the item with a copy. Set_Top (StackAccess).all := Value; Modify (Item => Set_Top (StackAccess).all);

211 Matthew Heaney211 Applied to active iterators You can generalize this idea, allowing you to modify any item in the collection, by using an active iterator: Set_Item (Iterator).all := Value; Modify (Item => Set_Item (Iterator).all);

212 Matthew Heaney212 Command

213 Matthew Heaney213 Whats A Command Object? An object that manages the processing that occurs when a user issues a command. Store the command in a ring and use it to implement undo and redo. Store the command on disk to implement record and playback. Store the command in a queue and execute it later.

214 Matthew Heaney214 package Commands is type Root_Command_Type (<>) is abstract tagged limited private; type Command_Access is access all Root_Command_Type'Class; procedure Execute (Command : access Root_Command_Type) is abstract; procedure Free (Command : in out Command_Access); …

215 Matthew Heaney215 … private type Root_Command_Type is abstract tagged limited null record; procedure Do_Free (Command : access Root_Command_Type); end Commands;

216 Matthew Heaney216 package body Commands is procedure Free (Command : in out Command_Access) is begin if Command /= null then Do_Free (Command); Command := null; end if; end Free; procedure Do_Free (Command : access Root_Command_Type) is begin null; end; end Commands;

217 Matthew Heaney217 package Commands.Open_Commands is type Command_Type is new Root_Command_Type with private; function New_Command (App : access Application_Type'Class) return Command_Access; procedure Execute (Command : access Command_Type); …

218 Matthew Heaney218... private type Command_Type (App : access Application_Type'Class) is new Root_Command_Type with null record; procedure Do_Free (Command : access Command_Type); end Commands.Open_Commands;

219 Matthew Heaney219 package body Commands.Open_Commands is type Open_Command_Access is access all Command_Type; procedure Deallocate is new Ada.Unchecked_Deallocation (Command_Type, Open_Command_Access); function New_Command (App : access Application_Type'Class) return Command_Access is Command : constant Open_Command_Access := new Command_Type (App); begin return Command_Access (Command); end;

220 Matthew Heaney220 procedure Do_Free (Command : access Command_Type) is CA : Open_Command_Access := Open_Command_Access (Command); begin Deallocate (CA); end; procedure Execute (Command : access Command_Type) is Doc : constant Document_Access := New_Doc (Name => Get_Filename_From_User); begin Add (Doc, To => Command.App); Documents.Open (Doc); end Execute; end Commands.Open_Commands;

221 Matthew Heaney221 App : aliased Application_Type;... declare Command : Command_Access := Open_Commands.New_Command (App'Access); begin end; … procedure Execute_Menu_Function_CB (W : in Widget) is begin Execute (W.Command); end;

222 Matthew Heaney222 generic type Receiver_Type (<>) is limited private; with procedure Action (Receiver : in out Receiver_Type); package Commands.Simple is type Command_Type is new Root_Command_Type with private; function New_Command (Receiver : access Receiver_Type) return Command_Access; procedure Execute (Command : access Command_Type);... end Commands.Simple;

223 Matthew Heaney223 package body Commands.Simple is... procedure Execute (Command : access Command_Type) is begin Action (Command.Receiver.all); end; end Commands.Simple;

224 Matthew Heaney224 package Applications is type Application_Type is tagged limited private;... end Applications; procedure Applications.Count_Docs (App : in out Application_Type'Class);

225 Matthew Heaney225 with Applications.Count_Docs; use Applications; with Commands.Simple; package Commands.App_Commands is new Simple (Receiver_Type => Application_Type'Class, Action => Count_Docs); (This is similar to the generic dispatching trick.)

226 Matthew Heaney226 Composite

227 Matthew Heaney227 Whats A Composite? Technique for assembling recursive data structures. Example: a macro command (a command comprising a set of commands). Example: UNIX directory tree (a directory is a file comprising other files).

228 Matthew Heaney228 package Equipment is type Equipment_Type (<>) is abstract tagged limited private; type Equipment_Access is access all Equipment_Type'Class; type Power_In_Watts_Type is delta 0.1 range 0.0.. 100.0; type Dollars_Type is delta 0.01 digits 6; function Get_Power_In_Watts (Equipment : access Equipment_Type) return Power_In_Watts_Type is abstract; function Get_Price_In_Dollars (Equipment : access Equipment_Type) return Dollars_Type is abstract;

229 Matthew Heaney229 type Composite_Type is abstract new Equipment_Type with private; type Composite_Access is access all Composite_Type'Class; function Get_Power_In_Watts (Composite : access Composite_Type) return Power_In_Watts_Type; function Get_Price_In_Dollars (Composite : access Composite_Type) return Dollars_Type; procedure Add (Equipment : access Equipment_Type'Class; To : access Composite_Type);

230 Matthew Heaney230 procedure Free (Equipment : in out Equipment_Access); private type Equipment_Type is abstract tagged limited record Next : Equipment_Access; end record; procedure Do_Free (Equipment : access Equipment_Type); …

231 Matthew Heaney231 type Composite_Type is abstract new Equipment_Type with record Head : Equipment_Access; end record; function Do_Get_Price (Composite : Composite_Type) return Dollars_Type; function Do_Get_Power (Composite : Composite_Type) return Power_In_Watts_Type; procedure Free_Items (Composite : access Composite_Type'Class); end Equipment;

232 Matthew Heaney232 package body Equipment is procedure Do_Free (Equipment : access Equipment_Type) is begin raise Program_Error; end; procedure Free (Equipment : in out Equipment_Access) is begin if Equipment /= null then Do_Free (Equipment); Equipment := null; end if; end Free;

233 Matthew Heaney233 function Do_Get_Price (Composite : Composite_Type) return Dollars_Type is begin return 0.0; end; function Do_Get_Power (Composite : Composite_Type) return Power_In_Watts_Type is begin return 0.0; end;

234 Matthew Heaney234 function Get_Power_In_Watts (Composite : access Composite_Type) return Power_In_Watts_Type is Power : Power_In_Watts_Type := Do_Get_Power (Composite_TypeClass (Composite.all)); Item : Equipment_Access := Composite.Head; begin while Item /= null loop Power := Power + Get_Power_In_Watts (Item); Item := Item.Next; end loop; return Power; end Get_Power_In_Watts;

235 Matthew Heaney235 function Get_Price_In_Dollars (Composite : access Composite_Type) return Dollars_Type is Price : Dollars_Type := Do_Get_Price (Composite_TypeClass (Composite.all)); Item : Equipment_Access := Composite.Head; begin while Item /= null loop Price := Price + Get_Price_In_Dollars (Item); Item := Item.Next; end loop; return Price; end Get_Price_In_Dollars;

236 Matthew Heaney236 procedure Add (Equipment : access Equipment_Type'Class; To : access Composite_Type) is Item : constant Equipment_Access := Equipment_Access (Equipment); begin pragma Assert (Equipment.Next = null, "equipment already on another list"); Item.Next := To.Head; To.Head := Item; end Add;

237 Matthew Heaney237 procedure Free_Items (Composite : access Composite_Type'Class) is Item : Equipment_Access; Head : Equipment_Access renames Composite.Head; begin while Head /= null loop Item := Head; Head := Head.Next; Do_Free (Item); end loop; end Free_Items; end Equipment;

238 Matthew Heaney238 package Equipment.Hard_Disks is type Hard_Disk_Type is new Equipment_Type with private; function New_Hard_Disk return Equipment_Access; function Get_Price_In_Dollars (Hard_Disk : access Hard_Disk_Type) return Dollars_Type; function Get_Power_In_Watts (Hard_Disk : access Hard_Disk_Type) return Power_In_Watts_Type;

239 Matthew Heaney239... private type Hard_Disk_Type is new Equipment_Type with null record; procedure Do_Free (Hard_Disk : access Hard_Disk_Type); end Equipment.Hard_Disks;

240 Matthew Heaney240 package body Equipment.Hard_Disks is type Hard_Disk_Access is access all Hard_Disk_Type; procedure Free is new Ada.Unchecked_Deallocation (Hard_Disk_Type, Hard_Disk_Access);...

241 Matthew Heaney241 function New_Hard_Disk return Equipment_Access is Hard_Disk : constant Hard_Disk_Access := new Hard_Disk_Type; begin return Equipment_Access (Hard_Disk); end; procedure Do_Free (Hard_Disk : access Hard_Disk_Type) is Hard_Disk_A : Hard_Disk_Access := Hard_Disk_Access (Hard_Disk); begin Free (Hard_Disk_A); end;

242 Matthew Heaney242 function Get_Price_In_Dollars (Hard_Disk : access Hard_Disk_Type) return Dollars_Type is begin return 350.0; end; function Get_Power_In_Watts (Hard_Disk : access Hard_Disk_Type) return Power_In_Watts_Type is begin return 20.0; end; end Equipment.Hard_Disks;

243 Matthew Heaney243 package Equipment.Cabinets is type Cabinet_Type is new Composite_Type with private; function New_Cabinet return Composite_Access; private type Cabinet_Type is new Composite_Type with null record; procedure Do_Free (Cabinet : access Cabinet_Type); function Do_Get_Price (Cabinet : Cabinet_Type) return Dollars_Type; end Equipment.Cabinets;

244 Matthew Heaney244 package body Equipment.Cabinets is type Cabinet_Access is access all Cabinet_Type; procedure Free is new Ada.Unchecked_Deallocation (Cabinet_Type, Cabinet_Access); function New_Cabinet return Composite_Access is Cabinet : constant Cabinet_Access := new Cabinet_Type; begin return Composite_Access (Cabinet); end;

245 Matthew Heaney245 procedure Do_Free (Cabinet : access Cabinet_Type) is Cabinet_A : Cabinet_Access := Cabinet_Access (Cabinet); begin Free_Items (Cabinet); Free (Cabinet_A); end Do_Free; function Do_Get_Price (Cabinet : Cabinet_Type) return Dollars_Type is begin return Dollars_Type'(125.0); end; end Equipment.Cabinets;

246 Matthew Heaney246 declare Cabinet : Composite_Access := New_Cabinet; Chassis : Composite_Access := New_Chassis; Price : Price_In_Dollars_Type; Power : Power_In_Watts_Type; begin Add (New_Floppy_Disk, To => Cabinet); Add (New_CD_ROM, To => Cabinet); Add (New_Hard_Disk, To => Chassis); Add (Chassis, To => Cabinet); Price := Get_Price_In_Dollars (Cabinet); Power := Get_Power_In_Watts (Cabinet); end;

247 Matthew Heaney247 package Commands.Macro_Commands is type Command_Type is new Root_Command_Type with private; type Macro_Command_Access is access all Command_Type; function New_Command return Macro_Command_Access; procedure Execute (Command : access Command_Type); procedure Add (Command : access Root_Command_Type'Class; To : access Command_Type);...

248 Matthew Heaney248 … private type Command_Type is new Root_Command_Type with record Head : Command_Access; -- really, a queue end record; procedure Do_Free (Command : access Command_Type); end Commands.Macro_Commands;

249 Matthew Heaney249 package body Commands.Macro_Commands is … procedure Execute (Command : access Command_Type) is procedure Traverse_Then_Execute (C : in Command_Access) is begin if C /= null then Traverse_Then_Execute (C.Next); Execute (C); end if; end Traverse_Then_Execute; begin Traverse_Then_Execute (Command.Head); end Execute;

250 Matthew Heaney250 procedure Add (Command : access Root_Command_Type'Class; To : access Command_Type) is begin pragma Assert (Command.Next = null, "command already on another list"); Command.Next := To.Head; To.Head := Command_Access (Command); end; end Commands.Macro_Commands;

251 Matthew Heaney251 declare Command : constant Macro_Command_Access := New_Command; begin Add (New_Open (AppAccess), To => Command); Add (New_Paste (Doc), To => Command); … Execute (Command); -- Execute all the commands contained by -- the macro command. end;

252 Matthew Heaney252 Bounded Buffer

253 Matthew Heaney253 Whats A Bounded Buffer? Classic way of asynchronously transferring data between producer(s) and consumer(s). Consumer gets an item from the buffer; it blocks if theres nothing to get. Producer puts an item in the buffer; it blocks if the buffer is full.

254 Matthew Heaney254 generic type Item_Type is private; package Buffers is type Item_Array is array (Positive range <>) of Item_Type; protected type Buffer_Type (Size : Positive) is entry Put (Item : in Item_Type); entry Get (Item : out Item_Type);...

255 Matthew Heaney255... private Items : Item_Array (1.. Size); Get_Index : Positive := 1; Put_Index : Positive := Size; Count : Natural := 0; end Buffer_Type; end Buffers;

256 Matthew Heaney256 with Buffers; package Character_Buffers is new Buffers (Character);

257 Matthew Heaney257 with Character_Buffers; use Character_Buffers; package Consumers is type Consumer_Type (Buffer : access Buffer_Type) is limited private; private task type Consumer_Type (Buffer : access Buffer_Type) is end; end Consumers;

258 Matthew Heaney258 declare Buffer : aliased Buffer_Type (Size => 5); Consumer : Consumer_Type (Buffer'Access); begin for C in Character range a.. z loop Buffer.Put (C); end loop; end;

259 Matthew Heaney259 package body Consumers is task body Consumer_Type is C : Character; begin Main: loop Buffer.Get (C); end loop Main; end Consumer_Type; end Consumers;

260 Matthew Heaney260 package body Buffers is protected body Buffer_Type is entry Put (Item : in Item_Type) when Count < Size is begin Put_Index := Put_Index mod Size + 1; Items (Put_Index) := Item; Count := Count + 1; end;...

261 Matthew Heaney261... entry Get (Item : out Item_Type) when Count > 0 is begin Item := Items (Get_Index); Get_Index := Get_Index mod Size + 1; Count := Count - 1; end Get; end Buffer_Type; end Buffers;

262 Matthew Heaney262 Termination Problem Theres a slight problem with this implementation: How do you terminate the consumer task? One way is to designate a value in the set of Item_Type as a meta-item that means no more items. This will work for discrete types like Character, but not easily for more complex data, or when there are multiple consumers.

263 Matthew Heaney263 task body Consumer_Type is C : Character; begin Main: loop Buffer.Get (C); exit Main when C = Latin_1.EOT; end loop Main; end Consumer_Type;

264 Matthew Heaney264 declare Buffer : aliased Buffer_Type (Size => 5); Consumer : Consumer_Type (Buffer'Access); begin for C in Character range a.. z loop Buffer.Put (C); end loop; Buffer.Put (Latin_1.EOT); end;

265 Matthew Heaney265 Termination Solution Give the producer a way to indicate explicitly that there are no more items. Pass status back to the consumer, to let her know all the items have been consumed. This solution is completely general, and works for any kind of data, and for any number of consumers.

266 Matthew Heaney266 protected type Buffer_Type (Size : Positive) is entry Put (Item : in Item_Type); procedure Put_EOF; entry Get (Item : out Item_Type; EOF : out Boolean); private Items : Item_Array (1.. Size); Get_Index : Positive := 1; Put_Index : Positive := Size; Count : Natural := 0; End_Of_File : Boolean := False; end Buffer_Type;

267 Matthew Heaney267 task body Consumer_Type is C : Character; EOF : Boolean; begin Main: loop Buffer.Get (C, EOF); exit Main when EOF; end loop Main; end Consumer_Type;

268 Matthew Heaney268 declare Buffer : aliased Buffer_Type (Size => 5); Consumer : Consumer_Type (Buffer'Access); begin for C in Character range a.. z loop Buffer.Put (C); end loop; Buffer.Put_EOF; end;

269 Matthew Heaney269 protected body Buffer_Type is entry Put (Item : in Item_Type) …; procedure Put_EOF is begin End_Of_File := True; end;...

270 Matthew Heaney270 entry Get (Item : out Item_Type; EOF : out Boolean) when Count > 0 or End_Of_File is begin if Count > 0 then Item := Items (Get_Index); Get_Index := Get_Index mod Size + 1; Count := Count - 1; EOF := False; else EOF := True; end if; end Get;

271 Matthew Heaney271 Resource Control Lots O Stuff For Task Synchronization

272 Matthew Heaney272 Canonical Form A protected object with protected subprograms that set and get the data. Simplicity has a price: protected operations cannot make blocking calls, and should only require a short amount of time to execute. These restrictions allow us to solve simple synchronization problems efficiently.

273 Matthew Heaney273 type Time_Type is record Hour, Min, Sec : Natural; end record; protected Protected_Time is procedure Set_Time (Time : in Time_Type); function Get_Time return Time_Type; private Time : Time_Type; end Protected_Time;

274 Matthew Heaney274 protected body Protected_Time is procedure Set (Time : in Time_Type) is begin Protected_Time.Time := Time; end; function Get_Time return Time_Type is begin return Time; end; end Protected_Time;

275 Matthew Heaney275 Semaphore A semaphore is a low-level primitive used to synchronize concurrent access to a resource. Yes, its still necessary in Ada95, even though we have protected types now. Youll need it when you need to make blocking calls in the critical region. Use it only if youre unable to satisfy the constraints of the canonical form.

276 Matthew Heaney276 package Binary_Semaphores is protected type Semaphore_Type is procedure Release; entry Seize; private In_Use : Boolean := False; end Semaphore_Type; end Binary_Semaphores;

277 Matthew Heaney277 package body Binary_Semaphores is protected body Semaphore_Type is procedure Release is begin In_Use := False; end; entry Seize when not In_Use is begin In_Use := True; end; end Semaphore_Type; end Binary_Semaphores;

278 Matthew Heaney278 Semaphore : Semaphore_Type; Resource : Resource_Type; Task_1 : Task_1_Type; Task_2 : Task_2_Type;... task body Task_x_Type is begin … Semaphore.Seize; Semaphore.Release; … end Task_x_Type;

279 Matthew Heaney279 package Message_IO is procedure Put_Line (Message : in String); end Message_IO;

280 Matthew Heaney280 with Ada.Text_IO; package body Message_IO is protected Synchronization is procedure Put_Line (Message : in String); end Synchronization; procedure Put_Line (Message : in String) is begin Synchronization.Put_Line (Message); end;...

281 Matthew Heaney281... protected body Synchronization is procedure Put_Line (Message : in String) is begin -- Wrong! Per RM95 9.5.1 (8, 18) Ada.Text_IO.Put_Line (Message); end; end Synchronization; end Message_IO;

282 Matthew Heaney282 package body Message_IO is Semaphore : Semaphore_Type; procedure Put_Line (…) is begin Semaphore.Seize; -- OK; not inside a protected op. Ada.Text_IO.Put_Line (Message); Semaphore.Release; end; end Message_IO;

283 Matthew Heaney283 Deadlock X, Y, Z : Integer; Semaphore : Semaphore_Type;... Semaphore.Seize; … X := 0; … Z := Y / X; -- Oops! Raises CE... Semaphore.Release; -- Bigger oops! -- Sema not released.

284 Matthew Heaney284 package Binary_Semaphores.Controls is type Semaphore_Control (Semaphore : access Semaphore_Type) is limited private; private type Semaphore_Control (Semaphore : access Semaphore_Type) is new Limited_Controlled with null record; procedure Initialize (...); procedure Finalize (...); end Binary_Semaphores.Controls;

285 Matthew Heaney285 package body Binary_Semaphores.Controls is procedure Initialize (Control : in out Semaphore_Control) is begin Control.Semaphore.Seize; end; procedure Finalize (Control : in out Semaphore_Control) is begin Control.Semaphore.Release; end; end Binary_Semaphores.Controls;

286 Matthew Heaney286 Deadlock Avoided X, Y, Z : Integer; Sema : aliased Semaphore_Type; … declare Control : Semaphore_Control (SemaAccess); -- Automatically seizes semaphore. begin … X := 0; … Z := Y / X; -- Oops! Raises CE. … end; -- OK; semaphore released automatically.

287 Matthew Heaney287 Concurrent Stack generic... package Stacks is type Stack_Type is limited private; procedure Push (Item : in Item_Type; On : in out Stack_Type); procedure Pop (Stack : in out Stack_Type); function Get_Top (Stack : Stack_Type) return Item_Type;

288 Matthew Heaney288... private type Item_Array is array (1.. Max_Depth) of Item_Type; type Stack_Type is limited record Items : Item_Array; Top : Natural := 0; Sema : aliased Semaphore_Type; end record; end Stacks;

289 Matthew Heaney289 package body Stacks is procedure Push (Item : in Item_Type; On : in out Stack_Type) is Control : Semaphore_Control (On.Sema'Access); begin On.Top := On.Top + 1; On.Items (On.Top) := Item; end; procedure Pop (Stack : in out Stack_Type) is Control : Semaphore_Control (Stack.Sema'Access); begin Stack.Top := Stack.Top - 1; end;

290 Matthew Heaney290... function Get_Top (Stack : Stack_Type) return Item_Type is Control : Semaphore_Control (Stack.Sema'Access); ^^^^^^^^^^^^^^^^^ begin return SA.Items (SA.Top); end Get_Top; end Stacks; stacks.adb:42:40: access-to-variable designates constant

291 Matthew Heaney291 package body Stacks is package Addr_To_Acc_Conversions is new System.Address_To_Access_Conversions (Stack_Type);... function Get_Top (Stack : Stack_Type) return Item_Type is SA : constant Object_Pointer := To_Pointer (Stack'Address); -- OK, per RM95 13.3 (16), because Stack_Type is -- a by-reference type (full view is limited). Control : Semaphore_Control (SA.Sema'Access); begin return SA.Items (SA.Top); end Get_Top; end Stacks;

292 Matthew Heaney292 package Stacks is type Stack_Type is limited private;... private... type Handle_Type (Stack : access Stack_Type) is limited null record; type Stack_Type is limited record Handle : Handle_Type (Stack_Type'Access); Items : Item_Array; Top : Natural := 0; Sema : aliased Semaphore_Type; end record; end Stacks;

293 Matthew Heaney293 function Get_Top (Stack : Stack_Type) return Item_Type is Control : Semaphore_Control (Stack.Handle.Stack.Sema'Access); begin return Stack.Items (Stack.Top); end Get_Top;

294 Matthew Heaney294 Concurrent Stack Note To be honest: the example is a bit contrived, and was really designed to show a couple of ways of converting from a constant view of an object to a variable view. We dont really need a semaphore here, because no blocking calls are made. The canonical monitor form satisfies our synchronization needs, and is probably more efficient anyway.

295 Matthew Heaney295 In general, you want to advertise that your abstraction synchronizes its callers. This is especially true if the abstraction has blocking behavior. This allows your callers to make timed or conditional entry calls. In practice this will mean wrapping the data inside a protected object. Clients that need access to the resource use the protected interface explicitly.

296 Matthew Heaney296 protected Stack is procedure Push (I : Integer); procedure Pop; function Top return Integer; private List : List_Type; end Stack;

297 Matthew Heaney297 protected body Stack is procedure Push (I : Integer) is begin Push_Back (List, I); end; procedure Pop is begin Pop_Back (List); end; function Top return Integer is begin return Get_Back (List); end; end Stack;

298 Matthew Heaney298 protected Queue is entry Remove (Item : out Integer); procedure Add (Item : in Integer); private List : List_Type; end Queue;

299 Matthew Heaney299 protected body Queue is entry Remove (Item : out Integer) when not Is_Empty (List) is begin Item := Get_Front (List); Pop_Front (List); end; procedure Add (Item : in Integer) is begin Push_Back (List, Item); end; end Queue;

300 Matthew Heaney300 generic... package Stacks is type Stack_Type is limited private; function "=" (L, R : Stack_Type) return Boolean; … end Stacks; Static Locking Order

301 Matthew Heaney301 package body Stacks is function Do_Equality (L, R : Stack_Type) return Boolean is begin if L.Top /= R.Top then return False; end if; for I in Integer range 1.. L.Top loop if L.Items (I) /= R.Items (I) then return False; end if; end loop; return True; end Do_Equality;...

302 Matthew Heaney302... function "=" (L, R : Stack_Type) return Boolean is LA : constant Object_Pointer := To_Pointer (L'Address); RA : constant Object_Pointer := To_Pointer (R'Address); L_Control : Semaphore_Control (LA.Sema'Access); R_Control : Semaphore_Control (RA.Sema'Access); begin return Do_Equality (L, R); end "=";... end Stacks;

303 Matthew Heaney303 Deadlock Again First, if you compare a stack to itself, youll deadlock the second time you seize the semaphore (here, when you seize R). Second, the seizing of the pair of stacks is not an atomic operation. Youll still deadlock because of circularity.

304 Matthew Heaney304 S1, S2 : Stack_Type; T1 : Task_1_Type; T2 : Task_2_Type; task body Task_1_Type is begin … if S1 = S2 then … end; task body Task_2_Type is begin … if S2 = S1 then … end;

305 Matthew Heaney305 A possible ordering of actions is: Seize (S1.Sema); -- T1 Seize (S2.Sema); -- T2 Seize (S2.Sema); -- T1 Seize (S1.Sema); -- T2 T1 is waiting for S2, which has already been seized by T2. T2 is waiting for S1, which has already been seized by T1. Circularity has caused deadlock.

306 Matthew Heaney306 Removing the Circularity Define an order over the set of resources. Clients needing exclusive access to multiple resources lock them in resource order. By-reference resources already have an implicit order -- their address. Use RM95 13.3 (6) to get the address, and lock resources in order of their address.

307 Matthew Heaney307 function "=" (L, R : Stack_Type) return Boolean is LA : constant Object_Pointer := To_Pointer (L'Address); RA : constant Object_Pointer := To_Pointer (R'Address); begin if L'Address < R'Address then declare L_Control : Semaphore_Control (LA.Sema'Access); R_Control : Semaphore_Control (RA.Sema'Access); begin return Do_Equality (L, R); end;...

308 Matthew Heaney308... elsif L'Address = R'Address then return True; else -- R'Address < L'Address declare R_Control : Semaphore_Control (RA.Sema'Access); L_Control : Semaphore_Control (LA.Sema'Access); begin return Do_Equality (L, R); end; end if; end "=";

309 Matthew Heaney309 package Multiple_Reader_Semaphores is type Seize_Kind is (For_Reading, For_Writing); protected type Semaphore_Type is entry Seize (Kind : in Seize_Kind); procedure Release_For_Reading; procedure Release_For_Writing; private entry Waiting_To_Write; Reader_Count : Natural := 0; Writing : Boolean := False; end Semaphore_Type; end Multiple_Reader_Semaphores;

310 Matthew Heaney310 package body Multiple_Reader_Semaphores is protected body Semaphore_Type is entry Seize (Kind : in Seize_Kind) when Waiting_To_Write'Count = 0 and not Writing is begin case Kind is when For_Reading => Reader_Count := Reader_Count + 1; when For_Writing => requeue Waiting_To_Write with abort; end case; end Seize;...

311 Matthew Heaney311 procedure Release_For_Reading is begin Reader_Count := Reader_Count - 1; end; procedure Release_For_Writing is begin Writing := False; end; entry Waiting_To_Write when Reader_Count = 0 is begin Writing := True; end; end Semaphore_Type; end Multiple_Reader_Semaphores;

312 Matthew Heaney312 task body Reader_Type is begin … Sema.Seize (For_Reading); Sema.Release_For_Reading; … end Reader_Type;

313 Matthew Heaney313 task body Writer_Type is begin … Sema.Seize (For_Writing); Sema.Release_For_Writing; … end Writer_Type;

314 Matthew Heaney314 package Counting_Semaphores is protected type Semaphore_Type (Default : Natural := 1) is entry Wait; procedure Signal; private Count : Natural := Default; end Semaphore_Type; end Counting_Semaphores;

315 Matthew Heaney315 package body Counting_Semaphores is protected body Semaphore_Type is entry Wait when Count > 0 is begin Count := Count - 1; end; procedure Signal is begin Count := Count + 1; end; end Semaphore_Type; end Counting_Semaphores;

316 Matthew Heaney316 Maitre_D : Semaphore_Type (Default => 4); -- Not reqd if you pick up chopsticks in order. task body Philosopher_Task_Type is begin loop Maitre_D.Wait; -- to sit down at table Pick_Up (Left); Pick_Up (Right); delay Delay_Time; -- eat Put_Down (Left); Put_Down (Right); Maitre_D.Signal; -- leave the table delay Delay_Time; -- think end loop;

317 Matthew Heaney317 -- No maitre d is reqd, since we pick up the -- chopsticks according to their locking order. task body Philosopher_Task_Type is begin loop Pick_Up (Chopstick_IdMin (Left, Right)); Pick_Up (Chopstick_IdMax (Left, Right)); delay Delay_Time; -- eat Put_Down (Left); Put_Down (Right); delay Delay_Time; -- think end loop; end Philosopher_Task_Type;

318 Matthew Heaney318 Producer-Consumer When a producer of a resource must wait for it to be consumed, and the consumer must wait until the resource is produced. You can implement the communication using a task rendevous (the high-level way), or using a pair of semaphores (the low-level way).

319 Matthew Heaney319 task Producer is entry Get(Item : out Item_Type); end; task body Producer is begin accept Get (Item : out Item_Type) Item :=... end;... end Producer;

320 Matthew Heaney320 task body Consumer is begin... Producer.Get(Item);... end Consumer;

321 Matthew Heaney321 package Binary_Semaphores is protected type Semaphore_Type (Default : Boolean := False) is entry Wait; procedure Signal; private Signaled : Boolean := Default; end Semaphore_Type; end Binary_Semaphores;

322 Matthew Heaney322 Producer_Semaphore : Semaphore_Type (Default => False); Consumer_Semaphore : Semaphore_Type (Default => True); task body Producer is begin loop -- Wait for consumer to finish consuming. Consumer_Semaphore.Wait; -- Let consumer know producer is done producing. Producer_Semaphore.Signal; end loop; end Producer;

323 Matthew Heaney323 task body Consumer is begin loop -- Wait for producer to finish producing. Producer_Semaphore.Wait; -- Let producer know consumer has consumed. Consumer_Semaphore.Signal; end loop; end Consumer;

324 Matthew Heaney324 Recursive Semaphore When a task needs to seize a semaphore more than once prior to releasing it. Task that owns semaphore is allowed to call Seize multiple times without blocking. Other tasks wait if a task already owns semaphore. A new task assumes ownership when current task has Released every Seize.

325 Matthew Heaney325 package Recursive_Semaphores is protected type Semaphore_Type is procedure Release; entry Seize; private entry Waiting; Owner : Ada.Task_Identification.Task_Id; Count : Natural := 0; end Semaphore_Type; end Recursive_Semaphores;

326 Matthew Heaney326 package body Recursive_Semaphores is protected body Semaphore_Type is procedure Release is begin Count := Count - 1; end; entry Seize when True is begin if Seize'Caller = Owner then Count := Count + 1; else requeue Waiting with abort; end if; end;

327 Matthew Heaney327 entry Waiting when Count = 0 is begin Count := 1; Owner := Waiting'Caller; end; end Semaphore_Type; end Recursive_Semaphores;

328 Matthew Heaney328 Semaphore : Semaphore_Type; procedure Op1 is begin Semaphore.Seize;... if P then Op2; end if;... Semaphore.Release; end Op1; procedure Op2 is begin Semaphore.Seize; … Semaphore.Release; end Ops;

329 Matthew Heaney329 Persistent Signal To permanently record that an event has happened. Keep task(s) waiting until the event occurs. If the event has already happened, then task proceeds immediately. Useful for removing a tasks Initialize or Start entry that only gets called once.

330 Matthew Heaney330 protected Task_Initialization is entry Wait; procedure Start; private OK_To_Start : Boolean := False; end Task_Initialization;

331 Matthew Heaney331 protected body Task_Initialization is entry Wait when OK_To_Start is begin null; end; procedure Start is begin OK_To_Start := True; end; end Task_Initialization;

332 Matthew Heaney332 task Philosopher_Task_Type (…); -- no Init entry reqd Philosophers : Philosopher_Task_Array; … task body Philosopher_Task_Type is begin Task_Initialization.Wait; -- wait until its OK -- to philosophize loop … end loop; end Philosopher_Task_Type;

333 Matthew Heaney333 Barrier Blocks a group of tasks until all have arrived.

334 Matthew Heaney334 protected Task_Finalization is entry Wait; private Release_All : Boolean := False; end Task_Finalization;

335 Matthew Heaney335 protected body Task_Finalization is entry Wait when Wait'Count = Philosopher_Id'Last or Release_All is begin Release_All := True; end; end Task_Finalization;

336 Matthew Heaney336 task Philosopher_Task_Type (…); -- no Init entry reqd Philosophers : Philosopher_Task_Array; … task body Philosopher_Task_Type is begin Task_Initialization.Wait; -- they arent necessarily -- born together... loop … end loop; Task_Finalization.Wait; -- … but they do all -- die together end Philosopher_Task_Type;

337 Matthew Heaney337 All-or-Nothing Resource Allocation Suppose one philosopher is eating, and the other three philosophers at the table have each picked up their left chopstick. The issue is that two philosophers could be eating. Now, one eats and three wait. Make a new rule: if both chopsticks are available, then pick them both up; otherwise, wait to eat.

338 Matthew Heaney338 protected Chopsticks is entry Pick_Up (Id : Philosopher_Id); procedure Put_Down (Id : Philosopher_Id); private entry Waiting (Id : Philosopher_Id); Available : Boolean_Array := (others => True); Retrying : Boolean := False; end Chopsticks;

339 Matthew Heaney339 task body Philosopher_Task_Type is begin loop Chopsticks.Pick_Up (Id); delay Delay_Time; -- eat Chopsticks.Put_Down (Id); delay Delay_Time; -- think end loop; end Philosopher_Task_Type;

340 Matthew Heaney340 protected body Chopsticks is entry Pick_Up (Id : Philosopher_Id) when not Retrying is Next : constant Philosopher_Id := Id mod 5 + 1; begin if Available (Id) and Available (Next) then Available (Id) := False; Available (Next) := False; else requeue Waiting with abort; end if; end Pick_Up;...

341 Matthew Heaney341 procedure Put_Down (Id : Philosopher_Id) is Next : constant Philosopher_Id := Id mod 5 + 1; begin Available (Id) := True; Available (Next) := True; Retrying := Waiting'Count > 0; end; entry Waiting (Id : Philosopher_Id) when Retrying is begin if Waiting'Count = 0 then Retrying := False; end if; requeue Pick_Up with abort; end Waiting; end Chopsticks;

342 Matthew Heaney342 ATC Abort just the processing being done by a task, without aborting the task. Done through a protected object. The task doesnt have to interrupt its own processing to poll a Shutdown or Cancel entry. Need to carefully think about whether to requeue with abort, or to just requeue (without abort), if youre using ATC.

343 Matthew Heaney343 protected type Signal_Type is entry Wait; procedure Send; private Occurred : Boolean := False; end Signal_Type; -- This is a permanent signal.

344 Matthew Heaney344 protected body Signal_Type is entry Wait when Occurred is begin null; end; procedure Send is begin Occurred := True; end; end Signal_Type;

345 Matthew Heaney345 Task_Initialization : Signal_Type; Task_Finalization : Signal_Type; procedure Start (Update : in Update_Access) is begin Task_Initialization.Send; end; procedure Stop is begin Task_Finalization.Send; end;

346 Matthew Heaney346 task body Philosopher_Task_Type is begin Task_Initialization.Wait; select Task_Finalization.Wait; then abort loop Chopsticks.Pick_Up (Id); delay Delay_Time; -- eat Chopsticks.Put_Down (Id); delay Delay_Time; -- think end loop; end select; end Philosopher_Task_Type;


Download ppt "Matthew Heaney1 Implementing Design Patterns in Ada95 Tips, Tricks,and Idioms by Matthew Heaney."

Similar presentations


Ads by Google