2

受到罗马人、红宝石和 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")

首先,由于我是 Template Haskell 的新手,我想知道我是否做对了。实际计算发生在编译时,对吗?

其次,如何改进语法?

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

4

2 回答 2

3

实际计算发生在编译时,对吗?

正确的。您的模板 Haskell 代码正在生成一个整数文字,显然必须在编译时对其进行评估。要在运行时进行计算,您必须生成某种其他类型的表达式,例如函数应用程序。

其次,如何改进语法?

你不能,真的。编译时代码被设计为从常规代码中脱颖而出是有充分理由的,因为编译时代码的行为可能与常规代码完全不同。另一种方法是编写一个准引用器,它允许您使用语法[roman| CCLXXXI |]

但是,您在这里使用($)运算符是多余的,因此您也可以编写

print $(roman "CCLXXI")

这可能看起来更漂亮一些。

于 2012-03-30T19:40:40.630 回答
1

首先,如果你能解释你想要什么,那就太好了。我从您希望将罗马数字编译时翻译为的链接中Num a => a获取它,但也许我在简短的阅读中并没有完全理解它。

我不明白为什么额外的 TH 语法是一个问题,但我认为你可以在没有 Template Haskell 的情况下做到这一点。一种是使用准引用器,导致语法如下:

[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
    ...
于 2012-03-30T19:46:10.017 回答