3

With IHP (the haskell web framework) I created a web application. Now I want to create a IHP Script to load some external data into my database. However I'm getting a lot of import conflicts from the Prelude, but not the types I expected.

#!/usr/bin/env run-script
module Application.Script.DataLoader where

import Application.Script.Prelude      hiding (decode, pack, (.:))
import qualified Data.ByteString.Lazy  as     BL
import Data.Csv
import Data.Text                              (pack)
import qualified Data.Vector           as     V
import Control.Monad                          (mzero)

instance FromNamedRecord Product where
    parseNamedRecord r = Product def <$> r .: "title" <*> r .: "price" <*> r .: "category" <*> pure def

run :: Script
run = do
    csvData <- BL.readFile "~/tender/data/Boiler-en-kookkraan_Boiler.csv"
    case decodeByName csvData of
        Left err -> putStrLn $ pack err
        Right (_, v) -> V.forM_ v $ \ p ->
            putStrLn $ (get #title p) ++ ", " ++ show (get #price p) ++ " euro"

Where my Product schema looks like this:

CREATE TABLE products (
    id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
    title TEXT NOT NULL,
    price DOUBLE PRECISION NOT NULL,
    category TEXT NOT NULL
);

Is there a way to use the types I created as a Data object to e.g. read my csv to?

[Updated output]

Application/Script/DataLoader.hs:12:26: error:
    • Couldn't match type ‘MetaBag -> Product' a1’
                     with ‘Product' (QueryBuilder ProjectProduct)’
      Expected type: Parser Product
        Actual type: Parser (MetaBag -> Product' a1)
    • In the expression:
        Product def <$> r .: "title" <*> r .: "price" <*> r .: "category"
          <*> pure def
      In an equation for ‘parseNamedRecord’:
          parseNamedRecord r
            = Product def <$> r .: "title" <*> r .: "price" <*> r .: "category"
                <*> pure def
      In the instance declaration for ‘FromNamedRecord Product’
   |
12 |     parseNamedRecord r = Product def <$> r .: "title" <*> r .: "price" <*> r .: "category" <*> pure def
   |                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

[Solved, all credits to the help of @mpscholten]

#!/usr/bin/env run-script
module Application.Script.DataLoader where

import Application.Script.Prelude      hiding (decode, pack, (.:))
import qualified Data.ByteString.Lazy  as     BL
import Data.Csv
import Data.Text                              (pack)
import qualified Data.Vector           as     V
import Control.Monad                          (mzero)

parseProduct :: NamedRecord -> Parser Product
parseProduct r = do
  title <- r .: "title"
  price <- r .: "price"
  category <- r .: "category"

  newRecord @Product
    |> set #title title
    |> set #price price
    |> set #category category
    |> pure

run :: Script
run = do
    csvData <- BL.readFile "data/Boiler-en-kookkraan_Boiler.csv"
    case decodeByNameWithP parseProduct defaultDecodeOptions csvData of
        Left err -> putStrLn $ pack err
        Right (_, v) -> V.forM_ v $ \ p ->
            putStrLn $ (get #title p) ++ ", " ++ show (get #price p) ++ " euro"
Tom Hemmes
  • 2,000
  • 2
  • 17
  • 23

2 Answers2

3

Can you maybe share what errors you get exactly? Your desired product type should be in Generated.Types which intern is loaded by Application.Script.Prelude.

I think you might have two models that both have the field title. In haskell fields are functions and they may not be used twice.

3

Inside the FromNamedRecord instance you are missing two fields: id and meta. The id field is the first field of the record. The meta field is a hidden field used by IHP to keep track of validation errors. It's always the last field of a record.

The easiest way to solve this is to use newRecord and write out the code in a more explicit way:

instance FromNamedRecord Product where
    parseNamedRecord r = do
        title <- r .: "title"
        price <- r .: "price"
        category <- r .: "category"

        newRecord @Product
            |> set #title title
            |> set #price price
            |> set #category category
            |> pure

For the error "Ambiguous occurrence ‘title’" try to use the get function instead of using the normal haskell accessor function:

putStrLn $ (get #title p) ++ ", " ++ show (get #price p) ++ " euro"
Marc Scholten
  • 1,351
  • 3
  • 5