United Kingdom: +44 (0)208 088 8978

Calling Python from F# with Fable

This week Ryan checks out Fable's new Python compilation support by trying to import and call TensorFlow functions.

We're hiring Software Developers

Click here to find out more

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

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