I solved the exercise with regular Julia code, wasn't too hard:
function to_roman(number::Int64) 0 < number < 4000 || error("not in range") parts = Char[] while number > 0 if number >= 1000 push!(parts, 'M') number -= 1000 elseif number >= 500 push!(parts, 'D') number -= 500 # ... number -= 10 elseif number >= 5 push!(parts, 'V') number -= 5 elseif number >= 1 push!(parts, 'I') number -= 1 end end join(parts)end
This only solves naïve numerals, but keeps the example shorter. Now me being me, I'm appalled by the redundancy in this. So I made iteration 2:
const values = Dict('M' => 1000,'C' => 100,'X' => 10,'I' => 1,'D' => 500,'L' => 50,'V' => 5,)Base.:+(number::Integer, c::Char) = number + values[c]Base.:-(number::Integer, c::Char) = number - values[c]function to_roman(number::Int64) 0 < number < 4000 || error("must be between 1 and 3999 inclusive") parts = Char[] while number > 0 for digit in "MDCLXVI" if number >= values[digit] push!(parts, digit) number -= digit break end end end join(parts)end
That also does it, but got me thinking. Couldn't I create the redundant if
s above with metaprogramming? That's something I wanted to learn about anyway and this seems like a not too hard place to wet my feet. But it's also not overly simple, like an unless
.
I'm aware that this is not what you use metaprogramming for. It is about the learning experience. Just as nobody needs to compute Fibonacci, but still we all learn to code it in FP.
But I can't even get close:
macro gen(number) code = quote parts = Char[] while $(esc(number)) > 0 end end for (i,c) in enumerate("MDCLXVI") exp = :( if $(esc(number)) >= $(values[c]) push!(parts, $c) $(esc(number)) -= $(values[c]) end ) if i > 1 exp.head = :elseif end # This is not correct anymore because I introduced the while. push!(code.args, exp) end push!(code.args, :(return parts)) dump(code) return codeendfunction test(number) @gen numberendtest(1111)
This is the best I could do, but then I noticed I need to have the while
around (so this code doesn't work). Now I don't even know which .args
to append to, because also there are several LineNumberNode
nodes in there that shift the important ones around.
I got the feeling, I'm not approaching this from the right angle / am using the right tools.
So the overall question would be “How can I use metaprogramming to abstract away the if
chain and redundancy of the primitive example in spirit of the second example?”