    {-# LANGUAGE DataKinds #-}
    {-# LANGUAGE DefaultSignatures #-}
    {-# LANGUAGE FlexibleInstances #-}
    {-# LANGUAGE GADTs #-}
    {-# LANGUAGE InstanceSigs #-}
    {-# LANGUAGE PolyKinds #-}
    {-# LANGUAGE ScopedTypeVariables #-}
    {-# LANGUAGE StandaloneDeriving #-}
    {-# LANGUAGE TemplateHaskell #-}
    {-# LANGUAGE TypeApplications #-}
    {-# LANGUAGE TypeFamilies #-}
    {-# LANGUAGE TypeOperators #-}
    {-# LANGUAGE UndecidableInstances #-}
    {-# OPTIONS_GHC -Wno-orphans #-}

    -----------------------------------------------------------------------------
    -- |
    -- Module      :  Data.Singletons.Prelude.Functor
    -- Copyright   :  (C) 2018 Ryan Scott
    -- License     :  BSD-style (see LICENSE)
    -- Maintainer  :  Ryan Scott
    -- Stability   :  experimental
    -- Portability :  non-portable
    --
    -- Defines the promoted and singled versions of the 'Functor' type class.
    --
    ----------------------------------------------------------------------------

    module Data.Singletons.Prelude.Functor (
      PFunctor(..), SFunctor(..),
      type ($>),  (%$>),
      type (<$>), (%<$>),
      type (<&>), (%<&>),
      Void, sVoid,

      -- * Defunctionalization symbols
      FmapSym0, FmapSym1, FmapSym2,
      type (<$@#@$),  type (<$@#@$$),  type (<$@#@$$$),
      type ($>@#@$),  type ($>@#@$$),  type ($>@#@$$$),
      type (<$>@#@$), type (<$>@#@$$), type (<$>@#@$$$),
      type (<&>@#@$), type (<&>@#@$$), type (<&>@#@$$$),
      VoidSym0, VoidSym1
      ) where

    import Data.Ord (Down(..))
    import Data.Singletons.Prelude.Base
    import Data.Singletons.Prelude.Instances
    import Data.Singletons.Prelude.Monad.Internal
    import Data.Singletons.Prelude.Ord
    import Data.Singletons.Single

    $(singletonsOnly [d|
      infixl 4 <$>

      -- -| An infix synonym for 'fmap'.
      --
      -- The name of this operator is an allusion to '$'.
      -- Note the similarities between their types:
      --
      -- >  ($)  ::              (a -> b) ->   a ->   b
      -- > (<$>) :: Functor f => (a -> b) -> f a -> f b
      --
      -- Whereas '$' is function application, '<$>' is function
      -- application lifted over a 'Functor'.
      --
      -- ==== __Examples__
      --
      -- Convert from a @'Maybe' 'Int'@ to a @'Maybe' 'String'@ using 'show':
      --
      -- >>> show <$> Nothing
      -- Nothing
      -- >>> show <$> Just 3
      -- Just "3"
      --
      -- Convert from an @'Either' 'Int' 'Int'@ to an @'Either' 'Int'@
      -- 'String' using 'show':
      --
      -- >>> show <$> Left 17
      -- Left 17
      -- >>> show <$> Right 17
      -- Right "17"
      --
      -- Double each element of a list:
      --
      -- >>> (*2) <$> [1,2,3]
      -- [2,4,6]
      --
      -- Apply 'even' to the second element of a pair:
      --
      -- >>> even <$> (2,2)
      -- (2,True)
      --
      (<$>) :: Functor f => (a -> b) -> f a -> f b
      (<$>) = fmap

      infixl 4 $>

      -- -| Flipped version of '<$>'.
      --
      -- @
      -- ('<&>') = 'flip' 'fmap'
      -- @
      --
      -- @since 4.11.0.0
      --
      -- ==== __Examples__
      -- Apply @(+1)@ to a list, a 'Data.Maybe.Just' and a 'Data.Either.Right':
      --
      -- >>> Just 2 <&> (+1)
      -- Just 3
      --
      -- >>> [1,2,3] <&> (+1)
      -- [2,3,4]
      --
      -- >>> Right 3 <&> (+1)
      -- Right 4
      --
      (<&>) :: Functor f => f a -> (a -> b) -> f b
      as <&> f = f <$> as

      infixl 1 <&>

      -- -| Flipped version of '<$'.
      --
      -- @since 4.7.0.0
      --
      -- ==== __Examples__
      --
      -- Replace the contents of a @'Maybe' 'Int'@ with a constant 'String':
      --
      -- >>> Nothing $> "foo"
      -- Nothing
      -- >>> Just 90210 $> "foo"
      -- Just "foo"
      --
      -- Replace the contents of an @'Either' 'Int' 'Int'@ with a constant
      -- 'String', resulting in an @'Either' 'Int' 'String'@:
      --
      -- >>> Left 8675309 $> "foo"
      -- Left 8675309
      -- >>> Right 8675309 $> "foo"
      -- Right "foo"
      --
      -- Replace each element of a list with a constant 'String':
      --
      -- >>> [1,2,3] $> "foo"
      -- ["foo","foo","foo"]
      --
      -- Replace the second element of a pair with a constant 'String':
      --
      -- >>> (1,2) $> "foo"
      -- (1,"foo")
      --
      ($>) :: Functor f => f a -> b -> f b
      ($>) = flip (<$)

      -- -| @'void' value@ discards or ignores the result of evaluation, such
      -- as the return value of an 'System.IO.IO' action.
      --
      -- ==== __Examples__
      --
      -- Replace the contents of a @'Maybe' 'Int'@ with unit:
      --
      -- >>> void Nothing
      -- Nothing
      -- >>> void (Just 3)
      -- Just ()
      --
      -- Replace the contents of an @'Either' 'Int' 'Int'@ with unit,
      -- resulting in an @'Either' 'Int' '()'@:
      --
      -- >>> void (Left 8675309)
      -- Left 8675309
      -- >>> void (Right 8675309)
      -- Right ()
      --
      -- Replace every element of a list with unit:
      --
      -- >>> void [1,2,3]
      -- [(),(),()]
      --
      -- Replace the second element of a pair with unit:
      --
      -- >>> void (1,2)
      -- (1,())
      --
      -- Discard the result of an 'System.IO.IO' action:
      --
      -- >>> mapM print [1,2]
      -- 1
      -- 2
      -- [(),()]
      -- >>> void $ mapM print [1,2]
      -- 1
      -- 2
      --
      void :: Functor f => f a -> f ()
      void x = () <$ x

      deriving instance Functor ((,) a)
      deriving instance Functor Down
      |])

    -- Workaround for #326
    infixl 4 <$>
    infixl 4 $>
    infixl 1 <&>