packages feed

disco-0.1.0.0: src/Disco/Effects/Fresh.hs

{-# LANGUAGE BlockArguments             #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE TemplateHaskell            #-}

-----------------------------------------------------------------------------
-- |
-- Module      :  Disco.Effects.Fresh
-- Copyright   :  disco team and contributors
-- Maintainer  :  byorgey@gmail.com
--
-- SPDX-License-Identifier: BSD-3-Clause
--
-- Polysemy effect for fresh name generation, compatible with the
-- unbound-generics library.
--
-----------------------------------------------------------------------------

module Disco.Effects.Fresh where

import           Disco.Effects.Counter
import           Disco.Names                           (QName, localName)
import           Polysemy
import           Polysemy.ConstraintAbsorber
import qualified Unbound.Generics.LocallyNameless      as U
import           Unbound.Generics.LocallyNameless.Name

-- | Fresh name generation effect, supporting raw generation of fresh
--   names, and opening binders with automatic freshening.  Simply
--   increments a global counter every time 'fresh' is called and
--   makes a variable with that numeric suffix.
data Fresh m a where
  Fresh  :: Name x -> Fresh m (Name x)

makeSem ''Fresh

-- | Dispatch the fresh name generation effect, starting at a given
--   integer.
runFresh' :: Integer -> Sem (Fresh ': r) a -> Sem r a
runFresh' i
  = runCounter' i
  . reinterpret \case
      Fresh x -> case x of
        Fn s _  -> Fn s <$> next
        nm@Bn{} -> return nm

    -- Above code copied from
    -- https://hackage.haskell.org/package/unbound-generics-0.4.1/docs/src/Unbound.Generics.LocallyNameless.Fresh.html ;
    -- see instance Monad m => Fresh (FreshMT m) .

    -- It turns out to make things much simpler to reimplement the
    -- Fresh effect ourselves in terms of a state effect, since then
    -- we can immediately dispatch it.  The alternative would be to
    -- implement it in terms of (Embed U.FreshM), but then we are
    -- stuck with that constraint.  Given the constraint-absorbing
    -- machinery below, just impementing the 'fresh' effect itself
    -- means we can then reuse other things from unbound-generics that
    -- depend on a Fresh constraint, such as the 'unbind' function
    -- below.

-- | Run a computation requiring fresh name generation, beginning with
--   0 for the initial freshly generated name.
runFresh :: Sem (Fresh ': r) a -> Sem r a
runFresh = runFresh' 0

-- | Run a computation requiring fresh name generation, beginning with
--   1 instead of 0 for the initial freshly generated name.
runFresh1 :: Sem (Fresh ': r) a -> Sem r a
runFresh1 = runFresh' 1

------------------------------------------------------------
-- Other functions

-- | Open a binder, automatically creating fresh names for the bound
--   variables.
unbind :: (Member Fresh r, U.Alpha p, U.Alpha t) => U.Bind p t -> Sem r (p, t)
unbind b = absorbFresh (U.unbind b)

-- | Generate a fresh (local, free) qualified name based on a given
--   string.
freshQ :: (Member Fresh r) => String -> Sem r (QName a)
freshQ s = localName <$> fresh (string2Name s)

------------------------------------------------------------
-- Machinery for absorbing MTL-style constraint.
-- See https://hackage.haskell.org/package/polysemy-zoo-0.7.0.1/docs/Polysemy-ConstraintAbsorber.html
-- Used https://hackage.haskell.org/package/polysemy-zoo-0.7.0.1/docs/src/Polysemy.ConstraintAbsorber.MonadState.html#absorbState as a template.

-- | Run a 'Sem' computation requiring a 'U.Fresh' constraint (from
--   the @unbound-generics@ library) in terms of an available 'Fresh'
--   effect.
absorbFresh :: Member Fresh r => (U.Fresh (Sem r) => Sem r a) -> Sem r a
absorbFresh = absorbWithSem @U.Fresh @Action (FreshDict fresh) (Sub Dict)
{-# INLINEABLE absorbFresh #-}

newtype FreshDict m = FreshDict { fresh_ :: forall x. Name x -> m (Name x) }

-- | Wrapper for a monadic action with phantom type parameter for reflection.
--   Locally defined so that the instance we are going to build with reflection
--   must be coherent, that is there cannot be orphans.
newtype Action m s' a = Action (m a)
  deriving (Functor, Applicative, Monad)

instance ( Monad m
         , Reifies s' (FreshDict m)
         ) => U.Fresh (Action m s') where
  fresh x = Action $ fresh_ (reflect $ Proxy @s') x
  {-# INLINEABLE fresh #-}