Haskell and QML – Modeling Lists in HsQML

HsQML is a Haskell binding for Qt Quick 2 which provides a set of features for integrating QML and Haskell. You can find complete samples on hackage which are great for getting started.

In this post I will discuss how to create a model for a QML ListView in Haskell using HsQML. I want to thank the author of HsQML for his continued help and correspondence on this post, the HsQML library, and it’s development.

In QML, it is possible to define a Context Object to expose properties from C++. HsQML builds on this by utilizing objects as the primary bridge of communication between Haskell and QML code. QML documents may reference an object’s properties to fetch data and can also call an object’s methods to invoke actions. This makes for a very clean decoupling of UI elements and the supporting backend.

The problem is that many types of data we wish to model can get complicated. QML has different types of views which support arbitrarily complex model interfaces to represent dynamic datasets. Because there is no facility in HsQML to define a QAbstractItemModel directly, we can get by with marshalling lists of items in many situations.

Why Should I Care?

Haskell is a very powerful production-ready programming language with an aptitude for producing software that just works. My work at KDAB with Qt and enthusiasm for Haskell has driven me to research the various Haskell user interface toolkits and what they may have to offer for developers and end-users. The HsQML language bindings open up a myriad of opportunity for Haskell development that is otherwise lacking, under-developed, or often unwieldy in other toolkits I have worked with. HsQML brings the power of native fluid user interfaces to the Haskell programming language which could open up a wide range of possibilities across mobile, embedded, and desktop application development.

I won’t preach the benefits of adopting Haskell. Rather, if you are using Haskell (or considering adoption) and curious whether HsQML is a good fit as a user interface toolkit you might consider:

  • HsQML is cross-platform. Not only does HsQML run on OSX, Windows, and Linux, it is also possible to port applications to arm chipsets like the Raspberry Pi. Although it still needs some battle testing, this could prove to be a huge step over porting other native Haskell user interface frameworks or bindings.
  • HsQML is constantly enhanced with additions to Qt Quick, Qt Quick Controls and other ongoing QML enhancements that are not specific to Haskell. HsQML will continually reap the benefits of work done on the QML engine, QML proper, and Qt Quick. This is important in a smaller community where developer resources may be scarce for developing and maintaining new user interface toolkits.
  • HsQML is a much smaller binding than other toolkits. This could save time when compiling and also prevent associated issues with Cabal dependencies and small bugs that tend to crop up from larger bindings from time to time.
  • QML is a simple declarative UI language which gives designers ability to work alongside backend developers. Nuno Pinheiro, a KDAB designer, highlights the advantages of using QML from a designers perspective in the slides for his talk: http://www.desktopsummit.org/program/sessions/qml-designers-perspective.
  • HsQML code can generally be written in a highly decoupled manner in contrast to some of the traditional heavyweight ui toolkits in Haskell like Gtk2Hs or HTK. This could potentially lower the barrier of entry for writing user interfaces in Haskell while developing applications. This is not to suggest decoupling your ui code from your business logic is impossible, or even difficult in other toolkits; only that it is a very natural style that HsQML lends itself to.
  • Integration with Javascript is convenient due to HsQML’s marshaling features. The HsQML Morris Demo is a great example of using a thin JavaScript wrapper to help manage marshaled data. The goal, however, is to cut down on the amount of JavaScript code in general.
  • HsQML has thorough test coverage via QuickCheck. QuickCheck is an amazing combinator library designed to automate testing Haskell code. QuickCheck is a type-based “property” testing framework.

    Property-based testing encourages a high level approach to testing in the form of abstract invariants functions should satisfy universally, with the actual test data generated for the programmer by the testing library. In this way code can be hammered with thousands of tests that would be infeasible to write by hand, often uncovering subtle corner cases that wouldn’t be found otherwise. – Real World Haskell – Chapter 11

  • One major lacking feature for HsQML would be current OpenGL support. I have had many discussions with the author of HsQML and my impression is that he plans to implement OpenGL support in the future. Qt3d should should just work provided that you can add a Qt3d view-port to a Qt Quick scene. The QML examples in the Qt3d repository don’t do this, but I presume it’s on their road-map.

Imports

Our example program requires a few language extensions and imports to get started.


{-# LANGUAGE DeriveDataTypeable, TypeFamilies, OverloadedStrings #-}

import Control.Concurrent
import Data.Proxy
import qualified Data.Text as T
import Data.Typeable
import Graphics.QML

While a full explanation of the langauge extensions is beyond the scope of this post, it`s worth pointing out the extension DeriveDataTypeable and Data.Typeable allow a limited set of type casting utilities. The extension DeriveDataTypeable automatically handles data types deriving Typeable. TypeFamilies are the data type analogue of type classes.

Defining QML Types

Next, A data type representing the QML context object and list item is defined:


data ContextObj = ContextObj
                { _list :: MVar [ObjRef ListItem]
                } deriving Typeable

data ListItem = ListItem
              { _text :: T.Text
              } deriving Typeable

ContextObj is a record containing a single field which represents the list to be modeled in QML. _list is of the type MVar [ObjRef ListItem]; an MVar to a list of ObjRef ListItems. A breakdown of the type signature sheds some light on why all the types are necessary:

  • ListItem is a Haskell record containing the data for each item.
  • ObjRef ListItem is a handle to a QML object which wraps ListItem and makes data accessible to QML. Data types can reap the benefits of automated marshaling between QML and Haskell when qualified by the type ObjRef, e.g. `ObjRef tt`.
  • [ObjRef ListItem] is a list of ObjRef ListItem.
  • MVar [ObjRef ListItem] is a mutex-protected mutable reference to the list so that it can be update over the course of the programs execution. MVar provides an interface to modify and read persistent state concurrently.

Although ListItem only contains text, I find it a bit more useful as an example if you are working towards implementing more complicated list model types.

Signals

Signals are defined as empty data types which implement the SignalKeyClass type class and derive Typeable. Signals do not have any constructors and thus never exist as values. The empty type signatures are used to identify signals at the type-level. In this way, Signals can be declared top-level and used in our DefaultClass instances.

-- Signals
data ListChanged deriving Typeable

instance SignalKeyClass ListChanged where
    type SignalParams ListChanged = IO ()

The signal ListChanged is fired when a ListItem is added or removed from the _list property in ContextObj.

ListChanged does not define any SignalParams. The IO () (pronounced IO unit or IO action) describes the signal as an action within the IO Monad. The SignalParams type parameter specifies the type signature of the signal.

Necessary Type Classes

In order to create QML objects for values of ContextObj and ListItem, we have to provide class definitions for these object types. This can be done using the DefaultClass typeclass.


instance DefaultClass ContextObj where
    classMembers = [
          defPropertySigRO "list" (Proxy :: Proxy ListChanged) $ readMVar . _list . fromObjRef
        , defMethod "appendList" appendList
        ]

instance DefaultClass ListItem where
    classMembers = [
          defPropertyRO "text" $ return . _text . fromObjRef
        ]

A DefaultClass can define properties and methods which can be accessed in QML code. defPropertySigRO defines a named read-only property with an attached NOTIFY signal. This would be semantically identical to the Qt C++ macro:

     Q_PROPERTY(QList list READ list NOTIFY listChanged)

defPropertyRO is no different except that it has no attached NOTIFY signal. defMethod defines a named method for running a function in the IO Monad from QML. This would be semantically similar to using the Q_INVOKABLE macro in Qt C++.

ListItem defines a property text which returns the _text field of a ListItem. ListItem access from QML looks something like this:

    ListView {
        model: list
        delegate: Text { text: modelData.text }
    }

We assign the list property defined in ContextObj to model. Because list is not actually a QAbstractItemModel, there is no way to implement named roles for our list of items. modelData provides a reference to the corresponding ListItem in the delegate and is provided by the QML engine for models that only have one role.

Defining QML Functions

The function appendList is called by ContextObj and adds a new element to the mutable list of ListItem’s using modifyMVar_.


appendList :: ObjRef ContextObj -> T.Text -> IO ()
appendList co txt = modifyMVar_ list (\ls -> do l <- newObjectDC $ ListItem txt
                                                return (l:ls)) >>
                    fireSignal (Proxy :: Proxy ListChanged) co
    -- Retrieve the list field from the ContextObj
    where list = _list . fromObjRef $ co

fireSignal is called to notify the list view that changes have been made. fireSignal is thread-safe and passes the signal to the event loop thread for processing. Haskell functions called from QML will be run in the GUI thread. Use forkIO to deal with any long running operations to reduce UI latency.

Marshaling

HsQML defines Marshal instances for several types including Int, Double, Text, Bool, ObjRef t, List ([a]), and Maybe a. As I mentioned earier, defining our types in terms of ObjRef allows for automatic marshalling capabilities between the QML and Haskell code. It is possible to define a Marshal instance for our ListItem type:

instance Marshal ListItem where
    type MarshalMode ListItem c d = ModeObjFrom ListItem c
    marshaller = fromMarshaller fromObjRef

However, an instance of `Marshal ListItem` is not needed since we use `ObjRef ListItem` to reference each list item in our ContextObj data type.

Putting it all together

Finally, the main function.


main :: IO ()
main = do

    l <- newMVar []
    tc <- newObjectDC $ ContextObj l

    runEngineLoop defaultEngineConfig {
      initialDocument = fileDocument "ui.qml"
    , contextObject   = Just $ anyObjRef tc
    }

A newMVar with an empty list is used to construct an instance of ContextObj using newObjectDC. The DC at the end is used on types that are instances of DefaultClass. runEngineLoop starts the qml engine and sets the initial QML document as well as the contextObject.

hsqml-listmodel

When running the program you may notice the following error:

Expression depends on non-NOTIFYable properties

This can be avoided by using a method with no arguments instead of a property since there is no need to listen for updates. This would be similar to the CONST attribute in the C++ Q_PROPERTY macro. There are plans to support CONST properties in HsQML in the future.

The complete code for this post can be found at https://github.com/creichert/hsqml-listmodel.git. The post is written in Literate Haskell style and can be compiled directly. Check the README for instructions on how to build the project.

For a less trivial example of HsQML, you can check out my in-progress port of the Qt Quick StocQt demo here: https://github.com/creichert/hsqmlstockqt.

hsqmlstocqt hsqmlstocqt2 hsqmlstocqt3

If you have any other questions or would like to know how you can advocate for Haskell usage feel free to comment!

Share on FacebookTweet about this on TwitterShare on Google+
m4s0n501

4 thoughts on “Haskell and QML – Modeling Lists in HsQML

  1. Hello! Do you have an article which describes how it works internally? I’m looking at your code and it seems that you add methods dynamically, i.e. avoiding Qt moc’s execution. You have probably reimplemented some of its feature for your needs…

    Also, what reaction does Haskell community provide on your project? I have tried to PR my OCaml+QtQuick project in OCaml community but I can’t say that I got a lot of constructive feedback… TT

    • I do not know of any resources which describe the HsQML internals. The darcs repository for HsQML may be the best place to inspect the classes that interact with Qt and the QML engine. I would be interested to hear your findings regarding the dynamically added methods.

      The reaction from the Haskell community has been very positive. I have had some conversations with developers interested in porting existing projects to HsQML. I have also been asked about the possibities of HsQML on mobile devices so I’m hopeful that with more examples and advocation we can increase the potential.

  2. Excellent news :) I’ve seen HsQML earlier, but it only supported QtQuick 1 from Qt4. This is great news :)

    release-0.3.0.0 – 2014.05.04
    * Ported to Qt 5 and Qt Quick 2

    Finally I’ll be able to mix Qt and Haskell how ever I wish. I’m guessing it would also be possible to make several modules in C++, and several modules in Haskell and bind them together in QML somehow.

    • There are also a lot of great upcoming features too: http://trac.gekkou.co.uk/hsqml/report/1

      I haven’t seen many examples of mixing Qt/C++ and Haskell via QML (if I understand correctly). It should definitely be possible although I think you may want to try out Singleton QML components on the C++ side since HsQML will assume control of the ContextObject for the root context.

      You could also check out the HsQML source for a good example of connecting to Qt/C++ to Haskell via the FFI.

Leave a Reply

Your email address will not be published. Required fields are marked *