packages feed

sdp-0.2.1: src/SDP/Nullable.hs

{-# LANGUAGE CPP, MagicHash, PatternSynonyms, ViewPatterns, DefaultSignatures #-}
{-# LANGUAGE Trustworthy, ConstraintKinds #-}

#if __GLASGOW_HASKELL__ >= 806
{-# LANGUAGE QuantifiedConstraints, RankNTypes #-}
#endif

{- |
    Module      :  SDP.Nullable
    Copyright   :  (c) Andrey Mulik 2020-2021
    License     :  BSD-style
    Maintainer  :  work.a.mulik@gmail.com
    Portability :  non-portable (GHC extensions)
    
    "SDP.Nullable" provides 'Nullable' - class of types with empty values.
-}
module SDP.Nullable
(
  -- * Nullable
  Nullable (..), Nullable1, Nullable2,
  
#if __GLASGOW_HASKELL__ >= 806
  -- ** Rank 2 quantified constraints
  -- | GHC 8.6.1+ only
  Nullable', Nullable'',
#endif
  
  -- ** Patterns
  pattern NULL, pattern Z
)
where

import Foreign.Ptr

import GHC.ForeignPtr
import GHC.Stable
import GHC.Base
import GHC.Exts

import Control.Exception.SDP

default ()

--------------------------------------------------------------------------------

{- |
  'Nullable' is class of types which have empty values.
  
  Nullable instances must follow some rules:
  
  @
    isNull Z === True
    x == Z ==> isNull x == True
    x == y === isNull x == isNull y
    
    -- For 'Foldable' instances
    toList Z === []
    fold   Z === mempty
    isNull x === null x
    isNull x === length x == 0
    
    sum      Z === 0
    product  Z === 1
    elem   x Z === False
    foldr  f Z === foldl  f Z === id
    foldr1 f Z === foldl1 f Z === undefined
  @
-}
class Nullable e
  where
    -- | Empty value.
    lzero  :: e
    lzero  =  mempty
    default lzero :: (Monoid e) => e
    
    -- | Is value empty?
    isNull :: e -> Bool
    isNull =  (== lzero)
    default isNull :: (Eq e) => e -> Bool

--------------------------------------------------------------------------------

-- | Originally defined in @sdp-ctypes@ (now @sdp-foreign@), same as @Z@ now.
pattern NULL :: (Nullable e) => e
pattern NULL <- (isNull -> True) where NULL = lzero

{- |
  Other empty value pattern: @Z === NULL@.
  
  Defined in "SDP.Nullable" since @sdp-0.2.1@, earlier - in "SDP.Linear".
-}
pattern Z :: (Nullable e) => e
pattern Z =  NULL

--------------------------------------------------------------------------------

{- |
  @since 0.2.1
  'Nullable' contraint for @(Type -> Type)@-kind types.
-}
type Nullable1 rep e = Nullable (rep e)

{- |
  @since 0.2.1
  'Nullable' contraint for @(Type -> Type -> Type)@-kind types.
-}
type Nullable2 rep i e = Nullable (rep i e)

#if __GLASGOW_HASKELL__ >= 806
{- |
  @since 0.2.1
  'Nullable' contraint for @(Type -> Type)@-kind types.
-}
type Nullable' rep = forall e . Nullable (rep e)

{- |
  @since 0.2.1
  'Nullable' contraint for @(Type -> Type -> Type)@-kind types.
-}
type Nullable'' rep = forall i e . Nullable (rep i e)
#endif

--------------------------------------------------------------------------------

{-# COMPLETE Z,    Just #-}
{-# COMPLETE NULL, Just #-}

instance Nullable (Maybe e)
  where
    isNull = \ mx -> case mx of {Nothing -> True; _ -> False}
    lzero  = Nothing

{-# COMPLETE Z,    (:) #-}
{-# COMPLETE NULL, (:) #-}

instance Nullable [e] where isNull = null

instance Nullable (Ptr e) where lzero = nullPtr

-- Stolen from @bytestring@ package.
instance Nullable (ForeignPtr e)
  where
#if MIN_VERSION_base(4,15,0)
    lzero = ForeignPtr nullAddr# FinalPtr
#else
    lzero =
      let err = throw $ UnreachableException "in SDP.Nullable.lzero :: ForeignPtr e"
      in  ForeignPtr nullAddr# err
#endif
    
    isNull (ForeignPtr addr# _) = Ptr addr# == nullPtr

instance Nullable (StablePtr e) where lzero = StablePtr (unsafeCoerce# 0#)

-- | @since 0.2.1
instance Nullable (FunPtr e) where lzero = nullFunPtr