kingofthehomeless / in-other-words Goto Github PK
View Code? Open in Web Editor NEWA higher-order effect system where the sky's the limit
License: BSD 3-Clause "New" or "Revised" License
A higher-order effect system where the sky's the limit
License: BSD 3-Clause "New" or "Revised" License
polysemy-plugin
is a godsend for working with polysemy
, and it's possible its functional dependencies component could be adapted to work with in-other-words
.
I'm on a sabbatical, and I'm devoting some of that time to slamming myself face first into the bleeding edge of stuff that I think is neat in Haskell. I'm presently throwing myself at getting in-other-words
building on 9.2.2 and then going on to write some libraries on top of it, so I mostly just want to use this ticket to track my findings and potentially get your input on how to fix things that are broken.
It'll take me a bit to get spun up on all the machinery here, so any pointers are highly valued. :)
StarIsType
warnings (easy fix)Carrier
instancesControl.Effect.Internal.Newtype.UnwrapTopC
(looks like UnwrappedEff
is hanging around too long, maybe?)Control.Effect.Writer.ListenIntoEndoListenC
: Could not deduce (Member (Tell (Endo o)) (Derivs m)) ... from the context: (Monoid o, HeadEffs '[Listen (Endo o), Tell (Endo o)] m)
Control.Effect.Writer.WriterIntoEndoWriterC
: As aboveControl.Effect.Union.UnionizeHeadC
Could not deduce (Member (Union b) (Derivs m))
arising from a use of ‘inj’
from the context: (HeadEff (Union b) m, KnownList b)
Could not deduce: Derivs m
~ (_e0 : StripPrefix '[Union b] (Derivs m))
from the context: (HeadEff (Union b) m, KnownList b)
Expected: Union (Derivs m) z a
Actual: Union (_e0 : StripPrefix '[Union b] (Derivs m)) z a
interpretViaHandler
usesI suspect these are all tied up in the Carrier
instances, but I don't understand the machinery well enough yet to fix it.
Control.Effect.Trace.traceIntoTell
: Could not deduce (Member (Tell String) (Derivs m)) ... from the context: HeadEff (Tell String) m
Control.Effect.Writer.tellIntoEndoTell
: Could not deduce (Member (Tell (Endo o)) (Derivs m)) ... from the context: (Monoid o, HeadEff (Tell (Endo o)) m)
Control.Effect.Writer.listenIntoEndoListen
: As above, plus Listen (Endo o)
Control.Effect.Writer.writerIntoEndoWriter
: As above, plus Pass (Endo o)
Control.Effect.Writer.tellIntoTell
and Control.Effect.Writer.tellIntoTellSimple
: Could not deduce (Member (Tell o') (Derivs m))
, which I expect is just more of the carrier issue poking through.I'm using GHC 8.8.4 and I get
[typecheck] [E] • Overlapping instances for Member
(State [i0]) '[State [String], Tell String, Throw [Char]]
arising from a use of ‘get’
Matching instances:
instance forall a (e :: a) (r :: [a]) (_e :: a).
Member e r =>
Member e (_e : r)
-- Defined in ‘Control.Effect.Internal.Membership’
instance [overlapping] forall a (e :: a) (r :: [a]).
Member e (e : r)
-- Defined in ‘Control.Effect.Internal.Membership’
(The choice depends on the instantiation of ‘i0’
To pick the first instance above, use IncoherentInstances
when compiling the other instance declarations)
• In the first argument of ‘(>>=)’, namely ‘get’
In a stmt of a 'do' block:
get
>>=
\case
[] -> throw "Inputs exhausted!"
(x : xs) -> put xs >> return x
In the first argument of ‘runAskActionSimple’, namely
‘(do get
>>=
\case
[] -> throw "Inputs exhausted!"
(x : xs) -> put xs >> return x)’
I have all the required language extensions in my cabal file, not sure if I need IncohrentInstances.
Like polysemy
's Tagged
and fused-effects
's Labelled
. I've held off for three reasons:
Bundle
, so users can use Eff (Tagged s (Error e))
constraints in code, and this raises a lot of questions about implementation.polysemy
's Tagged
-- and one function where there's a functional dependency from the label to the effect -- like fused-effects
's Labelled
. This also raises a lot of questions about implementation.in-other-words
that the lack of Tagged
isn't so bad.It could be possible that there exists more general primitive effects that would encompass some that are currently used by the library without being more demanding to ThreadsEff
.
Any such effect could be then added as a helper primitive effect, and the primitive effects it encompasses could then be specializations of the more general primitive effect.
Mask
in particular strikes me as an effect for which a more general primitive effect exists. Perhaps the following:
data Bifunctorial s m a where
Bifunctorially :: s a (m a) -> Bifunctorial s m a
(where s
is constrained to be a Bifunctor
)
or the following:
data BaseReify b m a where
BaseReify :: (forall f. (forall x. m x -> b (f x)) -> b (f a))
-> BaseReify b m a
(essentially a more restricted version of BaseControl
.)
These are enough to implement Mask
, and you can cook up monad transformers that can thread these but not BaseControl
, but it's not clear if there are monad transformers that can thread Mask
but not these.
What about exporting the constructor of Conc
and/or
unliftConc :: Eff Conc m => ((forall x. m x -> IO x) -> IO a) -> m a
from e.g. Control.Effect.Internal.Conc
in order to be able to use other methods in the style of Control.Concurrent.Async
, e.g. from UnliftIO.Async
?
First off: I like this effect system a lot, I think this is a very promising direction.
I have implemented readline-in-other-words for providing a haskeline compatible Readline effect utilizing the InputT
monad transformer as a novel monad transformer, and was able to do everything reasonably well using the guides on the wiki, but have a few lingering questions:
Carrier m
constraint on most interpreters even when it isn't strictly necessary. I was able to compile
main :: IO ()
main = runM $
runReadline defaultSettings $
handleInterrupt (outputStrLn "Interrupt!" *> repl) $
withInterrupt $ do
mline <- getInputLine "> "
case mline of
Nothing -> pure ()
Just line -> outputStrLn line *> repl
runReadline
didn't have any of the threading/Carrier
constraints. I think I will probably add them for consistency, but it feels like a mistake to have extraneous constraints unless they are needed for type safety in a way that I don't understand.BaseControl
's constructor isn't exported except through internal modules. This was an obstacle when trying to write a ThreadsEff InputT (BaseControl b)
instance because InputT
doesn't conform to MonadBaseControl
thus I couldn't use threadBaseControlViaClass
but it still admits an implementation via withRunInBase
. This ended up not being a problem because I implemented the threading constraints on ReadlineC
anyways, and then just implemented a MonadBaseControl
instance for ReadlineC
, but it might be nice to expose it anyways for similar circumstances if that would be possibleMonadIO m
in
runReadline ::
(MonadIO m, MonadMask m) =>
H.Settings m ->
ReadlineInterruptC m a ->
m a
runReadline settings = H.runInputT settings . unReadlineInterruptC
runInputT
has it as a constraint. Similarly I require that constraint for my Carrier
instance because most of the actions require it. I don't see any instances where MonadIO
constrains the base monad the main library, instead it seems to use Eff (Embed IO) m
when it needs to accomplish something similar. I don't see how to perform this conversion. If that is possible, is a similar conversion possible for MonadMask
?ThreadsEff
instances on one of my carrier newtypes ReadlineC
instead of InputT
directly to avoid orphan instances. Its still ergonomic because I can coerce between the two different carriers, but doesn't follow the main pattern which is for the threading constraints to be defined directly on the monad transformers. Do you have an opinion on this? In general it seems that libraries outside of the main one will always have this problem because the only way orphans are avoided in the main library is by defining them in the same module as the class.*-in-other-words
naming scheme rather than the traditional in-other-words-*
naming scheme for integration package (e.g. polysemy-readline
) because it reads better in English grammarOof, this ended up a lot longer than I initially expected! If you made it this far, I'll add that I would appreciate it if you reviewed my library before I publish it on hackage: https://github.com/lehmacdj/readline-in-other-words/blob/main/src/Control/Effect/Readline/Internal.hs
Thanks!
The documentation says that interpret
is more performant than interpretSimple
. But It seems that the only difference is that interpret
uses reify
/reflect
to pass the effect handler where interpretSimple
uses a ReaderT
monad. My question is how exactly does this make a difference? I have a vague feeling that this has something to do with the typeclass machinery but I'm not very sure.
The traditional ContT
-- which is used for runContFast
and runShiftFast
-- has always had problems lifting higher-order effects. It currently only threads ReaderPrim
, building on the MonadReader
instance that ContT
has. Now, it turns out that the MonadReader
instance has bad semantics. Because of that, I'm considering removing the ThreadsEff
instance ContT
has for ReaderPrim
, making it so that ContFastThreads
accepts no primitive effects whatsoever.
Since the Error
effect can be interpreted as exceptions, one reasonable use case of Error
is to mark codes that might throw exceptions, such as readFile
:
readFile' :: (Eff (Embed IO) m, Eff (Error IOException) m) => FilePath -> m String
readFile' = embed . readFile
test :: IO (Either IOException String)
test = runM $ errorToIO $ readFile "testfile"
But because errorToErrorIO
throws and catches the wrapper OpaqueExc
instead of the exception itself, the underlying exception IOException
won't actually get handled as one might want it to. I feel like there should be an interpreter that operates on the exception itself instead of OpaqueExc
. Also, I'd like to argue that interpreting two identical Error e
effect isn't really a common use case.
This is a big one. It's possible that with enough type-level hackery, most of the many obtuse error messages detailed on the wiki can be made less obtuse. This warrants a lot of research and experimentation. At the moment, I don't know the best approach.
threadBaseControlViaUnlift
requires that the relevant transformer is representational in the transformed monad,which is not satisfied by abstract monad transformers. A relevant use-case for removing this restriction is for haskeline's InputT
, as shown in #14.
Two plausible approaches to solve this:
BaseControl
MonadTransControl
-based rather than MonadBaseControl
-basedMonadBaseControl
dictionary into BaseControl
.A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.