Today I finally had a chance to play with Python compilation support in the recent Fable alpha.
Fable
For those who aren't familiar, Fable is a tool that takes F# and compiles it to other languages. Up until recently that only included Javascript. This is how we write our client-side app code at CIT - Fable is the 'F' in SAFE Stack!
With the latest release, target languages have been expanded to include Python, Rust and Dart!
I did have a quick explore of the REPL after seeing the tweet announcing the alpha, but my eye was really caught by a comment underneath asking if it would be possible to import Python libraries to F#, and the answer that it should work just the same as JS.
I immediately thought of TensorFlow. If you have followed my previous blogs documenting my journey into machine learning in .NET, you will remember I experimented with TensorFlow.NET.
I had quite a bit of success with this, however the API doesn't always exactly match the Python binding (although usually close) and of course every sample or guide online uses Python.
Python Interop
With Fable 4, the potential now existed to just use Python's TensorFlow bindings directly, so I decided to give it a go.
The steps I followed were
- Install Python 3.9
- Install the VSCode Python extension
I initially faced issues as I had Python 3.7 on my machine. Then, after installing it, VSCode still defaulted to 3.7 for the interactive window. This is easy to switch in the GUI but it caught me out (and it resets after you close and open the IDE).
- Create a new folder and navigate to it using the terminal
- Install the Fable 4 dotnet tool preview
dotnet new tool-manifest
dotnet tool install fable --local --version "4.0.0-snake-island-*"
- Install TensorFlow using pip
python -m pip install tensorflow-cpu
- Create an .fsx script
- Import Fable.Core from nuget
- Import Tensorflow
Once I had done that, I employed the (not recommended!) dynamic method calling Fable syntax, using a ?
to query my CPU info.
#r "nuget: Fable.Core, 4.0.0-snake-island-alpha-007"
open Fable.Core
open Fable.Core.PyInterop
[<ImportAll("tensorflow")>]
let tensorflow: obj = nativeOnly
tensorflow?config?list_physical_devices("CPU")
Now I just had to compile the script to Python using the Fable dotnet tool:
dotnet fable --lang Python "Fable 4 Python Example.fsx"
This outputs a file with the same name but a .py
extension in the same directory, plus a bunch of fable modules in a sub folder.
There is an optional nuget package called Fable.Python which contains bindings for the Python Standard Library as well as Jupyter, Flask and more - I just didn't need it here.
Right clicking on the .py
file and selecting Run current file in interactive window
executes the code and prints out the CPU info:
[PhysicalDevice(name='/physical_device:CPU:0', device_type='CPU')]
Remember to select Python 3.9 if you have more than one version installed!
I did continue to try porting the complete handwritten digit recognition sample from my previous TensorFlow blog. I managed to load the data and print it out in the console, but once I tried to create a model I hit a requirement of using named method arguments which I couldn't get to work with the quick-and-dirty dynamic invocation approach.
Conclusion
I think that the introduction of Python interop is a very exciting development in the F# space. It is no secret that the ML community is very established in the Python world, and therefore many of the most widely used and supported tools exist in that ecosystem. Combining them with the power and elegance of F#'s type system and syntax is an enticing prospect.
I'll continue to experiment with TensorFlow, perhaps starting a proper Fable binding to avoid the dynamic invocation.
Photo by David Clode on Unsplash