如果實例依賴於某個運行時值,那麼您真正想要的是在運行時創建實例的能力。您可以在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'實例? –