packages feed

futhark-0.19.7: src/Futhark/Binder.hs

{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE Trustworthy #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}

-- | This module defines a convenience monad/typeclass for creating
-- normalised programs.  The fundamental building block is 'BinderT'
-- and its execution functions, but it is usually easier to use
-- 'Binder'.
--
-- See "Futhark.Construct" for a high-level description.
module Futhark.Binder
  ( -- * A concrete @MonadBinder@ monad.
    BinderT,
    runBinderT,
    runBinderT_,
    runBinderT',
    runBinderT'_,
    BinderOps (..),
    Binder,
    runBinder,
    runBinder_,
    runBodyBinder,

    -- * The 'MonadBinder' typeclass
    module Futhark.Binder.Class,
  )
where

import Control.Arrow (second)
import Control.Monad.Error.Class
import Control.Monad.Reader
import Control.Monad.State.Strict
import Control.Monad.Writer
import qualified Data.Map.Strict as M
import Futhark.Binder.Class
import Futhark.IR

-- | A 'BinderT' (and by extension, a 'Binder') is only an instance of
-- 'MonadBinder' for representations that implement this type class,
-- which contains methods for constructing statements.
class ASTRep rep => BinderOps rep where
  mkExpDecB ::
    (MonadBinder m, Rep m ~ rep) =>
    Pattern rep ->
    Exp rep ->
    m (ExpDec rep)
  mkBodyB ::
    (MonadBinder m, Rep m ~ rep) =>
    Stms rep ->
    Result ->
    m (Body rep)
  mkLetNamesB ::
    (MonadBinder m, Rep m ~ rep) =>
    [VName] ->
    Exp rep ->
    m (Stm rep)

  default mkExpDecB ::
    (MonadBinder m, Bindable rep) =>
    Pattern rep ->
    Exp rep ->
    m (ExpDec rep)
  mkExpDecB pat e = return $ mkExpDec pat e

  default mkBodyB ::
    (MonadBinder m, Bindable rep) =>
    Stms rep ->
    Result ->
    m (Body rep)
  mkBodyB stms res = return $ mkBody stms res

  default mkLetNamesB ::
    (MonadBinder m, Rep m ~ rep, Bindable rep) =>
    [VName] ->
    Exp rep ->
    m (Stm rep)
  mkLetNamesB = mkLetNames

-- | A monad transformer that tracks statements and provides a
-- 'MonadBinder' instance, assuming that the underlying monad provides
-- a name source.  In almost all cases, this is what you will use for
-- constructing statements (possibly as part of a larger monad stack).
-- If you find yourself needing to implement 'MonadBinder' from
-- scratch, then it is likely that you are making a mistake.
newtype BinderT rep m a = BinderT (StateT (Stms rep, Scope rep) m a)
  deriving (Functor, Monad, Applicative)

instance MonadTrans (BinderT rep) where
  lift = BinderT . lift

-- | The most commonly used binder monad.
type Binder rep = BinderT rep (State VNameSource)

instance MonadFreshNames m => MonadFreshNames (BinderT rep m) where
  getNameSource = lift getNameSource
  putNameSource = lift . putNameSource

instance
  (ASTRep rep, Monad m) =>
  HasScope rep (BinderT rep m)
  where
  lookupType name = do
    t <- BinderT $ gets $ M.lookup name . snd
    case t of
      Nothing -> error $ "BinderT.lookupType: unknown variable " ++ pretty name
      Just t' -> return $ typeOf t'
  askScope = BinderT $ gets snd

instance
  (ASTRep rep, Monad m) =>
  LocalScope rep (BinderT rep m)
  where
  localScope types (BinderT m) = BinderT $ do
    modify $ second (M.union types)
    x <- m
    modify $ second (`M.difference` types)
    return x

instance
  (ASTRep rep, MonadFreshNames m, BinderOps rep) =>
  MonadBinder (BinderT rep m)
  where
  type Rep (BinderT rep m) = rep
  mkExpDecM = mkExpDecB
  mkBodyM = mkBodyB
  mkLetNamesM = mkLetNamesB

  addStms stms =
    BinderT $
      modify $ \(cur_stms, scope) ->
        (cur_stms <> stms, scope `M.union` scopeOf stms)

  collectStms m = do
    (old_stms, old_scope) <- BinderT get
    BinderT $ put (mempty, old_scope)
    x <- m
    (new_stms, _) <- BinderT get
    BinderT $ put (old_stms, old_scope)
    return (x, new_stms)

-- | Run a binder action given an initial scope, returning a value and
-- the statements added ('addStm') during the action.
runBinderT ::
  MonadFreshNames m =>
  BinderT rep m a ->
  Scope rep ->
  m (a, Stms rep)
runBinderT (BinderT m) scope = do
  (x, (stms, _)) <- runStateT m (mempty, scope)
  return (x, stms)

-- | Like 'runBinderT', but return only the statements.
runBinderT_ ::
  MonadFreshNames m =>
  BinderT rep m () ->
  Scope rep ->
  m (Stms rep)
runBinderT_ m = fmap snd . runBinderT m

-- | Like 'runBinderT', but get the initial scope from the current
-- monad.
runBinderT' ::
  (MonadFreshNames m, HasScope somerep m, SameScope somerep rep) =>
  BinderT rep m a ->
  m (a, Stms rep)
runBinderT' m = do
  scope <- askScope
  runBinderT m $ castScope scope

-- | Like 'runBinderT_', but get the initial scope from the current
-- monad.
runBinderT'_ ::
  (MonadFreshNames m, HasScope somerep m, SameScope somerep rep) =>
  BinderT rep m a ->
  m (Stms rep)
runBinderT'_ = fmap snd . runBinderT'

-- | Run a binder action, returning a value and the statements added
-- ('addStm') during the action.  Assumes that the current monad
-- provides initial scope and name source.
runBinder ::
  ( MonadFreshNames m,
    HasScope somerep m,
    SameScope somerep rep
  ) =>
  Binder rep a ->
  m (a, Stms rep)
runBinder m = do
  types <- askScope
  modifyNameSource $ runState $ runBinderT m $ castScope types

-- | Like 'runBinder', but throw away the result and just return the
-- added statements.
runBinder_ ::
  ( MonadFreshNames m,
    HasScope somerep m,
    SameScope somerep rep
  ) =>
  Binder rep a ->
  m (Stms rep)
runBinder_ = fmap snd . runBinder

-- | Run a binder that produces a t'Body', and prefix that t'Body' by
-- the statements produced during execution of the action.
runBodyBinder ::
  ( Bindable rep,
    MonadFreshNames m,
    HasScope somerep m,
    SameScope somerep rep
  ) =>
  Binder rep (Body rep) ->
  m (Body rep)
runBodyBinder = fmap (uncurry $ flip insertStms) . runBinder

-- Utility instance defintions for MTL classes.  These require
-- UndecidableInstances, but save on typing elsewhere.

mapInner ::
  Monad m =>
  ( m (a, (Stms rep, Scope rep)) ->
    m (b, (Stms rep, Scope rep))
  ) ->
  BinderT rep m a ->
  BinderT rep m b
mapInner f (BinderT m) = BinderT $ do
  s <- get
  (x, s') <- lift $ f $ runStateT m s
  put s'
  return x

instance MonadReader r m => MonadReader r (BinderT rep m) where
  ask = BinderT $ lift ask
  local f = mapInner $ local f

instance MonadState s m => MonadState s (BinderT rep m) where
  get = BinderT $ lift get
  put = BinderT . lift . put

instance MonadWriter w m => MonadWriter w (BinderT rep m) where
  tell = BinderT . lift . tell
  pass = mapInner $ \m -> pass $ do
    ((x, f), s) <- m
    return ((x, s), f)
  listen = mapInner $ \m -> do
    ((x, s), y) <- listen m
    return ((x, y), s)

instance MonadError e m => MonadError e (BinderT rep m) where
  throwError = lift . throwError
  catchError (BinderT m) f =
    BinderT $ catchError m $ unBinder . f
    where
      unBinder (BinderT m') = m'