packages feed

Frames-streamly-0.3.1.0: src/Frames/Streamly/OrMissing.hs

{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeFamilies #-}
module Frames.Streamly.OrMissing
  (
    OrMissing(..)
  , toMaybe
  , toOrMissing
  , orMissing
  , derivingOrMissingUnboxVectorFor
  , derivingOrMissingUnboxVectorFor'
  -- * re-export derivingUnbox for ergonomics
  , derivingUnbox
    -- * re-exports for deriving
  , module Data.Vector.Unboxed -- for Vector
  , module Frames.Streamly.InCore -- for VectorFor
  )
where

import Frames.Streamly.InCore as FStreamly
import Frames.Streamly.InCore (VectorFor)

import Prelude hiding (Type, lift)
import qualified Data.Text as T
import Data.Vector.Unboxed.Deriving
import Data.Vector.Unboxed (Unbox)
import qualified Data.Vector as Vec
import qualified Data.Vector.Unboxed as UVec
import Data.Vector.Unboxed (Vector)
import Language.Haskell.TH

-- | Represent data that may be present or missing in a data-set, but is definitely of type @a@
-- This is isomorphic to Maybe but a different type is used to avoid orphan instances for
-- @Unbox@ and type instance @VectorFor@
data OrMissing a = Missing | Present a deriving (Show, Eq, Ord, Generic, Functor, Typeable)

toMaybe :: OrMissing a -> Maybe a
toMaybe Missing = Nothing
toMaybe (Present a) = Just a
{-# INLINE toMaybe #-}

toOrMissing :: Maybe a -> OrMissing a
toOrMissing Nothing = Missing
toOrMissing (Just a) = Present a
{-# INLINE toOrMissing #-}

orMissing :: a -> (b -> a) -> OrMissing b -> a
orMissing defA toA = \case
  Missing -> defA
  Present b -> toA b
{-# INLINE orMissing #-}

derivingUnbox
  "OrMissingInt"
  [t|OrMissing Int -> (Bool, Int)|]
  [e|orMissing (False, 0)(\x -> (True, x))|]
  [e|\(b, x) -> if b then Present x else Missing|]

derivingUnbox
  "OrMissingBool"
  [t|OrMissing Bool -> (Bool, Bool)|]
  [e|orMissing (False, False)(\x -> (True, x))|]
  [e|\(b, x) -> if b then Present x else Missing|]

derivingUnbox
  "OrMissingDouble"
  [t|OrMissing Double -> (Bool, Double)|]
  [e|orMissing (False, 0)(\x -> (True, x))|]
  [e|\(b, x) -> if b then Present x else Missing|]

type instance FStreamly.VectorFor (OrMissing Bool) = UVec.Vector
type instance FStreamly.VectorFor (OrMissing Int) = UVec.Vector
type instance FStreamly.VectorFor (OrMissing Double) = UVec.Vector
type instance FStreamly.VectorFor (OrMissing Text) = Vec.Vector


-- | Derive Unbox instance and the corresponding type family instance for
-- @VectorFor@ and @Unbox@.  Requires @VectorFor@ (from Frames)
-- and @Vector@ (from Data.Vector.Unbox)
-- to be in scope.
-- NB: Importing this module unqualified will put the correct things in scope.
-- NB: This may conflict with other vector imports which also use the @Vector@ name.
-- @Data a@ is required to avoid requiring @Lift a@ which causes stage-restriction issues.
derivingOrMissingUnboxVectorFor :: Text -- ^ Unique constructor suffix for the MVector and Vector data families.  Usually the type name.
                                -> ExpQ -- ^ TH expression for a default value of the type. E.g. @[e|MyType 0|]@
                                -> DecsQ -- ^ declarations for the @Unbox@ and @VectorFor@ instances
derivingOrMissingUnboxVectorFor name defAExpQ = do
  let nameQ = conT (mkName $ T.unpack name)
      typQ = [t|() => OrMissing $(nameQ) -> (Bool, $(nameQ))|]
--      anyAName = mkName "anyA" -- this name must match the argument.
      expNothingQ :: Q Exp = tupE [return $ ConE 'False, defAExpQ]
      srcToRepExpQ = lamE [] $ appE (appE [e|orMissing|] expNothingQ) [e|\x->(True,x)|]
      repToSrcExpQ = [e|\(b, x) -> if b then Present x else Missing|]
  unboxDecs <- derivingUnbox (T.unpack $ "OrMissing" <> name) typQ srcToRepExpQ repToSrcExpQ
  vectorDecs <- [d|type instance VectorFor (OrMissing $(nameQ)) = Vector|]
  return $ unboxDecs <> vectorDecs


-- | Derive @Unbox@ instance and the corresponding type family instance
-- @VectorFor@ for @a@ *and* @OrMissing a@.  Requires @VectorFor@ (from Frames)
-- and @Vector@ (from Data.Vector.Unbox)
-- to be in scope.
-- NB: Importing this module *unqualified* will put the correct things in scope.
-- NB: This may conflict with other vector imports which also use the @Vector@ name.
derivingOrMissingUnboxVectorFor' :: DecsQ
                                 -> Text -- ^ Unique constructor suffix for the MVector and Vector data families.  Must be the type name.
                                 -> ExpQ -- ^ TH expression for a default value of the type. E.g. @[e|EnumA|]@
                                 -> DecsQ -- ^ declarations for the @Unbox@ and @VectorFor@ instances
derivingOrMissingUnboxVectorFor' underlyingUnboxDecsQ name defAExpQ = do
  let nameQ = conT (mkName $ T.unpack name)
  underlyingUnboxDecs <-  underlyingUnboxDecsQ
  underlyingVectorDecs <- [d|type instance VectorFor $(nameQ) = Vector|]
  unboxAndVectorDecs <- derivingOrMissingUnboxVectorFor name defAExpQ
  return $ underlyingUnboxDecs <> underlyingVectorDecs <> unboxAndVectorDecs --unboxDecs <> vectorDecs