Skip to content

Commit

Permalink
Improve Labeled and add labeled versions of base effects (#228)
Browse files Browse the repository at this point in the history
* Improve Labeled and add labeled versions of base effects

* Remove .VDQ modules for now

* tests

* doctest

* more doctest

* ci

* run doctest with 9.10

* run polysemy with 9.10
  • Loading branch information
arybczak committed Aug 22, 2024
1 parent 14bbcfd commit 0ef04f2
Show file tree
Hide file tree
Showing 27 changed files with 638 additions and 55 deletions.
20 changes: 10 additions & 10 deletions .github/workflows/haskell-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ jobs:
- name: cache (tools)
uses: actions/cache/restore@v4
with:
key: ${{ runner.os }}-${{ matrix.compiler }}-tools-36dffdd0
key: ${{ runner.os }}-${{ matrix.compiler }}-tools-caa01dbf
path: ~/.haskell-ci-tools
- name: install cabal-plan
run: |
Expand All @@ -158,13 +158,13 @@ jobs:
cabal-plan --version
- name: install doctest
run: |
if [ $((HCNUMVER < 91000)) -ne 0 ] ; then $CABAL --store-dir=$HOME/.haskell-ci-tools/store v2-install $ARG_COMPILER --ignore-project -j2 doctest --constraint='doctest ^>=0.22.0' ; fi
if [ $((HCNUMVER < 91000)) -ne 0 ] ; then doctest --version ; fi
$CABAL --store-dir=$HOME/.haskell-ci-tools/store v2-install $ARG_COMPILER --ignore-project -j2 doctest --constraint='doctest ^>=0.22.0'
doctest --version
- name: save cache (tools)
uses: actions/cache/save@v4
if: always()
with:
key: ${{ runner.os }}-${{ matrix.compiler }}-tools-36dffdd0
key: ${{ runner.os }}-${{ matrix.compiler }}-tools-caa01dbf
path: ~/.haskell-ci-tools
- name: checkout
uses: actions/checkout@v4
Expand Down Expand Up @@ -247,12 +247,12 @@ jobs:
$CABAL v2-test $ARG_COMPILER $ARG_TESTS $ARG_BENCH all --test-show-details=direct
- name: doctest
run: |
if [ $((HCNUMVER < 91000)) -ne 0 ] ; then cd ${PKGDIR_effectful_core} || false ; fi
if [ $((HCNUMVER < 91000)) -ne 0 ] ; then doctest -XHaskell2010 -XBangPatterns -XConstraintKinds -XDataKinds -XDeriveFunctor -XDeriveGeneric -XFlexibleContexts -XFlexibleInstances -XGADTs -XGeneralizedNewtypeDeriving -XImportQualifiedPost -XLambdaCase -XMultiParamTypeClasses -XNoStarIsType -XRankNTypes -XRoleAnnotations -XScopedTypeVariables -XStandaloneDeriving -XTupleSections -XTypeApplications -XTypeFamilies -XTypeOperators src ; fi
if [ $((HCNUMVER < 91000)) -ne 0 ] ; then cd ${PKGDIR_effectful_th} || false ; fi
if [ $((HCNUMVER < 91000)) -ne 0 ] ; then doctest -XHaskell2010 -XBangPatterns -XConstraintKinds -XDataKinds -XDeriveFunctor -XDeriveGeneric -XFlexibleContexts -XFlexibleInstances -XGADTs -XGeneralizedNewtypeDeriving -XImportQualifiedPost -XLambdaCase -XMultiParamTypeClasses -XNoStarIsType -XRankNTypes -XRecordWildCards -XRoleAnnotations -XScopedTypeVariables -XStandaloneDeriving -XTupleSections -XTypeApplications -XTypeFamilies -XTypeOperators src ; fi
if [ $((HCNUMVER < 91000)) -ne 0 ] ; then cd ${PKGDIR_effectful} || false ; fi
if [ $((HCNUMVER < 91000)) -ne 0 ] ; then doctest -XHaskell2010 -XBangPatterns -XConstraintKinds -XDataKinds -XDeriveFunctor -XDeriveGeneric -XFlexibleContexts -XFlexibleInstances -XGADTs -XGeneralizedNewtypeDeriving -XImportQualifiedPost -XLambdaCase -XMultiParamTypeClasses -XNoStarIsType -XRankNTypes -XRecordWildCards -XRoleAnnotations -XScopedTypeVariables -XStandaloneDeriving -XTupleSections -XTypeApplications -XTypeFamilies -XTypeOperators src ; fi
cd ${PKGDIR_effectful_core} || false
doctest -XHaskell2010 -XBangPatterns -XConstraintKinds -XDataKinds -XDeriveFunctor -XDeriveGeneric -XFlexibleContexts -XFlexibleInstances -XGADTs -XGeneralizedNewtypeDeriving -XImportQualifiedPost -XLambdaCase -XMultiParamTypeClasses -XNoStarIsType -XPolyKinds -XRankNTypes -XRoleAnnotations -XScopedTypeVariables -XStandaloneDeriving -XTupleSections -XTypeApplications -XTypeFamilies -XTypeOperators src
cd ${PKGDIR_effectful_th} || false
doctest -XHaskell2010 -XBangPatterns -XConstraintKinds -XDataKinds -XDeriveFunctor -XDeriveGeneric -XFlexibleContexts -XFlexibleInstances -XGADTs -XGeneralizedNewtypeDeriving -XImportQualifiedPost -XLambdaCase -XMultiParamTypeClasses -XNoStarIsType -XPolyKinds -XRankNTypes -XRecordWildCards -XRoleAnnotations -XScopedTypeVariables -XStandaloneDeriving -XTupleSections -XTypeApplications -XTypeFamilies -XTypeOperators src
cd ${PKGDIR_effectful} || false
doctest -XHaskell2010 -XBangPatterns -XConstraintKinds -XDataKinds -XDeriveFunctor -XDeriveGeneric -XFlexibleContexts -XFlexibleInstances -XGADTs -XGeneralizedNewtypeDeriving -XImportQualifiedPost -XLambdaCase -XMultiParamTypeClasses -XNoStarIsType -XPolyKinds -XRankNTypes -XRecordWildCards -XRoleAnnotations -XScopedTypeVariables -XStandaloneDeriving -XTupleSections -XTypeApplications -XTypeFamilies -XTypeOperators src
- name: cabal check
run: |
cd ${PKGDIR_effectful_core} || false
Expand Down
2 changes: 1 addition & 1 deletion cabal.haskell-ci
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
branches: master

doctest: <9.9
doctest: <9.11
doctest-skip: effectful-plugin

tests: True
Expand Down
1 change: 1 addition & 0 deletions doctest.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ run_doctest() {
-XLambdaCase \
-XMultiParamTypeClasses \
-XNoStarIsType \
-XPolyKinds \
-XRankNTypes \
-XRecordWildCards \
-XRoleAnnotations \
Expand Down
3 changes: 3 additions & 0 deletions effectful-core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
last parameter to `Effectful.Dispatch.Dynamic`.
* Add utility functions for handling first order effects to
`Effectful.Dispatch.Dynamic`.
* Improve `Effectful.Labeled`, add `Effectful.Labeled.Error`,
`Effectful.Labeled.Reader`, `Effectful.Labeled.State` and
`Effectful.Labeled.Writer`.

# effectful-core-2.3.1.0 (2024-06-07)
* Drop support for GHC 8.8.
Expand Down
5 changes: 5 additions & 0 deletions effectful-core/effectful-core.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ common language
LambdaCase
MultiParamTypeClasses
NoStarIsType
PolyKinds
RankNTypes
RoleAnnotations
ScopedTypeVariables
Expand Down Expand Up @@ -88,6 +89,10 @@ library
Effectful.Internal.Unlift
Effectful.Internal.Utils
Effectful.Labeled
Effectful.Labeled.Error
Effectful.Labeled.Reader
Effectful.Labeled.State
Effectful.Labeled.Writer
Effectful.NonDet
Effectful.Prim
Effectful.Provider
Expand Down
2 changes: 1 addition & 1 deletion effectful-core/src/Effectful/Error/Dynamic.hs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ catchError m = send . CatchError m
-- | The same as @'flip' 'catchError'@, which is useful in situations where the
-- code for the handler is shorter.
handleError
:: Error e :> es
:: (HasCallStack, Error e :> es)
=> (E.CallStack -> e -> Eff es a)
-- ^ A handler for errors in the inner computation.
-> Eff es a
Expand Down
3 changes: 2 additions & 1 deletion effectful-core/src/Effectful/Error/Static.hs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ module Effectful.Error.Static
) where

import Control.Exception
import Data.Kind
import GHC.Stack

import Effectful
Expand All @@ -107,7 +108,7 @@ import Effectful.Dispatch.Static.Primitive
import Effectful.Internal.Utils

-- | Provide the ability to handle errors of type @e@.
data Error e :: Effect
data Error (e :: Type) :: Effect

type instance DispatchOf (Error e) = Static NoSideEffects
newtype instance StaticRep (Error e) = Error ErrorId
Expand Down
57 changes: 23 additions & 34 deletions effectful-core/src/Effectful/Labeled.hs
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE PolyKinds #-}
-- | Labeled effects.
--
-- Any effect can be assigned multiple labels so you have more than one
-- available simultaneously.
--
-- @since 2.3.0.0
module Effectful.Labeled
( -- * Example
-- $example

-- * Effect
Labeled
( -- * Effect
Labeled(..)

-- ** Handlers
, runLabeled
Expand All @@ -22,42 +21,30 @@ import Unsafe.Coerce (unsafeCoerce)
import Effectful
import Effectful.Dispatch.Static

-- $example
-- | Assign a label to an effect.
--
-- An effect can be assigned multiple labels and you can have all of them
-- available at the same time.
-- The constructor is for sending labeled operations of a dynamically dispatched
-- effect to the handler:
--
-- >>> import Effectful.Reader.Static
-- >>> import Effectful.Dispatch.Dynamic
--
-- >>> :{
-- action
-- :: ( Labeled "a" (Reader String) :> es
-- , Labeled "b" (Reader String) :> es
-- , Reader String :> es
-- )
-- => Eff es String
-- action = do
-- a <- labeled @"b" @(Reader String) $ do
-- labeled @"a" @(Reader String) $ do
-- ask
-- b <- labeled @"b" @(Reader String) $ do
-- ask
-- pure $ a ++ b
-- data X :: Effect where
-- X :: X m Int
-- type instance DispatchOf X = Dynamic
-- :}
--
-- >>> :{
-- runPureEff @String
-- . runLabeled @"a" (runReader "a")
-- . runLabeled @"b" (runReader "b")
-- . runReader "c"
-- $ action
-- runPureEff . runLabeled @"x" (interpret_ $ \X -> pure 333) $ do
-- send $ Labeled @"x" X
-- :}
-- "ab"

-- | Assign a label to an effect.
data Labeled (label :: k) (e :: Effect) :: Effect
-- 333
--
newtype Labeled (label :: k) (e :: Effect) :: Effect where
-- | @since 2.4.0.0
Labeled :: forall label e m a. e m a -> Labeled label e m a

type instance DispatchOf (Labeled label e) = Static NoSideEffects
type instance DispatchOf (Labeled label e) = DispatchOf e

data instance StaticRep (Labeled label e)

Expand All @@ -70,7 +57,9 @@ runLabeled
-> Eff es b
runLabeled runE m = runE (fromLabeled m)

-- | Bring an effect into scope to be able to run its operations.
-- | Bring an effect into scope without a label.
--
-- Useful for running code written with the non-labeled effect in mind.
labeled
:: forall label e es a
. Labeled label e :> es
Expand Down
111 changes: 111 additions & 0 deletions effectful-core/src/Effectful/Labeled/Error.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
{-# LANGUAGE AllowAmbiguousTypes #-}
-- | Convenience functions for the 'Labeled' 'Error' effect.
--
-- @since 2.4.0.0
module Effectful.Labeled.Error
( -- * Effect
Error(..)

-- ** Handlers
, runError
, runErrorWith
, runErrorNoCallStack
, runErrorNoCallStackWith

-- ** Operations
, throwError
, catchError
, handleError
, tryError

-- * Re-exports
, E.HasCallStack
, E.CallStack
, E.getCallStack
, E.prettyCallStack
) where

import GHC.Stack (withFrozenCallStack)

import Effectful
import Effectful.Dispatch.Dynamic
import Effectful.Labeled
import Effectful.Error.Dynamic (Error(..))
import Effectful.Error.Dynamic qualified as E

-- | Handle errors of type @e@ (via "Effectful.Error.Static").
runError
:: forall label e es a
. Eff (Labeled label (Error e) : es) a
-> Eff es (Either (E.CallStack, e) a)
runError = runLabeled @label E.runError

-- | Handle errors of type @e@ (via "Effectful.Error.Static") with a specific
-- error handler.
runErrorWith
:: forall label e es a
. (E.CallStack -> e -> Eff es a)
-- ^ The error handler.
-> Eff (Labeled label (Error e) : es) a
-> Eff es a
runErrorWith = runLabeled @label . E.runErrorWith

-- | Handle errors of type @e@ (via "Effectful.Error.Static"). In case of an
-- error discard the 'E.CallStack'.
runErrorNoCallStack
:: forall label e es a
. Eff (Labeled label (Error e) : es) a
-> Eff es (Either e a)
runErrorNoCallStack = runLabeled @label E.runErrorNoCallStack

-- | Handle errors of type @e@ (via "Effectful.Error.Static") with a specific
-- error handler. In case of an error discard the 'CallStack'.
runErrorNoCallStackWith
:: forall label e es a
. (e -> Eff es a)
-- ^ The error handler.
-> Eff (Labeled label (Error e) : es) a
-> Eff es a
runErrorNoCallStackWith = runLabeled @label . E.runErrorNoCallStackWith

-- | Throw an error of type @e@.
throwError
:: forall label e es a
. (HasCallStack, Labeled label (Error e) :> es)
=> e
-- ^ The error.
-> Eff es a
throwError e = withFrozenCallStack $ send (Labeled @label $ ThrowError e)

-- | Handle an error of type @e@.
catchError
:: forall label e es a
. (HasCallStack, Labeled label (Error e) :> es)
=> Eff es a
-- ^ The inner computation.
-> (E.CallStack -> e -> Eff es a)
-- ^ A handler for errors in the inner computation.
-> Eff es a
catchError m = send . Labeled @label . CatchError m

-- | The same as @'flip' 'catchError'@, which is useful in situations where the
-- code for the handler is shorter.
handleError
:: forall label e es a
. (HasCallStack, Labeled label (Error e) :> es)
=> (E.CallStack -> e -> Eff es a)
-- ^ A handler for errors in the inner computation.
-> Eff es a
-- ^ The inner computation.
-> Eff es a
handleError = flip (catchError @label)

-- | Similar to 'catchError', but returns an 'Either' result which is a 'Right'
-- if no error was thrown and a 'Left' otherwise.
tryError
:: forall label e es a
. (HasCallStack, Labeled label (Error e) :> es)
=> Eff es a
-- ^ The inner computation.
-> Eff es (Either (E.CallStack, e) a)
tryError m = catchError @label (Right <$> m) (\es e -> pure $ Left (es, e))
66 changes: 66 additions & 0 deletions effectful-core/src/Effectful/Labeled/Reader.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
{-# LANGUAGE AllowAmbiguousTypes #-}
-- | Convenience functions for the 'Labeled' 'Reader' effect.
--
-- @since 2.4.0.0
module Effectful.Labeled.Reader
( -- * Effect
Reader(..)

-- ** Handlers
, runReader

-- ** Operations
, ask
, asks
, local
) where

import Effectful
import Effectful.Dispatch.Dynamic
import Effectful.Labeled
import Effectful.Reader.Dynamic (Reader(..))
import Effectful.Reader.Dynamic qualified as R

-- | Run the 'Reader' effect with the given initial environment (via
-- "Effectful.Reader.Static").
runReader
:: forall label r es a
. r
-- ^ The initial environment.
-> Eff (Labeled label (Reader r) : es) a
-> Eff es a
runReader = runLabeled @label . R.runReader

----------------------------------------
-- Operations

-- | Fetch the value of the environment.
ask
:: forall label r es
. (HasCallStack, Labeled label (Reader r) :> es)
=> Eff es r
ask = send $ Labeled @label Ask

-- | Retrieve a function of the current environment.
--
-- @'asks' f ≡ f '<$>' 'ask'@
asks
:: forall label r es a
. (HasCallStack, Labeled label (Reader r) :> es)
=> (r -> a)
-- ^ The function to apply to the environment.
-> Eff es a
asks f = f <$> ask @label

-- | Execute a computation in a modified environment.
--
-- @'runReader' r ('local' f m) ≡ 'runReader' (f r) m@
--
local
:: forall label r es a
. (HasCallStack, Labeled label (Reader r) :> es)
=> (r -> r)
-- ^ The function to modify the environment.
-> Eff es a
-> Eff es a
local f = send . Labeled @label . Local f
Loading

0 comments on commit 0ef04f2

Please sign in to comment.