Closed MangoIV closed 2 weeks ago
Not at all! Sorry, you misunderstand me. My point is that this is a functionality that should be provided at extremely low cost for the programmer. My argument is that every „workaround“ to not having this provided at as low cost as possible increases the likeliness that bad practises are being used. I don’t think that people usually add and remove packages to their cabal files just to do what is equivalent to todo. They’d rather use undefined and error "", at least that’s my impression. So what I’m saying is that in practise, contrary to what is claimed, adding and removing a cabal package for this over and over is not an alternative.
@MangoIV You give the following reasons for not using typed holes for this:
- they are, by default, errors, not warnings
- they are very noisy (by default they produce a lot of output)
- they are slow (by default)
As an alternative it might be possible to special case a named typed hole _todo
in GHC which would then by default not have these downsides but the semantics you want? Upsides would be that there isn't an additional definition in base and it is almost impossible to break any code since published code usually doesn't contain typed holes.
i think this sounds like an actual alternative. Of course this would mean added code to the compiler, and certainly not two lines, but I think it’s a cool idea!
Are you suggesting we add stuff to base, because after editing your cabal file, HLS doesn't start fast enough??
While I personally don't care too much about the restart time of HLS (only one of the five steps mentioned by @MangoIV), I do think the rest of the steps are slightly annoying. You have to context switch to another file, find the relevant lines, modify them, etc. For a typical quick script I find that I need to:
filepath
temporary
glob
or directory
process
extra
unordered-containers
Each of which induces a trip down to the Cabal file. What happens in practice is that I end up writing it in Python. On the one hand you could argue this is a tooling issue (HLS should offer to add the dependencies), on the other hand base
is just too limited to write anything practical.
All of this is to say: I don't think @MangoIV's comment on adding a package can be viewed in isolation. It's just another housekeepy thing you need to do even when writing fairly simple code.
Edit: as far as this proposal goes: I'm in favor, though we probably should put it in Debug.Trace
for reasons already mentioned by others.
All of this is to say: I don't think @MangoIV's comment on adding a package can be viewed in isolation. It's just another housekeepy thing you need to do even when writing fairly simple code.
I don't disagree too much with your analysis, but I do think that solving these problems is a totally different topic, and adding more to base
itself doesn't seem, in principle, to be the only (or best) answer here.
As in, I think we're desperately in need of tooling to start new projects in a more convenient way, which would help a lot; but that's a bit of a different issue I think.
I totally agree with @silky here. Personally I like this proposal a lot (although I'm skeptical about exporting todo
from Prelude
) but I don't think the justifications for including things in base
(or Prelude) that "it's hard to depend on packages" or "it's hard to import identifiers" hold any water. They are good arguments for improving tooling though!
but that's a bit of a different issue I think.
It is also a different issue because the libraries listed by @martijnbastiaan provide non-trivial functionality, that working code will depend on. That's not the case with todo
lib, which you want to get rid of, at some point.
So even if adding it to the Cabal file would be automatic, removing it is another issue altogether (not to mention, adding/removing per development cycle). I agree with @MangoIV that this is just not a viable alternative.
On the other hand, for HLS
users at least, I don't think it is too much of a burden to import todo
from, say, Debug.Trace
. It will end up as an unused import at some point, but this is fine, as dealing with unused imports is a separate issue.
I think @BinderDavid's suggestion from https://github.com/haskell/core-libraries-committee/issues/260#issuecomment-2016429304 is very reasonable, avoids namespace pollution and thus breakage, and probably is not all that hard to implement either.
For a typical quick script I find that I need to:
* Construct some path: `filepath` * Create a temporary directory: `temporary` * Find some files: `glob` or `directory` * Launch a process: `process` * Trim write space from read lines: `extra` * Do some basic processing: `unordered-containers`
Each of which induces a trip down to the Cabal file.
@martijnbastiaan but why? Cabal supports single-file scripts fairly well:
#!/usr/bin/env cabal
{- cabal:
build-depends: base, filepath, temporary, directory, process, extra, unordered-containers
-}
main :: IO ()
main = pure ()
@Bodigrim Cabal scripts complicate caching on CI. In any case, my main point was that setting up new projects / executables / libraries is already a noisy affair in Haskell. Hence, "just use the todo
package" doesn't really do it for me. (I think others have articulated it better than me, and perhaps we can continue this specific discussion somewhere else to not pollute this thread.)
My initial reaction was "sure, do whatever you want, I'm not going to use it", but after @mixphix pointed out that todo
-in-base means you can't guarantee a package is free from TODOs, and @Bodigrim pointed out that todo
will appear as a valid hole fit for anything, I'm against it. Adding things that require additional compiler magic (to not interfere with other features) seems like a bad idea.
Cabal scripts complicate caching on CI.
Again, this line of argumentation is bizarre to me.
I don't really care what problems people have with adding packages to their cabal files (as a CLC member, at least). This is part of doing Haskell.
Whether to add a function/symbol to base
should have a higher bar than "I'm too lazy to import a package". Now, todo
definitely may qualify. Other stdlibs have it (the rust macro was mentioned).
However, in all this discussion I'm still missing a good argument why we can't just add a warning to undefined
and call it a day. "Werror users will be sad" is not a good argument to me. That would mean we can never introduce deprecations or new warnings. That's not how we operate, I believe.
I'd also be interested in the opinion of @chshersh
However, in all this discussion I'm still missing a good argument why we can't just add a warning to undefined and call it a day.
I believe https://github.com/haskell/core-libraries-committee/issues/260#issuecomment-2016332241 is a somewhat good reason to not do this.
But those warnings are entirely suppressible, no? Like disabling orphan warnings in modules that contain orphan instances.
I do think a warning on undefined
is an outright terrible idea.
It is a combinator that has a number of legitimate uses. I mean it says it right on the tin, you may well have to use it when the answer is undefined
. I say this as someone who is not a member of the Werror crowd but likes to have his code build clean because its used by so many users.
There's tons of base packages that use it as a form of detailed strictness annotation. Witness all the macros in libraries for stuff like bytestring
that do things like:
foo a b c | a `seq` b `seq` False -> undefined
before proceeding to all the actual patterns to avoid encoding necessary strictness as a side-effect of the first pattern, and/or to support ancient platforms that might not have bang patterns.
I have several hundred uses of it (modulo what I'm sure are a few false positives) in my codebase.
... and the only way to suppress the warnings it would pick up are insufficiently granular and would mask much more legitimate warnings.
I have little objection to adding TODO
to a module in base
as I think it is quite clever and crosses a non-triviality threshold with the warning, interactions with callstack, interactions with polykinds and runtime representations. Especially once the pattern is introduced. That sort of thing is pretty tightly coupled with the compiler and isn't what we should expect the typical Haskeller to be capable of fully rolling on their own.
I personally do think it fails to rise to the Prelude
bar, though. Especially not in one fell swoop. If we add it to base
and in a few years it is seeing widespread community adoption, by all means we can consider re-exporting it from Prelude
, but this rarely turns out to be the case, as adoption usually lags early enthusiasm, and practices take a long time to change.
There's lots of useful stuff we could pack into Prelude
. We import minimal subsets of a lot of libraries for historical reasons, and taking fresh names, especially useful common-looking ones, there has to be a very carefully considered thing as they break the code of anybody else who took that same name unless they explicitly exclude that import.
Haskell's semantics could have been defined such that local names trump imported names, and this would mitigate this to some extent, (though you'd still run into it the moment someone exported that symbol and used it in another module), but it wasn't.
And no, I don't think requiring everyone to import Prelude
qualified or with explicit usage masks as some vague future-proofing technique is an acceptable alternative. The rate of these incidents is quite low and the maintenance burden of such explicit imports is quite high.
I believe #260 (comment) is a somewhat good reason to not do this.
Those are, and I'm also firmly in the "no warning on undefined
" camp, but I do feel I'd be remiss to observe that this particular case would have been better handled via the Proxy
trick had it existed at the time those libraries were written.
An interesting exercise for the committee would be to find a way to migrate siblings of those methods to Proxy
and/or explicit type arguments with an eye towards the eventual deprecation of the originals.
If not Prelude
, which module would be best suited for this?
Debug.Trace
is imported very often for similar (only temporarily for debugging) purposes, so I would propose that one, else, but perhaps less probable, I would even prefer to have a Debug
module… (less to type, less misleading)
I can refine the proposal accordingly if people think that’s a good idea.
While I think the module name is a bold choice, I'd be quite fine with Debug
. It is clear that you are doing something temporary.
And to a lesser extent with Debug.Todo
(which I think would be confusing with the pattern being named TODO) or even something in GHC.Debug
if push came to shove.
I kind of think Debug.Trace
has some connotations to it that are lacking here. This isn't about tracing execution and carrying on. That module is basically all about printf-style debugging. This ain't that.
The main question I'd have is if something like unimplemented
should go in the same place (sans warning) to cleave apart the idea of something that the author intends to be implemented later and deserves a warning from the stuff that is merely not implemented because you are forcibly implementing a typeclass that doesn't quite fit your semantics or whatever.
I have one more question, which I didn't see addressed above. I realize the primary intended use of these is progressive implementation, but I try not to be TOO prescriptive in language design. Should this use a raise# of an errorCallWithException
like it does now or should it use a typed exception? Error calls definitely get handled if someone is handling "error", but if we ever want someone to be able to distinguish these without checking the exact text of the message in a handler, then a typed exception with enclosed callstack might be a better fit.
Also if you are going to use raise# for this, it starts to makes sense to include todoIO
to ensure that it doesn't get lifted into the wrong place when used to replace an IO action, like with how we have both throw
and throwIO
. Note that this is definitely pushing in the direction of it being in a module of its own.
Thank you for the remarks, I will consider the typed exceptions part but I don’t know if the other proposals (adding even more things) are in scope for this proposal:
As I see that including in Prelude
seems unlikely at this point in time as many CLC members and also users object, I will adjust the proposal as follows:
Debug
that reexports Debug.Trace
Debug.<I have to come up with a good name suggestions welcome>
(edit: I think Debug.Placeholder
might be a good idea?) that defines the todo
function as proposed originally and may be amended in future by similar functions Debug
Prelude
after a reasonable testing periodIf CLC members so wish, I can also make a separate proposal for the Debug
module itself.
@ekmett oh I could see interesting tooling that would aggregate and report on UnimplementedException
s. Nice idea!
Exporting it from a module named Debug.Todo
on its own is exactly what the todo
package already does. You can already write import Debug.Todo
at the top of your module and, if you have chosen to depend on the todo
package, all of its apparent glory and wonder is available. It does not need to be exported from base
.
I think it is unhelpful to ignore a large part of the arguments made if we want to come to a good conclusion. :/
Let's invite the maintainer of todo
, @nyson, to the discussion.
ignore a large part of the arguments made
Don't piss me off.
I likeDebug.Placeholder
and top level Debug
re-export of that and Debug.Trace
. If you are going to put together the module I'd include the todoIO
and unimplemented
and unimplementedIO
bits there, no point leaving the job half done. I admit I'm just one voice, and I'm not even on the CLC these days, so take my input for what it is worth. If we make these throw proper exception types, then including the exception types themselves in here also makes sense.
It seems worth noting that this is a different API than @nyson's Debug.Todo
for better or worse. More general (works at more type reps), but less control over error messages, and doesn't include the trace machinery in the placeholder module, though does in the Debug
top level export.
Also the suggestion of a pattern synonym I think would flesh things out well into non-trivial territory.
I'm very sympathetic to wanting an easy at hand tool for debugging purposes. One reason why I favor base
over a separate package is quite banal: I do switch in and out of using these sorts of tools rapidly, and stuff that forces me to change a cabal file over and over and over as these come in and out of use means that tools like ghcid
break every time I change the package dependency set. That sort of rapid oscillation isn't really the case with something like, say, filepath
, etc. where the addition to a package's dependencies tends to be quite monotone.
A workflow where I can be chugging along, decide I'm not ready for something and just casually tack in an 'import Debug' and put TODO
cases in my code that then get tracked and reported during builds rather than yelled about purely at runtime seems desirable, and I can know I've well and truly cleaned up the code when the import Debug
statements are gone as a sanity check.
A typed hole works just as well, is more common and established in Haskell codebases, provides more information, and prevents accidental compilation of incomplete code. What's the problem?
For the record, my current favourite implementation is @ekmett's at https://github.com/haskell/core-libraries-committee/issues/260#issuecomment-2016344264. I don't know what module it should go in, but I don't understand why it should be in the Debug
hierarchy. What does it have to do with debugging?
A typed hole works just as well, is more common and established in Haskell codebases, provides more information, and prevents accidental compilation of incomplete code. What's the problem?
I want to be able to run the code that I do have complete. A typed hole doesn't let me do that without using a much more drastic flag like delaying everything to runtime type errors and that sweeps everything under the rug, so it isn't suitable for purpose.
I don't know what module it should go in, but I don't understand why it should be in the
Debug
hierarchy. What does it have to do with debugging?
Fair point. I think we were perhaps primed by the existing package which lives in Debug.Todo
.
Perhaps Control.Placeholder
or Control.Todo
or Control.Unimplemented
would fit better with the current hierarchical namespace standard. Even if it is slightly long, nobody could really complain about taking a name like that.
On the other hand, on reflection I'm somewhat negative on the top level Debug
module, because there's a lot of folks who develop in haskell for applications with top level namespaces, and a Debug
module is a common end-user type name, and they'd have to do something awful like use package imports to hack around GHC stealing their module name from them.
One negligible advantage of Control.Todo
is a "grep -r -i todo src"
would find those and your todo-list items in one go, and is basically a typical sort of scan folks use to find todos today. The downside is that it'd find imports of that module for unimplemented
's too, which pushes me towards Control.Placeholder
.
Once again, base
is not the place for an unstable or experimental design. Vacillating between pattern synonyms and a top-level declaration only leads me to believe that this proposal has not been well thought out and is not suitable for the standard library. Once evidence has been gathered for an interface that is both stable and in common use, I may reconsider my position on this issue. Until then, continuing this discussion when there already exist multiple ways of obtaining this functionality that have been repeated several times over in this thread does nothing but to increase my desire not to effectuate this change to base
.
Once again,
base
is not the place for an unstable or experimental design. Vacillating between pattern synonyms and a top-level declaration only leads me to believe that this proposal has not been well thought out and is not suitable for the standard library.
The pattern synonym is an augmentation to a solid base proposal. I'd live without it, but I do think this is the right time to bring up such wibbles and to steelman the proposal to be the best it can be.
The existence of such wibbles and talking them through in excruciating detail is the very thing the core libraries committee was created to do. If every discussion that ever made it here went through without discussion and without variants being proposed and discarded, I'd view that as frankly a broken and flawed process. This is precisely the time and place to sharpen proposals. I'd rather see more proposals than fewer, and that means that some of them are going to be less than fully baked. I don't think we should merge everybody's first idea into base
, but that doesn't mean they can't be the seed of the right idea.
I also accept the fact that I don't always get what I want, democratic sausage gets made and the process is unsightly.
Once evidence has been gathered for an interface that is both stable and in common use
Both undefined
and error
are stable and in common use, so evidence is already here (does not apply to pattern synonym, but it might be strictly better, I don't know).
Both of your proposed alternatives completely miss the point; holes introduce compile errors, while todo
removes it, a library is not a match to built-in functionality for "quick and dirty" programming technique. Doing nothing and sticking to error "TODO"
is a better alternative than those two (and, IMO, an acceptable one).
I offer another improvement to the proposal: let's mark todo
as deprecated, with the message that it is deprecated and can be removed at any point in the future. That way, no promise of any kind of stability is being made.
I've taken the liberty of throwing something together over in ekmett/placeholder with all the suggestions above taken in. (You may have seen a notification cross-post to here a half-hour ago.)
If somebody wants to hack and slash on that to make a bunch of examples and tests, and/or even co-maintain this on hackage I'm all about that. Reach out.
If this proposal goes through, then maybe that can serve as a robustly worked candidate.
If not, then maybe it can serve as @mixphix's "do this outside of base" Schelling Point.
ignore a large part of the arguments made
Don't piss me off.
@mixphix Respectfully, this is straying from the HF Guidelines For Respectful Communication. While they're merely guidelines, I'd like to think that the CLC could be held to such a standard.
I do think a warning on undefined is an outright terrible idea.
It is a combinator that has a number of legitimate uses.
This argument has been laid out multiple times and yet I've not seen an explanation why we should treat undefined
any different from head
and tail
, which have even more legitimate uses and are throwing warnings now.
Thats a bit of an imprecise argument but I think that
(I personally would also not have proceeded by adding warnings to head and tail, tbh)
The only argument I can see is that:
head
and tail
: uncons
undefined
(which is what I gathered from @ekmett's post)But then: do all warnings have to be actionable, in the sense that: "I can rewrite my code in a semantic preserving way to get rid of the warning"?
do all warnings have to be actionable, in the sense that: "I can rewrite my code in a semantic preserving way to get rid of the warning"?
Definitely.
But then: do all warnings have to be actionable, in the sense that: "I can rewrite my code in a semantic preserving way to get rid of the warning"?
I would say the answer should be yes, but independent of that, I think more importantly, I would expect the scale to be different. I e very, very few legitimate uses of head and tail and a lot of legitimate uses of undefined. This is again very anecdotal, I don’t have any numbers on that.
I have an idea for an alternative to this proposal: do what Haskell does best... abuse do
notation.
foo :: Bool -> Int
foo = \cases
True -> do
let bar = "something"
do -- do it later
False -> do -- same
This doesn't work right now (and won't ever with NondecreasingIndentation
) as it is an error (Empty 'do' block
), but if GHC had a flag to defer it... Bonus points: it is simpler to type than todo
:)
Just as a joke to steelman @jeukshi's comment...
ghci> :set -XBlockArguments
ghci> let to = error
ghci> to do "something"
*** Exception: something
CallStack (from HasCallStack):
error, called at <interactive>:4:10 in interactive:Ghci2
I have an idea for an alternative to this proposal: do what Haskell does best... abuse do notation.
For the matter of this proposal, I would like to keep it as simple and obvious as possible, while I think overloading syntax per se is a nice idea for certain usecases, I don't think it's appropriate for this usecase, at all.
after this long and, I think, very fruitful discussion, I have amended this proposal with several small improvements and adjustments s.t. the CLC is more likely to accept it and the ecosystem will benefit even more from it.
Thanks @MangoIV, I like the amendments, except I still don't see why this belongs in the Debug
hierarchy. I'm not sure what more I can add about that than it's simply not used for debugging. I don't find the claim that '"writing code" is closest to "debugging"' convincing at all. I don't have a particularly good alternative though.
I think the proposal and implementation are excellent. Normally I am very opposed to adding anything to base
that can be defined outside it but the case of todo
is a special situation. It's a good argument that pulling in a separate package for it is a heavyweight operation. I sympathise with both sides of the matter.
I don't have a particularly good alternative though.
I also don't find my arguments very convincing, I'm still searching for a third alternative; I personally find Control
even worse though.
I don't think it's appropriate for this usecase, at all.
I disagree, although I don't think it is worth discussing right now.
I just want to state that, I like this proposal, and I like it better than any alternative posted here. I don't know if it deserves to be accepted, but I, for sure, would love the future where I can replace my (bad?) habit of spamming undefined
with spamming todo
.
Dear Haskell core library committee.
Currently there are multiple ways of describing something as unimplemented
Relude
placeholders or the todo packageproblems of the above solutions
base
:undefined
:error
:todo
, it doesn't seem worth adding an alternativePrelude
or even add a dependency just for the sake of thisThat's why propose a function
todo
that has the following properties:"todo remains in code"
in groupx-todo
so that it can be used withWError
for e.g. CIundefined
base:Prelude
implementation of the solution
try it out in a separate module, as such:
This implementation will work from
>= ghc981
impact
on hoogle I currently find 4 functions which this would break, two of which have similar semantics to this one, making it improbable that they will be found in code out there.
Another advantage of this function is that there will be no need of a
*-compat
package because code that does contain this function is not supposed to live anywhere or compile with more than the compiler than thebase
version this is going to be shipped with supports.I will obviously run a proper impact assessment though.
why I think this belongs in
Prelude
The main reason I think this belongs into
Prelude
is because it is not possible to replace this with the same level of simplicity with any other solutionmixins
or add new imports; Additionally already having to add an additional dependency to use this, would, I think, be too much effort for many people to use itGHC
, they are not supported first class byGHC
, with the advent of the{-# WARNING -#}
we have first class support for these kinds of things that don't add any additional overheaderror
andundefined
don't haveWARNING
s attached to them and I don't think they should have; they have been as they are for a long time, it would impose a great cost to change their semantics; even though it is debatable whether you should, people useundefined
in their code to signify unreachable or permanently unimplemented parts of their code, if we want to add a warning, I think it should be on something that clearly tells the programmer and the reader of the code "this is not done yet"I think this will also have the positive impact of offering a real alternative to dangerous uses of
undefined
also look at
rust std's
todo!
andunimplemented!
macrosrendered docs
some more screenshots
how it looks, loading a module using
todo
intoGHCi
:how it looks when trying to load a module using
todo
with{-# OPTIONS_GHC -Werror=x-todo #-}
intoGHCi
:Amendment to this proposal (28 Mar 2024)
After what I consider a very productive and diverse discussion, I think the most prominent conclusion is that this might not make the jump into
Prelude
all at once. I still want to proceed with this proposal for the following two reasons:base
Prelude
eventually; this seems to be only possible when something has been inbase
for a long time and one has to start at some point ;)a new module,
Debug.Placeholder
Develop.Placeholder
There will be a new module
Debug.Placeholder
Develop.Placeholder
that will contain both thetodo
function as described above and implemented in ekmett'splaceholder
library (thank you for throwing this together) (except some improvements I will propose wrt type applications)TODO
pattern synonym as is implemented in theplaceholder
library (except some improvements I will propose wrt type applications)These implementations include many useful improvements to the function described above, which is really awesome.
The name of the module is justified as follows:
Control
or the like because it's only temporarily, you do something with the definitions in the process of programming, but don't really intend to keep the definitions@tbidne has proposed the namespaceDebug
is the namespace that I think most fits the semantics of these functions intuitively, after all,todo
is something you insert in the code while "writing code" which is closest to "debugging", I'd sayDevelop
which I think is the most fitting until nowNote: if people think that the proposed namespace is incorrect, I would like to hear convincing arguments and am happy to adjust accordingly.
out of scope for this proposal
While there were some really nice suggestions to make the proposal "more complete", I will consider the following out of scope for this proposal while expressing the strong intention to later add them in a follow-up proposal:
{u,U}nimplemented
function and patterns: the reason why I want to avoid them for now is that they will spark additional discussions on whether you really need both of these and what the semantics of them should really be, whether they require warnings, etc.{unimplemented,todo}IO
variations of these: the reason why I want to avoid these is that I think it will spark discussion on whether we will need these in the case of onlytodo
existing, as it may never remain in codeDevelop
that will export many useful functions that you need only for work-in-progress code, such as thetodo
family of functions as well as thetrace*
family of functions