Question

Motivated by Romans, rubies and the D, I wanted to see if the same could be done in Haskell.

module Romans where

import Language.Haskell.TH
import Language.Haskell.TH.Syntax
import Data.Text

num :: String -> String
num s = rep $ pack s
  where
    r1 s1 = replace (pack "IV") (pack "IIII")  s1
    r2 s2 = replace (pack "IX") (pack "VIIII") s2
    r3 s3 = replace (pack "XL") (pack "XXXX")  s3
    r4 s4 = replace (pack "XC") (pack "LXXXX") s4
    rep   = unpack . r4 . r3 . r2 . r1

value :: String -> Int
value s = cnt $ pack s
  where
    c1 s1 = (count (pack "I") s1) * 1
    c2 s2 = (count (pack "V") s2) * 5
    c3 s3 = (count (pack "X") s3) * 10
    c4 s4 = (count (pack "L") s4) * 50
    c5 s5 = (count (pack "C") s5) * 100
    cnt t = c5 t + c4 t + c3 t + c2 t + c1 t

roman :: String -> ExpQ
roman s = return $ LitE (IntegerL (compute s))
  where
    compute s = fromIntegral $ value $ num s

and:

{-# LANGUAGE TemplateHaskell #-}

import Romans

main = print $ $(roman "CCLXXXI")

First, as I am new to Template Haskell, I would like to know if I've got it right. The actual computation happens at compile time, correct?

and second, how do I improve the syntax?

Instead of $(roman "CCLXXXI") I would like something like roman "CCLXXXI", or even something better. So far I've failed to improve the syntax.

Was it helpful?

Solution

The actual computation happens at compile time, correct?

Correct. Your Template Haskell code is generating an integer literal, which must obviously be evaluated at compile-time. To have the computation happen at run-time you would have to generate some other kind of expression, e.g. a function application.

and second, how do I improve the syntax?

You can't, really. Compile-time code is designed to stand out from regular code for a good reason, as compile-time code can behave quite differently from regular code. The alternative is to write a quasi-quoter, which would allow you to use the syntax [roman| CCLXXXI |] instead.

However, your use of the ($) operator is redundant here, so you can also write

print $(roman "CCLXXI")

which perhaps looks a little prettier.

OTHER TIPS

First, it would be nice if you explained what you want. I take it from the link that you want compile-time translation of roman numerals to Num a => a, but perhaps I didn't get it exactly in my brief read.

I don't see why the extra TH syntax is an issue, but I think you can do it without Template Haskell. One would be using a quasi-quoter, resulting in syntax like:

[r|XXVI|]

But that still isn't very clean.

Another way would be an interpreter for a data type of Roman numerals:

data Roman = M Roman | D Roman | C Roman | X Roman | V Roman | I Roman | O
romanToInt :: Roman -> Int
romanToInt = ...

-- or use a shorter function name for obvious reasons.
r = romanToInt

{-# rewrite
     "Roman M" forall n. romanToInt (M n) -> 1000 + romanToInt n
  #-}
-- many more rewrite rules are needed to ensure the simplifier does the work

-- The resulting syntax would have spaces:
val95 = r (V C)

Or perhaps ghc's -O2 will optimize toInteger calls already? I'm not sure about that, but if so then you could just use a trivial Integral instance:

instance Integral Roman where
    toInteger (M n) = 1000 + toInteger n
    ...
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top