Dear Peter
This issue has nothing to do with monads, but is rather a
design decision how to identify GUI elements. One particular disadvantage of
the
w <- window []
style is that
GUI creation is an (opaque) action, rather than a first-class description of
the GUI. Identification values become available only after creation of GUI
elements, which is less convenient than knowing them beforehand. The only
'danger' is forgetting to associate the identification value with the window,
but the same problem exists with the other style:
- w <- window [] --
window identified by w
- ...
- close
w
-- window identified by w destroyed
- ...
- setTitle w "Hi there" -- unbound
There is more to this. Since ID's are untyped, it
happened to me a few times that I attached the wrong id to the
wrong widget. Each widget is later accessed with an
untyped ID, that may be connected
to a completely different widget kind. With
the monadic style, not an identifier
is returned but the typed entity itself and thus
all further access is also checked.
The other problem (closing and accessing) seems
only solvable within a higher-level framework.
The above is not a critique on Port or GIO (honest!). I just wanted to
explain part of the design reasons for Object I/O and show that it supports
elegant ideas such as advocated by Daan.
Off course it does :-) Since ObjectIO is in this
respect on a lower-level, GIO could be implemented on top of Object/IO -- that
is also why one could implement the
attribute approach on top of the ObjectIO constructors. The whole issue
is probably more of a question of what should be part of a medium-level
GUI library (and what not).
In this respect, I believe that a medium-level library should
- Offer as much type safety as possible without sacrificing flexibility or
give incomprehensible type errors. That's why GIO doesn't use multiple
parameter type classes to to implement attributes as in your example :-). This
is also what I like ObjectIO -- it uses little overloading (but
a fairly difficult GUI type).
- Identify entities by value instead of by ID.
- Not offer a model for structuring state but stay in the IO monad and use
simple mutable variables. Even though a model like that of ObjectIO is
convenient, I believe this belongs to a higher-level library.
- Be fully dynamic -- each attribute should be changeable later (if
sensible), like layout, titles and event handlers. The advantage of having
attributes that can be set or read at any time, is that we don't need to
double functionality, like WindowViewDomain,
getWindowViewDomain, setWindowViewDomain, ControlViewDomain,
setControlViewDomain and
getControlViewDomain. For a user we just have an attribute
"domain" that can be get or set.
> class HasDomain w where
> domain :: Attr w Size
> instance HasDomain Window
> instance HasDomain CompoundControl
>
> get :: w -> Attr w a -> IO a
> set :: w -> [Prop w] -> IO ()
> (=:) :: Attr w a -> a -> Prop w
>
> test = do w <- window []
> set w [domain =: Size 100 100]
> ....
(btw. Allthough fairly straightforward, this nice
attribute stuff is invented (I think) by Koen Claessen -- I read his
lecture notes).
The "dynamic" ness of the library is also used to
circumvent the problem that one wants to refer to a window before it is
created, for example while specifying the layout of buttons in a window. In
GIO, the layout can be set later, after the creation of the child controls.
> do w <- window [title =: "Demo"]
> q <- button [text =: "Quit", on command =: close w]
> set w [layout =: center q]
Same story for event handlers. I am not sure
though how inconvenient this can be when creating larger applications
but I feel that the extra type safety
is worth the inconvenience. (Btw, layout is another dark area: what does it
mean for example when a button is put next to itself (q <<< q), or
when a button
created in some window is layed out in
another?)
All the best,
Daan.
ps. I have been thinking about more
'improvements' for the (haskell?) ObjectIO library. For example, getting rid of
state transformers in the GUI monad, like the GUIFun for example: "(ls,ps) ->
GUI ps (ls,ps)". The idea is put every state in a "UI ls ps a" monad with
"getLS :: UI ls ps ls" etc. functions. I believe that the types become
much easier to comprehend but it is a
fairly extensive change. Maybe we should talk about this off-line some
time.