packages feed

polysemy-socket-0.0.1.0: src/Polysemy/Socket.hs

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeOperators #-}
{-# OPTIONS_GHC -fplugin=Polysemy.Plugin #-}

-- |
--   Module     : Polysemy.Socket
--   License    : MIT
--   Stability  : experimental
--
-- Socket effect for polysemy.
module Polysemy.Socket
  ( -- * Definition
    Socket (..),
    acquire,
    bind,
    listen,
    send,
    receive,
    release,
    accept,
    close,

    -- * Adapters
    adaptSocket,
    adaptSocketSem,

    -- * Eliminators
    runSocketTcp,
    runSocketVirtual,
    Virtual,

    -- * Tracing
    traceSocket,
  )
where

import Control.Monad
import Data.ByteString (ByteString)
import Polysemy
import Polysemy.Input
import Polysemy.Resource
import Polysemy.State
import Polysemy.Trace
import qualified System.Socket as S
import qualified System.Socket.Family.Inet6 as S
import qualified System.Socket.Protocol.TCP as S
import qualified System.Socket.Type.Stream as S

-- |
--
-- @since 0.0.1.0
data Socket s k b c m a where
  Acquire :: Socket s k b c m s
  Bind :: s -> Socket s k b c m ()
  Release :: s -> Socket s k b c m ()
  Listen :: s -> Int -> Socket s k b c m ()
  Accept :: s -> Socket s k b c m (s, k)
  Send :: s -> c -> Socket s k b c m ()
  Receive :: s -> Socket s k b c m b
  Close :: s -> Socket s k b c m ()

makeSem ''Socket

-- |
--
-- @since 0.0.1.0
traceSocket :: forall s k b c r a. Members '[Socket s k b c, Trace] r => Sem r a -> Sem r a
traceSocket = intercept $ \case
  Acquire -> trace "acquring" >> acquire
  Bind s -> trace "binding" >> bind s
  Listen s x -> trace "listening" >> listen s x
  Receive s -> trace "receiving" >> receive s
  Send s x -> trace "sending" >> send s x
  Release s -> trace "releasing" >> release s
  Accept s -> trace "accept" >> accept s
  Close s -> trace "closing" >> close s
{-# INLINE traceSocket #-}

-- |
--
-- @since 0.0.1.0
adaptSocket :: Members '[Socket s k b' c'] r => (b' -> b) -> (c -> c') -> Sem (Socket s k b c ': r) a -> Sem r a
adaptSocket f g = adaptSocketSem (pure . f) (pure . g)
{-# INLINE adaptSocket #-}

-- |
--
-- @since 0.0.1.0
adaptSocketSem :: Members '[Socket s k b' c'] r => (b' -> Sem r b) -> (c -> Sem r c') -> Sem (Socket s k b c ': r) a -> Sem r a
adaptSocketSem f g = interpret $ \case
  Acquire -> acquire
  Bind s -> bind s
  Release s -> release s
  Listen s x -> listen s x
  Accept s -> accept s
  Send s c -> g c >>= send s
  Receive s -> receive s >>= f
  Close s -> close s
{-# INLINE adaptSocketSem #-}

-- |
--
-- @since 0.0.1.0
runSocketTcp ::
  forall s r a.
  ( S.Socket S.Inet6 S.Stream S.TCP ~ s,
    Members '[Resource, Embed IO, Input S.Inet6Port] r
  ) =>
  Sem (Socket s (S.SocketAddress S.Inet6) ByteString ByteString ': r) a ->
  Sem r a
runSocketTcp = interpret $ \case
  Acquire -> embed $ S.socket
  Bind s -> do
    x <- input @S.Inet6Port
    Polysemy.embed $ do
      S.setSocketOption s (S.ReuseAddress True)
      S.setSocketOption s (S.V6Only False)
      S.bind s (S.SocketAddressInet6 S.inet6Any x 0 0)
  Listen s x -> embed $ S.listen s x
  Receive s -> embed $ S.receive s 1024 mempty
  Send s x -> void $ embed $ S.sendAll s x S.msgNoSignal
  Release s -> embed $ S.close s
  Accept s -> embed $ S.accept s
  Close s -> embed $ S.close s
{-# INLINE runSocketTcp #-}

data Virtual = Virtual

-- |
--
-- @since 0.0.1.0
runSocketVirtual :: Members '[State p] r => (c -> p) -> (p -> b) -> Sem (Socket Virtual Virtual b c ': r) a -> Sem r a
runSocketVirtual f g = interpret $ \case
  Acquire -> pure Virtual
  Bind _ -> pure ()
  Release _ -> pure ()
  Listen _ _ -> pure ()
  Accept _ -> pure (Virtual, Virtual)
  Send _ c -> put (f c)
  Receive _ -> g <$> get
  Close _ -> pure ()
{-# INLINE runSocketVirtual #-}