
DISCLAIMER: I don't pretend to have had new ideas, nor that what I say
here is correct or good. It is premature to post my code here, I have
not studied everything I wanted to, but since the discussion is up and
running, I have to :)
On Wed, 5 Mar 2003 16:13:03 -0800 (PST)
David Sankel
I propose we use the GIO/Object IO style. Does anyone have any other preferred styles that I'm missing? Which style do you prefer?
There are others. First, you can define widget and combinators as purely functional values, and really "create" widgets only in the runGUI (or whatever you'd name it) operation. I have a working example, which I wrote just to classify all my thougts, in wich the simple program proposed by John Meacham looks like: main = do (w,b) <- managedButton "Bye" Application a <- runGUI (window (vBox [label "Hello World!",w])) processOutput_ (buttonClicked b) [putInput (buttonLabel b) "Goodbye", putInput a ()] managedButton :: String -> IO (Widget,Button) button :: String -> Widget label :: String -> Widget ...and so on. I wrote it to test an idea I have about changing the state, note how the Widget type is purely functional (it encapsulates the IO needed to create the widget). I attach my code, wich is for Gtk2hs, in case someone wants to read it. Let's come back to the topic. I apologize for my bad english, as usual, and for the length of this e-mail. ======= 1. How is the local state of a widget changed? Basically, the world is divided between people that like monadic operations, and people that prefer streams. Manuel Chakravarty in his "Ports" library combines the two, even if he is more biased to streams. When it's time to choose between two different approaches, which prove both useful, I prefer when I can have both! We can do a more fine-grained work by separating the read and write operations (thus avoiding a runtime error implementing the write operation of read-only properties). This leads to the following definition for a variable: data Input a = Input { putInput :: a -> IO () } data Output a = Output { readOutput :: IO a, waitOutput :: IO a, -- wait for a state change listenOutput :: IO [a] } data Var a = Var { varInput :: Input a, varOutput :: Output a } We can define in a purely functional way the map operation on an output, and many transformations on inputs and outputs, as it's done in FRAN; (There are issues to consider, such as "closing" the Var; I leave it for tomorrow and for more expert people; also, someone willing to speak about arrows and Var?). Using this idea, an handler for a button has type "Output ()", or at least contains this type. You can also install a callback on state changes, with the operations: onOutput :: Output a -> (a -> IO b) -> IO () onOutput o m = forkIO ( listenOutput o >>= mapM_ m) >> return () onVar = onOutput . varOutput (BTW, threads in GHC *are* lightweight! I tested listening to thousands of Vars simultaneously without troubles on a celeron some month ago) And, besides, separating input and output you can give a simple interface to many things, for example input devices are "Output"s from the point of view of the application. NOTE: With Ports, or Vars or Attrs, one can easily make a variable persistent. I think this hasn't been spotted, but deserves more attention! ===== 2. How is a widget created? Everyone seems to agree on createSomething :: SomethingConfiguration -> IO Something but this clutters the code with many unneeded monadic operations. We can use data constructors when needed, or postpone the creation in a "runGUI" function and just compose the needed actions: notice how readable is runGUI (window (vBox [label "Hello World!",w]) due to the absence of monadic operators where they are not needed, if compared to a standard monadic sequence. In "managedButton" (the creation of w), I think monadic operator ARE needed because we need to create an "entaglement" between the button data and the IO operation that will create it, but I am not completely sure. ===== 3. How is the configuration data passed to a widget? This opens many questions, and surely many other that I don't mention here. - We could have two constructors for a button, one "a la GIO" and one with standard function syntax, wich is more readable in some cases. Example: button "Hi all" versus button [label=:"Hi all"] Of course, this is not Daan Leijen's fault: it's difficult to handle defaults to avoid entering ten or twelve configuration parameters. In my example, I don't care about a button being also a widget with size, for example. So I propose to give both the interfaces, one with only basic options and a functional form, another, more complex, like the one in GIO or with records. - Some subtyping trick with multiparameter type classes can make the operator =: accept properties for a generic, and apply them to a button, I think. - We need to consider the loading from resource files as a constructor, not everything is pragmatic. I think this is not a big issue. - I would like to know more about named widgets and their use. - We could just use record types, filling undefined fields with defaults in the constructor. This approach can easily live side by side with the GIO one. - We could just use implicit parameters, but I don't think this would feel comfortable. ===== I hope someone is interested... Vincenzo