packages feed

morley-1.16.4: src/Morley/Util/Named.hs

-- SPDX-FileCopyrightText: 2021 Oxhead Alpha
-- SPDX-License-Identifier: LicenseRef-MIT-OA

{-# OPTIONS_GHC -Wno-orphans #-}

-- | Additional functionality for @named@ package.
module Morley.Util.Named
  ( (!)
  , (:!)
  , (:?)
  , (<:!>)
  , (<:?>)
  , ApplyNamedFunctor
  , NamedInner
  , KnownNamedFunctor (..)
  , NamedF (.., (:!), (:?), N, M, SomeArg, NoArg)
  , Name
  , arg
  , argF
  , argDef
  ) where


import Control.Lens (Iso', Wrapped(..), iso)
import Data.Aeson (FromJSON, ToJSON)
import Data.Data (Data)
import Fmt (Buildable(..))
import GHC.TypeLits (KnownSymbol, symbolVal)
import Named (Name(..), NamedF(..), arg, argDef, argF, (!), (:!), (:?))
import Text.Show qualified as T

import Morley.Util.Label (Label)

{- | Convenience pattern synonym. Use to construct a named value.

To pattern-match on a named value, use 'arg' in a view pattern:

>>> :{
let someFunc (arg #arg1 -> arg1) (arg #arg2 -> arg2) = arg1 + arg2
in someFunc ! #arg1 1 ! #arg2 2
:}
3
-}
pattern (:!) :: Name name -> a -> NamedF Identity a name
pattern (:!) n v <- ((Name,) -> (n, ArgF (Identity v)))
  where (:!) _ v = ArgF (Identity v)
{-# COMPLETE (:!) #-}

{- | Convenience pattern synonym. Use to construct an optional named value.

To pattern-match on an optional named value, use 'argDef' or 'argF' in a view
pattern:

>>> :{
let someFunc (argDef #arg1 "" ->arg1) (argDef #arg2 "" -> arg2) = arg1 <> arg2
in someFunc ! #arg1 "asd" ! #arg2 "efg"
:}
"asdefg"
-}
pattern (:?) :: Name name -> Maybe a -> NamedF Maybe a name
pattern (:?) n v <- ((Name,) -> (n, ArgF v))
  where (:?) _ v = ArgF v
{-# COMPLETE (:?) #-}

-- | Convenience pattern synonym, use this instead of 'Named.arg' with @ViewPatterns@
-- when the @name@ can be inferred.
pattern N :: a -> NamedF Identity a name
pattern N a = ArgF (Identity a)
{-# COMPLETE N #-}
{-# DEPRECATED N "Please use view patterns with 'arg' instead." #-}

-- | Convenience pattern synonym, use this instead of 'Named.argF' with @ViewPatterns@
-- when the @name@ can be inferred. Matches only on @Just@ values
pattern SomeArg :: a -> NamedF Maybe a name
pattern SomeArg a = ArgF (Just a)

-- | Convenience pattern synonym, use this instead of 'Named.argF' with @ViewPatterns@
-- when the @name@ can be inferred. Matches only on @Nothing@ values
pattern NoArg :: NamedF Maybe a name
pattern NoArg = ArgF Nothing
{-# COMPLETE NoArg, SomeArg #-}

-- | Convenience pattern synonym for @NamedF Maybe name a@, use this instead of 'Named.argF' with @ViewPatterns@
-- when the @name@ can be inferred.
pattern M :: Maybe a -> NamedF Maybe a name
pattern M a = ArgF a
{-# COMPLETE M #-}
{-# DEPRECATED M, SomeArg, NoArg "Please use view patterns with 'argF' instead." #-}

-- | Special version of 'Morley.Util.Named.(:!)' for monadic operations
(<:!>) :: Functor m => Name name -> m a -> m (NamedF Identity a name)
(<:!>) name = fmap (name :!)
infixl 4 <:!>

-- | Special version of 'Morley.Util.Named.(:?)' for monadic operations
(<:?>) :: Functor m => Name name -> m (Maybe a) -> m (NamedF Maybe a name)
(<:?>) name = fmap (name :?)
infixl 4 <:?>

type family ApplyNamedFunctor (f :: Type -> Type) (a :: Type) where
  ApplyNamedFunctor Identity a = a
  ApplyNamedFunctor Maybe a = Maybe a

type family NamedInner (n :: Type) where
  NamedInner (NamedF f a _) = ApplyNamedFunctor f a

-- | Isomorphism between named entity and the entity itself wrapped into the
-- respective functor.
namedFL :: Label name -> Iso' (NamedF f a name) (f a)
namedFL _ = iso (\(ArgF x) -> x) ArgF

class KnownNamedFunctor f where
  -- | Isomorphism between named entity and the entity itself.
  namedL :: Label name -> Iso' (NamedF f a name) (ApplyNamedFunctor f a)

instance KnownNamedFunctor Identity where
  namedL l = namedFL l . _Wrapped'

instance KnownNamedFunctor Maybe where
  namedL l = namedFL l

----------------------------------------------------------------------------
-- Instances
----------------------------------------------------------------------------

deriving stock instance Eq (f a) => Eq (NamedF f a name)
deriving stock instance Ord (f a) => Ord (NamedF f a name)

instance (Show a, KnownSymbol name) => Show (NamedF Identity a name) where
  showsPrec d (ArgF (Identity a)) = T.showParen (d > app_prec) $
    T.showString "fromLabel @" . T.shows (symbolVal (Proxy @name)) .
    T.showString " :! " . T.showsPrec (bang_prec+1) a
    where
      app_prec = 10
      -- precedence of (:!)
      bang_prec = 9

instance (KnownSymbol name, Buildable (f a)) => Buildable (NamedF f a name) where
  build (ArgF a) = build (symbolVal (Proxy @name)) <> ": " <> build a

instance (NFData (f a)) => NFData (NamedF f a name) where
  rnf (ArgF a) = rnf a

deriving stock instance
  (Typeable f, Typeable a, KnownSymbol name, Data (f a)) =>
  Data (NamedF f a name)

deriving newtype instance ToJSON a => ToJSON (NamedF Identity a name)
deriving newtype instance ToJSON a => ToJSON (NamedF Maybe a name)
deriving newtype instance FromJSON a => FromJSON (NamedF Identity a name)
deriving newtype instance FromJSON a => FromJSON (NamedF Maybe a name)