packages feed

large-records-0.1.0.0: src/Data/Record/QQ/Runtime/MatchHasField.hs

{-# LANGUAGE DataKinds              #-}
{-# LANGUAGE FlexibleContexts       #-}
{-# LANGUAGE FlexibleInstances      #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE KindSignatures         #-}
{-# LANGUAGE MultiParamTypeClasses  #-}
{-# LANGUAGE ScopedTypeVariables    #-}
{-# LANGUAGE TypeApplications       #-}
{-# LANGUAGE UndecidableInstances   #-}
{-# LANGUAGE ViewPatterns           #-}

-- | Infrastructure for supporting matching on records
--
-- We are be careful not to reintroduce quadratic code size here.
module Data.Record.QQ.Runtime.MatchHasField (
    MatchHasField -- opaque
  , matchHasField
  , fieldNamed
  , viewAtType
  ) where

import Data.Kind
import GHC.Records.Compat
import GHC.TypeLits

{-------------------------------------------------------------------------------
  Infrastructure
-------------------------------------------------------------------------------}

-- | Pattern match on 'HasField'
--
-- This is intended to be used together with 'matchHasField'. Example usage:
--
-- > data Foo a
-- >
-- > instance HasField "fooX" (Foo a) Int where ..
-- > instance HasField "fooY" (Foo a) [a] where ..
-- >
-- > _example :: Foo Char -> (Int, [Char])
-- > _example (matchHasField -> ( fieldNamed @"fooX" -> x
-- >                            , fieldNamed @"fooY" -> y
-- >                            ) ) = (x, y)
class MatchHasField a b | b -> a where
  matchHasField :: a -> b

-- | To be used in conjunction with 'MatchHasField'.
--
-- See 'MatchHasField' for details.
fieldNamed :: GetField x r a -> a
fieldNamed (GetField a) = a

data GetField (x :: Symbol) (r :: Type) (a :: Type) = GetField a

instance HasField x r a => MatchHasField r (GetField x r a) where
  matchHasField = GetField . (getField @x)

instance (MatchHasField a b, MatchHasField a c) => MatchHasField a (b, c) where
  matchHasField r = (matchHasField r, matchHasField r)

-- | Can be used alongside 'matchHasField' to fix the type of the argument
--
-- This avoids inferring types in terms of @HasField ..@; see example below.
viewAtType :: a -> a -> a
viewAtType = const id

{-------------------------------------------------------------------------------
  Example
-------------------------------------------------------------------------------}

data Foo a = MkFoo

instance HasField "fooX" (Foo a) Int where hasField = undefined
instance HasField "fooY" (Foo a) [a] where hasField = undefined

_example1 :: (HasField "fooX" a b, HasField "fooY" a c) => a -> (b, c)
_example1 (matchHasField -> ( fieldNamed @"fooX" -> x
                            , fieldNamed @"fooY" -> y
                            ) ) = (x, y)

_example2 :: Foo a -> (Int, [a]) -- This is the inferred signature
_example2 (viewAtType MkFoo -> matchHasField -> ( fieldNamed @"fooX" -> x
                                                , fieldNamed @"fooY" -> y
                                                ) ) = (x, y)