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?
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?
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:
-
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
-
Already using a single concrete monad
AppT m a
is not really better than
ReaderT Handles m a
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? 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:
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?
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.
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.
Thank you, I should stop caring about having too many parameters and embrace the length of types.
With some global variables sprinkled around, it should be manageable I think.