Skip to content
Snippets Groups Projects
Reduce.hs 6.27 KiB
Newer Older
  • Learn to ignore specific revisions
  • chrg's avatar
    chrg committed
    {-# LANGUAGE BlockArguments #-}
    {-# LANGUAGE DerivingVia #-}
    {-# LANGUAGE FlexibleContexts #-}
    {-# LANGUAGE FlexibleInstances #-}
    {-# LANGUAGE FunctionalDependencies #-}
    {-# LANGUAGE KindSignatures #-}
    {-# LANGUAGE LambdaCase #-}
    {-# LANGUAGE RankNTypes #-}
    {-# LANGUAGE ScopedTypeVariables #-}
    {-# LANGUAGE UndecidableInstances #-}
    {-# LANGUAGE ViewPatterns #-}
    
    {- |
    Module: Control.Monad.Reduce
    -}
    module Control.Monad.Reduce (
      MonadReduce (..),
    
    chrg's avatar
    chrg committed
    
      -- * Constructors
    
    chrg's avatar
    chrg committed
      (<|),
      (|>),
      splitOn,
      given,
      givenThat,
      givenWith,
      check,
      checkThat,
    
    chrg's avatar
    chrg committed
    
      -- * Combinators
    
    chrg's avatar
    chrg committed
      collect,
      collectNonEmpty,
      collectNonEmpty',
    
    chrg's avatar
    chrg committed
    
      -- * Algorithms
    
    chrg's avatar
    chrg committed
      ddmin,
      linearSearch,
      linearSearch',
      binarySearch,
      exponentialSearch,
    
    chrg's avatar
    chrg committed
    
      -- * Helpers
    
    chrg's avatar
    chrg committed
      onBoth,
    
    chrg's avatar
    chrg committed
      liftMaybe,
    
    chrg's avatar
    chrg committed
    ) where
    
    
    chrg's avatar
    chrg committed
    import Control.Applicative
    
    chrg's avatar
    chrg committed
    import Control.Monad
    
    chrg's avatar
    chrg committed
    import Control.Monad.Trans.Maybe
    import qualified Data.List.NonEmpty as NE
    import Data.Maybe
    
    
    chrg's avatar
    chrg committed
    import Data.Valuation (Truth (..))
    
    
    chrg's avatar
    chrg committed
    -- {- | A reducer should extract itself
    -- @
    --  extract . red = id
    -- @
    -- -}
    -- lawReduceId :: (MonadReduce l m, Eq i) => (i -> m i) -> i -> Bool
    -- lawReduceId red i = extract (red i) == i
    
    -- | The Monad Reduce class.
    class (Monad m) => MonadReduce l m | m -> l where
    
    chrg's avatar
    chrg committed
      {-# MINIMAL splitWith | checkWith #-}
    
      -- | Split the world into the a reduced world (left) without an element and a world
    
    chrg's avatar
    chrg committed
      -- with that element (right). Optionally, labeled with l.
    
    chrg's avatar
    chrg committed
      splitWith :: Maybe (Truth l) -> m i -> m i -> m i
    
    chrg's avatar
    chrg committed
      splitWith l r1 r2 =
        checkWith l >>= \case
    
    chrg's avatar
    chrg committed
          False -> r1
          True -> r2
    
    chrg's avatar
    chrg committed
      {-# INLINE splitWith #-}
    
    
    chrg's avatar
    chrg committed
      -- | Check with returns a boolean, that can be used to split the input into a world where
    
    chrg's avatar
    chrg committed
      -- the optional truth assignement is satisfiable and where it is not.
      checkWith :: Maybe (Truth l) -> m Bool
    
    chrg's avatar
    chrg committed
      checkWith l = splitWith l (pure False) (pure True)
      {-# INLINE checkWith #-}
    
    
    chrg's avatar
    chrg committed
    -- | Split with no label.
    
    chrg's avatar
    chrg committed
    split :: (MonadReduce l m) => m i -> m i -> m i
    split = splitWith Nothing
    {-# INLINE split #-}
    
    
    chrg's avatar
    chrg committed
    -- | Infix split.
    
    chrg's avatar
    chrg committed
    (<|) :: (MonadReduce l m) => m i -> m i -> m i
    (<|) = split
    {-# INLINE (<|) #-}
    
    chrg's avatar
    chrg committed
    
    
    chrg's avatar
    chrg committed
    infixr 3 <|
    
    
    chrg's avatar
    chrg committed
    -- | Infix split, to the right.
    
    chrg's avatar
    chrg committed
    (|>) :: (MonadReduce l m) => m i -> m i -> m i
    r1 |> r2 = r2 <| r1
    {-# INLINE (|>) #-}
    
    chrg's avatar
    chrg committed
    
    
    chrg's avatar
    chrg committed
    infixl 3 |>
    
    
    chrg's avatar
    chrg committed
    -- | Split on a label.
    
    chrg's avatar
    chrg committed
    splitOn :: (MonadReduce l m) => Truth l -> m i -> m i -> m i
    
    chrg's avatar
    chrg committed
    splitOn l = splitWith (Just l)
    {-# INLINE splitOn #-}
    
    -- | Split the world on a fact. False it does not happen, and True it does happen.
    check :: (MonadReduce l m) => m Bool
    check = checkWith Nothing
    {-# INLINE check #-}
    
    -- | Split the world on a labeled fact. False it does not happen, and True it does happen.
    
    chrg's avatar
    chrg committed
    checkThat :: (MonadReduce l m) => Truth l -> m Bool
    
    chrg's avatar
    chrg committed
    checkThat l = checkWith (Just l)
    {-# INLINE checkThat #-}
    
    -- | Continues if the fact is true.
    given :: (MonadReduce l m) => MaybeT m ()
    given = givenWith Nothing
    {-# INLINE given #-}
    
    -- | Continues if the labeled fact is true.
    
    chrg's avatar
    chrg committed
    givenWith :: (MonadReduce l m, MonadPlus m) => Maybe (Truth l) -> m ()
    
    chrg's avatar
    chrg committed
    givenWith l = splitWith l mzero (pure ())
    
    chrg's avatar
    chrg committed
    {-# INLINE givenWith #-}
    
    -- | Continues if the labeled fact is true.
    
    chrg's avatar
    chrg committed
    givenThat :: (MonadReduce l m, MonadPlus m) => Truth l -> m ()
    
    chrg's avatar
    chrg committed
    givenThat l = givenWith (Just l)
    {-# INLINE givenThat #-}
    
    -- | Given a list of item try to remove each of them from the list.
    collect :: (MonadReduce l m) => (a -> MaybeT m b) -> [a] -> m [b]
    collect fn = fmap catMaybes . traverse (runMaybeT . fn)
    {-# INLINE collect #-}
    
    -- | Given a list of item try to remove each of them, but keep atleast one.
    
    chrg's avatar
    chrg committed
    collectNonEmpty' :: (MonadReduce l m, MonadPlus m) => (a -> MaybeT m b) -> [a] -> m [b]
    
    chrg's avatar
    chrg committed
    collectNonEmpty' fn as =
      NE.toList <$> collectNonEmpty fn as
    {-# INLINE collectNonEmpty' #-}
    
    -- | Given a list of item try to remove each of them, but keep atleast one.
    
    chrg's avatar
    chrg committed
    collectNonEmpty :: (MonadReduce l m, MonadPlus m) => (a -> MaybeT m b) -> [a] -> m (NE.NonEmpty b)
    
    chrg's avatar
    chrg committed
    collectNonEmpty fn as = do
    
    chrg's avatar
    chrg committed
      as' <- fmap catMaybes . traverse (runMaybeT . fn) $ as
      maybe mzero pure $ NE.nonEmpty as'
    
    chrg's avatar
    chrg committed
    {-# INLINE collectNonEmpty #-}
    
    
    chrg's avatar
    chrg committed
    instance (MonadReduce l m) => MonadReduce l (MaybeT m) where
      splitWith m (MaybeT lhs) (MaybeT rhs) = MaybeT (splitWith m lhs rhs)
    
    
    chrg's avatar
    chrg committed
    -- | Helper that lifts a maybe into MonadPlus (or MaybeT)
    liftMaybe :: (MonadPlus m) => Maybe a -> m a
    liftMaybe = maybe mzero pure
    
    
    chrg's avatar
    chrg committed
    -- | Returns either of the maybes or combines them if both have values.
    
    chrg's avatar
    chrg committed
    onBoth :: (MonadPlus m) => m a -> m a -> (a -> a -> m a) -> m a
    onBoth mlhs mrhs fn =
      join $ (fn <$> mlhs <*> mrhs) <|> fmap pure mrhs <|> fmap pure mlhs
    
    chrg's avatar
    chrg committed
    
    
    chrg's avatar
    chrg committed
    {- | Given a list of ordered options, choose the first that statisfy the constraints,
    returning the last element if nothing else matches.
    -}
    linearSearch :: (MonadReduce l m) => NE.NonEmpty i -> m i
    linearSearch = foldr1 (<|) . fmap pure
    
    {- | Given a list of ordered options, choose the first that statisfy the
    constraints, potentially returning nothing.
    -}
    
    chrg's avatar
    chrg committed
    linearSearch' :: (MonadReduce l m, MonadPlus m) => [i] -> m i
    linearSearch' is = do
      mp <- linearSearch (NE.fromList (fmap Just is ++ [Nothing]))
      liftMaybe mp
    
    chrg's avatar
    chrg committed
    
    -- | Given
    ddmin :: (MonadReduce l m) => [i] -> m [i]
    ddmin = \case
      [] -> pure []
      [a] -> pure [a]
      as -> go 2 as
     where
      go n lst
        | n' <= 0 = pure lst
        | otherwise = do
            r <- runMaybeT $ linearSearch' (partitions n' lst ++ composites n' lst)
            case r of
              Nothing -> go (n * 2) lst <| pure lst -- (for efficiency :D)
              Just lst' -> ddmin lst'
       where
        n' = length lst `div` n
      partitions n lst =
        case lst of
          [] -> []
          _ -> let (h, r) = splitAt n lst in h : partitions n r
      composites n lst =
        case lst of
          [] -> []
          _ -> let (h, r) = splitAt n lst in r : fmap (h ++) (composites n r)
    
    {- | Given a progression of inputs that are progressively larger, pick the smallest using
    binary search.
    -}
    binarySearch :: (MonadReduce l m) => NE.NonEmpty i -> m i
    binarySearch = \case
      a NE.:| [] -> pure a
      d -> binarySearch l <| binarySearch f
       where
        (NE.fromList -> f, NE.fromList -> l) = NE.splitAt (NE.length d `div` 2) d
    
    {- | Given a progression of inputs that are progressively larger, pick the smallest using
    binary search.
    -}
    exponentialSearch :: (MonadReduce l m) => NE.NonEmpty i -> m i
    exponentialSearch = go 1
     where
      go n = \case
        d
          | n >= NE.length d -> binarySearch d
          | otherwise -> go (n * 2) l <| binarySearch f
         where
          (NE.fromList -> f, NE.fromList -> l) = NE.splitAt n d