Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Non-empty nondeterminism effect #198

Merged
merged 42 commits into from
Sep 18, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
0bf995a
Stub in a module for non-empty nondeterminism.
robrix Aug 25, 2019
5633307
Define a NonDet effect.
robrix Aug 25, 2019
27509ae
Derive a Functor instance for NonDet.
robrix Aug 25, 2019
22cde3f
Derive a Generic1 instance for NonDet.
robrix Aug 25, 2019
e0eb437
Define HFunctor & Effect instances for NonDet.
robrix Aug 25, 2019
98cd780
Define a carrier for non-empty nondeterminism.
robrix Aug 25, 2019
2123fd6
Define a smart constructor for Choose.
robrix Aug 25, 2019
32de1d3
Improve the docs for runNonDetC.
robrix Aug 25, 2019
9e29027
Define an Applicative instance for NonDetC.
robrix Aug 25, 2019
af80d08
Define a Monad instance for NonDetC.
robrix Aug 25, 2019
d371d49
Define a MonadTrans instance for NonDetC.
robrix Aug 25, 2019
3bb2dff
Define a MonadFail instance for NonDetC.
robrix Aug 25, 2019
20fcef4
Define a MonadIO instance for NonDetC.
robrix Aug 25, 2019
40646e6
Define a handler for NonDetC.
robrix Aug 25, 2019
9937560
Define a type of non-empty binary trees.
robrix Aug 25, 2019
99e5595
Define an Applicative instance for BinaryTree.
robrix Aug 25, 2019
84f96e9
Define a Monad instance for BinaryTree.
robrix Aug 25, 2019
bb328d3
Define an eliminator for BinaryTree.
robrix Aug 25, 2019
123e6ae
Define a Carrier instance for NonDetC.
robrix Aug 25, 2019
4150e66
Define a MonadFix instance for NonDetC.
robrix Aug 25, 2019
d4132f9
Correct a doc comment.
robrix Sep 3, 2019
5a201f1
Merge branch 'binary-nondet' into nonempty-nondet
robrix Sep 3, 2019
e95a2cc
Don’t shadow pure.
robrix Sep 3, 2019
9d8dbce
Qualify the import of and references to MonadFail.
robrix Sep 3, 2019
d0ac9b5
Define <*> using fold.
robrix Sep 3, 2019
daf3c1c
Define >>= using fold.
robrix Sep 3, 2019
6b23eb7
Rename NonDet to Choose.
robrix Sep 3, 2019
1e8d3e1
Rename Control.Effect.NonDet.NonEmpty to Control.Effect.Choose.
robrix Sep 3, 2019
713394d
Add a changelog entry.
robrix Sep 3, 2019
b0e1185
Define >>= tacitly.
robrix Sep 3, 2019
8524968
Define mfix tacitly.
robrix Sep 3, 2019
9b2cb36
Reformat.
robrix Sep 3, 2019
72abc72
:memo: choose.
robrix Sep 3, 2019
91b5e15
Link to the PR.
robrix Sep 3, 2019
f124776
Merge branch 'binary-nondet' into nonempty-nondet
robrix Sep 8, 2019
abd633f
Merge branch 'master' into nonempty-nondet
robrix Sep 11, 2019
bc99fa6
Inline all the things!
robrix Sep 11, 2019
432f280
Define an optional operator for Choose.
robrix Sep 12, 2019
5ef4237
:memo: optional.
robrix Sep 12, 2019
2f62573
Define zero-or-more repetition using Choose.
robrix Sep 12, 2019
6c0aefb
Define one-or-more repetition using Choose.
robrix Sep 12, 2019
ed78263
Define one-or-more repetion into NonEmpty.
robrix Sep 12, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
- Adds an `Empty` effect, modelling nondeterminism without choice ([#196](https://github.com/fused-effects/fused-effects/pull/196)).

- Adds a `Choose` effect, modelling nondeterminism without failure ([#198](https://github.com/fused-effects/fused-effects/pull/198)).

- Adds a `oneOf` function to `Control.Effect.NonDet` to provide an idiom for the common case of nondeterministically selecting from a container. ([#201](https://github.com/fused-effects/fused-effects/pull/201))

## Backwards-incompatible changes
Expand Down
1 change: 1 addition & 0 deletions fused-effects.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ library
import: common
exposed-modules: Control.Effect
, Control.Effect.Carrier
, Control.Effect.Choose
, Control.Effect.Cull
, Control.Effect.Cut
, Control.Effect.Empty
Expand Down
1 change: 1 addition & 0 deletions src/Control/Effect.hs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module Control.Effect
) where

import Control.Effect.Carrier as X ((:+:), Carrier, Effect, Member)
import Control.Effect.Choose as X (Choose, ChooseC)
import Control.Effect.Cull as X (Cull, CullC, OnceC)
import Control.Effect.Cut as X (Cut, CutC)
import Control.Effect.Empty as X (Empty, EmptyC)
Expand Down
120 changes: 120 additions & 0 deletions src/Control/Effect/Choose.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
{-# LANGUAGE DeriveGeneric, DeriveTraversable, FlexibleContexts, FlexibleInstances, MultiParamTypeClasses, RankNTypes, TypeOperators, UndecidableInstances #-}
module Control.Effect.Choose
( -- * Choose effect
Choose(..)
, choose
, optional
, many
, some
, some1
-- * Choose carrier
, runChoose
, ChooseC(..)
) where

import Control.Applicative ((<|>), liftA2)
import Control.Effect.Carrier
import Control.Monad (join)
import qualified Control.Monad.Fail as Fail
import Control.Monad.Fix
import Control.Monad.IO.Class
import Control.Monad.Trans.Class
import Data.Bool (bool)
import Data.List.NonEmpty (NonEmpty (..))
import Data.Maybe (fromJust)
import GHC.Generics (Generic1)

data Choose m k
= Choose (Bool -> m k)
deriving (Functor, Generic1)

instance HFunctor Choose
instance Effect Choose

-- | Nondeterministically choose between two computations.
choose :: (Carrier sig m, Member Choose sig) => m a -> m a -> m a
choose a b = send (Choose (bool b a))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn’t want to bring in a dependency on semigroupoids (and all of the transitive dependencies it entails), or this might have been an Alt instance (tho the n² instance problem would apply to that anyway).

NB: this name conflicts with #201, but I think we might want to generalize it to a foldMapA function instead anyway.


Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could consider defining functionality like optional against this interface, since it doesn’t require empty. Likewise, possibly something like Data.List.NonEmpty.some1.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done! See below.

-- | Select between 'Just' the result of an operation, and 'Nothing'.
optional :: (Carrier sig m, Member Choose sig) => m a -> m (Maybe a)
optional a = choose (Just <$> a) (pure Nothing)

-- | Zero or more.
many :: (Carrier sig m, Member Choose sig) => m a -> m [a]
many a = go where go = choose ((:) <$> a <*> go) (pure [])

-- | One or more.
some :: (Carrier sig m, Member Choose sig) => m a -> m [a]
some a = (:) <$> a <*> many a

-- | One or more, returning a 'NonEmpty' list of the results.
some1 :: (Carrier sig m, Member Choose sig) => m a -> m (NonEmpty a)
some1 a = (:|) <$> a <*> many a


runChoose :: (m b -> m b -> m b) -> (a -> m b) -> ChooseC m a -> m b
runChoose fork leaf m = runChooseC m fork leaf

-- | A carrier for 'Choose' effects based on Ralf Hinze’s design described in [Deriving Backtracking Monad Transformers](https://www.cs.ox.ac.uk/ralf.hinze/publications/#P12).
newtype ChooseC m a = ChooseC
{ -- | A higher-order function receiving two continuations, respectively implementing choice and 'pure'.
runChooseC :: forall b . (m b -> m b -> m b) -> (a -> m b) -> m b
}
deriving (Functor)

instance Applicative (ChooseC m) where
pure a = ChooseC (\ _ leaf -> leaf a)
{-# INLINE pure #-}
ChooseC f <*> ChooseC a = ChooseC $ \ fork leaf ->
f fork (\ f' -> a fork (leaf . f'))
{-# INLINE (<*>) #-}

instance Monad (ChooseC m) where
ChooseC a >>= f = ChooseC $ \ fork leaf ->
a fork (runChoose fork leaf . f)
{-# INLINE (>>=) #-}

instance Fail.MonadFail m => Fail.MonadFail (ChooseC m) where
fail s = lift (Fail.fail s)
{-# INLINE fail #-}

instance MonadFix m => MonadFix (ChooseC m) where
mfix f = ChooseC $ \ fork leaf ->
mfix (runChoose (liftA2 Fork) (pure . Leaf)
. f . fromJust . fold (<|>) Just)
>>= fold fork leaf
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like #197, this too suffers #209. I intend to fix this in a future PR (i.e. once I have any idea how to).

{-# INLINE mfix #-}

instance MonadIO m => MonadIO (ChooseC m) where
liftIO io = lift (liftIO io)
{-# INLINE liftIO #-}

instance MonadTrans ChooseC where
lift m = ChooseC (\ _ leaf -> m >>= leaf)
{-# INLINE lift #-}

instance (Carrier sig m, Effect sig) => Carrier (Choose :+: sig) (ChooseC m) where
eff (L (Choose k)) = ChooseC $ \ fork leaf -> fork (runChooseC (k True) fork leaf) (runChooseC (k False) fork leaf)
eff (R other) = ChooseC $ \ fork leaf -> eff (handle (Leaf ()) (fmap join . traverse (runChoose (liftA2 Fork) (pure . Leaf))) other) >>= fold fork leaf
{-# INLINE eff #-}


data BinaryTree a = Leaf a | Fork (BinaryTree a) (BinaryTree a)
deriving (Eq, Foldable, Functor, Ord, Show, Traversable)

instance Applicative BinaryTree where
pure = Leaf
{-# INLINE pure #-}
f <*> a = fold Fork (<$> a) f
{-# INLINE (<*>) #-}

instance Monad BinaryTree where
a >>= f = fold Fork f a
{-# INLINE (>>=) #-}


fold :: (b -> b -> b) -> (a -> b) -> BinaryTree a -> b
fold fork leaf = go where
go (Leaf a) = leaf a
go (Fork a b) = fork (go a) (go b)
{-# INLINE fold #-}