From dca2e4cd3dbb29ea95f8e8a0821696cc553b67e5 Mon Sep 17 00:00:00 2001 From: stlutz Date: Fri, 16 Feb 2018 23:02:50 +0100 Subject: [PATCH] Update README to include usage instructions and known issues. [ci skip] --- README.md | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 85 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 9f0d736..19d4298 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ -# Squeak Active Expressions [![Build Status][travis_b]][travis_url] [![Coverage Status][coveralls_b]][coveralls_url] +# Squeak-ActiveExpressions [![Build Status][travis_b]][travis_url] [![Coverage Status][coveralls_b]][coveralls_url] -Squeak-ActiveExpressions aims to offer a Squeak implementation of [Active Expressions] - a simple state-based reactive concept. - -The first implementation of Active Expressions was in [JavaScript][JavaScript Implementation]. +Squeak-ActiveExpressions aims to offer an implementation of [Active Expressions] in Squeak. ## Installation Instructions @@ -15,10 +13,91 @@ Metacello new load. ``` +## Usage + +An ActiveExpression requires an expression as its description of state. Expressions in this implementation are represented by BlockClosures. Creating an ActiveExpression therefore only requires a block: +```smalltalk +aexp := ActiveExpression monitoring: ["expression"] +``` +(Please note that an ActiveExpression's expression should not contain any side effects.)
+Programmers can now subscribe callbacks to the created ActiveExpression: +```smalltalk +aexp onChangeDo: ["callback"] +``` +Whenever the ActiveExpression is updated/checked, the value of its expression at the previous update and the current value are compared. If the two values differ, all callbacks are invoked. There are different methods for when to update: + +#### Convention +Update whenever the programmer manually causes an update through `ActiveExpression>>update`. +```smalltalk +aMorph := Morph new. +aexp := ActiveExpression monitoring: [aMorph position]. +aexp onChangeDo: [Transcript show: aMorph position]. +aMorph position: 100@100. +aexp update. +"'100@100' is written to the Transcript" +``` + +#### Monitoring Variables +Update whenever a variable (instance, temporary, global, class) is changed.
+(NOTE: Currently only changes to instance variables made through assignments can be caught and acted upon.) +```smalltalk +aMorph := Morph new. +aexp := MonitoringActiveExpression monitoring: [aMorph position]. +aexp onChangeDo: [Transcript show: aMorph position]. +aMorph position: 100@100. "~~> causes automatic update" +"'100@100' is written to the Transcript" +``` + +#### Polling +Update whenever a fixed duration has passed. +```smalltalk +This is not yet implemented. +``` + +### Quick Reference + +| Method | Description | +| --- | --- | +| `ActiveExpression>>lastValue` | Returns the last recorded result of ActiveExpression's expression. | +| `ActiveExpression>>currentValue` | Returns the current result of ActiveExpression's expression without recording it (or updating in general). | +| `ActiveExpression>>update` | Checks whether the current result of ActiveExpression's expression differs from the previously recorded one. If it does, all calbacks are invoked. | +| `ActiveExpression>>dispose` | Undoes all changes the ActiveExpression has made to the system. Removes all callbacks. The ActiveExpression should not be used afterwards. | +| `ActiveExpression>>onChangeDo:` | Adds a callback to the ActiveExpression. Expects a BlockClosure with 0 to 2 arguments. | +| `ActiveExpression>>disable` | Disables all callbacks. Updates will now still update to the current result but not invoke any callbacks. | +| `ActiveExpression>>enable` | (Re-)Enables all callbacks. Callbacks are enabled by default. | + +#### Callbacks +Callbacks are BlockClosures with up to 2 arguments. The optional arguments reference the new and the old value of the ActiveExpression's expression. Upon invocation of the callback these arguments are passed automatically into the callback as follows: +```smalltalk +["callback with 0 args"] +[:newValue | "callback with 1 arg"] +[:oldValue :newValue | "callback with 2 args"] +``` + +#### Syntactic Sugar +ActiveExpressions can also be more easily created by directly passing `onChangeDo:` to a BlockClosure: +```smalltalk +aexp := ["expression"] onChangeDo: ["callback"]. +``` +Note though, that the resulting ActiveExpression will be of the class `ActiveExpression` and therefore will have to be update manually. + +## Issues +At the moment there are several problems with this implementation of ActiveExpressions. Most of them come with the very invasive nature of the "monitoring variables" approach of detecting changes: +- When listening and reacting to all variable assignments, it is possible to accidentally try to use an object in an undefined state (e.g. during an atomic operation with 2 variable assignments). This might either create an error (which will simply be ignored, aborting the update) or update successfully. In case of the latter, callbacks will be triggered and potentially lead to work being done with this currently corrupted state. +- When updating an ActiveExpression, the comparison between the current value and the previous value is based on object identity. Value objects (like e.g. `Point` and `Rectangle`) have a tendency to change identity without being perceived by the programmer as having changed. These changes might be reacted upon by ActiveExpressions. The following expression for example will always invoke all callbacks upon updating: `ActiveExpression monitoring: [1@1]`. +- When trying to monitor all relevant instance variable assignments throughout the system, the methods in which these assignments occur are recompiled to contain intercepting code. While all following message sends might invoke these new methods, already invoked methods are not changed. This is not a conceptual problem, since it could be possible to rewrite all contexts referencing the old method to update to the rewritten ones. +- Changes to instance variables made through primitives cannot be intercepted unless it is known which primitives influence which variables. Most notably this prevents the monitoring of Collections, since any changes made to the collection's array will not be noticed. In theory it should be possible to fix this problem with collections by intercepting all calls to `Object>>at:put:`. + +Some code examples for the above-mentioned problems can be found in the class `AExpIssues` found in the `ActiveExpressions-Examples` package. ([Link][Issues]) + ## History This project was started as part of the [Software Architecture Group]'s seminar «Programming Languages: Design and Implementation» held at the [Hasso-Plattner Institute][HPI]. +A more complete implementation of Active Expressions in JavaScript which is also referenced in the [original paper][Active Expressions] can be found [here][JavaScript Implementation]. + + + [travis_b]: https://travis-ci.org/stlutz/Squeak-ActiveExpressions.svg?branch=master [travis_url]: https://travis-ci.org/stlutz/Squeak-ActiveExpressions [coveralls_b]: https://coveralls.io/repos/github/stlutz/Squeak-ActiveExpressions/badge.svg?branch=master @@ -28,5 +107,7 @@ This project was started as part of the [Software Architecture Group]'s seminar [JavaScript Implementation]: https://github.com/active-expressions/active-expressions [Metacello]: https://github.com/Metacello/metacello +[Issues]: https://github.com/stlutz/Squeak-ActiveExpressions/tree/master/src/ActiveExpressions-Examples.package/AExpIssues.class + [Software Architecture Group]: https://www.hpi.uni-potsdam.de/swa [HPI]: https://hpi.de