Data.Text.Text and quasiquoting - haskell

I have a parser which parses to an ast which contains Text values. I
am trying to use this parser with quasiquoting, but the implementation
of Data for Text is incomplete. I've attached a smallish test case, when I try to compile Text.hs I get:
Text.hs:17:9:
Exception when trying to run compile-time code:
Data.Text.Text.toConstr
Code: Language.Haskell.TH.Quote.quoteExp expr " test "
Is there a way to get this working?
I read through the discussion here: http://www.haskell.org/pipermail/haskell-cafe/2010-January/072379.html
It seems that no-one has found a proper solution to this issue? Also, I tried the Data instance given there and it didn't work, I have no idea how to fix it (or how to use it since the text package already has a Data instance for Text). I don't really understand a lot of the generics stuff and how it works.
The only solution I have so far is to give up using Text in the ast and go back to using String.
{-# LANGUAGE DeriveDataTypeable #-}
module Syntax where
import Data.Data
import Data.Text
data Expr = Iden Text
| Num Integer
| AntiIden Text
deriving (Eq,Show,Data,Typeable)
---------------------
module Parser where
import Control.Applicative
import Control.Monad.Identity
import qualified Data.Text as T
import Text.Parsec hiding (many, optional, (<|>), string, label)
import Text.Parsec.Language
import qualified Text.Parsec.Token as P
import Text.Parsec.Text ()
import Syntax
parseExpr :: T.Text -> Either ParseError Expr
parseExpr s =
runParser expr () "" s
expr :: ParsecT T.Text () Identity Expr
expr =
whiteSpace >> choice
[do
_ <- char '$'
AntiIden <$> identifier
,Num <$> natural
,Iden <$> identifier
]
identifier :: ParsecT T.Text () Identity T.Text
identifier = T.pack <$> P.identifier lexer
natural :: ParsecT T.Text () Identity Integer
natural = P.natural lexer
lexer :: P.GenTokenParser T.Text () Identity
lexer = P.makeTokenParser langDef
whiteSpace :: ParsecT T.Text () Identity ()
whiteSpace = P.whiteSpace lexer
langDef :: GenLanguageDef T.Text st Identity
langDef = P.LanguageDef
{ P.commentStart = "{-"
, P.commentEnd = "-}"
, P.commentLine = "--"
, P.nestedComments = True
, P.identStart = letter <|> char '_'
, P.identLetter = alphaNum <|> oneOf "_"
, P.opStart = P.opLetter langDef
, P.opLetter = oneOf "+-*/<>="
, P.reservedOpNames= []
, P.reservedNames = []
, P.caseSensitive = False
}
-------------------
module Quasi where
import Language.Haskell.TH.Quote
import Language.Haskell.TH
import Data.Generics
import qualified Data.Text as T
import Syntax
import Parser (parseExpr)
expr :: QuasiQuoter
expr = QuasiQuoter {quoteExp = prs
,quotePat = undefined
,quoteType = undefined
,quoteDec = undefined}
where
prs :: String -> Q Exp
prs s = p s
>>= dataToExpQ (const Nothing
`extQ` antiExpE
)
p s = either (fail . show) return (parseExpr $ T.pack s)
antiExpE :: Expr -> Maybe ExpQ
antiExpE v = fmap varE (antiExp v)
antiExp :: Expr -> Maybe Name
antiExp (AntiIden v) = Just $ mkName $ T.unpack v
antiExp _ = Nothing
----------------------------
-- test.hs:
{-# LANGUAGE QuasiQuotes #-}
import Syntax
import Quasi
test,test1,test2 :: Expr
-- works
test = [expr| 1234 |]
-- works
test1 = let stuff = Num 42
in [expr| $stuff |]
-- doesn't work
test2 = [expr| test |]
main :: IO ()
main = putStrLn $ show test2
Solution: add this function using extQ to the dataToExpQ call:
handleText :: T.Text -> Maybe ExpQ
handleText x =
-- convert the text to a string literal
-- and wrap it with T.pack
Just $ appE (varE 'T.pack) $ litE $ StringL $ T.unpack x

Add an extQ for handleText where handleText explicitly takes Text to an ExpQ, rather than going through generic machinery.
Here's one for Strings, for example, that renders them more efficiently than as explicit cons cells:
handleStr :: String -> Maybe (TH.ExpQ)
handleStr x = Just $ TH.litE $ TH.StringL x

Related

How to generate imports and boilerplate lists using Template Haskell?

I'd like to replace this boilerplate with code generation:
import qualified Y15.D01
import qualified Y15.D02
import qualified Y15.D03
import qualified Y15.D04
import qualified Y15.D05
import qualified Y15.D06HM
import qualified Y15.D06IO
import qualified Y15.D06ST
import qualified Y15.D07
import qualified Y15.D08
import qualified Y15.D09
import qualified Y15.D10
import qualified Y15.D11
import qualified Y15.D12
import qualified Y15.D13
...
days :: [(String, [String -> IO String])]
days =
[ ("Y15.D01", i2ios [Y15.D01.solve1, Y15.D01.solve2])
, ("Y15.D02", i2ios [Y15.D02.solve1, Y15.D02.solve2])
, ("Y15.D03", i2ios [Y15.D03.solve1, Y15.D03.solve2])
, ("Y15.D04", i2ios [Y15.D04.solve1, Y15.D04.solve2])
, ("Y15.D05", i2ios [Y15.D05.solve1, Y15.D05.solve2])
, ("Y15.D06HM",i2ios [Y15.D06HM.solve1, Y15.D06HM.solve2]) -- Data.Map.Strict
, ("Y15.D06IO",ioi2ios [Y15.D06IO.solve1, Y15.D06IO.solve2]) -- Data.Array.IO
, ("Y15.D06ST",i2ios [Y15.D06ST.solve1, Y15.D06ST.solve2]) -- Data.Array.ST
, ("Y15.D07", i2ios [Y15.D07.solve1, Y15.D07.solve2])
, ("Y15.D08", i2ios [Y15.D08.solve1, Y15.D08.solve2])
, ("Y15.D09", i2ios [Y15.D09.solve1, Y15.D09.solve2])
, ("Y15.D10", i2ios [Y15.D10.solve1, Y15.D10.solve2])
, ("Y15.D11", s2ios [Y15.D11.solve1, Y15.D11.solve2])
, ("Y15.D12", i2ios [Y15.D12.solve1, Y15.D12.solve2])
, ("Y15.D13", i2ios [Y15.D13.solve1, Y15.D13.solve2])
]
where s2ios :: [a -> b] -> [a -> IO b]
s2ios = fmap (return .)
i2ios :: [a -> Int] -> [a -> IO String]
i2ios = fmap ((return . show) .)
ioi2ios :: [a -> IO Int] -> [a -> IO String]
ioi2ios = fmap (fmap show .)
https://github.com/oshyshko/adventofcode/blob/master/src/Main.hs
I am new to Template Haskell and I would appreciate any help/suggestions on where to start with these questions:
How to list modules in a project that match /Y\d\d.D\d\d.*/ pattern?
How to generate imports for p.1?
How to retrieve types of solve1 and solve2 fns from a given module?
How to generate days list?
With respect to question (2), Template Haskell cannot generate import statements. You can see a very old feature request for it in the bug tracker on GitLab but no one's been sufficiently inspired to implement it.
With respect to question (3), if modules have been imported and their names are available as strings, you can use TH to retrieve the type of a binding in each module like so. Given:
-- M001.hs
module M001 where
solve1 :: Int
solve1 = 10
-- M002.hs
module M002 where
solve1 :: IO Int
solve1 = return 20
-- THTest1.hs
{-# LANGUAGE TemplateHaskell #-}
module THTest1 where
import M001
import M002
import Language.Haskell.TH
let
modules = ["M001", "M002"]
showType :: String -> Q ()
showType nm = do
Just n <- lookupValueName nm
VarI _ typ _ <- reify n
reportWarning $ show nm ++ " has type " ++ show typ
return ()
in do mapM_ showType (map (++ ".solve1") modules)
return []
Then compiling THTest.hs will generate two warnings:
warning: "M001.solve1" has type ConT GHC.Types.Int
warning: "M002.solve1" has type AppT (ConT GHC.Types.IO)
(ConT GHC.Types.Int)
For question (4), here's a simplified example using modules M001 and M002 as defined above. Compile this program with ghc -ddump-splices to see the definition generated for days:
-- THTest2.hs
{-# LANGUAGE TemplateHaskell #-}
import M001
import M002
import Control.Monad
import GHC.Types
import Language.Haskell.TH
let
-- list of modules to search
modules = ["M001", "M002"]
-- assoc list of adapter function by argument type
funcs = [(ConT ''Int, 'return), (AppT (ConT ''IO) (ConT ''Int), 'id)]
getDay :: String -> Q Exp
getDay modname = do
-- look up name (e.g., M001.solve1)
Just n <- lookupValueName (modname ++ ".solve1")
-- get type of binding
VarI _ typ _ <- reify n
-- look up appropriate adapter function
let Just f = lookup typ funcs
-- ("M001", adapter_f M001.solve1)
[|($(pure $ LitE (StringL modname)),
$(pure $ AppE (VarE f) (VarE n)))|]
makeDays :: Q [Dec]
makeDays = do
[d| days :: [(String, IO Int)]
days = $(ListE <$> mapM getDay modules)
|]
in makeDays
main = do
forM days $ \(modname, action) -> do
putStr modname
putStr ": "
print =<< action
Then running it will output:
M001: 10
M002: 20

Attoparsec - ensure entire contents consumed with sepBy1

I would like the below code to return [LoadInt 1,LoadDub 2.5,LoadInt 3], but it fails after parsing [LoadInt 1,LoadDub 2] and facing .5,3. How do I make it so it must parse all the way to the comma for a parse to succeed, and an int parse on 2.5 is a fail?
import qualified Data.Attoparsec.ByteString.Char8 as A
import Data.Attoparsec.ByteString.Char8 (Parser)
import Data.ByteString.Char8 (pack)
import Data.Attoparsec.Combinator
import Control.Applicative ((*>),(<$>),(<|>))
data LoadNum = LoadInt Int | LoadDub Double deriving (Show)
someFunc :: IO ()
someFunc = putStrLn . show $ A.parseOnly (lnParser <* A.endOfInput) (pack testString)
testString :: String
testString = "1,2.5,3"
lnParser :: Parser [LoadNum]
lnParser = (sepBy1' (ld <* A.atEnd) (A.char ','))
double :: Parser Double
double = A.double
int :: Parser Int
int = A.signed A.decimal
ld :: Parser LoadNum
ld = ((LoadInt <$> int ) <|> (LoadDub <$> double))
You could use a tiny bit of lookahead to decide whether you reached the end of a list element. So:
int :: Parser Int
int = do
i <- A.signed A.decimal
next <- A.peekChar
case next of
Nothing -> pure i
Just ',' -> pure i
_ -> fail "nah"

How can I write a pattern quasi quoter in Haskell?

I use quasi quoters to create my smart-constructed data types at compile time. This looks something like:
import qualified Data.Text as T
import Language.Haskell.TH.Quote (QuasiQuoter(..))
import Language.Haskell.TH (Q, Exp, Pat(..), Lit(..))
import Language.Haskell.TH.Syntax (Lift(..))
import qualified Language.Haskell.TH.Syntax as TH
import Instances.TH.Lift () -- th-lift-instances package
newtype NonEmptyText = NonEmptyText Text
textIsWhitespace :: Text -> Bool
textIsWhitespace = T.all (== ' ')
mkNonEmptyText :: Text -> Maybe NonEmptyText
mkNonEmptyText t = if textIsWhitespace t then Nothing else (Just (NonEmptyText t))
compileNonEmptyText :: QuasiQuoter
compileNonEmptyText = QuasiQuoter
{ quoteExp = compileNonEmptyText'
, quotePat = error "NonEmptyText is not supported as a pattern"
, quoteDec = error "NonEmptyText is not supported at top-level"
, quoteType = error "NonEmptyText is not supported as a type"
}
where
compileNonEmptyText' :: String -> Q Exp
compileNonEmptyText' s = case mkNonEmptyText (pack s) of
Nothing -> fail $ "Invalid NonEmptyText: " ++ s
Just txt -> [| txt |]
(I can provide a standalone working example if necessary—I just pulled this example out of a larger codebase)
Essentially, by just deriving Lift for my newtypes, I can place the data type in an expression quasi quoter [| txt |] to implement quoteExp.
But I'm having trouble with quotePat. If I do e.g.:
Just txt -> [p| txt |]
Then I get a warning that the first txt is unused, and the second shadows the first. I'm pretty sure that that pattern is just creating a new name txt rather than splicing in the in-scope txt like the expression quasi quoter did, since when I do:
f :: NonEmptyText -> Bool
f [compileNonEmptyText|test|] = True
f _ = False
everything matches the first statement.
Alright I think I've got it. Starting from the base string s, I can wrap that in StringL and LitP to get a literal string, which because of Text's IsString instance will become a Text. From there I need to apply the NonEmptyText constructor using ConP:
compileNonEmptyTextPattern' :: String -> Q TH.Pat
compileNonEmptyTextPattern' s = case mkNonEmptyText (pack s) of
Nothing -> fail $ "Invalid NonEmptyText: " ++ s
Just (NonEmptyText txt) -> pure $ ConP 'NonEmptyText [(LitP (StringL (T.unpack txt)))]
It's unfortunate that this is so much more verbose than the expression version, though! I wonder if there could be a typeclass for Q Pat like Lift is for Q Exp?

Monad escaping inside a StateT context

I am trying to get back a value that is in a json feed (via Aeson) directly inside a StateT stacked on IO:
{-# LANGUAGE DeriveGeneric #-}
module MyFeed where
import Data.Aeson
import Network.URI (parseURI, URI(..))
import Data.Maybe (fromJust)
import Data.Text (Text, unpack)
import Control.Monad.State
import Network.HTTP
import GHC.Generics
import Control.Applicative
import Network.HTTP.Conduit (simpleHttp)
import qualified Data.ByteString.Lazy as B
type Feed a = StateT MyIndex IO a
data MyIndex = MyIndex {
index :: Int
}
data FooBar = Foo | Bar
data MyFeed = MyFeed {
idx :: !Text,
key :: !Text
} deriving (Show,Generic)
instance FromJSON MyFeed
instance ToJSON MyFeed
getJSON :: String -> IO B.ByteString
getJSON url = simpleHttp url
getFeed :: String -> IO (Maybe MyFeed)
getFeed url = (decode <$> getJSON url) :: IO (Maybe MyFeed)
getIndex :: FooBar -> Feed MyIndex
getIndex fb = do
cursor <- get
let newCursor = case fb of
Foo -> do myFeed <- liftIO $ getFeed "http://echo.jsontest.com/key/value/idx/1"
let i = read $ unpack $ idx $ fromJust myFeed
return $ cursor { index = i }
Bar -> return cursor
put newCursor
return newCursor
In the Foo case I fetch the feed as expected but when the required value is returned I get:
src/MyFeed.hs:47:10:
Couldn't match expected type ‘MyIndex’
with actual type ‘m0 MyIndex’
Relevant bindings include
newCursor :: m0 MyIndex (bound at src/MyFeed.hs:40:7)
In the first argument of ‘return’, namely ‘newCursor’
In a stmt of a 'do' block: return newCursor
The Actual Type looks still in a Monad context (do {...}). Is there a way to take it out or I am using a wrong approach?
The error was due to the fact I used:
let newCursor = case fb of
instead of
newCursor <- case fb of
For this reason the final value never get "unwrapped" from its monad context.

how to parse yahoo csv with parsec

how to parse into array such as open[i],high[i],low[i],close[i]
testhaskell.hs:22:5:
Couldn't match type `[]' with `IO'
Expected type: IO a0
Actual type: [a0]
In the return type of a call of `map'
In a stmt of a 'do' block: map (\ line -> sentence line) allLines
In the expression:
do { handle <- openFile
"C:\\Users\\ivan\\Downloads\\0388.HK.csv" ReadMode;
contents <- hGetContents handle;
let allLines = lines contents;
map (\ line -> sentence line) allLines;
.... }
testhaskell.hs:22:19:
Couldn't match expected type `String -> a0'
with actual type `Text.Parsec.Prim.ParsecT
String () Data.Functor.Identity.Identity [String]'
import System.IO
import qualified Data.ByteString.Char8 as BS
import qualified Data.ByteString as Str
import Text.ParserCombinators.Parsec
word :: Parser String
word = many1 letter
sentence :: Parser [String]
sentence = do{ words <- sepBy1 word separator
; oneOf ".?!" <?> "end of sentence"
; return words
}
separator :: Parser ()
separator = skipMany1 (space <|> char ',' <?> "")
main = do
handle <- openFile "C:\\Users\\ivan\\Downloads\\0005.HK.csv" ReadMode
contents <- hGetContents handle
let allLines = lines contents
map (\line -> sentence line) allLines
--putStr contents
hClose handle
update:
module Main where
import qualified Data.ByteString.Char8 as B
import Data.Map ((!))
import Data.Text
import qualified Data.Vector as V
import System.Directory
import Test.Framework (Test, defaultMain, testGroup)
import Test.Framework.Providers.API
import Test.HUnit ((#=?))
import Data.CSV.Conduit
main :: IO ()
main = defaultMain tests
tests :: [Test]
tests = [testGroup "Basic Ops" baseTests]
baseTests :: [Test]
baseTests =
[
testCase "simple parsing works" test_simpleParse
]
test_simpleParse :: IO ()
test_simpleParse = do
(d :: V.Vector (MapRow B.ByteString)) <- readCSVFile csvSettings testFile1
V.mapM_ assertRow d
where
assertRow r = v3 #=? (v1 + v2)
where v1 = readBS $ r ! "Open"
v2 = readBS $ r ! "High"
v3 = readBS $ r ! "Low"
v4 = readBS $ r ! "Close"
csvSettings :: CSVSettings
csvSettings = defCSVSettings { csvQuoteChar = Just '`'}
testFile1 :: FilePath
testFile1 = "C:\\Users\\ivan\\Downloads\\0005.HK.csv"
readBS :: B.ByteString -> Int
readBS = read . B.unpack
testhaskell.hs:52:5: Not in scope: `testCase'
testhaskell.hs:58:9:
Illegal type signature: `V.Vector (MapRow B.ByteString)'
Perhaps you intended to use -XScopedTypeVariables
In a pattern type-signature
I'd strongly recommend you not do this. There are a number of high-quality CSV libraries on Hackage, and rolling your own is a recipe of problems. At FP Complete, we use csv-conduit, though cassava is also a great library. I'd recommend you try out one of them.

Resources