Dataflow programming is a programming paradigm that models a program as a directed graph of the data flowing between operations. It is mainly used in Unix shell languages: small single-purpose tools are easily glued together using the pipeline symbol: |. Mainstream programming languages do not support it, though there are some libraries around.
In this example, we will demonstrate SubScript dataflow programming by means of of simple Google Search application. This is an application which enables the user to issue a Google search, without a full Internet browser. Also, the user gets a list of suggestions as he or she types the search query. Suggestions shouldn’t be confused with search results: suggestions try to complete the incomplete search query as user types; search results are links returned by a search engine on a particular query.
GUI and Search API
Our sample application will have a simple GUI that contains:
- A text field for the user to type his or her search query
- A suggestions box with a list of suggestions. The items in this box are clickable; when a user clicks on some suggestion an event is generated.
- A search box with a list of search results. The items in this box are also clickable.
- An embedded browser window that will display a website to user after he/she clicks on some search result.
Most of modern GUI libraries harness the Observer pattern: other objects, called Observers, can register themselves at GUI objects to receive various events via callbacks.
More complex systems can be built using this pattern. From a callback-registering method a Future[T] may be produced, where T is the type of some event-specific data.
Thus we can safely put the following methods into the GUI trait with the guarantee that they can be implemented in vast majority of GUI libraries:
def textChange : Future[String] def suggestionsClick : Future[String] def searchResultsClick: Future[String]
Here, textChange represents a Future that will be completed once the input string in the search text field experiences some changes. Meaning, a new character input by user will complete this Future. This Future will be completed with the new input string as its result.
suggestionsClick represents a Future that will be completed on mouse click event that happened on some suggestion from the suggestion list box. This Future will be completed with the text of the selected suggestion.
searchResultsClick is similar to suggestionsClick, but it responds to clicks on search results rather then suggestions. It is completed by the clicked URL address.
Also, the application requires means to provide output to the user. Specifically, methods for setting current list of suggestions, search results and navigating the embedded browser to a given URL are required:
def setInputString (str: String ): Unit def setSuggestions (ls : Seq[String]): Unit def setSearchResults(ls : Seq[String]): Unit def setBrowserUrl (url: String ): Unit
This comprises a GUI interface of the application. It is possible to create concrete implementations of such an interface in the majority of GUI libraries for Java/Scala, such as Swing.
Also, an API for doing actual Google search and retrieving suggestions will be required:
def suggestions(req: String): Future[Seq[String]] def search (req: String): Future[Seq[String]]
They will accept a String request as an argument and will return a \code{Future} of the required result.
Reactive flow specification
Given the API mentioned above, it is easy to describe the lifecycle of the application in SubScript:
live = textChange ~~(req: String)~~>
( suggestions(req)~~>setSuggestions(_);
search (req)~~>setSearchResults(_)
)
|| suggestionsClick ~~>setInputString(_)
|| searchResultsClick ~~>setBrowserUrl(_)
; ...
This is an eternal sequential loop ; … of event handling expressions. There are three kinds of events, handled in an or-parallel composition ||.
The first operand defines the handling of changes in the input text field. textChange returns a Future[String]. This Future is converted using an implicit conversion. Once it has success, the right-hand operand of the dataflow operator
~~(req: String)~~>
is activated. The arrow specifies that it transmits the String result of textChange under the name req to the right hand side: this retrieves suggestions and updates the GUI correspondingly; then it retrieves search results and again updates the GUI.
suggestions and search are also Futures (this time from the Search API), converted into scripts using implicit conversions. Here the dataflow operators are curly arrows that do not name what is transmitted; the right hand side have the underscore symbols as parameter, which, like in Scala, creates a parameterized lambda.
The other two operands of || respond to clicks on the presented suggestions and on search results, by updating the GUI. Again, futures are implicitly converted into scripts; and the results are transmitted using the dataflow operator into the GUI update code. These handlers take little time. Meanwhile a longer lasting text change handling may have been ongoing; then this would be disrupted because of the or-parallel composition.
