packages feed

spectacle-1.0.0: src/Language/Spectacle/Syntax/NonDet.hs

{-# LANGUAGE TupleSections #-}

-- | The 'NonDet' effect models nondeterminism.
--
-- @since 1.0.0
module Language.Spectacle.Syntax.NonDet
  ( NonDet (Choose, Empty),
    Effect (MSplit),
    oneOf,
    foldMapA,
    msplit,
    runNonDetA,
  )
where

import Control.Applicative (Alternative (empty, (<|>)), Applicative (liftA2))
import Data.Foldable (msum)
import Data.Monoid (Alt (Alt, getAlt))

import Data.Functor.Loom (hoist, runLoom, (~>~))
import Language.Spectacle.Lang
  ( Effect,
    Lang (Op, Pure, Scoped),
    Member (project),
    decomposeOp,
    decomposeS,
    scope,
  )
import Language.Spectacle.Syntax.NonDet.Internal (Effect (MSplit), NonDet (Choose, Empty))

-- ---------------------------------------------------------------------------------------------------------------------

-- | Nondeterministically choose an element from a foldable container.
--
-- @since 1.0.0
oneOf :: (Foldable t, Alternative m) => t a -> m a
oneOf = foldMapA pure
{-# INLINE oneOf #-}

-- | Like `foldMap`, but folds under 'Alternative' rather than 'Monoid'.
--
-- @since 1.0.0
foldMapA :: (Foldable t, Alternative m) => (a -> m b) -> t a -> m b
foldMapA f = getAlt . foldMap (Alt . f)
{-# INLINE foldMapA #-}

-- | Splits a 'Lang' into its 'Alternative' branches, if it has any.
--
-- @since 1.0.0
msplit :: Member NonDet effs => Lang ctx effs a -> Lang ctx effs (Maybe (Lang ctx effs a, Lang ctx effs a))
msplit m = scope (MSplit m)
{-# INLINE msplit #-}

-- | Discharge a 'NonDet' effect into some 'Alternative' functor @f@.
--
-- @since 1.0.0
runNonDetA :: Alternative f => Lang ctx (NonDet ': effs) a -> Lang ctx effs (f a)
runNonDetA = \case
  Pure x -> pure (pure x)
  Op op k -> case decomposeOp op of
    Left other -> Op other k'
    Right Empty -> pure empty
    Right Choose -> liftA2 (<|>) (k' True) (k' False)
    where
      k' = runNonDetA . k
  Scoped scoped loom -> case decomposeS scoped of
    Left other -> Scoped other (loom ~>~ hoist runNonDetA)
    Right (MSplit m) -> runLoom (loom ~>~ hoist runNonDetA) (handleMSplit m)
    where
      handleMSplit :: Member NonDet effs => Lang ctx effs a -> Lang ctx effs (Maybe (Lang ctx effs a, Lang ctx effs a))
      handleMSplit = loop []
  where
    loop xs (Pure x) = pure (Just (pure x, msum xs))
    loop xs (Op op k) = case project op of
      Nothing -> Op op (loop xs . k)
      Just Empty -> case xs of
        [] -> pure Nothing
        (x : xs') -> loop xs' x
      Just Choose -> loop (k False : xs) (k True)
    loop xs (Scoped scoped loom) = Scoped scoped (loom ~>~ hoist (loop xs))