Building GUIs in Haskell Building GUIs in Haskell Comparing Gtk2Hs and wxhaskell, Universiteit Utrecht December 14, 2006
Building GUIs in Haskell Dazzle
Building GUIs in Haskell Proxima
Building GUIs in Haskell hide
Building GUIs in Haskell Frag
Building GUIs in Haskell Overview 1 Introduction 2 Gtk2Hs 3 wxhaskell 4 Comparison 5 Styles of libraries 6 Conclusion
Building GUIs in Haskell > Introduction Prejudice of building GUIs in Haskell Building GUIs in Haskell is underrated, many prejudices exist: Most Haskell applications do not require a GUI Very few useful GUI libraries exist Implementing a GUI requires an object-oriented language
Building GUIs in Haskell > Introduction Prejudice of building GUIs in Haskell Building GUIs in Haskell is underrated, many prejudices exist: Most Haskell applications do not require a GUI Very few useful GUI libraries exist Implementing a GUI requires an object-oriented language Haskell features that facilitate building GUIs in Haskell: Monads Higher-order functions Polymorphism Type classes Phantom types
Building GUIs in Haskell > Introduction Implementation of GUI libraries Building a GUI library in Haskell from scratch is fruitless: Lifting on success of another library is easier Implementing a complete library takes a long time Extension to another library is more maintainable Many extensive libraries already exist
Building GUIs in Haskell > Introduction Implementation of GUI libraries Building a GUI library in Haskell from scratch is fruitless: Lifting on success of another library is easier Implementing a complete library takes a long time Extension to another library is more maintainable Many extensive libraries already exist Problem: Existing extensive libraries are mostly written in C/C++
Building GUIs in Haskell > Introduction Implementation of GUI libraries Building a GUI library in Haskell from scratch is fruitless: Lifting on success of another library is easier Implementing a complete library takes a long time Extension to another library is more maintainable Many extensive libraries already exist Problem: Existing extensive libraries are mostly written in C/C++ Solution: Use the Foreign Function Interface (FFI)
Building GUIs in Haskell > Introduction Foreign Function Interface FFI allows to interact with another language, two major problems: 1 Haskell value can reference C/C++ value Problem C/C++ expects the user to explicitly free memory Solution Use a ForeignPtr with reference scheme
Building GUIs in Haskell > Introduction Foreign Function Interface FFI allows to interact with another language, two major problems: 1 Haskell value can reference C/C++ value Problem C/C++ expects the user to explicitly free memory Solution Use a ForeignPtr with reference scheme 2 C/C++ value referencing Haskell value Problem Garbage collection for Haskell can free memory too early move data around in memory Solution Use a StablePtr, which is untouched by the garbage collector
Building GUIs in Haskell > Introduction Comparison of libraries Two different styles of libraries: Monadic, imperative feel to building GUIs Declarative, point-free style using arrows
Building GUIs in Haskell > Introduction Comparison of libraries Two different styles of libraries: Monadic, imperative feel to building GUIs Declarative, point-free style using arrows Two monadic libraries will be compared: Gtk2Hs Developed by Axel Simon, currently maintained by Duncan Coutts wxhaskell Developed by Daan Leijen
Building GUIs in Haskell > Introduction Comparison of libraries Two different styles of libraries: Monadic, imperative feel to building GUIs Declarative, point-free style using arrows Two monadic libraries will be compared: Gtk2Hs Developed by Axel Simon, currently maintained by Duncan Coutts wxhaskell Developed by Daan Leijen Question: What determines the usability of a GUI library in Haskell?
Building GUIs in Haskell > Introduction Running example Application converting currencies: Managing state Managing widgets Frame Input field Button Implementing layout Events Gtk2Hs wxhaskell
Building GUIs in Haskell > Introduction Library-independent code Currency data type data Currency = Euro Guilder deriving Show Showing a currency tolabel :: Currency String tolabel = ("To " +). show type State = (Float, Currency)
Building GUIs in Haskell > Introduction Library-independent code Currency data type data Currency = Euro Guilder deriving Show Showing a currency tolabel :: Currency String tolabel = ("To " +). show type State = (Float, Currency) Flipping the current state flipstate :: State State flipstate (rate, Euro) = (1 / rate, Guilder) flipstate (rate, Guilder) = (1 / rate, Euro)
Building GUIs in Haskell > Gtk2Hs > Example Gtk2Hs in action (1) Building the GUI main :: IO () main = do initgui var newioref (2.20371, Euro) frm windownew windowsettitle frm "Converter" ent entrynew entrysettext ent "0" btn buttonnew buttonsetlabel (tolabel Guilder)...
Building GUIs in Haskell > Gtk2Hs > Example Gtk2Hs in action (1) Building the GUI main :: IO () main = do initgui var newioref (2.20371, Euro) frm windownew windowsettitle frm "Converter" ent entrynew entrysettext ent "0" btn buttonnew buttonsetlabel (tolabel Guilder)...
Building GUIs in Haskell > Gtk2Hs > Example Gtk2Hs in action (1) Building the GUI main :: IO () main = do initgui var newioref (2.20371, Euro) frm windownew windowsettitle frm "Converter" ent entrynew entrysettext ent "0" btn buttonnew buttonsetlabel (tolabel Guilder)...
Building GUIs in Haskell > Gtk2Hs > Example Gtk2Hs in action (1) Building the GUI main :: IO () main = do initgui var newioref (2.20371, Euro) frm windownew windowsettitle frm "Converter" ent entrynew entrysettext ent "0" btn buttonnew buttonsetlabel (tolabel Guilder)...
Building GUIs in Haskell > Gtk2Hs > Example Gtk2Hs in action (1) Building the GUI main :: IO () main = do initgui var newioref (2.20371, Euro) frm windownew windowsettitle frm "Converter" ent entrynew entrysettext ent "0" btn buttonnew buttonsetlabel (tolabel Guilder)...
Building GUIs in Haskell > Gtk2Hs > Example Gtk2Hs in action (2) Building the GUI cont d main :: IO () main = do... box hboxnew True 5 set box [containerchild := ent ] set box [containerchild := btn] set frm [containerchild := box ] let click = change var ent btn onclicked btn click widgetshowall frm maingui
Building GUIs in Haskell > Gtk2Hs > Example Gtk2Hs in action (2) Building the GUI cont d main :: IO () main = do... box hboxnew True 5 set box [containerchild := ent ] set box [containerchild := btn] set frm [containerchild := box ] let click = change var ent btn onclicked btn click widgetshowall frm maingui
Building GUIs in Haskell > Gtk2Hs > Example Gtk2Hs in action (2) Building the GUI cont d main :: IO () main = do... box hboxnew True 5 set box [containerchild := ent ] set box [containerchild := btn] set frm [containerchild := box ] let click = change var ent btn onclicked btn click widgetshowall frm maingui
Building GUIs in Haskell > Gtk2Hs > Example Gtk2Hs in action (3) Implementing the event handler type StateVar = IORef State change :: StateVar Entry Button IO () change var ent btn = do state@(rate, currency) readioref var input entrygettext ent case string2float input of Just x do writeioref var (flipstate state) entrysettext ent (show $ rate x) buttonsetlabel btn (tolabel currency) Nothing return ()
Building GUIs in Haskell > Gtk2Hs > Example Gtk2Hs in action (3) Implementing the event handler type StateVar = IORef State change :: StateVar Entry Button IO () change var ent btn = do state@(rate, currency) readioref var input entrygettext ent case string2float input of Just x do writeioref var (flipstate state) entrysettext ent (show $ rate x) buttonsetlabel btn (tolabel currency) Nothing return ()
Building GUIs in Haskell > Gtk2Hs > Example Gtk2Hs in action (3) Implementing the event handler type StateVar = IORef State change :: StateVar Entry Button IO () change var ent btn = do state@(rate, currency) readioref var input entrygettext ent case string2float input of Just x do writeioref var (flipstate state) entrysettext ent (show $ rate x) buttonsetlabel btn (tolabel currency) Nothing return ()
Building GUIs in Haskell > Gtk2Hs > Example Gtk2Hs in action (3) Implementing the event handler type StateVar = IORef State change :: StateVar Entry Button IO () change var ent btn = do state@(rate, currency) readioref var input entrygettext ent case string2float input of Just x do writeioref var (flipstate state) entrysettext ent (show $ rate x) buttonsetlabel btn (tolabel currency) Nothing return ()
Building GUIs in Haskell > Gtk2Hs > Example Gtk2Hs in action (3) Implementing the event handler type StateVar = IORef State change :: StateVar Entry Button IO () change var ent btn = do state@(rate, currency) readioref var input entrygettext ent case string2float input of Just x do writeioref var (flipstate state) entrysettext ent (show $ rate x) buttonsetlabel btn (tolabel currency) Nothing return ()
Building GUIs in Haskell > Gtk2Hs > Example Gtk2Hs in action (3) Implementing the event handler type StateVar = IORef State change :: StateVar Entry Button IO () change var ent btn = do state@(rate, currency) readioref var input entrygettext ent case string2float input of Just x do writeioref var (flipstate state) entrysettext ent (show $ rate x) buttonsetlabel btn (tolabel currency) Nothing return ()
Building GUIs in Haskell > Gtk2Hs > Example Gtk2Hs in action (3) Implementing the event handler type StateVar = IORef State change :: StateVar Entry Button IO () change var ent btn = do state@(rate, currency) readioref var input entrygettext ent case string2float input of Just x do writeioref var (flipstate state) entrysettext ent (show $ rate x) buttonsetlabel btn (tolabel currency) Nothing return ()
Building GUIs in Haskell > Gtk2Hs > Example Gtk2Hs in action (3) Implementing the event handler type StateVar = IORef State change :: StateVar Entry Button IO () change var ent btn = do state@(rate, currency) readioref var input entrygettext ent case string2float input of Just x do writeioref var (flipstate state) entrysettext ent (show $ rate x) buttonsetlabel btn (tolabel currency) Nothing return ()
Building GUIs in Haskell > Gtk2Hs > Architecture Underlying toolkit Gtk2Hs built on top of GTK+: Large open source toolkit Many widgets available Platform independent Written in C Gtk2Hs API generated from GTK+ source
Building GUIs in Haskell > Gtk2Hs > Architecture Main features General features: Almost covered the complete GTK+ API Platform independence Bindings for Cairo vector graphics library Gnome modules (GConf and SourceView) Mozilla browser rendering engine Extensive documentation Unicode support Important feature to discuss: Support for visual GUI builder, called Glade
Building GUIs in Haskell > Gtk2Hs > Glade The visual GUI builder Defining (complex) layout by hand is hard: Not really intuitive Looking up library functions Effect of change visible after compilation
Building GUIs in Haskell > Gtk2Hs > Glade The visual GUI builder Defining (complex) layout by hand is hard: Not really intuitive Looking up library functions Effect of change visible after compilation do... edit textviewnew vbox vboxnew False 0 set vbox [boxhomogeneous := False ] boxpackstart vbox menubar PackNatural 0 boxpackstart vbox toolbar PackNatural 0 boxpackstart vbox edit PackGrow 0...
Building GUIs in Haskell > Gtk2Hs > Glade Glade to the rescue! Glade, the visual GUI builder: Comes with GTK+ Drag n drop widgets Designed GUI can be imported using Gtk2Hs
Building GUIs in Haskell > Gtk2Hs > Glade Exporting GUIs Glade saves GUI to.glade file (XML format)... <child> <widget class="gtkhbox" id="box"> <property name="visible">true</property> <property name="homogeneous">false</property> <property name="spacing">0</property> <child> <widget class="gtkentry" id="ent">...
Building GUIs in Haskell > Gtk2Hs > Glade Exporting GUIs Glade saves GUI to.glade file (XML format)... <child> <widget class="gtkhbox" id="box"> <property name="visible">true</property> <property name="homogeneous">false</property> <property name="spacing">0</property> <child> <widget class="gtkentry" id="ent">... Import widgets with xmlgetwidget using the id attribute
Building GUIs in Haskell > Gtk2Hs > Glade Gtk2Hs and Glade in action Creating and laying out widgets is done using Glade: Building the GUI main :: IO () main = do initgui Just xml xmlnew "converter.glade" frm xmlgetwidget xml casttowindow "frm" ent xmlgetwidget xml casttoentry "ent" btn xmlgetwidget xml casttobutton "btn"... The rest of the code is left unchanged
Building GUIs in Haskell > Gtk2Hs > Glade Gtk2Hs and Glade in action Creating and laying out widgets is done using Glade: Building the GUI main :: IO () main = do initgui Just xml xmlnew "converter.glade" frm xmlgetwidget xml casttowindow "frm" ent xmlgetwidget xml casttoentry "ent" btn xmlgetwidget xml casttobutton "btn"... The rest of the code is left unchanged
Building GUIs in Haskell > Gtk2Hs > Glade Gtk2Hs and Glade in action Creating and laying out widgets is done using Glade: Building the GUI main :: IO () main = do initgui Just xml xmlnew "converter.glade" frm xmlgetwidget xml casttowindow "frm" ent xmlgetwidget xml casttoentry "ent" btn xmlgetwidget xml casttobutton "btn"... The rest of the code is left unchanged
Building GUIs in Haskell > wxhaskell > Example wxhaskell in action (1) Building the GUI main :: IO () main = start $ do var variable [value := (2.20371, Euro)] frm frame [text := "Converter"] ent mkvalueentry frm [typedvalue := Just 0] btn button frm [text := tolabel Guilder ] set frm [layout := row 5 [widget ent, widget btn]] let click = change var ent btn set btn [on command := click ]
Building GUIs in Haskell > wxhaskell > Example wxhaskell in action (1) Building the GUI main :: IO () main = start $ do var variable [value := (2.20371, Euro)] frm frame [text := "Converter"] ent mkvalueentry frm [typedvalue := Just 0] btn button frm [text := tolabel Guilder ] set frm [layout := row 5 [widget ent, widget btn]] let click = change var ent btn set btn [on command := click ]
Building GUIs in Haskell > wxhaskell > Example wxhaskell in action (1) Building the GUI main :: IO () main = start $ do var variable [value := (2.20371, Euro)] frm frame [text := "Converter"] ent mkvalueentry frm [typedvalue := Just 0] btn button frm [text := tolabel Guilder ] set frm [layout := row 5 [widget ent, widget btn]] let click = change var ent btn set btn [on command := click ]
Building GUIs in Haskell > wxhaskell > Example wxhaskell in action (1) Building the GUI main :: IO () main = start $ do var variable [value := (2.20371, Euro)] frm frame [text := "Converter"] ent mkvalueentry frm [typedvalue := Just 0] btn button frm [text := tolabel Guilder ] set frm [layout := row 5 [widget ent, widget btn]] let click = change var ent btn set btn [on command := click ]
Building GUIs in Haskell > wxhaskell > Example wxhaskell in action (1) Building the GUI main :: IO () main = start $ do var variable [value := (2.20371, Euro)] frm frame [text := "Converter"] ent mkvalueentry frm [typedvalue := Just 0] btn button frm [text := tolabel Guilder ] set frm [layout := row 5 [widget ent, widget btn]] let click = change var ent btn set btn [on command := click ]
Building GUIs in Haskell > wxhaskell > Example wxhaskell in action (1) Building the GUI main :: IO () main = start $ do var variable [value := (2.20371, Euro)] frm frame [text := "Converter"] ent mkvalueentry frm [typedvalue := Just 0] btn button frm [text := tolabel Guilder ] set frm [layout := row 5 [widget ent, widget btn]] let click = change var ent btn set btn [on command := click ]
Building GUIs in Haskell > wxhaskell > Example wxhaskell in action (1) Building the GUI main :: IO () main = start $ do var variable [value := (2.20371, Euro)] frm frame [text := "Converter"] ent mkvalueentry frm [typedvalue := Just 0] btn button frm [text := tolabel Guilder ] set frm [layout := row 5 [widget ent, widget btn]] let click = change var ent btn set btn [on command := click ]
Building GUIs in Haskell > wxhaskell > Example wxhaskell in action (2) Implementing the event handler type StateVar = Var State change :: StateVar ValueEntry Float () Button () IO () change var ent btn = do (rate, currency) get var value input get ent typedvalue case input of Just x do set var [value : flipstate ] set ent [typedvalue := Just (rate x)] set btn [text := tolabel currency ] Nothing return ()
Building GUIs in Haskell > wxhaskell > Example wxhaskell in action (2) Implementing the event handler type StateVar = Var State change :: StateVar ValueEntry Float () Button () IO () change var ent btn = do (rate, currency) get var value input get ent typedvalue case input of Just x do set var [value : flipstate ] set ent [typedvalue := Just (rate x)] set btn [text := tolabel currency ] Nothing return ()
Building GUIs in Haskell > wxhaskell > Example wxhaskell in action (2) Implementing the event handler type StateVar = Var State change :: StateVar ValueEntry Float () Button () IO () change var ent btn = do (rate, currency) get var value input get ent typedvalue case input of Just x do set var [value : flipstate ] set ent [typedvalue := Just (rate x)] set btn [text := tolabel currency ] Nothing return ()
Building GUIs in Haskell > wxhaskell > Example wxhaskell in action (2) Implementing the event handler type StateVar = Var State change :: StateVar ValueEntry Float () Button () IO () change var ent btn = do (rate, currency) get var value input get ent typedvalue case input of Just x do set var [value : flipstate ] set ent [typedvalue := Just (rate x)] set btn [text := tolabel currency ] Nothing return ()
Building GUIs in Haskell > wxhaskell > Example wxhaskell in action (2) Implementing the event handler type StateVar = Var State change :: StateVar ValueEntry Float () Button () IO () change var ent btn = do (rate, currency) get var value input get ent typedvalue case input of Just x do set var [value : flipstate ] set ent [typedvalue := Just (rate x)] set btn [text := tolabel currency ] Nothing return ()
Building GUIs in Haskell > wxhaskell > Example wxhaskell in action (2) Implementing the event handler type StateVar = Var State change :: StateVar ValueEntry Float () Button () IO () change var ent btn = do (rate, currency) get var value input get ent typedvalue case input of Just x do set var [value : flipstate ] set ent [typedvalue := Just (rate x)] set btn [text := tolabel currency ] Nothing return ()
Building GUIs in Haskell > wxhaskell > Example wxhaskell in action (2) Implementing the event handler type StateVar = Var State change :: StateVar ValueEntry Float () Button () IO () change var ent btn = do (rate, currency) get var value input get ent typedvalue case input of Just x do set var [value : flipstate ] set ent [typedvalue := Just (rate x)] set btn [text := tolabel currency ] Nothing return ()
Building GUIs in Haskell > wxhaskell > Example wxhaskell in action (2) Implementing the event handler type StateVar = Var State change :: StateVar ValueEntry Float () Button () IO () change var ent btn = do (rate, currency) get var value input get ent typedvalue case input of Just x do set var [value : flipstate ] set ent [typedvalue := Just (rate x)] set btn [text := tolabel currency ] Nothing return ()
Building GUIs in Haskell > wxhaskell > Architecture Underlying toolkit wxhaskell built on top of wxwidgets: Large open source toolkit Many widgets available Platform independent Written in C++ wxhaskell API generated from wxwidgets source
Building GUIs in Haskell > wxhaskell > Architecture Main features General features: Completely covered wxwidgets API Platform independence Extensive documentation Unicode support Important features to discuss: Inheritance between widgets Shared attributes Typed widgets
Building GUIs in Haskell > wxhaskell > Architecture Inheritance between widgets (1) Pointer to a C++ object type Object a = Ptr a
Building GUIs in Haskell > wxhaskell > Architecture Inheritance between widgets (1) Pointer to a C++ object type Object a = Ptr a For each C++ class, a phantom type is introduced: Phantom data types data CWindow a data CControl a data CButton a
Building GUIs in Haskell > wxhaskell > Architecture Inheritance between widgets (1) Pointer to a C++ object type Object a = Ptr a For each C++ class, a phantom type is introduced: Phantom data types data CWindow a data CControl a data CButton a Model inheritance using type synonyms: type Window a = Object (CWindow a) type Control a = Window (CControl a) type Button a = Control (CButton a)
Building GUIs in Haskell > wxhaskell > Architecture Inheritance between widgets (2) Unfold the definition of Button Button a = Control (CButton a) = Window (CControl (CButton a)) = Object (CWindow (CControl (CButton a)))
Building GUIs in Haskell > wxhaskell > Architecture Inheritance between widgets (2) Unfold the definition of Button Button a = Control (CButton a) = Window (CControl (CButton a)) = Object (CWindow (CControl (CButton a))) button :: Window a [Prop (Button ())] IO (Button ()) Window a is at least an instance of Window Button () is exactly an instance of Button
Building GUIs in Haskell > wxhaskell > Architecture Shared attributes Using inheritance for shared attributes text :: Attr (Window a) String Unfortunately, there is no uniform implementation available
Building GUIs in Haskell > wxhaskell > Architecture Shared attributes Using inheritance for shared attributes text :: Attr (Window a) String Unfortunately, there is no uniform implementation available Combination of inheritance and overloading class Textual w where text :: Attr w String instance Textual (Button a) where text =...
Building GUIs in Haskell > wxhaskell > XTC Typed widgets The extended and Typed Controls (XTC) library: Developed by Arjan van IJzendoorn en Martijn Schrage Parsing and showing of values by XTC Visual feedback given when parsing fails No need for explicit conversions
Building GUIs in Haskell > wxhaskell > XTC Typed widgets The extended and Typed Controls (XTC) library: Developed by Arjan van IJzendoorn en Martijn Schrage Parsing and showing of values by XTC Visual feedback given when parsing fails No need for explicit conversions Remember the following line from the wxhaskell example:... ent mkvalueentry frm [typedvalue := Just 0]...
Building GUIs in Haskell > Comparison Strongest points of Gtk2Hs Glade, the visual GUI builder Graphical power, especially using the cairo vector graphics library
Building GUIs in Haskell > Comparison Strongest points of wxhaskell In general, there are more abstractions available: Administrative tasks (e.g, start) Gtk2Hs should have offered some abstraction for this: Possible definition of start in Gtk2Hs start :: WidgetClass a IO a IO () start gui = do initgui frame gui widgetshowall frame maingui Inheritance between widgets Shared attributes Typed widgets
Building GUIs in Haskell > Styles of libraries Different styles of libraries Monadic style: Imperative intuition Declarative style: Functional intuition Monadic libraries: Declarative libraries:
Building GUIs in Haskell > Styles of libraries Different styles of libraries Monadic style: Imperative intuition Binding variables Declarative style: Functional intuition Point-free programming Monadic libraries: Declarative libraries:
Building GUIs in Haskell > Styles of libraries Different styles of libraries Monadic style: Imperative intuition Binding variables Explicit event handling Declarative style: Functional intuition Point-free programming Implicit event handling Monadic libraries: Declarative libraries:
Building GUIs in Haskell > Styles of libraries Different styles of libraries Monadic style: Imperative intuition Binding variables Explicit event handling Static aspect (layout) of building GUIs Monadic libraries: Declarative style: Functional intuition Point-free programming Implicit event handling Dynamic aspect (events) of building GUIs Declarative libraries:
Building GUIs in Haskell > Styles of libraries Different styles of libraries Monadic style: Imperative intuition Binding variables Explicit event handling Static aspect (layout) of building GUIs Monadic libraries: Gtk2Hs wxhaskell HToolkit... Declarative style: Functional intuition Point-free programming Implicit event handling Dynamic aspect (events) of building GUIs Declarative libraries: Fudgets Fruit Yampa...
Building GUIs in Haskell > Conclusion Concluding remarks Question: What determines the usability of a GUI library in Haskell? Usability of a library depends on its abstractions Cross-pollination improves development of libraries
Building GUIs in Haskell > Conclusion Concluding remarks Question: What determines the usability of a GUI library in Haskell? Usability of a library depends on its abstractions Cross-pollination improves development of libraries General remark about the two libraries introduced: Gtk2Hs is powerful, but misses a lot of abstractions wxhaskell has nice abstractions, but could improve on: Bindings for (more powerful) graphical libraries A visual GUI builder (thesis project anyone?)
Building GUIs in Haskell > Conclusion Concluding remarks Question: What determines the usability of a GUI library in Haskell? Usability of a library depends on its abstractions Cross-pollination improves development of libraries General remark about the two libraries introduced: Gtk2Hs is powerful, but misses a lot of abstractions wxhaskell has nice abstractions, but could improve on: Bindings for (more powerful) graphical libraries A visual GUI builder (thesis project anyone?) If you are interested, visit the websites of the libraries discussed: haskell.org/gtk2hs wxhaskell.sourceforge.net