How to create objects and how to get/set their attributes

Thanks to Wolfgang for sorting out the current discussion topics. I hope this to be the thread for 1 on the issue list. There have been three, that I have seen, competing ideas. The first is the GIO/Object IO style of objects: button <- Button [ title =: "Hello", position =: (0,0), size =: (300,50) ] set button [ onPress =: (buttonPressed button) ] set parentWindow [children ~= (/a -> button:a)] The second is a motif style (please correct me if I have this wrong): button <- create "Button" parentWindow [ title =: "Hello", position =: (0,0), size =: (300,50) ] set button [ onPress =: (buttonPressed button) ] The third is the OO approach button <- new Button button # setOnPress (buttonPressed button) button # setPosition (0,0) button # setSize (300,50) parentWindow # addChild button 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? David J. Sankel

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

--- Nick Name
===== 2. How is a widget created? Everyone seems to agree on
createSomething :: SomethingConfiguration -> IO Something
notice how readable is
runGUI (window (vBox [label "Hello World!",w])
This is a very cool idea. I do think it is a bit on the Fudgets (experimental) side of things though. For CGA I would probably lean against such an idea since it hasn't be practically proven. I'm not sure how often I would use this in my day-to-day gui coding. I do really like the idea of "lazily" creating widgets as it is much in the spirit of haskell ;-).
- 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"]
Although the GOI Attributes are extremely verbose in comparison to the standard functional syntax, I see them to be superior in many ways: those unfamiliar with CGA would have a much better time picking up the syntax, and it also shows itself to be an improvement of the Object Oriented style of default parameters.
- We need to consider the loading from resource files as a constructor, not everything is pragmatic. I think this is not a big
I personally think this is out of scope for an initial release of CGA, but this is most definitely something to keep in mind. You've got some very interesting ideas, I hope you continue to participate in this group. David J. Sankel

On Thu, Mar 06, 2003 at 08:34:54PM -0800, David Sankel wrote:
runGUI (window (vBox [label "Hello World!",w])
This is a very cool idea. I do think it is a bit on the Fudgets (experimental) side of things though. For CGA I would probably lean against such an idea since it hasn't be practically proven. I'm not sure how often I would use this in my day-to-day gui coding. I I would say not quite. Fudgets use combinators to but widgets together. Beyond that these combinators connect channels between widgets which specifiy information flow (which sometimes becomes a real pain if the widget tree does not reflect the way information flows).
Actually the "build a description of an algebraic data type and then push the button to create the corresponding GUI is the Object I/O approach. Axel.

David Sankel wrote:
The second is a motif style (please correct me if I have this wrong):
button <- create "Button" parentWindow [ title =: "Hello", position =: (0,0), size =: (300,50) ] set button [ onPress =: (buttonPressed button) ]
Xt (Athena/Motif) style is more like:
formW :: FormWidget
acceptB, cancelB :: ButtonWidget
formW <- create "yesNoForm" shellW []
acceptB <- create "accept" formW []
addCallback acceptB onAction acceptFunc
addCallback acceptB onAction closeFunc
cancelB <- create "cancel" formW []
addCallback cancelB onAction cancelFunc
addCallback cancelB onAction closeFunc
I suppose that you could make the callback list a property, e.g.:
acceptB <- create "accept" formW [onAction =: [acceptFunc, closeFunc]]
cancelB <- create "cancel" formW [onAction =: [cancelFunc, closeFunc]]
but it's more usual to add/remove callbacks independently than to set
the entire callback list in one go.
Everything else can (and probably should) go into the resource files.
--
Glynn Clements
participants (4)
-
Axel Simon
-
David Sankel
-
Glynn Clements
-
Nick Name