Open dmcclean opened 13 years ago
Hmmm...
I've a bit of an aversion to monads, so I guess I'm biased. I've done a tiny bit of work with the GHC internals (20+ year old codebase) and worked extensively on UHC (5+ years old). Both of those use monad stacks internally, which I found very hard to use. I guess for people experienced with the code base its easier, but for someone not familiar with the code, it looks like a big, unnavigable, mess.
I really like the simplicity of the idea that a Method is a name, parameters and a list of instructions. Easy to understand, even for newcomers.
One of my main concerns is keeping the library simple for newcomers, I don't want to scare people off (which I personally have been from a couple of complicated looking hackage libraries).
However, you're probably right, that extra stuff like fresh name generation and automatic maxstack calculation is useful for more experienced users of the library. So adding some alternative way of building a [MethodDecl]
might be useful.
My first suggestion would be to add a module Language.Cil.Build.MethodBuilder
(probably needs a better name). That could be useful for people needing more complex build functionality and more automation. This could expose one or more functions that generate a [MethodDecl]
. Stuff like maxstack calculation could also be here.
Any thoughts?
Also, in the current design, it is probabily a good idea to add stlocNm :: LocalName -> OpCode
.
Then I'd implement the first example as: ioAge :: MethodDef ioAge = Method [MaStatic, MaPublic] Void "ioAge" [] [ localsInit [ Local Int32 x , Local (ValueType "mscorlib" "System.DateTime") d1 ] , call [] String "mscorlib" "System.Console" "ReadLine" [] , call [] Int32 "" "int32" "Parse" [String] , stlocNm x , call "mscorlib" "System.DateTime" "get_Now" [] , stlocNm d1 , ret ] where x :: LocalName x = "x" d1 :: LocalName d1 = "d1"
Not as useful for a code generator where you want to convenience of automatic unique names. But very useful when you want to generate nice looking IL (which I do, because I read it a lot).
Is that really simpler? I think the local declarations are a fair bit easier to read in the monadic version, and you don't have to manage the commas, but I may be mistaken.
We could do both as you suggest, but we would either need two copies of all the opcode-named building functions or we would need to introduce
class HasOpCodes m where
injectOpCode :: OpCode -> m
instance HasOpCodes OpCode where
injectOpCode = id
instance HasOpCodes MethodDecl where
injectOpCode = mdecl
instance HasOpCodes (MethodBuilder ()) where
injectOpCode = append . mdecl
I have absolutely no problem with the commas, this is no Lisp, but I'm sure every Haskell programmer knows how lists work. I think the conceptual overhead of the monad with the return, bind and (>>)
(what's that operator called?), is higher than the visual overhead of a couple of commas.
For now, I'm in favour of duplicating the opcode builder functions in the MethodBuilder module. I'd like to try out both versions in a practical setting. If it turns out they're both useful we can think of abstracting away the commonalities. If on the other hand the [MethodDecl]
version turns out to be unnecessary, those builders can be completely removed in favour of the MethodBuilder monad.
Ultimately its of course best if there is no duplication, but for now I think it best that the MethodBuilder module is an addition. An addition that can or can't be used without impacting the rest of the code. I don't want to fall in the trap of premature abstraction.
Sounds like a good plan. I have a side-by-side implementation that works. Later this evening I will do some renaming and pass it along.
The goal of making use of the library read as much like the resulting IL as possible, together with some issues around alpha-conversion, led me to this.
Instead of writing a method body as a [MethodDecl], I propose something like this change from:
to:
With appropriate supporting code, sketched below:
and with appropriate changes to the building methods, e.g. from:
to:
(Potentially for customizability you might want to define MethodBuilder as a class instead of a type, make append a function in that class, change the builder type of the example from Label -> MethodBuilder () to (MethodBuilder m) => Label -> m (), etc. This would allow people to use a more feature-rich method building monad if their backend had some reason to do so. Ignore that complexity for now.)
tl;dr The monadic version both looks a lot more like the IL than the [MethodDecl] version and provides a place to hang a few other things that practical code-generators are going to need.