Phooey is a functional UI library for Haskell. Or it's two of them, as it provides a
Monad interface and an
Applicative interface. The simplicity of Phooey's implementation is due to its use of Reactive for applicative, data-driven computation. (Before version 2.0, Phooey used the DataDriven library.)
Besides this wiki page, here are more ways to find out about Phooey:
- Read the Haddock docs (with source code, additional examples, and Comment/Talk links).
- Get the code repository: darcs get http://conal.net/repos/phooey.
cabal install phooey
Phooey is also used in GuiTV, a library for composable interfaces and "tangible values".
Since Reactive is currently broken (as of February 2010), Phooey is also broken.
GUIs are usually programmed in an unnatural style, in that implementation dependencies are inverted, relative to logical dependencies. This reversal results directly from the push (data-driven) orientation of most GUI libraries. While outputs depend on inputs from a user and semantic point of view, the push style imposes an implementation dependence of inputs on outputs.
A second drawback of the push style is that it is imperative rather than declarative. A GUI program describes actions to update a model and view in reaction to user input. In contrast to the how-to-update style of an imperative program, a functional GUI program would express what-it-is of a model in terms of the inputs and of the view in terms of the model.
The questions of push-vs-pull and imperative-vs-declarative are related. While an imperative GUI program could certainly be written to pull (poll) values from input to model and model to view, thus eliminating the dependency inversion, I don't know how a declarative program could be written in the inverted-dependency style. (Do you?).
A important reason for using push rather than pull in a GUI implementation is that push is typically much more efficient. A simple pull implementation would either waste time recomputing an unchanging model and view (pegging your CPU for no benefit), or deal with the complexity of avoiding that recomputation. The push style computes only when inputs change. (Continuous change, i.e. animation, negates this advantage of push.)
Phooey ("Phunctional ooser ynterfaces") adopts the declarative style, in which outputs are expressed in terms of inputs. Under the hood, however, the implementation is push-based (data-driven). Phooey uses the Reactive library to perform the dependency inversion invisibly, so that programmers may express GUIs simply and declaratively while still getting an efficient implementation.
Phooey came out of Pajama and Eros. Pajama is a re-implementation of the Pan language and compiler for function synthesis of interactive, continuous, infinite images. Pan and Pajama use a monadic style for specifying GUIs and are able to do so because they use the implementation trick of Compiling Embedded Languages, in which one manipulates expressions rather than values. (This trick is mostly transparent, but the illusion shows through in places.)
One example, two interfaces
As an example, below is a simple shopping list GUI. The
total displayed at the bottom of the window always shows the sum of the values of the
bananas input sliders. When a user changes the inputs, the output updates accordingly.
Phooey presents two styles of functional GUI interfaces, structured as a monad and as an applicative functor. (I have removed the original arrow interface.) Below you can see the code for the shopping list example in each of these styles.
The examples below are all found under
src/Examples/ in the phooey distribution, in the modules
Applicative.hs. In each case, the example is run by loading the corresponding example module into ghci and typing
ui1 :: UI () ui1 = title "Shopping List" $ do a <- title "apples" $ islider (0,10) 3 b <- title "bananas" $ islider (0,10) 7 title "total" $ showDisplay (liftA2 (+) a b)
The relevant library declarations:
-- Input widget type (with initial value) type IWidget a = a -> UI (Source a) -- Output widget type type OWidget a = Source a -> UI () islider :: (Int,Int) -> IWidget Int showDisplay :: Show a => OWidget a title :: String -> UI a -> UI a
Source type is a (data-driven) source of time-varying values. (
Source is a synonym for
Reactive.) By using
Source Int instead of
Int for the type of
b above, we do not have to rebuild the GUI every time an input value changes.
The down side of using source types is seen in the
showDisplay line above, which requires lifting. We could partially hide the lifting behind overloadings of
Num and other classes (as in Fran, Pan, and other systems). Some methods, however, do not not have sufficiently flexible types (e.g.,
(==)), and the illusion becomes awkward. The
Applicative interfaces hide the source types.
Before we move on to other interface styles, let's look at some refactorings. First pull out the slider minus initial value:
sl0 :: IWidget Int sl0 = islider (0,10)
Then the titled widgets:
apples, bananas :: UI (Source Int) apples = title "apples" $ sl0 3 bananas = title "bananas" $ sl0 7 total :: Num a => OWidget a total = title "total" . showDisplay
And use them:
ui1x :: UI () ui1x = title "Shopping List" $ do a <- apples b <- bananas total (liftA2 (+) a b)
We can go point-free by using
-- Sum UIs infixl 6 .+. (.+.) :: Num a => UIS a -> UIS a -> UIS a (.+.) = liftA2 (liftA2 (+)) fruit :: UI (Source Int) fruit = apples .+. bananas ui1y :: UI () ui1y = title "Shopping List" $ fruit >>= total
Applicative functors (AFs) provide still another approach to separating static and dynamic information. Here is our example, showing just the changes relative to the monadic version. (See the Applicative interface doc and its source code.)
ui1 :: UI (IO ()) ui1 = title "Shopping List" $ fruit <**> total fruit :: UI Int fruit = liftA2 (+) apples bananas total :: Num a => OWidget a total = title "total" showDisplay
I chose reversed AF application
(<**>) rather than
(<*>) so the fruit (argument) would be displayed above the total (function).
The UI-building functions again have the same types as before, relative to these new definitions:
type IWidget a = a -> UI a type OWidget a = UI (a -> IO ())
- Output widgets are function-valued UI.
fruithas a simpler definition, requiring only one lifting instead of two.
totalis subtly different, because output widgets are now function-valued.
ui1uses the reverse application operator
(<**>). This reversal causes the function to appear after (below) the argument.
ui1is an IO-valued UI.
The applicative UI interface (
Graphics.UI.Phooey.Applicative) is implemented as a very simple layer on top of the monadic interface, using type composition (from TypeCompose):
type UI = M.UI :. Source
Thanks to properties of
O, this definition suffices to make
UI an AF.
By default, UI layout follows the order of the specification, with earlier-specified components above later-specified ones. This layout may be overridden by explicit layout functions. For instance, the following definitions form variations of
ui1 laid out from bottom to top and from left to right.
GUIs & code:
uiB1 = fromBottom ui1 uiL1 = fromLeft ui1
We can also lay out a sub-assembly, as in
ui3 = fromBottom $ title "Shopping List" $ fromRight fruit >>= total
The shopping examples above demonstrate the simple case of outputs (
total) as functions of varying inputs (
bananas). Events were hidden inside the implementation of reactive values.
This section shows two classic functional GUI examples involving a visible notion of events.
Here is simple counter, which increments or decrements when the "up" or "down" button is pressed. The example is from "Structuring Graphical Paradigms in TkGofer"
The first piece in making this counter is a button, having a specified value and a label. The button GUI's value is an event rather than a source:
smallButton :: a -> String -> UI (Event a)
To make the up/down counter, we'll want two such buttons, labeled "up" and "down". But with what values? The buttons won't know what the counter value is, but they will know how to change the value, so the events will be function-valued. The two events resulting from the two buttons are then merged into a single function-valued event via
mappend. (If you're curious about events at this point, take a detour and read about them.)
The pair of buttons and combined event could be written as follows:
upDown :: Num a => UIE (a -> a) upDown = do up <- smallButton (+ 1) "up" down <- smallButton (subtract 1) "down" return (up `mappend` down)
If you've been hanging around with monad hipsters, you'll know that we can write this definition more simply:
upDown = liftM2 mappend (smallButton (+ 1) "up") (smallButton (subtract 1) "down")
Personally, I'm on an
Applicative kick lately, so I prefer
liftA2 in place of
Still more sleekly, let's hide the
liftA2) by using the
Monoid (UI o) instance, which holds whenever
upDown = smallButton (+ 1) "up" `mappend` smallButton (subtract 1) "down"
To finish the counter, use the
accumR function, which makes a source from an initial value and an function-valued event. The source begins as the initial value and grows by applying the functions generated by the event.
accumR :: a -> UI (Event (a -> a)) -> UI (Source a) counter :: UI () counter = title "Counter" $ fromLeft $ do e <- upDown showDisplay (0 `accumR` e)
The second event example is a calculator, as taken from "Lightweight GUIs for Functional Programming".
The basic structure of this example is just like the previous one. Each key has a function-valued event, and the keys are combined (visually and semantically) using
First a single key. For variety, we'll postpone interpreting the key's event as a function.
key :: Char -> UIE Char key c = button' c [c]
We'll combine keys with the help of a friend of
mconcatMap :: Monoid b => (a -> b) -> [a] -> b mconcatMap f = mconcat . map f
With this helper, it's especially easy to turn several keys into a row and several rows into a keyboard.
row :: [Char] -> UIE Char row = fromLeft . mconcatMap key rows :: [[Char]] -> UIE Char rows = fromTop . mconcatMap row calcKeys :: UIE Char calcKeys = rows [ "123+" , "456-" , "789*" , "C0=/" ]
Next, let's turn
calcKeys's character-valued event into a function-valued event. While the state of the counter was a single number, the calculator state is a little more complicated. It consists of a number being formed and a continuation.
type CState = (Int, Int -> Int) startCS :: CState startCS = (0, id)
Keyboard characters have interpretations as state transitions.
cmd :: Char -> CState -> CState cmd 'C' _ = startCS cmd '=' (d,k) = (k d, const (k d)) cmd c (d,k) | isDigit c = (10*d + ord c - ord '0', k) | otherwise = (0, op c (k d)) op :: Char -> Int -> Int -> Int op c = fromJust (lookup c ops) where ops :: [(Char, Binop Int)] ops = [('+',(+)), ('-',(-)), ('*',(*)), ('/',div)]
To compute the (reactive) value, from a key-generating event, accumulate transitions, starting with the initial state, and extract the value.
compCalc :: Event Char -> Source Int compCalc key = fmap fst (startCS `accumR` fmap cmd key)
Show the result:
showCalc :: Event Char -> UI () showCalc = title "result" . showDisplay . compCalc
The whole calculator then snaps together:
calc :: UI () calc = title "Calculator" $ calcKeys >>= showCalc
wxHaskell is therefore built on top of wxWidgets -- a comprehensive C++ library that is portable across all major GUI platforms; including GTK, Windows, X11, and MacOS X.
So I expect that Phooey runs on all of these platforms. That said, I have only tried Phooey on Windows. Please give it a try and leave a message on the talk page.
- Recursive examples don't work (consumes memory) in the Arrow or Applicative interface.