a universal printer for Haskell?
Hi all (warning long post), Sorry to dredge up past discussions, but this has been burning in my mind and I wanted to get further opinions. I, and I assume many other Haskell programmers would from time to time like a universal print mechanism, something that can turn an arbitrary value into a String. According to Wadler and others [1], this gives a language designer nightmares. It certainly gives me headaches. So I want: toString :: a -> String We all know you can't do that in Haskell (without some fancy primitive), so maybe: toString :: ToString a => a -> String (of course I've just asked for the Show class). But the problem is not solved (drum roll please). Even first time programmers know the pitfalls of trying to show the empty list: show [] ---> kapow! But you ought to be able to print the empty list, without having to make some bogus type qualification (which is in general not a solution to the problem of printing arbitrary values). Previous discussions [2], have considered passing bottom dictionaries for variables like the 'a' in 'forall a . [a]', in cases where the "a does not matter". The approach discussed was converting such "don't matter" variables into an Empty type which is magically an instance of every class, or at least the Show class. Arguments were raised against this showing that it is quite hard to determine when such variables really do not matter. Subtyping was also considered, but I don't see that coming to Haskell in the short term. What if only the compiler could instantiate the ToString class? What I mean is, imagine the class ToString being avilable in every program, but restricted so that the programmer could not make their own instances of the class, only the compiler. Lets generalise a bit. We have a type: data Meta = MApp String [Meta] | MInt Int | MChar Char ... | MFunction and a class: class Reify a where reify :: a -> Meta thus: toString :: Reify a => a -> String toString x = metaToString $ reify x where metaToString x = ... {- easy enough -} The compiler is required to generate instances of Reify for all types in the program (instances for built in types are hand-coded in a Standard Library). Instances should be derived directly from the syntax of each algebraic data type. So for example: data List a = Nil | Cons a (List a) The compiler derives: nilStr = "Nil" consStr = "Cons" instance (Reify a) => Reify (List a) where reify Nil = MApp nilStr [] reify (Cons h t) = MApp consStr [reify h, reify t] Deriving could be done ala Drift, but in the compiler itself. Since it is impossible for the programmer to define their own Reify instance, it should be possible to adopt the 'defaulting to Empty' rule for "type variables that don't matter", as in: reify Nil ---> Mapp "Nil" [] and so: toString Nil ---> "Nil" No matter which type of list Nil belongs to it always and only reifies to the one representation. The problem with defaulting to Empty with Show is that Show can be user-defined. What about overloaded constants? class Boo a where constant :: a reify constant ---> unresolved constraint '(Reify a, Boo a) => a' We could make Reify a superclass of Boo: class (Reify a) => (Boo a) where ... Does this mean we have to modify the Prelude? class Reify a => Num a ... ... I'm not sure. If we take for granted that EVERY type in the program is an instance of Reify, perhaps there is a way to modify the dictionary transformation to insert Reify dictionaries where needed, without having to modify the text of the Prelude and user classes and so on - as if the superclass relationship was implicit in the program. Okay, so this is very hazy. The mechanism breaks abstraction boundaries, but when I am debugging my program and all I want to do is see what the data looks like, I don't care much about the abstraction boundaries, I just want to print something meaningful out. I can't print functions with this, except to get "Function". But I can live with that. Cheers, Bernie. [1] A Second Look at Overloading, Wadler, Odersky, Wehr. [2] (see http://www.mail-archive.com/haskell%40haskell.org/msg09292.html)
Hi Bernard, Bernard> I, and I assume many other Haskell programmers would from Bernard> time to time like a universal print mechanism, something Bernard> that can turn an arbitrary value into a String. yes, I'm such a Haskell programmer that would like such a mechanism but it only makes sense for me if you also have the inverse function. In this case, you could save the state of your computation on disk and recover it later, send run-time functions to a remote machine and execute them on that machine with full control by the programmer. This marshalling/unmarshalling mechanism is provided partially by some Haskell tools. As far as I heard, OCaml permits a more general mechanism, but I didn't manage to get it running with OCaml. Anyway, I don't want to program in OCaml. Probably the reason that this mechanism doen't exist in Haskell yet is that it is difficult to implement. Also, efficient marshalling will likely produce unreadable strings for elements of the Show class and thus, would be an additional feature, no extension. Cheers -- Christoph
Hi Christoph and all,
Bernie: talking about universal printer...
yes, I'm such a Haskell programmer that would like such a mechanism but it only makes sense for me if you also have the inverse function.
For the simple scheme I presented the inverse is easy enough.
In this case, you could save the state of your computation on disk and recover it later, send run-time functions to a remote machine and execute them on that machine with full control by the programmer. This marshalling/unmarshalling mechanism is provided partially by some Haskell tools.
Handling functions is always going to be hard. Actually, a related issue in Haskell is what do you do with partially evaluated structures? Certainly in some circumstances you don't want to force the value just so that you can write it out. If you compiled to byte code, life would be a lot easier, however everything will get a lot messier if you want to mix machine code and byte code. urgh... I carefully avoided functions, because they require a lot more effort.
Probably the reason that this mechanism doen't exist in Haskell yet is that it is difficult to implement.
It is especially difficult if you want to make persistent data across different implementations of the language. It would be nice if you could write out some data to file from GHC and then read it in using NHC :) There are obviously various degress of "persistence". I think Clean has a prototype persistence mechanism, I saw a short demo late last year. I'm not sure how they represent functional values. I think you can only read the data from within the same program that wrote the data. Cheers, Bernie.
Hi, Doesn't Hugs basically do just this when you don't have +u set? Why not simply mimick their approach? I mean, sure, it's not written in haskell, but does that really matter for the printing for debugging issue? - Hal -- Hal Daume III "Computer science is no more about computers | hdaume@isi.edu than astronomy is about telescopes." -Dijkstra | www.isi.edu/~hdaume On Tue, 19 Feb 2002, Bernard James POPE wrote:
Hi Christoph and all,
Bernie: talking about universal printer...
yes, I'm such a Haskell programmer that would like such a mechanism but it only makes sense for me if you also have the inverse function.
For the simple scheme I presented the inverse is easy enough.
In this case, you could save the state of your computation on disk and recover it later, send run-time functions to a remote machine and execute them on that machine with full control by the programmer. This marshalling/unmarshalling mechanism is provided partially by some Haskell tools.
Handling functions is always going to be hard. Actually, a related issue in Haskell is what do you do with partially evaluated structures? Certainly in some circumstances you don't want to force the value just so that you can write it out. If you compiled to byte code, life would be a lot easier, however everything will get a lot messier if you want to mix machine code and byte code. urgh...
I carefully avoided functions, because they require a lot more effort.
Probably the reason that this mechanism doen't exist in Haskell yet is that it is difficult to implement.
It is especially difficult if you want to make persistent data across different implementations of the language. It would be nice if you could write out some data to file from GHC and then read it in using NHC :) There are obviously various degress of "persistence".
I think Clean has a prototype persistence mechanism, I saw a short demo late last year. I'm not sure how they represent functional values. I think you can only read the data from within the same program that wrote the data.
Cheers, Bernie. _______________________________________________ Haskell mailing list Haskell@haskell.org http://www.haskell.org/mailman/listinfo/haskell
Hello, Bernard wrote: <<snip>>
But the problem is not solved (drum roll please). Even first time programmers know the pitfalls of trying to show the empty list:
show [] ---> kapow!
But you ought to be able to print the empty list, without having to make some bogus type qualification (which is in general not a solution to the problem of printing arbitrary values).
But how about the difference between show ([] :: [Int] ) == "[]" show ([] :: [Char]) == "\"\"" and Prelude> (putStr.show) ([] :: [Int]) [] Prelude> (putStr.show) ([] :: [Char]) "" You probably want the first, but I want the second in the cases I'm working with strings. This problem is apparently not solved by simply adding a defaulting meganism for Show or Reify or whatever. It's not clear to me how much your proposal differs from extending the default meganism (http://www.haskell.org/onlinereport/decls.html#default-decls) to allow arbitrary classes, rather than only class Num, which would be more consistent with the current Haskell 98. Do Ghc and/or Hugs allow other defaults then Num already? Regards, Rijk-Jan van Haaften
Hi,
But you ought to be able to print the empty list, without having to make some bogus type qualification (which is in general not a solution to the problem of printing arbitrary values).
But how about the difference between show ([] :: [Int] ) == "[]" show ([] :: [Char]) == "\"\""
You probably want the first, but I want the second in the cases I'm working with strings.
There is a reason why I want the first and not the second. Imagine a debugger for Haskell that prints values from computations, so that the user can inspect what is happening. How is the debugger to present these values? The danger of using user defined print mechanisms is that they might have bugs in them. Okay so the user of the debugger can say: "I really really trust my print mechanisms, so please use them". But there ought to be some fallback strategy where the debugger can always print something which is meaningful. The most basic representation I can think of that would make any sense is something derived directly from the definition of the type. Sure, the abstraction boundary is broken, but sometimes you just might need to do that to debug your program. There are also other uses for such canonical printing. Somtimes you want to write out a data structure and read it back in again, and you want to be sure that what you write will always be readable again. It is quite an annoying maintenance problem when you have to keep the printer and reader in sync. If such synchronisation can be handled by the compiler, then all the better. If you want something prettier, then you can always resort to the usual techniques and put up with the odd unresolved overloading. The basic problem with type classes for this is that there are too many possible meanings for the one program, hence the example with show []. Of course there should only be one meaning, which is why I suggested making the printing mechanism defineable only by the compiler. You don't have to use the type class system to provide generic printing. It could just be a primitive, Hugs has (or at least used to have) this. Such a primitive is going to be a lot harder for something like the STG machine however.
It's not clear to me how much your proposal differs from extending the default meganism (http://www.haskell.org/onlinereport/decls.html#default-decls) to allow arbitrary classes, rather than only class Num, which would be more consistent with the current Haskell 98.
Perhaps you could extend defaulting to include classes outside the Num hierarchy, but I suspect this is a minefield. I was hoping to avoid this by just making printing a special case, since at least I think it is quite useful. If we want to be really adventurous we might want to look at alternative overloading schemes that do not suffer the same restrictions as type classes do. I think type classes are here to stay, can we put up with more than one overloading mechanism in Haskell, I don't know? Cheers, Bernie.
participants (4)
-
unknown@example.com -
Bernard James POPE -
Ch. A. Herrmann -
Hal Daume III