Below are some suggestions for extensions to Haskell records. These are just naive ideas. Of course the extensions should integrate nicely w.r.t. the existing syntax, type system, and module system. Perhaps you can point me to opinions and research on what has already been done, or to why it should not, or can not. 1) I would like to write down default values for record components data Foo = Foo { x :: Bar; y :: Baz; x = some_expression } We can do this for methods in type classes, so why not here. 2) With classes, we can do even more: in the default method, we can access other methods. This suggests that the above example should in fact read data Foo = Foo { x :: Bar; y :: Baz; x = \ self -> some_expression } where `self' might occur in some_expression. that is, the default value of `me.x ' gets computed by applying a function to `me'. 3) I want to be able, in modules that use the type, to add more components (with defaults) to the type: This would avoid lengthy recomputations (at the cost of additional space). Imagine the difference between data Tree a = Node { key :: a, children :: [ Tree a ] } size t = 1 + sum (map size (children t)) and data Tree a = Node { key :: a, children :: [ Tree a ] , size :: Integer , size = \ self -> 1 + sum (map size (children self)) } when you have one large tree and frequently need sizes of subtrees. (by the way, what type does `size' have now - do we write `Integer' or `Tree a -> Integer'?) yes, I can do this manually, replacing `Node' by a smart constructor that computes and stores the size. What I don't like about this is that I have to know in advance (i. e. at the moment I define the type) what functions I want to be "cached" this way. Imagine `size' as some more complicated function, only defined many modules later. Of course I see that this would be difficult to implement w.r.t. separate compilation. But I would be happy with a compiler that ignores the "cache" specs during single-module compilations, but honors them when doing a full rebuild of a set of modules. I imagine that inter-module inlining behaves similar. -- -- Johannes Waldmann ---- http://www.informatik.uni-leipzig.de/~joe/ -- -- joe@informatik.uni-leipzig.de -- phone/fax (+49) 341 9732 204/252 --
Tue, 9 Jan 2001 09:46:31 +0100 (MET), Johannes Waldmann <joe@isun.informatik.uni-leipzig.de> pisze:
1) I would like to write down default values for record components
My proposal for remaking records ina more flexible way includes this.
2) With classes, we can do even more: in the default method, we can access other methods.
And this too.
3) I want to be able, in modules that use the type, to add more components (with defaults) to the type:
This would avoid lengthy recomputations (at the cost of additional space). Imagine the difference between
data Tree a = Node { key :: a, children :: [ Tree a ] } size t = 1 + sum (map size (children t))
and
data Tree a = Node { key :: a, children :: [ Tree a ] , size :: Integer , size = \ self -> 1 + sum (map size (children self)) }
when you have one large tree and frequently need sizes of subtrees.
And this too, but it's a bit tricky because a field changes the type covariantly. data Tree a = record key :: a children :: [Tree a] children = [] -- A function working on a tree must be prepared for extensions. -- The following function does not actually refer to the above type, -- but can be used on Tree Int. isInTree:: (Eq a, tree.key :: a, tree.children :: [tree]) => a -> tree -> Bool isInTree a tree = a == tree.key || any (isInTree a) tree.children -- Trees extended by size cache. The type below does not refer to -- the type Tree because the type of children has changed. Otherwise -- it could include Tree as a field and delegate key and children to -- this field instead of defining them itself. Well, it could do it, -- but it would have to store children as SizedTrees somewhere too. -- I don't know yet if it can be solved more elegantly. data SizedTree a = record key :: a children :: [SizedTree a] size :: Int children = [] size = 1 + sum (map (.size) children) -- Creation of a tree is the same for both kinds. A different kind of -- context is needed to create a record than to extract its fields. -- This context includes the context used in isInTree (except Eq a) -- in its superclasses. flatTree:: (tree.record {key :: a; children :: [tree]}) => a -> [a] -> tree flatTree root leaves = record key = root children = [record key = leaf | leaf <- leaves] I haven't written a complete description of the proposal yet, but will do it in two weeks. I posted basic info here some time ago. Some details changed since then. -- __("< Marcin Kowalczyk * qrczak@knm.org.pl http://qrczak.ids.net.pl/ \__/ ^^ SYGNATURA ZASTÊPCZA QRCZAK
participants (2)
-
Johannes Waldmann -
qrczak@knm.org.pl