From mboxrd@z Thu Jan 1 00:00:00 1970 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on polar.synack.me X-Spam-Level: X-Spam-Status: No, score=-1.9 required=5.0 tests=BAYES_00,FREEMAIL_FROM autolearn=unavailable autolearn_force=no version=3.4.4 X-Received: by 10.50.18.50 with SMTP id t18mr25934252igd.8.1445427937050; Wed, 21 Oct 2015 04:45:37 -0700 (PDT) X-Received: by 10.182.116.130 with SMTP id jw2mr123246obb.4.1445427937028; Wed, 21 Oct 2015 04:45:37 -0700 (PDT) Path: eternal-september.org!reader01.eternal-september.org!reader02.eternal-september.org!news.eternal-september.org!mx02.eternal-september.org!feeder.eternal-september.org!news.glorb.com!kq10no24610950igb.0!news-out.google.com!z4ni27568ign.0!nntp.google.com!kq10no20676322igb.0!postnews.google.com!glegroupsg2000goo.googlegroups.com!not-for-mail Newsgroups: comp.lang.ada Date: Wed, 21 Oct 2015 04:45:36 -0700 (PDT) In-Reply-To: Complaints-To: groups-abuse@google.com Injection-Info: glegroupsg2000goo.googlegroups.com; posting-host=46.218.2.213; posting-account=21X1fwoAAABfSGdxRzzAXr3Ux_KE3tHr NNTP-Posting-Host: 46.218.2.213 References: User-Agent: G2/1.0 MIME-Version: 1.0 Message-ID: Subject: Re: Musings on RxAda From: Hadrien Grasland Injection-Date: Wed, 21 Oct 2015 11:45:37 +0000 Content-Type: text/plain; charset=ISO-8859-1 Content-Transfer-Encoding: quoted-printable Xref: news.eternal-september.org comp.lang.ada:28009 Date: 2015-10-21T04:45:36-07:00 List-Id: Interesting problem ! Let me try to play with it too. If you need asynchronicity, the standard Ada answer is tasks. So here, you = would like an "observable task", which can hold data of any type, perform a= ny action on this data, and return data of another (possibly different) typ= e. The type-safe and statically checkable way to hold data of any type is to b= uild a generic package. So I would argue that this is the idiomatic Ada way= to solve this problem, and a better alternative to the type erasure you at= tempt to implement using null interfaces. It also has the important advanta= ge of working with built-in Ada tyes. If try to make a generic task which fits our needs, we might get something = like this as a first attempt (NOTE : This code is wrong, read below to know= why) : =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D generic type Input_Data is private; type Output_Data is private; package Observables is =20 -- Any valid mapping function for this observable can be matched by this= access type type Mapping_Function is access function (Input : Input_Data) return Out= put_Data; =20 -- Any subscribing procedure can be matched by this access type type Subscribing_Proc is access procedure (Input : Input_Data); =20 -- This task performs the asynchronous mapping task type Observable is entry Just (Data : Input_Data; Output : access Observable); entry Just (Data : Input_Data; Action : Subscribing_Proc); entry Map (Mapping : Mapping_Function; Output : access Observable); entry Map (Mapping : Mapping_Function; Action : Subscribing_Proc); end Observable; =20 end Observables; =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D Notice that Map and Just must return results through a parameter, because A= da task entries cannot be functions. This is somewhat annoying, but probabl= y motivated by the fact that a return in an accept statement would be highl= y confusing. Notice also that I have removed Subscribe, and replaced it by overloadings = of Just and Map. That is because an Ada task may return results immediately= within the bodies of Just and Map, in which case subscribing later would o= nly be a recipe for overhead and race conditions. More problematic, however, are the facts that... 1/ This code is illegal. Task entries Map and Just cannot take an access= parameter. 2/ Even if it were legal, it wouldn't do what we want. The Observer that= would be passed as a parameter to Map would expect Input_Type as input, wh= ereas we would want it to accept Output_Type as input and do not care about= its output. The "do not care" statement above is a telltale sign that we need another l= ayer of abstraction. We need something that can hold data (results) of a gi= ven type, is thread-safe, and allows us not to think about the data after i= t is sent. I would argue that this something is a protected object : =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D generic type Data_Type is private; package Async_Data is =20 -- This container is promised to hold a chunk of data someday protected type Datum is procedure Send (Result : Data_Type); entry Receive (Result : out Data_Type); private Data_Ready : Boolean :=3D False; Data : Data_Type; end Datum; =20 end Async_Data; =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D This is really a textbook example of a protected type, and indeed you will = find one like this in almost all Ada textbook. Nevertheless, for the sake o= f completeness, here is an implementation : =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D package body Async_Data is =20 protected body Datum is =20 procedure Send (Result : Data_Type) is begin Data :=3D Result; Data_Ready :=3D True; end Send; =20 entry Receive (Result : out Data_Type) when Data_Ready is begin Result :=3D Data; end Receive; =20 end Datum; =20 end Async_Data; =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D Okay, so now we have asynchronous communication channels. We can use them t= o rewrite the Observable code above so that its Map function actually sends= data to an asynchronous output channel, and that it can accept asynchronou= s input as well, like so : =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D with Async_Data; generic with package Async_Input is new Async_Data (others =3D> <>); with package Async_Output is new Async_Data (others =3D> <>); package Observables is =20 -- Let's clarify our input and output data types subtype Input_Data is Async_Input.Data_Type; subtype Output_Data is Async_Output.Data_Type; =20 -- Any valid mapping function for this observable can be matched by this= access type type Mapping_Function is access function (Input : Input_Data) return Out= put_Data; =20 -- Any subscribing procedure can be matched by this access type type Subscribing_Proc is access procedure (Data : Output_Data); =20 -- This task performs the asynchronous mapping task type Observable (Input : access Async_Input.Datum) is entry Map (Mapping : Mapping_Function; Output : in out Async_Output.D= atum); entry Map (Mapping : Mapping_Function; Action : Subscribing_Proc); end Observable; =20 end Observables; =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D Notice that I have suppressed the "Just" entry, whose complexity has become= unnecessary due to our use of asynchronous input objects. Also, here is a = possible implementation : =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D package body Observables is task body Observable is Data : Input_Data; begin Input.Receive (Data); select accept Map (Mapping : Mapping_Function; Action : Subscribing_Proc)= do Action.all (Mapping (Data)); end Map; or accept Map (Mapping : Mapping_Function; Output : in out Async_Outp= ut.Datum) do Output.Send (Mapping (Data)); end Map; end select; end Observable; =20 end Observables; =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D How would we use such an object ? Well, any self-respecting would have a bu= nch of predefined instances of these generic packages for standard types, l= ike this one : =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D with Async_Data; with Observables; package Predefined is =20 -- In a real library, basic asynchronous data types like this would be p= redefined package Async_Characters is new Async_Data (Character); subtype Async_Character is Async_Characters.Datum; package Async_Naturals is new Async_Data (Natural); subtype Async_Natural is Async_Naturals.Datum; =20 -- Same for basic observables like this package Char_To_Nat_Observables is new Observables (Async_Input =3D> As= ync_Characters, Async_Output =3D> As= ync_Naturals); subtype Char_To_Nat_Observable is Char_To_Nat_Observables.Observable; package Nat_To_Char_Observables is new Observables (Async_Input =3D> As= ync_Naturals, Async_Output =3D> As= ync_Characters); subtype Nat_To_Char_Observable is Nat_To_Char_Observables.Observable; =20 end Predefined; =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D And we would then want to use them like this : =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D with Ada.Text_IO; with Predefined; procedure Main is -- Here, we will try to map a character to a natural and back, then prin= t the result function Char_To_Nat_Mapping (Char : Character) return Natural is (Chara= cter'Pos (Char)); function Nat_To_Char_Mapping (Nat : Natural) return Character is (Charac= ter'Val (Nat)); =20 procedure Print_Char (Char : Character) is begin Ada.Text_IO.Put (Char); end Print_Char; =20 -- Let's try it ! Input : constant Character :=3D 'a'; Char_To_Nat : Predefined.Char_To_Nat_Observable (new Predefined.Async_Ch= aracter); Nat_To_Char : Predefined.Nat_To_Char_Observable (new Predefined.Async_Na= tural); begin Char_To_Nat.Input.Send (Input); Char_To_Nat.Map (Char_To_Nat_Mapping'Access, Nat_To_Char.Input.all); Nat_To_Char.Map (Nat_To_Char_Mapping'Access, Print_Char'Access); end Main; =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D Sadly, this does not work. The Ada accessibility rules prevent us to use th= e access to subprograms Char_To_Nat_Mapping, Nat_To_Char_Mapping, and Print= _Char, on the grounds that the task might subsequently leak pointers to non= existent subprograms. So we would need to declare our functions in a separa= te package. And this is where I will stop, because I think I have made my point : yes, = it is possible to implement something like the Observers you mentioned in A= da. But I think it is highly unlikely to be anywhere near as practical as i= t is in C# or Java. The reason lies in Ada's strict enforcement of pointer accessibility rules,= including for function pointers, something which garbage-collected languag= es need not care about but which is of critical importance for languages wh= ere scope determines variable and function lifetimes. Have a nice day ! Hadrien