如果实例依赖于某个运行时值,那么您真正想要的是在运行时创建实例的能力。您可以在Reader
中执行FromJSON
,因为它已在您的gist中完成。但是,正如你正确地注意到的,你不能这样做ToJSON
,因为你不知道这个精度。最简单的解决方案就是将数据类型中的单独字段存储为精度。就像这样:
data DecimalWithPrecision = MkDWP
{ value :: Decimal
, precision :: Word8
}
如果此数据类型存储在数据库和用户登录后查询它,那么这是最简单的解决方案,并没有从您所需要类型级别的技巧。
如果你事先不知道精度,例如用户通过控制台输入精度(我不知道为什么,但是让我们假设这个),那么这对你来说不起作用。大家都知道,«类型类数据类型只是语法糖»,您可以通过以下方式更换JsonDict
ToJSON/FromJSON
约束Money_
:
newtype Money_ = Money_ (Reader Word8 Decimal)
data JsonDict a = JsonDict
{ jdToJSON :: a -> Value
, jdParseJSON :: Value -> Parser a
}
mkJsonDict :: Word8 -- precision
-> JsonDict Money_
您可以创建这样的词典(或类似于它的东西)在上下文中使用Word8
,并将其传递给需要它的函数。有关详细信息,请参阅this blog post,作者为Gabriel Gonzalez。
如果您确实想要在实例中使用toJSON
实现,则可以使用库。精确度是一个自然数,可让您使用此库。使用它你基本上可以像以前的方法一样在运行时创建实例,但是你仍然有你的类型类。请参阅this blog post,其中应用了类似的技术以使Arbitrary
实例取决于运行时值。在你的情况,这将是这样的:
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE UndecidableInstances #-}
import Control.Monad.Reader (Reader, ask)
import Data.Aeson (FromJSON (..), Result (..), ToJSON (..),
Value, fromJSON, withNumber)
import Data.Aeson.Types (Parser)
import Data.Decimal (Decimal, realFracToDecimal)
import Data.Proxy (Proxy (..))
import Data.Reflection (Reifies (reflect), reify)
import Data.Word8 (Word8)
newtype PreciseDecimal s = PD Decimal
instance Reifies s Int => FromJSON (PreciseDecimal s) where
parseJSON = withNumber "a number" $ \n -> do
let precision = fromIntegral $ reflect (Proxy :: Proxy s)
pure $ PD $ realFracToDecimal precision n
instance Reifies s Int => ToJSON (PreciseDecimal s) where
toJSON (PD decimal) =
let precision = reflect (Proxy :: Proxy s)
ratDec = realToFrac decimal :: Double
in toJSON ratDec -- use precision if needed
makeMoney :: Decimal -> Reader Word8 (Value, Decimal)
makeMoney value = do
precision <- fromIntegral <$> ask
let jsoned = reify precision $ \(Proxy :: Proxy s) ->
toJSON (PD value :: PreciseDecimal s)
let parsed = reify precision $ \(Proxy :: Proxy s) ->
let Success (PD res :: PreciseDecimal s)
= fromJSON jsoned in res
pure (jsoned, parsed)
然后你就可以像这样运行它来测试:
ghci> runReader (makeMoney 3.12345) 2
(Number 3.12345,3.12)
'实例FromJSON(读者十进制)'或更好:'NEWTYPE DecimalWithPrec = d( Reader Precision Decimal);实例FromJSON DecimalWithPrec'。这仍然不会允许您根据环境进行解析选择,但这不是您的示例所需的。 – user2407038
@ user2407038听起来像这应该是一个答案:) –
@ user2407038我试着去你的方法,并得到以下 - https://gist.github.com/saurabhnanda/6b2eaa437be9a2fff14540e0dcbbc334 - 但我怎么写'ToJSON'实例? –