Processing standard quasiquote from external file in Haskell - haskell

I wanted to read an external Haskell source file for compile-time AST manipulation. How can I do that? I tried something like the following, but it didn't compile with the error message "TH.hs:15:12:
Declaration splices are not permitted inside declaration bracket".
--------
-- TH.hs
{-# LANGUAGE TemplateHaskell, QuasiQuotes #-}
module TH where
import Language.Haskell.TH
import Language.Haskell.TH.Quote
dd :: QuasiQuoter
dd = QuasiQuoter undefined undefined undefined ddDec
ddDec file_name = do
file_cts <- runIO (readFile file_name)
-- runQ [d| dummy = 0 |] -- This can compile.
runQ [d| file_cts |] -- This does not compile.
--------
-- main.hs
{-# LANGUAGE TemplateHaskell, QuasiQuotes #-}
import Language.Haskell.TH
import Language.Haskell.TH.Quote
import TH
[dd|input.hs|]
--------
-- input.hs
test = putStrLn "Hello."
--------
I also tried haskell-src-exts package, but this package seems to only parse and does not resolve identifiers and type checking. So I thought TH is a better choice.

import Language.Haskell.Exts.QQ
import Language.Haskell.TH.Quote
dd :: QuasiQuoter
dd = quoteFile dec

Related

DuplicateRecordFields causes Template Haskell 'Illegal variable name' error

I do not understand why the DuplicateRecordFields language pragma is causing a compile time error in a template haskell splice.
Example:
-- TypeModule.hs
{-# LANGUAGE DuplicateRecordFields #-}
module TypeModule where
data A = A {foo :: Int} deriving Show
-- ThModule.hs
{-# LANGUAGE TemplateHaskell #-}
module ThModule where
import Language.Haskell.TH
import Language.Haskell.TH.Syntax
import TypeModule
mkLambda :: Q [Dec]
mkLambda = [d| func :: A -> Int; func = foo |]
-- Lib.hs
module Lib where
import TypeModule
import ThModule
$mkLambda
{-
Illegal variable name: ‘$sel:foo:A’
When splicing a TH declaration: func_0 = (TypeModule.$sel:foo:A)
|
8 | $mkLambda
| ^^^^^^^^
-}
When I remove the DuplicateRecordFields pragma, the compile time error goes away.
I am using the DuplicateRecordFields pragma because I am parsing a number of different JSON objects which are responses from a REST API, and many of these JSON objects contain fields with identical names.
Right now I am looking for a way that does not use DuplicateRecordFields, but at the least I would like to understand what in particular is causing the compiler trouble.
This seems to be a known GHC issue: https://gitlab.haskell.org/ghc/ghc/-/issues/14848

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.

QuasiQuotes with OverloadedLabels

I'm trying to use OverloadedLabels with QuasiQuotes from here package. Using plain lenses works but #foo fails with parsing error during compilation. So does field #"foo". Is there a deeper reason this will not work or could it be be a bug in here's interpolated parser?
{-# language DataKinds #-}
{-# language DeriveGeneric #-}
{-# language DerivingStrategies #-}
{-# language OverloadedLabels #-}
{-# language OverloadedStrings #-}
{-# language TypeApplications #-}
{-# language QuasiQuotes #-}
import Control.Lens
import Data.Text (Text)
import qualified Data.Text.IO as T
import Data.Generics.Product
import Data.Generics.Labels
import Data.String.Here
import GHC.Generics (Generic)
data Test = Test
{ name :: Text
} deriving stock (Eq, Show, Generic)
_name :: Lens' Test Text
_name f (Test a) = fmap (\a' -> Test a') (f a)
t :: Test
t = Test "test"
test :: IO ()
test = do
-- ok
T.putStrLn $ t ^. field #"name"
T.putStrLn $ t ^. #name
putStrLn [i|${t ^. _name}|]
-- parse error
putStrLn [i|The name is ${t ^. field #"name"}|]
putStrLn [i|The name is ${t ^. #name}|]
Error for #name:
test.hs:36:12: error:
• Exception when trying to run compile-time code:
Failed to parse interpolated expression in string: The name is ${t ^. #name}
(line 1, column 25):
0
SrcLoc "" 1 6
Parse error in expression: t ^.
CallStack (from HasCallStack):
error, called at src/Data/String/Here/Interpolated.hs:64:33 in here-1.2.13-HU0AD0x0dD36rY9YuL1gwE:Data.String.Here.Interpolated
Code: Language.Haskell.TH.Quote.quoteExp
i "The name is ${t ^. #name}"
• In the quasi-quotation: [i|The name is ${t ^. #name}|]
|
36 | putStrLn [i|The name is ${t ^. #name}|]
|
It looks like haskell-src-meta doesn’t support OverloadedLabels yet. The haskell-src-exts parser has an OverloadedLabels case, but haskell-src-meta doesn’t have a case for it in the ToExp instance for Exp. I guess the “unsupported” error message from haskell-src-meta is getting swallowed by the error handling in here.
Just in case anyone else stumbles upon this, I've made a PR to fix this in haskell-src-meta: https://github.com/DanBurton/haskell-src-meta/pull/19

Haskell DeriveGeneric pragma not being recognized

I have the following at the top of my Haskell file:
{-# LANGUAGE DeriveGeneric, OverloadedStrings, DefaultSignatures, TypeOperators, FlexibleContexts, RecordWildCards, FlexibleInstances, ExtendedDefaultRules #-}
module Main where
import qualified Data.Map as Map
import qualified Data.Set as Set
import Data.Text (Text)
import Data.DateTime
import Data.Aeson
newtype Price = Price Float deriving Generic
However, when I run:
$ stack ghci
...
Prelude> :l myfile.hs
[1 of 1] Compiling Main ( myfile.hs, interpreted )
myfile.hs:13:38: error:
Not in scope: type constructor or class ‘Generic’
|
13 | newtype Price = Price Float deriving Generic
| ^^^^^^^
Failed, no modules loaded.
It seems to not accept my pragma for generics. What am I doing wrong? Looking at similar questions, this seems like it should work.
You also need to import GHC.Generics:
-- other imports
import GHC.Generics
-- more imports

Compile-time assertions with GHC Haskell?

Coming from C++, I'm used to be able to build simple forms of compile-time assertions, where I could emit warnings or errors during compilation if some simple conditions (e.g. over simple algebraic expressions) weren't met via use of template meta-programming and/or cpp(1)
For instance, if I wanted to make sure my program compiles only when Int has at least a certain minBound/maxBound range or alternatively, if a loss-free (as in reversible) conversion from Int64 to Int is possible with the current compilation target. Is this possible with some GHC Haskell extension? My first guess would have been to use TH. Are there other GHC facilities that could be exploited to this end?
Here's a generalized and slightly simplified version of Anthony's example:
{-# LANGUAGE TemplateHaskell #-}
module StaticAssert (staticAssert) where
import Control.Monad (unless)
import Language.Haskell.TH (report)
staticAssert cond mesg = do
unless cond $ report True $ "Compile time assertion failed: " ++ mesg
return [] -- No need to make a dummy declaration
Usage:
{-# LANGUAGE TemplateHaskell #-}
import StaticAssert
$(staticAssert False "Not enough waffles")
Using TH for this isn't too bad. Here is a module that defines the desired assertion as part of a vestigial declaration:
{-# LANGUAGE TemplateHaskell #-}
module CompileTimeWarning where
import Control.Monad (unless)
import Data.Int (Int64)
import Language.Haskell.TH
assertInt = let test = fromIntegral (maxBound::Int) == (maxBound::Int64)
in do unless test $ report True "Int is not safe!"
n <- newName "assertion"
e <- fmap NormalB [|()|]
return $ [FunD n [Clause [] e []]]
Using the assertion involves a top-level declaration that isn't used for anything other than the assertion:
{-# LANGUAGE TemplateHaskell #-}
import CompileTimeWarning
$(assertInt)

Resources