:+: and all that (was Re: Do we need a mouse trap?)

Peter Achten peter88@cs.kun.nl
Thu, 04 Mar 1999 15:25:05 +0100


Ana Maria Abrao wrote:

>...ask for
>help myself. ... My problem is
>the following: I want to glue controls and menu items
>at runtime. My idea was to make a list of strings, each
>one representing a control. Then, I would use a recursive
>program to  sweep through this list, and glue the
>controls together. Since the controls are not of the same
>type, it is not possible to use ListLS. The program that
>I want to write is something like this:
>
>MNU1 (MENU nm entries) ids ps
># (error, ps)= openMenu undef (mkM entries) ps
>| error <> NoError= closeProcess ps
>| otherwise= ps
>where
>{ //mkM xs= Menu nm ( ListLS [MenuItem item []\\ item <- xs]) []
>  mkM xs= Menu nm (menuM xs) []
>; menuM [x]= MenuItem x []
>; menuM [x:xs]= (MenuItem x []) :+: (menuM xs)
>};

The reason that the function menuM is rejected by the type system is because of the different derived types of the two alternatives. The first alternative  returns a value of type (MenuItem .ls .ps) while the second alternative returns a value of type (:+: MenuItem ??). These types can't be coerced. 

However, it is possible to write a function like menuM if you use overloading. But finding a solution for menuM is not sufficient. Let's assume that such a function exists (an example is given below the line starting with #######):

menuM :: [String] -> Maybe (m .ls .ps)
instance menuM MenuItem
instance menuM MenuSeparator
instance menuM (SubMenu m) | MenuElements m
instance menuM (:+: m1 m2) | MenuElements m1 & MenuElements m2

Then you still can't pass the result of menuM (MENU "menu" ["1","2"]) to openMenu because of internal overloading problems. This is similar to the classic example:

class toString a :: a -> String
class fromString a :: String -> a

f x = toString (fromString x)

This function can't be given a type because it is unknown what the intermediate type will be. 

It seems to me that this is the wrong direction. Here are some suggestions that might help:

* use the dynamic properties of the object I/O library:
every object in the object I/O library can be constructed at run-time (open and 
close menus, menu elements, and so on). Use the IODEVICE type and object I/O functions that create the proper elements. This might also fit better to your dynamic setting of a visual editor.

* use overloading all the way:
declare IODEVICE as an instance of the proper object I/O classes. (It should have two additional type variables, and probably also some more details).

Hope this helped,

Peter


###########################################################################
module typeval

// Consider the following algebraic type TYPE_VAL which pairs types 
// and values of that type:

:: TYPE_VAL
   = INT Int
   | BOOL Bool
   | PAIR TYPE_VAL TYPE_VAL
   | LIST [TYPE_VAL]

/* So a TYPE_VAL is either an Int, a Bool, a pair of two TYPE_VALs, 
   or a list of TYPE_VALs. Examples are:
*/
e1 = INT 0
e2 = BOOL True
e3 = PAIR e1 e2
e4 = LIST [e1,e2,e3]

/* Now we write an overloaded function fromTYPE_VAL that removes the 
   type tags. Because a TYPE_VAL can be of the wrong type tag it 
   returns a Maybe result.
*/
class fromTYPE_VAL a :: TYPE_VAL -> Maybe a

// The Int and Bool instances are trivial:
instance fromTYPE_VAL Int where
    fromTYPE_VAL (INT i)  = Just i
    fromTYPE_VAL _        = Nothing
instance fromTYPE_VAL Bool where
    fromTYPE_VAL (BOOL b) = Just b
    fromTYPE_VAL _        = Nothing

/* A (PAIR a b) will be mapped to (a` :^: b`) if a can be mapped to a` 
   and b can be mapped to b`. 
*/
instance fromTYPE_VAL (:^: a b) | fromTYPE_VAL a & fromTYPE_VAL b where
    fromTYPE_VAL (PAIR a b)
        | isJust a` && isJust b` = Just (fromJust a` :^: fromJust b`)
        | otherwise              = Nothing
    where
        a` = fromTYPE_VAL a
        b` = fromTYPE_VAL b
    fromTYPE_VAL _               = Nothing

/* Analogously for (LIST [x0..xn]): it is mapped to [x0`..xn`] if x0 
   can be mapped to x0`, x1 can be mapped to x1`, and so on:
*/
instance fromTYPE_VAL [a] | fromTYPE_VAL a where
    fromTYPE_VAL (LIST ls)
        | any isNothing ls` = Nothing
        | otherwise         = Just (map fromJust ls`)
    where
        ls` = map fromTYPE_VAL ls
    fromTYPE_VAL _          = Nothing

// With these definitions one can write:

Start :: (Maybe Int,Maybe Bool,Maybe (:^: Int Bool),Maybe [Int])
Start
   = (   fromTYPE_VAL e1      // yields (Just 0)
     ,   fromTYPE_VAL e2      // yields (Just True)
     ,   fromTYPE_VAL e3      // yields (Just (0 :^: True))
     ,   fromTYPE_VAL e4      // yields Nothing
     )

import StdEnv, StdIOCommon, StdMaybe