My aim is to write an RSS parser that can handle extensions in a generic and type-safe way. I already have a working RSS parser, the missing part is the extensions handling, which is the focus of this review.

By extension, I mean extra XML elements that aren't part of the RSS specification, usually in a dedicated namespace like the Dublin Core Metadata Set.

I'm interested in a type-safe implementation that doesn't sacrifice usability/readability too much. Performance isn't a concern for now, correctness prevails.

Current implementation

{-# LANGUAGE DataKinds           #-}
{-# LANGUAGE FlexibleContexts    #-}
{-# LANGUAGE FlexibleInstances   #-}
{-# LANGUAGE RankNTypes          #-}
{-# LANGUAGE TypeFamilies        #-}
{-# LANGUAGE TypeOperators       #-}
import           Control.Exception.Safe        -- from 'safe-exceptions' library
import           Data.Conduit                  -- from 'conduit' library
import           Data.Singletons.Prelude.List  -- from 'singletons' library
import           Data.Extensible.Class         -- from 'extensible' library
import           Data.Extensible.Product       -- from 'extensible' library
import           Data.XML.Types                -- from 'xml-types' library

-- Structure for RSS item
-- In place of 'e', I will be using a list of proxy types,
-- in other words: 'e :: [*]'
data RssItem e = RssItem
  { someField1 :: Text
  , someField2 :: Text
  -- (...)
  , itemExtensions :: RssItemExtension e

-- Open type family to associate proxy types to data types, for extensions
data family RssItemExtension :: * -> *
data instance RssItemExtension () = NoRssItemExtension
data instance RssItemExtension (Sing (a :: [*])) = SomeRssItemExtension { rssItemExtension :: RssItemExtension :* a }

-- Typeclass to associate a parsing function to an extension proxy type
class ParseRssExtension a where
  parseRssItemExtension :: MonadCatch m => ConduitM Event o m (RssItemExtension a)

instance ParseRssExtension () where
  parseRssItemExtension = pure NoRssItemExtension

instance ParseRssExtension (Sing ('[] :: [*])) where
  parseRssItemExtension = pure (SomeRssItemExtension nil)

instance (ParseRssExtension a, ParseRssExtension (Sing b)) => ParseRssExtension (Sing (a ': b)) where
  parseRssItemExtension = fmap SomeRssItemExtension $ getZipConduit $ (<:)
    <$> ZipConduit parseRssItemExtension
    <*> ZipConduit (rssItemExtension <$> parseRssItemExtension)

Example usage

Below is an example implementation of Dublin Core Metadata Set extension:

-- Proxy type to represent the extension
data DublinCore :: *

-- Associate proxy type to data type
data instance RssItemExtension DublinCore = DublinCoreItem { {- (...) -} }

-- Associate parser to proxy type
instance ParseRssExtension DublinCore where
  parseRssItemExtension    = _ -- Some function to parse a DublinCoreItem

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.