This is David Davó's work product submission for Google Summer of Code 2021 working with IHaskell fixing the widgets library.
The main PR with almost all the commits is #1242: Updating IHaskell-widgets, but you can find every PR I have made to the project before finishing GSoC clicking here.
You can test and play a bit with it and run the sample notebooks using this binder link. Warning: it may take a while to load.
I strongly recommend you to start with the Introduction to Widgets notebook, and then go try the other ones.
If you want to install it on your local machine, you can follow the installation guide at the project’s README.
Here I’ll explain some solved and unsolved problems that I stumbled upon while working on the project
The purpose of this GSoC was to fix ihaskell-widgets, which wasn’t working properly because the widget messaging specification changed A LOT.
I had to navigate through lots of resources and do some “reverse engineering” using network inspection tools (tcpdump).
Some of these resources have been compiled to MsgSpec.md
This library was written using the concept of dependent types. These are types whose definition depends on a value. In IHaskell-widgets, using dependent types allows checking properties of the widgets at compile time, using the powerful Haskell’s type system.
For example, in IHaskell-widgets, the type of a widget consists of an array of fields. Then we can check in setField
if the field that we want to set is among the array of fields of the widget at compile time!
But Haskell doesn’t support dependent types (yet), so we use the next best thing: a pattern called Singleton
that introduces reflection and reification and allows us to get types that depend
on values at runtime.
This was the most complex library I’ve had to learn to use, and it took me a lot of reading blogs and papers. If you want to learn more you can find more info here:
In Python, we are able to capture the output of any function using the Output
widget.
The output generated from the following lines would be captured and displayed wherever
the Output
widget has been displayed.
with out:
display(YouTubeVideo("dQw4w9WgXcQ"))
for int i in range(10):
print(i, "Hello World!")
What this code does is that it changes the msg_id
attribute to the msg_id of the execution_request
. For this to be possible, the output widget has to somehow access parts of the
Kernel state to obtain that msg_id
.
Now, what about implementing it in Haskell? We could do a simple interface with a custom monad. Or with a function that executes a function while capturing the output, like this:
capture :: OutputWidget -> IO () -> IO ()
capture o f = do
startCapturing o
f
endCapturing o
where startCapturing = setField o MsgID ???
endCapturing = setField o MsgID ""
Now, what’s the problem? Haskell is a functional language without side effects, so it’s a bit
more difficult than just accessing the msg_id
… In conclusion, we have two options:
msg_id
from here to there as a parameter in the comm
function of the widget (or any other function that is called between the execution_request
and the capturing)getExReqMsgId :: IO String
function that accesses an IORef
of the kernel state and
gets us the message ID.I think the latest is a bit dirtier, but easier to read.
The controller widget is created with empty arrays of Buttons and Axes. When a controller
is connected and configured, it passes via comm
its new attributes such as its name, mapping, etc. (like every widget modifiable by the frontend does). It also passes an array of Buttons and Axes widgets IDs. The problem is where do these IDs come from. Do we have to create widgets with these IDs?
Are these widgets already created and on the kernel state? How do we access the kernel state?
We end with a problem similar to “getting the execution request msg_id”, because we have to
access the kernel state, and get a widget given it’s ID… So we either pass some part of the kernel
state in the comm
function, or we create an getAxis :: String -> IO ControllerAxis
function.
If you want to continue or contribute to this project, here are some things that are still to be done as of today, 16th of August 2021. Some of them have been discussed at Technical difficulties
Color
data type instead of using strings. (Or using one from an external library)setField
so it can be used with wrapper types without using the data constructor every time
setField w Index 3
should just work instead of having to use setField w Index $ Just 3
setField
done, while ipython sends a final update message when all changes
have been made.