By what mechanism does Generic interact with Aeson's ToJSON class? - haskell

Looking at part of the servant example, I see:
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeOperators #-}
module Main where
import Prelude ()
import Prelude.Compat
import Control.Monad.Except
import Control.Monad.Reader
import Data.Aeson.Types
import Data.Attoparsec.ByteString
import Data.ByteString (ByteString)
import Data.List
import Data.Maybe
import Data.String.Conversions
import Data.Time.Calendar
import GHC.Generics
import Lucid
import Network.HTTP.Media ((//), (/:))
import Network.Wai
import Network.Wai.Handler.Warp
import Servant
import System.Directory
import Text.Blaze
import Text.Blaze.Html.Renderer.Utf8
import qualified Data.Aeson.Parser
import qualified Text.Blaze.Html
type UserAPI1 = "users" :> Get '[JSON] [User]
data User = User
{ name :: String
, age :: Int
, email :: String
, registration_date :: Day
} deriving (Eq, Show, Generic)
instance ToJSON User
When I removed the deriving of Generic, I got the following error:
• No instance for (Generic User)
arising from a use of ‘aeson-1.1.2.0:Data.Aeson.Types.ToJSON.$dmtoJSON’
So, it appears that the Generic typeclass instance for User enables instance ToJSON User to, I'm assuming, create a JSON Encoder for User.
What's the machinery of instance ToJSON User, i.e. type signature, if that's the right word?
I'm trying to look at its type from the stack ghci, i.e. REPL, but failing:
λ: >:t instance
<interactive>:1:1: error: parse error on input ‘instance’
λ: >:i instance
<interactive>:1:1: error: parse error on input ‘instance’

Let's look at the source for ToJSON:
class ToJSON a where
-- | Convert a Haskell value to a JSON-friendly intermediate type.
toJSON :: a -> Value
default toJSON :: (Generic a, GToJSON Zero (Rep a)) => a -> Value
toJSON = genericToJSON defaultOptions
The ToJSON class has a default toJSON implementation with additional type constraints (including Generic, as you've noticed). This requires the DefaultSignatures extension; notice at the top of that module you can see
{-# LANGUAGE DefaultSignatures #-}
The other constraint, GToJSON Zero (Rep a), imposes some further restrictions on the structure of a, and so not every type with a Generic instance will satisfy this signature.
Regarding your question about GHCi: instance is a Haskell keyword. Inspecting toJSON may be what you want instead. This will show you the same information we saw in the source:
λ> :i toJSON
class ToJSON a where
toJSON :: a -> Value
default toJSON :: (GHC.Generics.Generic a,
GToJSON Zero (GHC.Generics.Rep a)) =>
a -> Value
...
-- Defined in ‘aeson-1.1.2.0:Data.Aeson.Types.ToJSON’

Related

Scotty and persistence - type error when using insert function

I've got the following application:
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE MultiParamTypeClasses #-}
module Main where
-- Scotty
import qualified Web.Scotty as S
import Network.Wai.Middleware.RequestLogger
import Network.Wai.Middleware.Static
import qualified Data.Text.Lazy as L
-- HTML rendering
import Text.Blaze.Html.Renderer.Text (renderHtml)
import qualified Text.Blaze.Html5 as H
import qualified Text.Blaze.Html5.Attributes as A
import Control.Monad.IO.Class
-- Database
import Database.Persist
import Database.Persist.Sqlite
import Database.Persist.TH
import Control.Monad.Trans.Resource (runResourceT, ResourceT)
import Control.Monad.Logger
-- URL generation
import System.Random
import Control.Monad (replicateM)
-- JSON
import Data.Map (fromList)
share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
Link
shortUrl L.Text
URLKey shortUrl
Primary shortUrl
longUrl L.Text
counter Int
deriving Show
|]
getURL :: L.Text -> IO (Maybe (Entity Link))
getURL shortId = runSqlite "links.db" $ do
maybeOriginal <- getBy $ URLKey shortId
pure maybeOriginal
-- I don't know what type to give this, that's probably the problem
addURL short long = runSqlite "links.db" $ do
insert $ Link short long
main :: IO ()
main = do
-- Connect to db and run migration
runSqlite "links.db" $ do runMigration migrateAll
S.scotty 3000 $ do
...
S.post "/shorten" $ do
-- Get URL
url <- S.param "url" :: S.ActionM L.Text
-- Generate a random short URL
randStr <- liftIO $ getRandStr 5
-- Add the urls to the database
liftIO $ addURL (L.pack randStr) url
-- Send JSON response with ID
S.json $ fromList [("id" :: String, randStr)]
I get the following error:
shortener> build (lib + exe)
Preprocessing library for shortener-0.1.0.0..
Building library for shortener-0.1.0.0..
Preprocessing executable 'shortener-exe' for shortener-0.1.0.0..
Building executable 'shortener-exe' for shortener-0.1.0.0..
[2 of 2] Compiling Main
/home/henry/haskell/shortener/app/Main.hs:86:5: error:
• Couldn't match type ‘PersistEntityBackend (Int -> Link)’
with ‘SqlBackend’
arising from a use of ‘insert’
• In the first argument of ‘($)’, namely ‘insert’
In a stmt of a 'do' block: insert $ Link short long
In the second argument of ‘($)’, namely
‘do insert $ Link short long’
|
86 | insert $ Link short long
| ^^^^^^
-- While building package shortener-0.1.0.0 (scroll up to its section to see the error) using:
/home/henry/.stack/setup-exe-cache/x86_64-linux-tinfo6/Cabal-simple_mPHDZzAJ_3.4.1.0_ghc-9.0.2 --builddir=.stack-work/dist/x86_64-linux-tinfo6/Cabal-3.4.1.0 build lib:shortener exe:shortener-exe --ghc-options " -fdiagnostics-color=always"
Process exited with code: ExitFailure 1
I'm not sure how to resolve this type error and I haven't been able to find anything of use online. There was this answer with a similar problem, but the given type signature and several variations of it did not work.
It turns out I forgot a field when inserting. I had
insert $ Link short long
I needed
insert $ Link short long 0
for the counter field of Link.
Unfortunately the error didn't make that at all clear.

Typed version of Plutus Pionneers homework01 (week02) [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 1 year ago.
Improve this question
I've tried to code a typed version of the first homework exercise. It compiles but fails to render in the playground...
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
import Control.Monad hiding (fmap)
import GHC.Generics (Generic)
import Data.Aeson (FromJSON, ToJSON)
import Data.Map as Map
import Data.Text (Text)
import Data.Void (Void)
import Plutus.Contract hiding (when)
import PlutusTx (Data (..))
import qualified PlutusTx
import PlutusTx.Prelude hiding (Semigroup(..), unless)
import Ledger hiding (singleton)
import Ledger.Constraints as Constraints
import qualified Ledger.Scripts as Scripts
import qualified Ledger.Typed.Scripts as Scripts
import Ledger.Ada as Ada
import Playground.Contract (printJson, printSchemas, ensureKnownCurrencies, stage, ToSchema)
import Playground.TH (mkKnownCurrencies, mkSchemaDefinitions)
import Playground.Types (KnownCurrency (..))
import Prelude (Semigroup (..))
import Text.Printf (printf)
newtype DuoBoolRedeemer = DuoBoolRedeemer (Bool, Bool)
deriving (Generic, ToSchema)
PlutusTx.unstableMakeIsData ''DuoBoolRedeemer
{-# INLINABLE mkValidator #-}
-- This should validate if and only if the two Booleans in the redeemer are equal!
mkValidator :: () -> DuoBoolRedeemer -> ValidatorCtx -> Bool
mkValidator _ (DuoBoolRedeemer (b1,b2)) _ = traceIfFalse "wrong redeemer" $ b1 == b2
data Typed
instance Scripts.ScriptType Typed where
type instance DatumType Typed = ()
type instance RedeemerType Typed = DuoBoolRedeemer
inst :: Scripts.ScriptInstance Typed
inst = Scripts.validator #Typed
$$(PlutusTx.compile [|| mkValidator ||])
$$(PlutusTx.compile [|| wrap ||])
where
wrap = Scripts.wrapValidator #() #DuoBoolRedeemer
validator :: Validator
validator = Scripts.validatorScript inst
valHash :: Ledger.ValidatorHash
valHash = Scripts.validatorHash validator
scrAddress :: Ledger.Address
scrAddress = ScriptAddress valHash
type GiftSchema =
BlockchainActions
.\/ Endpoint "give" Integer
.\/ Endpoint "grab" DuoBoolRedeemer
give :: (HasBlockchainActions s, AsContractError e) => Integer -> Contract w s e ()
give amount = do
let tx = mustPayToTheScript () $ Ada.lovelaceValueOf amount
ledgerTx <- submitTxConstraints inst tx
void $ awaitTxConfirmed $ txId ledgerTx
logInfo #String $ printf "made a gift of %d lovelace" amount
grab :: forall w s e. (HasBlockchainActions s, AsContractError e) => DuoBoolRedeemer -> Contract w s e ()
grab bs = do
utxos <- utxoAt scrAddress
let orefs = fst <$> Map.toList utxos
lookups = Constraints.unspentOutputs utxos <>
Constraints.otherScript validator
tx :: TxConstraints Void Void
tx = mconcat [mustSpendScriptOutput oref $ Redeemer $ PlutusTx.toData bs | oref <- orefs]
ledgerTx <- submitTxConstraintsWith #Void lookups tx
void $ awaitTxConfirmed $ txId ledgerTx
logInfo #String $ "collected gifts"
endpoints :: Contract () GiftSchema Text ()
endpoints = (give' `select` grab') >> endpoints
where
give' = endpoint #"give" >>= give
grab' = endpoint #"grab" >>= grab
mkSchemaDefinitions ''GiftSchema
mkKnownCurrencies []
I don't understand why it fails. In the playground, for the "grab" action I have the message "Unsuported non record constructor". I think the problem is with ToSchema which may only accept reccords, but if I don't use it, I have an error message requiring it... I don't understand.
I'm no expert, but can you try a:
.\/ Endpoint "grab" (Bool, Bool)
Given that the simulator, imho, only expects simpler things

Haskell Illegal deriving item Generic

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeOperators #-}
import qualified Data.ByteString.Lazy as BL
import Data.Csv (DefaultOrdered, FromRecord,
ToNamedRecord, ToRecord)
import Data.Generics
import Data.Proxy
import qualified Data.Text as T
import Network.HTTP.Client (newManager)
import Network.HTTP.Client.TLS (tlsManagerSettings)
import Servant.API
import Servant.Client
import Servant.CSV.Cassava
import System.Environment
data Cata = Cata
{
csvDate :: String,
csvOpen :: String,
csvHigh :: String,
csvLow :: String,
csvClose :: String,
csvVolume :: String,
csvExDividend :: String,
csvSplitRatio :: String,
csvAdjOpen :: String,
csvAdjHigh :: String,
csvAdjLow :: String,
csvAdjClose :: String,
csvAdjVolume :: String
} deriving (Show, Generic)
instance DefaultOrdered Cata
instance FromRecord Cata
instance ToNamedRecord Cata
instance ToRecord Cata
I compile the above code and get the following error: why?
$ stack ghc servantcsv.hs
[1 of 1] Compiling Main ( servantcsv.hs, servantcsv.o )
servantcsv.hs:34:21: error:
• Illegal deriving item ‘Generic’
• In the data declaration for ‘Cata’
|
34 | } deriving (Show, Generic)
| ^^^^^^^
GHC Version: ghc-8.4.3
You want to import GHC.Generics not Data.Generics. What you have imported does have a type Generic but it isn't a class:
type Generic c = forall a. Data a => a -> c a
Certainly that can't be part of a "deriving" clause.

deriving Generic doesn't work even though DeriveGeneric is on

I'm trying to follow the tutorial for the Beam Haskell library: https://tathougies.github.io/beam/tutorials/tutorial1/
module Lib
( someFunc
) where
{-# LANGUAGE
DeriveGeneric
, GADTs
, OverloadedStrings
, FlexibleContexts
, FlexibleInstances
, TypeFamilies
, TypeApplications
#-}
import Database.Beam
import Database.Beam.Postgres
import GHC.Generics
import Data.Text (Text)
data UserT f
= User
{ _userEmail :: Columnar f Text
, _userFirstName :: Columnar f Text
, _userLastName :: Columnar f Text
, _userPassword :: Columnar f Text }
deriving Generic
someFunc :: IO ()
someFunc = putStrLn "someFunc"
This results in the following error:
• Can't make a derived instance of ‘Generic (UserT f)’:
You need DeriveGeneric to derive an instance for this class
• In the data declaration for ‘UserT’
|
27 | deriving Generic
| ^^^^^^^
Note that the DeriveGeneric language pragma is present.
What am I missing here?
Build environment:
stack lts-11.9
Linux
A {-# LANGUAGE #-} declaration needs to go at the very top of the file, before the module declaration.

Snap, IO and acid-state

Trying to use acid-state in Snap, and I hit a roadblock.
Here is what I got so far.
First my acid-state related objects (it's a dummy book with a isbn number):
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE OverloadedStrings #-}
module Models where
import Prelude hiding ((.), id)
import Control.Category ((.))
import Control.Monad.Reader (asks)
import Data.ByteString (ByteString)
import Data.SafeCopy (base, deriveSafeCopy)
import qualified Data.Text as T
import Data.Typeable (Typeable)
import Data.Acid (Update, Query, makeAcidic)
import Control.Monad.Reader (ask)
import Control.Applicative ((<$>))
import Data.Data (Data)
data Book = Book { isbn :: String }
deriving (Eq, Ord, Read, Data, Show, Typeable)
$(deriveSafeCopy 0 'base ''Book)
-- Retrieve the book's isbn
queryIsbn :: Query Book String
queryIsbn = isbn <$> ask
$(makeAcidic ''Book ['queryIsbn])
And then my actual attempt at integrating it with Snap. As you can see, I am having trouble defining __ doQuery__ function, that should return a string isbn:
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE OverloadedStrings #-}
module Application where
import Control.Monad.Trans.Class (lift)
import Data.Text.Encoding (decodeUtf8)
import Text.XmlHtml (Node(TextNode),Node (Element),
getAttribute, setAttribute, nodeText)
import Data.ByteString (ByteString)
import Data.Maybe
import Snap.Core
import Snap.Snaplet
import Snap.Snaplet.Heist (Heist, HasHeist(heistLens), heistInit,
addSplices, liftHeist, render)
import Snap.Util.FileServe
import Text.Templating.Heist (HeistT, Template, getParamNode)
import Data.Lens.Template
import Models
import Data.Acid.Advanced (query')
import Data.Acid (AcidState, openLocalState, closeAcidState, IsAcidic, query)
import Data.Text (pack)
import Control.Monad.IO.Class (liftIO, MonadIO)
import Snap (snapletValue)
import Data.Lens.Common (getL, (^$), (^.), Lens)
import Control.Monad.Reader (ask, asks)
import Control.Applicative ((<$>))
import Data.Typeable (typeOf)
import Prelude hiding ((.), id)
import Control.Category ((.), id)
------------------------------------------------------------------------------
type AppHandler = Handler App App
--------------
-- Acid
---------------
-- Used for holding data for the snapplet
data Acid st = Acid { _state :: AcidState st }
-- Initializer function for the snapplet
seedBook = Book "9213-23123-2311"
acidInit :: SnapletInit b (Acid Book)
acidInit = makeSnaplet "storage" "Snaplet providing storage functionality" Nothing initializer
--The 'm' is the type variable of the MonadSnaplet type class. 'b' is the base state, and 'v' is the state of the current "view" snaplet (or simply, current state).
initializer :: Initializer b v (Acid Book)
initializer = do
st <- liftIO (openLocalState seedBook)
--onUnload (closeAcidState st)
return $ Acid st
-----------------------
-- Snap Global State
--------------------
data App = App
{ _heist :: Snaplet (Heist App),
_acid :: Snaplet (Acid Book)
}
makeLens ''App
----------------------------------------------------------------------------------
instance HasHeist App where
heistLens = subSnaplet heist
-----------------------------------------------
-- | Initialize app
-----------------------------------------------
appInit :: SnapletInit App App
appInit = makeSnaplet "app" "Website" Nothing $ do
h <- nestSnaplet "" heist $ heistInit "templates"
a <- nestSnaplet "isbn" acid (acidInit)
addRoutes routes --see below
addSplices [ ("menuEntry", liftHeist menuEntrySplice) ]
return $ App h a
------------------------------------------------
-- | The application's routes.
------------------------------------------------
routes :: [(ByteString, Handler App App ())]
routes = [ ("/books", handleBooks)
, ("/contact", render "contact")
, ("/isbn", liftIO doQuery >>= writeBS )
, ("", serveDirectory "static")
]
-- Is this Function signature possible? Or must it run inside Snap or other monad?
doQuery :: IO ByteString
doQuery = do -- ???????????
--somehow retrieve acid store from snaplet
--run queryIsbn on it
--return isbn string
return "BLAH"
handleBooks :: Handler App App ()
handleBooks = render "books"
Any help on what I am missing would be greatly appreciated. If something is not clear, please let me know and I'll update the question.
MathematicalOrchid is correct, the simplest answer to your problem is to use liftIO on the openLocalState call.
But from a broader view, what you're doing here has already been done for you by the snaplet-acid-state package, so I would recommend that you just use that. The repository also includes an example application demonstrating how to use it.
I have no idea about the packages you're using, but it looks like the problem is simply that openLocalState is an IO action, but your type signature requires it to be an Initializer action.
Fixing it might be as simple as stuffing a call to liftIO in there. I'm not really sure though... I don't know which module each of these types comes from.

Resources