Prerequisites
- An understanding of Xamarin
- An simple understanding of Threads
- An understanding of F#
Recently I attended a Winter of Xamarin event hosted by Microsoft. It was a great event, but as always the code examples all in C#. I thought it would be a great a exercise to translate the C# app into F#. This post focuses on one particular section that I came across, some Xamarin Forms’ animations, that I found less than trivial to translate. The various attempts that I tried will be outlined, with the final solution at the end.
The animation code
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: |
|
Here is the main gist of the code. The method is part of a ContentPage. I’m assuming that most readers will understanding this, so will only go over this briefly. Task.WhenAll
executes the animations in parallel. The await
after each Task.WhenAll
means wait for it to complete, so the inside of the loop, contains 4 sequential animations. Finally the loop just runs the 4 animations, sequentially 8 times.
Async in F#
Understanding Async in F# can be a little tricky. This is not because is it complicated, rather it is more explicit. Tomas Petricek has a great ‘series on Async’, and another post on the ‘Async in C# and F#: gotchas in C#’. Armed with this knowledge here is my first attempt at translating the inner loop.
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: |
|
Here’s some F# code that compiles and was my first attempt. Preferring a functional style, I wanted to treat the animations as data, and then iter
(map with no result) over them. The function inside the iter contains the relevant transformations to make the code compile with F#. Let’s walk through it. First animations
is an array of Task
and F# needs Async
, so lets map
over them and convert them to Async
s. Next we we want to run them in parallel, Async.Parallel
for the job. This is the same as C#’s Task.WhenAll
. On to the next line, F# checks return types, and each of the animations return a bool. Async.Ignore
will fix up the return type since there is nothing to check. Finally, Async.StartImmediate starts on the main thread an returns immediately so it won’t block the main thread, but also doesn’t create a new thread. (Async.RunSynchronously can’t be used here since the animations must be run on the main thread and will wait for. The result: a deadlock). I’ll save you time of trying the show
function out; it doesn’t work. When you run it, nothing happens. No crash, no animation.
But it compiled
When dealing with side effects, all bets are off as to whether the code works; animations are side effects. A careful read on Thomas’ blog can give us an understanding of what is happening here. On the C# side, static methods for Tasks start immediately. This means that treating animations as data in a list (array) won’t work as they will be started immediately. Armed with this knowledge here is another attempt.
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: |
|
This looks a little better and this does in fact work. The animation runs. As before, for each of the arrays, they need to be converted for Task
s, set to run in parallel and have their result ignored. This time, instead of putting all the arrays in a list, they are inside an async block, and the do!
tells F# to wait for each one to finish before starting the next one. Async.StartImmediate
starts this off nicely on the main thread. I’ll come back to this later and refactor out some of the duplicated code, but now let’s get this running 8 times.
Running 8 times
Surely it can’t be hard to run an animation 8 times. Also were using F# so variables and loops are beneath us. Here is my (failed) first attempt at running the above code 8 times.
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: |
|
The last line is all that’s changed. An attempt was made to replicate the Async
action 8 times and then iter over the resulting list wrapping the animation in do!
and executing as Async.StartImmediate
. I had assuming that Async.StartImmediate
was the same as C# and that they could be chained together. As already stated, this doesn’t work. In F#, any async work must be enclosed in an async block, and started only once. Async.StartImmediate
is analogous with an a C# method of public async void
meaning you can not continue after the task has been started.
A working version
I still didn’t want to put a variable with a for loop in, so I found the best compromise I could handle. Here is the first working solution:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: |
|
The single shake action is bound to shake
, and then the method returns an async block. In the block, the shake
binding can then be replicated 8 times, and thanks to F#’s awesome for..in..do
syntax, we have a loop but without a counter variable. We’re also still inside the async block so do!
waits for the animation to completed on each run through the list of animations.
Refactoring
This working version has some repeated code that we could clean up a little bit. We can take that out and put that into a function:
1:
|
|
If that looks confusing, check out my post on point free notation and also this post on function composition. With the helper function the rest of code now looks as follows:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: |
|
This is the final version that I left in my code base. I will mention one last step that could be done. Running animations sequentially like this might be rather common in a mobile app. So the async loop could be refactored out. An easy way to do this would be as an extension method on Async. Here’s an example:
1: 2: 3: 4: 5: 6: 7: 8: |
|
Simple enough, it’s just pulled out the loop logic, (similar to how map has factored our the logic of a loop). The caller would then be as follows:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: |
|
Because we are using the shake directly, we no longer need to bind it to shake
. The piping operator is once again our friend and the code is easy to follow.
Summary
F#’s Async has all the power you need. let your codebase run wild with animations. Just remember that static methods on a C# Task
are started immediately so they will need to be coupled with appropriate !
op (ie do!
, let!
or use!
).