packages feed

data-effects-0.4.0.0: src/Data/Effect/NonDet.hs

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE QuantifiedConstraints #-}
{-# LANGUAGE UndecidableInstances #-}

-- SPDX-License-Identifier: MPL-2.0

{- |
Copyright   : (c) 2024-2025 Sayo contributors
License     :  MPL-2.0 (see the file LICENSE)
Maintainer  :  ymdfield@outlook.jp

Effects that realize non-deterministic computations.
-}
module Data.Effect.NonDet (
    module Data.Effect.NonDet,
    Empty (..),
    Choose (..),
    ChooseH (..),
)
where

#if ( __GLASGOW_HASKELL__ < 906 )
import Control.Applicative (liftA2)
#endif
import Control.Applicative ((<|>))
import Control.Effect.Interpret (interprets)
import Control.Exception (Exception, SomeException)
import Data.Bool (bool)
import Data.Effect (Choose (Choose), ChooseH (ChooseH), Emb, Empty (Empty), Shift, UnliftIO)
import Data.Effect.OpenUnion (nil, (!:))
import Data.Effect.Shift (abort, shift)
import UnliftIO (throwIO, try)

makeEffectF_' (def & noGenerateLabel & noGenerateOrderInstance) ''Empty
makeEffectF_' (def & noGenerateLabel & noGenerateOrderInstance) ''Choose
makeEffectH_' (def & noGenerateLabel & noGenerateOrderInstance) ''ChooseH

{- | t'ChooseH' effect elaborator.

    Convert a higher-order effect of the form

        @chooseH :: m a -> m a -> m a@

    into a first-order effect of the form:

        @choose :: m Bool@
-}
runChooseH
    :: forall es a ff c
     . (Choose :> es, Monad (Eff ff es), Free c ff)
    => Eff ff (ChooseH ': es) a
    -> Eff ff es a
runChooseH = interpret \(ChooseH a b) -> branch a b
{-# INLINE runChooseH #-}

-- | Faster than `<|>`.
branch :: forall es a ff c. (Choose :> es, Monad (Eff ff es), Free c ff) => Eff ff es a -> Eff ff es a -> Eff ff es a
branch a b = do
    world <- choose
    bool a b world
{-# INLINE branch #-}

infixl 3 `branch`

-- | Selects one element from the list nondeterministically, branching the control as many times as the number of elements.
choice :: forall es a ff c. (Choose :> es, Empty :> es, Monad (Eff ff es), Free c ff) => [a] -> Eff ff es a
choice = \case
    [] -> empty
    x : xs -> pure x `branch` choice xs
{-# INLINE choice #-}

-- | Selects one element from the list nondeterministically, branching the control as many times as the number of elements. Uses t'ChooseH'.
choiceH :: forall es a ff c. (ChooseH :> es, Empty :> es, Monad (Eff ff es), Free c ff) => [a] -> Eff ff es a
choiceH = \case
    [] -> empty
    x : xs -> pure x <|> choiceH xs
{-# INLINE choiceH #-}

runNonDetShift
    :: forall ans a es ref ff c
     . (Monoid ans, Shift ans ref :> es, forall f. Monad (ff f), Free c ff)
    => Eff ff (Choose ': Empty ': es) a
    -> Eff ff es a
runNonDetShift =
    interprets $
        (\Choose -> shift \k' -> liftA2 (<>) (k' True) (k' False))
            !: (\Empty -> abort mempty)
            !: nil
{-# INLINE runNonDetShift #-}

{- |
Interprets the [NonDet]("Data.Effect.NonDet") effects using IO-level exceptions.

When 'empty' occurs, an v'EmptyException' is thrown, and unless all branches from
 'chooseH' fail due to IO-level exceptions, only the leftmost result is returned
 as the final result.
-}
runNonDetIO
    :: forall es a ff c
     . (UnliftIO :> es, Emb IO :> es, forall f. Monad (Eff ff f), Free c ff)
    => Eff ff (ChooseH ': Empty ': es) a
    -> Eff ff es (Either SomeException a)
runNonDetIO m = try do
    let hdl =
            ( \(ChooseH a b) ->
                try a >>= \case
                    Right x -> pure x
                    Left (_ :: SomeException) -> b
            )
                !: (\Empty -> throwIO EmptyException)
                !: nil
     in interprets hdl m
{-# INLINE runNonDetIO #-}

-- | Exception thrown when 'empty' occurs in 'runNonDetIO'.
data EmptyException = EmptyException
    deriving stock (Show)
    deriving anyclass (Exception)