Is there any way to pass down a reader environment to the JSON (de)serialisation functions of Aeson? Here's a real-life example of why this could be required?
-- JSON instances for decimal -- ORPHAN instances
defaultPrecision :: Word8
defaultPrecision = fromInteger 2
instance ToJSON Data.Decimal.Decimal where
toJSON d = toJSON $ show d
instance FromJSON Data.Decimal.Decimal where
-- TODO: New problem! How do we get the precision dynamically, based on
-- the currency settings of the logged-in user
parseJSON (Number n) = return $ Data.Decimal.realFracToDecimal defaultPrecision n
parseJSON x = fail $ "Expectig a number in the JSON to parse to a Decimal. Received " ++ (show x)
If instance depends on some runtime value then what you really want is an ability to create instances at runtime. You can implement FromJSON for Reader as it's done in your gist. But as you correctly noticed, you can't do the same for ToJSON because you don't know this precision. The easiest solution would be just store precision as separate field in data type. Like this:
data DecimalWithPrecision = MkDWP
{ value :: Decimal
, precision :: Word8
}
If you store this data type in your data base and query it after user login, then this is the easiest solution and doesn't require type level tricks from you.
If you don't know precision in advance, for example user inputs precision through console (I don't know why, but lets assume this), then this wouldn't work for you. As we all know that «type classes are just syntax sugar for data types», you can replace ToJSON/FromJSON constraints with JsonDict for Money_ in the following manner:
newtype Money_ = Money_ (Reader Word8 Decimal)
data JsonDict a = JsonDict
{ jdToJSON :: a -> Value
, jdParseJSON :: Value -> Parser a
}
mkJsonDict :: Word8 -- precision
-> JsonDict Money_
You can create such dictionary (or something similar to it) on-the-fly using Word8 from context and just pass it to function which needs it. See this blog post by Gabriel Gonzalez for details.
If you really want to have toJSON implementation inside instance, you can use reflection library. Precision is a natural number which gives you ability to use this library. Using it you basically can create instances in runtime as in previous approach, but you still have your type classes. See this blog post where similar technique was applied to make Arbitrary instances depend on runtime values. In your case this will look like this:
{-# 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)
And then you can run it like this to test:
ghci> runReader (makeMoney 3.12345) 2
(Number 3.12345,3.12)
Related
Suppose I have the following data type which maps my database schema.
data Object = Object
{ classification :: Text
, country :: Text
, numberOfParts :: Int
-- Lots of other fields
}
I want to extract statistics for all objects in the database. For example, I want to extract the frequency of every field in the Person data constructor. So I would have the following function :
-- In the return type, the key of the Map is the field name.
-- Each map value represents possible values with frequency
-- (ex. "classification" -> [("table", 10), ("chair", 3), ("computer", 2)])
generateStats :: [Object] -> Map Text [(Text, Integer)]
This function would calculate the frequency of every field, so I would have to call id object, classification object, country object, etc. If the datatype has 50 fields, I would have to call 50 functions to access those fields.
Is there a way to generalize this ?
Can it be generalized to any data constructor ?
Is there a more elegant way to solve this type of problem ?
This sort of problem can be solved with generics. Usually, the syb package (Data.Generics or Data.Data or SYB or "scrap your boilerplate" generics) is the easiest to use, so it's worth trying it first and moving on to more complicated libraries only if you can't get it to work for a particular task.
Here, syb provides a straightforward way of retrieving the list of field names from a record constructor. If you derive a Data instance for some Object:
{-# LANGUAGE DeriveDataTypeable #-}
import Data.Generics
import Data.Text (Text)
import qualified Data.Text as Text
data Object = Object
{ classification :: Text
, country :: Text
, numberOfParts :: Int
} deriving (Data)
then you can fetch the field names at runtime with the following function:
-- Get field names (empty list if not record constructor)
getnames :: Data object => object -> [Text]
getnames = map Text.pack . constrFields . toConstr
like so:
λ> :set -XOverloadedStrings
λ> getnames $ Object "prime" "Canada" 5
["classification","country","numberOfParts"]
You can fetch field values as Text at runtime using a generic query gmapQ and writing a generic helper function toText that converts field values of various types to Text:
-- Get field values as Text.
getfields :: Data object => object -> [Text]
getfields = gmapQ toText
The toText function has type:
toText :: (Data a) => a -> Text
and needs to be prepared to handle any possible field encountered. A limitation of Data.Data generics is that you can only handle a fixed set of explicit types with a default value for "the rest". Here, we handle Text, String, Int, and Double types and throw an error with unknown for "the rest":
{-# LANGUAGE TypeApplications #-}
toText = mkQ unknown -- make a query with default value "unknown"
id -- handle: id :: Text -> Text
`extQ` Text.pack -- extend to: pack :: String -> Text
`extQ` tshow #Int -- extend to: tshow :: Int -> Text
`extQ` tshow #Double -- extend to: tshow :: Double -> Text
where tshow :: (Show a) => a -> Text
tshow = Text.pack . show
unknown = error "unsupported type"
If you wanted to handle all types with a Show (or some other) instance, then syb won't do the job. (If you tried dropping the type application above and writing `extQ` tshow to handle all Show cases, you'd get an error.) Instead, you'd need need to upgrade to syb-with-class or some other generics library to handle this.
With all that in place, getting a list of key/value pairs from any object is straightword:
getpairs :: Data object => object -> [(Text,Text)]
getpairs = zip <$> getnames <*> getfields
This works on Objects:
λ> concatMap getpairs [Object "prime" "Canada" 5, Object "substandard" "Fakeistan" 100]
[("classification","prime"),("country","Canada"),("numberOfParts","5")
,("classification","substandard"),("country","Fakeistan"),("numberOfParts","100")]
or anything else with a Data instance. Sum types and record-less constructors should work okay. With the type:
data OtherObject = Foo { foo :: String, factor :: Double }
| Bar { bar :: Int }
| NotARecord Int Int Int
deriving (Data)
we get:
λ> getpairs $ Foo "exchange" 0.75
[("foo","exchange"),("factor","0.75")]
λ> getpairs $ Bar 42
[("bar","42")]
λ> getpairs $ NotARecord 1 2 3
[]
Here's a complete code example:
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}
import Data.Generics
import Data.Text (Text)
import qualified Data.Text as Text
data Object = Object
{ classification :: Text
, country :: Text
, numberOfParts :: Int
} deriving (Data)
data OtherObject = Foo { foo :: String, factor :: Double }
| Bar { bar :: Int }
| NotARecord Int Int Int
deriving (Data)
-- Get field names (empty list if not record constructor)
getnames :: Data object => object -> [Text]
getnames = map Text.pack . constrFields . toConstr
-- Get field vales as Text.
getfields :: Data object => object -> [Text]
getfields = gmapQ toText
-- Generic function to convert one field.
toText :: (Data a) => a -> Text
toText = mkQ unknown -- make a query with default value "unknown"
id -- handle: id :: Text -> Text
`extQ` Text.pack -- extend to: pack :: String -> Text
`extQ` tshow #Int -- extend to: tshow :: Int -> Text
`extQ` tshow #Double -- extend to: tshow :: Double -> Text
where tshow :: (Show a) => a -> Text
tshow = Text.pack . show
unknown = error "unsupported type"
-- Get field name/value pairs from any `Data` object.
getpairs :: Data object => object -> [(Text,Text)]
getpairs = zip <$> getnames <*> getfields
main :: IO ()
main = mapM_ print $
[ getpairs $ Object "prime" "Canada" 5
, getpairs $ Foo "exchange" 0.75
, getpairs $ Bar 42
, getpairs $ NotARecord 1 2 3
]
This solution depends on the generics machinery from generics-sop and the streaming sinks from foldl.
Some required pragmas and imports:
{-# LANGUAGE DeriveGeneric,DeriveAnyClass,ScopedTypeVariables,FlexibleContexts,
GADTs,TypeApplications,OverloadedStrings,StandaloneDeriving, TypeOperators #-}
module Main (main) where
import qualified GHC.Generics as GHC
import Generics.SOP (All,And,IsProductType,productTypeFrom,
DatatypeInfo(..),datatypeInfo,
ConstructorInfo(..),FieldInfo(..),FieldName,
projections, I(..), K(..),type (-.->)(Fn),type (:.:)(Comp),
Generic,HasDatatypeInfo)
import Generics.SOP.NP -- All the *_NP functions come form here
import Generics.SOP.Dict (Dict(..),zipAll)
import qualified Control.Foldl as L
import Data.Proxy
import Data.Text (Text)
import qualified Data.Map.Strict as Map
Datatypes and functions to calculate histograms, which aren't tied to any concrete record:
newtype Histogram a = Histogram (Map.Map a Int) deriving Show
-- Hides the exact type of the key behind an existential
data SomeHistogram = forall a. (Ord a, Show a) => SomeHistogram (Histogram a)
deriving instance Show SomeHistogram
-- Streaming sink for a single field
histogram :: Ord a => L.Fold a (Histogram a)
histogram = (L.Fold step Map.empty Histogram)
where
step m a = Map.insertWith (+) a 1 m
-- For any record with Generics.SOP.Generic instance,
-- create a streaming sink that accepts record values and
-- returns a list of histograms, one for each field
recordHistogram :: forall r xs . (IsProductType r xs, All Ord xs, All Show xs)
=> L.Fold r [SomeHistogram]
recordHistogram =
let productOfFolds =
cliftA_NP
(Proxy #Ord)
(\(Fn proj) ->
Comp (L.premap (\o -> let np = productTypeFrom #r #xs o
I r = proj (K np)
in r)
histogram))
(projections #xs)
foldToProduct = sequence'_NP productOfFolds -- pull the Fold outward
-- convince GHC that we have a combination of Ord and Show for all fields
ordAndShow = zipAll (Dict #(All Ord) #xs) (Dict #(All Show) #xs)
foldToList = case ordAndShow of -- collapse result of Fold into a list
Dict -> collapse_NP . cliftA_NP (Proxy #(Ord `And` Show)) (K . SomeHistogram)
<$>
foldToProduct
in foldToList
In case we want a list of field names to zip with the list of histograms:
fieldNamesOf :: forall r xs. (IsProductType r xs, HasDatatypeInfo r)
=> Proxy r
-> [FieldName]
fieldNamesOf _ =
case datatypeInfo (Proxy #r) of
ADT _ _ ((Record _ fields) :* Nil) _ ->
collapse_NP (liftA_NP (\(FieldInfo i) -> K i) fields)
_ -> error "hey, not a record!"
Putting it all to work with Object:
data Object = Object
{ classification :: Text
, country :: Text
, numberOfParts :: Int
} deriving (GHC.Generic,Generic,HasDatatypeInfo)
-- Generic and HasDatatypeInfo from generics-sop
main :: IO ()
main = print $ L.fold recordHistogram [Object "foo" "Spain" 4, Object "bar" "France" 4]
This solution has two potential problems:
Internally, recordHistogram uses n-ary products from generics-sop. Constructing and traversing these products might incur in some overhead.
There might be some space leak in the streaming sink (the Fold) returned by recordHistogram. Some extra strictness might be necessary.
I am new to Haskell, so maybe I am missing some fundamental concepts here (or maybe failed to find the appropriate extension). I was wondering if there was a way to optimize or further abstract the following scenario. This code seems very redundant.
Let's say I have the following data classes:
data Person = Person
{ personName :: !String
, personAge :: !Int
} deriving Show
data Dog = Dog
{ dogName :: !String
, dogAge :: !Int
} deriving Show
Let's say I have a service and I'm only concerned with outputing records as strings. In reality, the strings will probably be JSON and the records fetched from the DB, but let's take a simpler case. I basically need a URL token to fetch an appropriate object (say, the string "dog" will get me a Dog, or even just the Haskell "show" string, without expressly declaring it as (value)::Dog).
I have attempted to implement this in several ways...the only thing that seems to work is the following:
data Creature = DogC Dog
| PersonC Person
deriving Show
fromString :: String -> Maybe Creature
fromString "dog" = Just $ DogC $ Dog "muffin" 8
fromString "person" = Just $ PersonC $ Person "John" 22
fromString _ = Nothing
main :: IO ()
main = do
putStrLn $ show $ fromString "dog"
I'm not entirely fond of the new type, nor the list of fromString declarations. And to benefit from the original data declarations, I would probably need to write a similarly tedious expression (eg, "fromCreature") to revert Creature back into my original types. This information might change, so I would probably need TH for a few of the declarations...
Is there a way around some of this? I fiddled with GADTs and classes, but both seemed to be dependent on type- rather than value- based polymorphism (A string identifier tends to cause issues with ambiguous instances). It would be nice to map the constructor to a string (Say, with Data.Map), but constructors often have different kinds.
Update
So, I went with an approach that isn't exactly relevant to the question I had asked, but it may be useful to someone. I did want to maintain some record types, but most didn't add much value and were getting in my way. The steps I had followed went something like:
Use a different/lower-level DB driver, that returns workable types (eg, [ColumnDef] and [[SQLValue]] instead of tuples and records...).
Create ToJSON instances for SQLValue -- most of the types were covered, except a few ByteString types, and I had to handle the conversion of SQLNull to Null. To maintain compatibility with some record types, my default handler looked like: toJSON = genericToJSON defaultOptions { sumEncoding = UnTaggedValue} The untagged value should allow one to read the JSON into defined data types (eg, Dog / Person ) if desired....
Given that column name is accessible from ColumnDef, I wrote an expression that zips [ColumnDef] and [SqlValue] to a list of Aeson-compatible key-value pairs, eg: toJsPairs :: [ColumnDef] -> [SqlValue] -> [(Text,Value)]
Then, I wrote an expression to fetch the JSON from a table name, which more or less serves as my "universal dispatcher." It references a list of authorized tables, so it's less crazy than it might sound.
The code looked a bit like this (using mysql-haskell).
{-# LANGUAGE OverloadedStrings #-}
import qualified Control.Applicative as App
import Database.MySQL.Base
import qualified System.IO.Streams as Streams
import Data.Aeson (FromJSON, ToJSON)
import Data.Aeson.Encode.Pretty (encodePretty)
import Data.Aeson.Types
import Data.Text.Encoding
import Data.String (fromString)
import Data.ByteString.Internal
import qualified Data.ByteString.Lazy.Internal as BLI
import Data.HashMap.Strict (fromList)
appConnectInfo = defaultConnectInfo {
ciUser = "some_user"
, ciPassword = "some_password"
, ciDatabase = "some_db"
}
instance FromJSON ByteString where
parseJSON (String s) = pure $ encodeUtf8 s
parseJSON _ = App.empty
instance ToJSON ByteString where
toJSON = String . decodeUtf8
instance ToJSON MySQLValue where
toJSON (MySQLNull) = Null
toJSON x = genericToJSON defaultOptions
{ sumEncoding = UntaggedValue } x
-- This expression should fail on dimensional mismatch.
-- It's stupidly lenient, but really dimensional mismatch should
-- never occur...
toJsPairs :: [ColumnDef] -> [MySQLValue] -> [(Text,Value)]
toJsPairs [] _ = []
toJsPairs _ [] = []
toJsPairs (x:xs) (y:ys) = (txt x, toJSON y):toJsPairs xs ys
where
-- Implement any modifications to the key names here
txt = decodeUtf8.columnName
listRecords :: String -> IO BLI.ByteString
listRecords tbl = do
conn <- connect appConnectInfo
-- This is clearly an injection vulnerability.
-- Implemented, however, the values for 'tbl' are intensely
-- vetted. This is just an example.
(defs, is) <- query_ conn $ fromString ( "SELECT * FROM `" ++ tbl ++ "` LIMIT 100")
rcrds <- Streams.toList is
return $ encodePretty $ map (jsnobj defs) rcrds
where
jsnobj :: [ColumnDef] -> [MySQLValue] -> Value
jsnobj defs x = Object $ fromList $ toJsPairs defs x
If what you want to consume at the end is json value - it might make sense to
represent result as json value using aeson library:
{-# LANGUAGE DeriveGeneric #-}
import Data.Aeson
import GHC.Generics
data Dog = Dog Int String deriving (Show, Generic)
data Cat = Cat Int String deriving (Show, Generic)
-- here I'm using instance derived with generics, but you can write one by
-- hands
instance ToJSON Dog
instance ToJSON Cat
-- actions to get stuff from db
getDog :: Monad m => Int -> m Dog
getDog i = return (Dog i (show i))
getCat :: Monad m => Int -> m Cat
getCat i = return (Cat i (show i))
-- dispatcher - picks which action to use
getAnimal :: Monad m => String -> Int -> m (Maybe Value)
getAnimal "dog" i = Just . toJSON <$> getDog i
getAnimal "cat" i = Just . toJSON <$> getCat i
getAnimal _ _ = return Nothing
main :: IO ()
main = do
getAnimal "dog" 2 >>= print
getAnimal "cat" 3 >>= print
getAnimal "chupakabra" 12 >>= print
High energy magic version
class Monad m => MonadAnimal m where
-- basically you want something that fetches extra argumets from HTTP or
-- whatevere, perform DB query and so on.
class Animal a where
animalName :: Proxy a -> String
animalGetter :: MonadAnimal m => m a
locateAnimals :: MonadAnimal m => Q [(String, m Value)]
locateAnimals -- implement using TH (reify function is your friend). It should look for
-- all the animal instances in scope and make a list from them with serialized
-- fetcher.
-- with that in place dispatcher should be easy to implement
I have the following code:
{-# LANGUAGE DeriveGeneric, OverloadedStrings #-}
import Data.Aeson
import GHC.Generics
data CharClass = Fighter | Rogue | Wizard deriving (Generic, Show)
instance FromJSON CharClass
instance ToJSON CharClass
I can encode values of this type:
*Main> encode Fighter
"\"Fighter\""
But round-tripping doesn't work:
*Main> eitherDecode $ encode Fighter
Left "Failed reading: satisfy"
*Main> :t eitherDecode $ encode Fighter
eitherDecode $ encode Fighter :: FromJSON a => Either String a
It looks a lot like this answered question, but adding the expected type doesn't work:
*Main> eitherDecode $ encode Fighter :: Either String CharClass
Left "Failed reading: satisfy"
Interestingly, it does work for fromJSON/toJSON:
*Main> fromJSON $ toJSON Fighter :: Result CharClass
Success Fighter
Making at least one of the constructors non-nullary also makes things work, like if I do this:
data CharClass = Fighter Int | Rogue | Wizard deriving (Generic, Show)
And then try to round-trip again:
*Main> decode $ encode (Fighter 0) :: Maybe CharClass
Just (Fighter 0)
I'm sure I'm missing something simple, but attempting to trace this through the generic code made my head spin.
JSON is fundamentally a collection of key-value pairs, where values can be a few primitive types or another collection of key-value pairs. Nullary types just don't fit in very well with the whole idea of being JSON entities by themselves. However, they work fine when placed within other types that mesh well with the JSON concept.
data F = F { a :: CharClass, b :: CharClass }
deriving (Generic, Show)
instance FromJSON F
instance ToJSON F
This looks more like the sort of key-value collection JSON was designed for.
*Main> let x = F Fighter Rogue
*Main> x
F {a = Fighter, b = Rogue}
*Main> decode $ encode x :: Maybe F
Just (F {a = Fighter, b = Rogue})
The version of aeson that stack installed on my machine was from the 0.8 series, and in aeson 0.8 or earlier, only objects and arrays were parsed at the root level.
In aeson 0.9, decode uses value parser. So nullable object (represented as string) at top-level will work.
For 0.8 the below example works. I don't know why decodeWith isn't exposed.
{-# LANGUAGE DeriveGeneric #-}
import Control.Applicative
import GHC.Generics
import Data.Aeson
import Data.Aeson.Parser
import Data.ByteString.Lazy as L
import Data.Attoparsec.ByteString.Char8 (endOfInput, skipSpace)
import qualified Data.Attoparsec.Lazy as L
data CharClass = Fighter | Rogue | Wizard deriving (Generic, Show)
instance ToJSON CharClass
instance FromJSON CharClass
decodeWith p to s =
case L.parse p s of
L.Done _ v -> case to v of
Success a -> Just a
_ -> Nothing
_ -> Nothing
{-# INLINE decodeWith #-}
valueEOF = value <* skipSpace <* endOfInput
decodeValue :: (FromJSON a) => L.ByteString -> Maybe a
decodeValue = decodeWith valueEOF fromJSON
main :: IO ()
main = print (decodeValue (encode Fighter) :: Maybe CharClass)
Using yesod and persistent, I made what I think is a handy type to handle Markdown data:
{-# LANGUAGE OverloadedStrings #-}
module Utils.MarkdownText where
import Prelude
import Data.Text.Lazy
import Data.Text as T
import Database.Persist
import Database.Persist.Sql
import Text.Blaze
import Text.Markdown
newtype MarkdownText = MarkdownText { rawMarkdown :: T.Text }
instance PersistField MarkdownText where
toPersistValue = PersistText . rawMarkdown
fromPersistValue (PersistText val) = Right $ MarkdownText { rawMarkdown = val }
fromPersistValue _ = Left "invalid type"
instance PersistFieldSql MarkdownText where
sqlType _ = SqlString
instance ToMarkup MarkdownText where
toMarkup = (markdown def) . fromStrict . rawMarkdown
preEscapedToMarkup = toMarkup . rawMarkdown
You may notice in the ToMarkup instance I use def to get markdown parameters. If I would like to change these settings, and not have it hardcoded in this module, what are my options?
I have considered the option of making MarkdownText take the settings information as a parameter, but what other options are there (if any)?
I'm going to simplify the problem so that we only need core libraries. We want to change how we Show a MarkdownText based on some ExampleSettings that contain a prefix and a suffix.
{-# LANGUAGE OverloadedStrings #-}
import Data.Text as T
import Data.Monoid
import Data.String
newtype MarkdownText = MarkdownText { rawMarkdown :: T.Text}
instance IsString MarkdownText where
fromString = MarkdownText . fromString
data ExampleSettings = ExampleSettings { prefix :: T.Text, suffix :: T.Text }
def = ExampleSettings "" ""
emphasise = def { prefix = "*", suffix = "*" }
showWithSettings :: ExampleSettings -> T.Text -> String
showWithSettings set = show . (\x -> prefix set <> x <> suffix set)
instance Show MarkdownText where
show = showWithSettings def . rawMarkdown
main = print $ MarkdownText "Hello World"
There are a number of options for how to solve this problem, first at the value level, then at the type level, and finally globally at the type level.
Add a field
We have a few options for how we can proceed. The simplest option is to add the setting at the value level. We'll wrap up the settings with the MarkdownText.
data ConfiguredMarkdownText = ConfiguredMarkdownText {
markdownText :: MarkdownText,
settings :: ExampleSettings }
instance Show ConfiguredMarkdownText where
show t = showWithSettings (settings t) (rawMarkdown . markdownText $ t)
main = print $ ConfiguredMarkdownText "Hello World" emphasise
For convenience, we added an IsString instance for MarkdownText in the first section.
Add a type parameter
We could carry the extra data we need around at the type level instead of at the value level. We add a type parameter to MarkdownText to indicate which settings to use.
newtype MarkdownText s = MarkdownText { rawMarkdown :: T.Text}
We make types to represent the possible settings
data Def = Def
data Emphasise = Emphasise
We can add a type class for types that determine settings, and instances for the possible settings.
{-# LANGUAGE FunctionalDependencies #-}
class Setting v k | k -> v where
setting :: proxy k -> v
instance Setting ExampleSettings Def where
setting _ = def
instance Setting ExampleSettings Emphasise where
setting _ = emphasise
We can Show any MarkdownText s as long as s provides the Setting.
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE UndecidableInstances #-}
instance (Setting ExampleSettings s) => Show (MarkdownText s) where
show t = showWithSettings (setting t) (rawMarkdown t)
main = print ("Hello World" :: MarkdownText Emphasise)
MarkdownText :: * -> * requires a slightly different IsString instance.
instance IsString (MarkdownText s) where
fromString = MarkdownText . fromString
Reflect the value from a type parameter
The reflection package provides a way to temporarily associate a value with a type. This lets us do the same thing as in the previous example, but without needing to make types of our own to represent the settings.
import Data.Reflection
We start by adding an extra type parameter to MarkdownText, the same as in the previous section.
newtype MarkdownText s = MarkdownText { rawMarkdown :: T.Text}
The reflection package defines a class, Reifies, that is almost identical to the Setting class we made for the previous section. This lets us jump straight to defining the Show instance.
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE UndecidableInstances #-}
instance (Reifies s ExampleSettings) => Show (MarkdownText s) where
show t = showWithSettings (reflect t) (rawMarkdown t)
We'll define a little convenience function to tag the type parameter of MarkdownTexts
markdownText :: proxy s -> T.Text -> MarkdownText s
markdownText _ = MarkdownText
and complete the example of setting the ExampleSettings to be used when showing a MarkdownText. We provide the reified value with reify :: a -> (forall s. Reifies s a => Proxy s -> r) -> r, which passes back a proxy for the type the value has been reified to.
main = reify emphasise (\p -> print (markdownText p "Hello World"))
This has an advantage over the simpler version from the next section; multiple settings can be used for MarkdownTexts with different type parameters.
main = reify emphasise $ \p1 ->
reify def $ \p2 ->
do
print (markdownText p1 "Hello World")
print (markdownText p2 "Goodbye")
Reflect a global configuration
The reflection package also defines a simpler class, Given. It's defined as class Given a where given :: a. It represents values that can be determined from the type of the value itself. This allows us to provide a single global configuration value for a specific type, like ExampleSettings. We can jump straight to writing the show instance for MarkdownText.
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE UndecidableInstances #-}
instance (Given ExampleSettings) => Show (MarkdownText) where
show = showWithSettings given . rawMarkdown
We provide the given ExampleSettings with give :: a -> (Given a => r) -> r.
main = give emphasise $ print (MarkdownText "Hello World")
I'm following the Aeson library documentation but their example doesn't seem to work for me:
Code:
{-# LANGUAGE OverloadedStrings #-}
import Data.Text
import Data.Aeson
import Control.Applicative ((<$>),(<*>))
import Control.Monad
instance FromJSON Person where
parseJSON (Object v) = Person <$>
v .: "name" <*>
v .: "age"
-- A non-Object value is of the wrong type, so fail.
parseJSON _ = mzero
data Person = Person
{ name :: Text
, age :: Int
} deriving Show
Error report:
ghci> decode "{\"name\":\"Joe\",\"age\":12}" :: Maybe Person
Couldn't match expected type `Data.ByteString.Lazy.Internal.ByteString'
with actual type `[Char]'
In the first argument of `decode', namely
`"{\"name\":\"Joe\",\"age\":12}"'
In the expression:
decode "{\"name\":\"Joe\",\"age\":12}" :: Maybe Person
In an equation for `a':
a = decode "{\"name\":\"Joe\",\"age\":12}" :: Maybe Person
Am i doing something wrong here ?
The problem is that decode expects a ByteString and you are passing a String.
Try this in ghci:
:m +Data.ByteString.Lazy.Char8
decode $ pack "{\"name\":\"Joe\",\"age\":12}" :: Maybe Person
In real code you shouldn't use the Char8 module as it just truncates Chars to 8 bits without taking any account of encoding. Generally you should aim to start out with a ByteString, e.g. by reading it from disk using the functions in Data.ByteString.Lazy.