What should I use for effect handling?

For small examples, the tutorial for dynamic and static effects has a few (also, for dynamic dispatch there’s a direct comparison with mtl style definition and additional info how to integrate with existing mtl style effects).

There’s also:

  • a filesize benchmark that contains implementations for various effect libraries, in particular mtl (though it’s still quite simple).

  • an example how effectful integrates with conduit in this ticket.

As for the actual real world usage, I know that flora-server and monocle (a link to the PR is here, they also wrote a blog post about it) both switched to effectful from the ReaderT pattern.

5 Likes

Try using search terms like:

  • haskell shared mutable state
  • haskell "data race"

…the latter brought up this article for me:

…with its data-race-example program.

Now, as for an example of definitions only looking free of observable effects behaving badly in the presence of multiple parallel threads…my current 762Mibyte installation of GHC lacks Control.Parallel, so whatever code examples I could write up and show here would be untested, so I’ve decided not to - we need more working examples, not less.


Ensuring correctness of effects usage is difficult enough in an I/O context, so the absence of that context and the manifest ordering it provides makes matters even more challenging:

I was passing handles individually as parameters, but I found number of arguments rising and eventually the type became too big. This is why I resorted to MTL style.

Haskell’s function type (->), when partially applied: (->) r …is monadic:

# ghci
GHCi, version 9.0.2: https://www.haskell.org/ghc/  :? for help
ghci> :i ->
type (->) :: * -> * -> *
type (->) = FUN 'Many :: * -> * -> *
  	-- Defined in ‘GHC.Types’
infixr -1 ->
instance Applicative ((->) r) -- Defined in ‘GHC.Base’
instance Functor ((->) r) -- Defined in ‘GHC.Base’
instance Monad ((->) r) -- Defined in ‘GHC.Base’
instance Monoid b => Monoid (a -> b) -- Defined in ‘GHC.Base’
instance Semigroup b => Semigroup (a -> b) -- Defined in ‘GHC.Base’
ghci> :q
Leaving GHCi.
#

If your code only uses that extra argument for reading, that is usually enough. But if writing is also required, you’ll need a specific variant of ((->) r) which is sequential. Assuming WriteOut is intended for writing:

{-# LANGUAGE BangPatterns, FlexibleInstances #-}

instance {-# OVERLAPPING #-} Monad ((->) WriteOut) where
    m >>= k = \ wr -> let !x = m wr in k x wr

instance {-# OVERLAPPING #-} Applicative ((->) WriteOut) where
    pure x    = \ wr -> x
    f' <*> x' = \ wr -> let !f = f' wr in
                        let !x = x' wr in
                        f x

instance {-# OVERLAPPING #-} Functor ((->) WriteOut) where
    fmap f m = \ wr -> let !x = m wr in f x

…and if needed, a type synonym can help to avoid repetitive keystrokes:

type WrOut a = WriteOut -> a

Those long argument lists can then be used more monadically…

I mean, my problem is just having a function with 10s of arguments. It’s too long and hard to track for me.

For inspiration, you could read this old thread, and the associated article:

So package up arguments that are commonly used together into a product type?

1 Like

Sorry, but I do not understand how the thread could help. Could you elaborate?

I cannot, each handle/config/parameter is sometimes used and sometimes not used and it all differs.

Why’s that a problem? If it’s not used, then just don’t use it!

Perhaps you could point to some example code, because I’m finding it hard to understand in the abstract what exactly you are looking for.

I mean if I were to use each as a separate parameter, I would have something like:

foo :: A -> B -> C -> D -> E -> IO ..
bar :: A -> C -> D -> E -> IO ..
baz :: B -> C -> D -> E -> IO ..
barf :: A -> B -> C -> E -> IO ..
baze :: A -> B -> C -> IO ..

Grouping parameters do not make it more convenient here.

Why not? Why not put them all in

Params = Params A B C D E

?

@tomjaguarpaw I don’t understand why you’re so adamant on using this (suboptimal) technique.

We had similar conversation on reddit recently and I pointed out the same thing @Abab9579 says, i.e. that passing these explicitly is unwieldy for more than a few effects and if you want to group them in a record, you’ll have to create a bunch of records because your functions take various subsets of these.

This will work for foo. What about the rest of functions?

1 Like

I don’t understand why you’re so adamant on using this (suboptimal) technique.

I don’t understand why you think it’s suboptimal! Normally in functional programming when we want to depend on something we pass it in. Why not for depending on effects, just the same as for depending on normal values? And if you get really tired of passing it in manually, use a ReaderT.

This will work for foo. What about the rest of functions?

The rest of the functions also take a subset of those parameters, so will work equally well. This is no worse than two other situations @Abab9579 might be in:

  1. Already using a sequence of concrete monad transformers, so passing in each one individually is not really worse

    AT a (BT b (CT c (DT d (ET e m)))) a
    

    is not really better than

    A -> B -> C -> D -> E -> m a
    
  2. Already using a single concrete monad

    AppT m a
    

    is not really better than

    ReaderT Handles m a
    
2 Likes

It won’t, because now your type signatures “lie” in a sense that it looks like they use all these effects, whereas in reality they don’t.

This is your post from the other thread:

With what you propose you lose this propoerty, don’t you? :thinking: Now it looks like your functions are using more effects than they do in reality.

Looks like he’s using a polymorphic monad and class constraints:

2 Likes

Sure, but only in the sense that pure () :: MonadState Int m => m () is a lie. It’s not a very bad lie.

With what you propose you lose this propoerty, don’t you? :thinking:

Yes, it’s a lie that acts against that nice property.

Looks like he’s using a polymorphic monad and class constraints

Yes maybe. I’m having trouble understanding the baseline for comparison, which is why I’m having trouble making a concrete suggestion. Most of my dialogue in this thread has been Socratic.

That said, I’ve been doing some experiments with “handles as arguments” and so far I’m very pleased with the experience, versus “handles only in a type-level list”.

Oh I forgot, there are also things like

createC :: A -> B -> IO C
createE :: A -> B  -> C -> D -> IO E

in addition to the above, used to create other handles.

For instance, Logger is positioned at the baseline and used for constructing other handles.

This is also why I found “tying the knot” approach enticing. I am wary of the additional dependency footprint though.

Interesting! I think I’m finding this code hard to imagine. I can certainly imagine a logger effect being used in handling another effect, but I can’t imagine it being used to create another effect. I guess at this point I’m out of suggestions unless you can link to some real code.

Sorry that I cannot specify concrete code now.
I could roughly talk why I need logging for creating handles though.

For instance, I find it good practice to log when connecting to a DB. That way, I can know if error happened later, and what was the problem.

I am making a desktop application for linux, and interfacing with DBus.
Connecting to DBus could be complicated, so I also put some logging inside it.

2 Likes

Oh yes, makes perfect sense. I guess I am just too used to seeing handlers in CPS form, such as

withC :: A -> B -> (C -> r) -> r

So I rescind my previous puzzlement.

2 Likes