2012-03-30 73 views
2

Romans, rubies and the D的启发,我想看看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 

和:

{-# LANGUAGE TemplateHaskell #-} 

import Romans 

main = print $ $(roman "CCLXXXI") 

首先,我是新来的模板哈斯克尔,我想知道如果我是正确的。实际的计算在编译时发生,对吗?

第二,我该如何改进语法?

而不是$(roman "CCLXXXI")我想要像roman "CCLXXXI",甚至更好的东西。到目前为止,我没有改进语法。

回答

3

实际计算发生在编译时,是否正确?

正确。您的模板Haskell代码正在生成一个整数字面值,显然它必须在编译时进行评估。为了让计算在运行时发生,你必须产生一些其他类型的表达,例如,功能应用程序。

第二,我该如何改进语法?

你不能,真的。编译时代码被设计为从常规代码中脱颖而出,因为编译时代码的行为与常规代码的行为完全不同。另一种方法是编写一个准引号,这将允许您使用语法[roman| CCLXXXI |]来代替。

但是,你的($)运营商的使用是多余的在这里,所以你也可以写

print $(roman "CCLXXI") 

这或许看起来有点漂亮。

+0

其实,唐·斯图尔特有一个很好的[博客文章(http://donsbot.wordpress.com/2010/03/01/evolving-faster-haskell-programs-now-with-llvm /)描述了如何使用适当的交换机的LLVM后端预编译没有模板扩展的Haskell 98代码。 – 2012-03-31 04:28:13

0

首先,如果你解释了你想要的,这将是很好的。我从链接中找到你希望罗马数字编译时翻译为Num a => a的地方,但也许我没有把它完全写在我的简短阅读中。

我不明白为什么额外的TH语法是一个问题,但我认为你可以做到这一点没有模板哈斯克尔。一个会使用准标价,导致语法如下:

[r|XXVI|] 

但这仍然不是很干净。

另一种方式是罗马数字的数据类型的解释:

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) 

也许GHC的-O2将优化toInteger已经要求?我不知道这一点,但如果是这样,那么你可以只使用一个简单的实例Integral

instance Integral Roman where 
    toInteger (M n) = 1000 + toInteger n 
    ... 
+1

'toInteger'是'Integral'成员,而不是'Num'。 (我猜你在想'fromInteger'?) – hammar 2012-03-30 19:52:49

+0

糟糕。很好的接收。有时我不会打电话,我会编辑它。 – 2012-03-31 00:48:36