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.

Était-ce utile?

La 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.

Autres conseils

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
    ...
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top