As we're going through the port from Javascript to Swift, we'll use this file to keep track of notes. Currently we outline our high level plan and identify the risks that we have observed so far.
The good news is that from a preliminary inspection, the functions that I've looked at in detail are pure functions, meaning that they take inputs and produce an output without any side effects. All of the state handling is on the native Swift side in Trio (at least so far). Pure functions will be easier to test and less risky to port incrementally.
At the highest level, our plan is to first do a line-by-line port of the Javascript implementation to build confidence that it works, then to make it more "Swift-y" after we have confidence in the logic. Doing a line-by-line port first makes it easier for us to debug, but we will use more idiomatic Swift patterns where it makes sense.
Also, we plan to release this as a SPM so that other iOS / OpenAPS systems can pick up this library, if it makes sense. But I'm open to something different if people have strong opinions here.
Our plan is:
Port one function at a time. The functions, in order, are:
makeProfileiobmealautosensedetermineBasalFor each function, the process will be:
We should run each function in shadow mode for a week without any inconsistencies before considering moving it to live execution. After we move to live execution of a native function, we should continue to run the Javascript implementation in shadow mode for 2 weeks to continue to check for inconsistencies.
Once all functions are running natively and without inconsistencies for two weeks, we can remove the Javascript implementation. After we remove the Javascript implementation, we will consider the line-by-line port to be complete, and can make decisions about any further changes we'd like to make to the Swift implementation to improve maintainability.
Our goal is to make each of the functions pure functions, meaning that they don't have any side effects and they're deterministic (given the same inputs they'll produce the same outputs). There are some caveats with floating point numbers and time (see risks), but so far it looks like it'll be possible.
Having pure functions is a big benefit from a correctness perspective, it makes testing easier and it makes it easier for people to use it since they don't have to worry about ordering or sequencing functions. Javascript has single-threaded semantics with an event system, but we can ignore this if we can keep our functions pure.
Here is a list of where we think bugs might crop up, so we're writing them down to make sure we can keep an eye on it.
Javascript pass-by-reference. Javascript uses pass-by-reference semantics, so if code modifies an input parameter then that value is changed. In our Swift port, we instead use pass-by-value semantics, trying to carefully navigate any visible changes that can come from modifications, which does happen in OpenAPS.
Javascript dynamic properties. Javascript can add properties on the fly, which is hard to get right. Our plan is to use static typing and make sure that we include properties that Javascript would generate dynamically, but this is a potential source of inconsistencies.
Javascript type switching. There is at least one property
(Profile.target_bg) where the Javascript implementation uses
boolean false as a proxy for Optional none, where the property
is a Number. I have a property annotation to deal with it, but
it's something we'll want to get rid of after the port. The Swift
implementation does not use this behavior, we try to constrain
it to the serialization routines to maintain JSON compatibility.
var now = new Date(); There are several places where the
Javascript implementation gets the current time using new
Date(). This style of time management can lead to issues if we're
right at a boundary when it runs. Since this is how the Javascript
is implemented we use it too, but we'll want to fix that soon.
Double vs Decimal. In Swift we use the Decimal class for floating point computation. However, our goal is to match the current Javascript implementation, which uses Double, so we need to keep an eye on this because the two can be different.
Trio-specific inputs. There are places where the Trio
implementation it a little different than what the Javascript
expects. An example is BasalProfileEntry doesn't have an i
property, so the sorting function for these entries in Javascript
is a no-op, so we excluded it.
Preferences -> Profile. The Javascript implementation copies
input properties into the Profile if they exist. In Trio, in
Javascript we copy the Preferences to the input for this
purpose. In this library, we do this copy by hard-coding all
properties that have the same CodingKeys, but this was a manual
process and something we need to remember to change if either
Profile or Preferences changes. We'll fix this with v2, but for
now this was the cleanest way I could come up with for handling it
in Swift. See the Profile extension that implements update for
more details.
So far, the biggest cleanup items are to see if we can avoid reproducing the logic that mutates inputs. There are a few TODOs in the code to mark these to evaluate later, but for now we'll just produce the same JSON that the Javascript library does.
The next biggest change is to be consistent with time. There are a bunch of places that the Javascript uses the current time of day, we should pass in one time to both algorithms so that they produce consistent results.
In terms of enhancements after the port, here are some issues that we created to track some cleanup that we should do:
For our port, we're using:
trio-oref (tcd branch) git SHA: ade267da32
Trio-dev (core-data-sync-trio branch) git SHA: dc43b0ae8f