haskell / core-libraries-committee

95 stars 16 forks source link

Add the `todo` function #260

Closed MangoIV closed 2 months ago

MangoIV commented 8 months ago

Dear Haskell core library committee.

Currently there are multiple ways of describing something as unimplemented

problems of the above solutions

  1. Problems of the functions currently in base:
    • undefined:
    • doesn't give you a warning if it remains in code
    • doesn't read as "this is open, I will definitely implement it soon"
    • a lot to type for "please compiler, be quiet for the second, I will come back to this"
    • error:
    • also doesn't give you a warning if it remains in code
    • may read as "this is open, I will definitely implement it soon" but this is a lot to type
    • even more to type for "please compiler, be quiet for the second, I will come back to this", sometimes even needs paranthesis
  2. external dependencies:
    • just for doing a quick debugging todo, it doesn't seem worth adding an alternative Prelude or even add a dependency just for the sake of this
  3. typed holes
    • they are, by default, errors, not warnings
    • they are very noisy (by default they produce a lot of output)
    • they are slow (by default)

That's why propose a function todo that has the following properties:

implementation of the solution

{-# LANGUAGE ImplicitParams #-}
{-# LANGUAGE MagicHash #-}
{-# OPTIONS_GHC -Wall #-}

module Todo (todo) where

import GHC.Base (raise#, TYPE, RuntimeRep)
import GHC.Exception (errorCallWithCallStackException)
import GHC.Stack (HasCallStack)

{- | 'todo' indicates unfinished code. 

It is to be used whenever you want to indicate that you are missing a part of 
the implementation and want to fill that in later. 

The main difference to other alternatives like typed holes and 'undefined' 
or 'error' is, this does not throw an error but only emits a warning. 

Similarly to 'undefined', 'error' and typed holes, this will throw an error if 
it is evaluated at runtime which can only be caught in 'IO'. 

This is intended to *never* stay in code but exists purely for signifying
"work in progress" code. 

To make the emitted warning error instead (e.g. for the use in CI), add 
the @-Werror=x-todo@ flag to your @OPTIONS_GHC@.

==== __Examples__

@
superComplexFunction :: 'Maybe' a -> 'IO' 'Int'
-- we already know how to implement this in the 'Nothing' case
superComplexFunction 'Nothing' = 'pure' 42
-- but the 'Just' case is super complicated, so we leave it as 'todo' for now
superComplexFunction ('Just' a) = 'todo'
@
-}
todo :: forall {r :: RuntimeRep} (a :: TYPE r). (HasCallStack) => a
todo = raise# (errorCallWithCallStackException "Prelude.todo: not yet implemented" ?callStack)
{-# WARNING in "x-todo" todo "todo remains in code" #-}

try it out in a separate module, as such:

module TodoExample where

import Todo

superComplexFunction :: Maybe a -> IO Int
-- we already know how to implement this in the 'Nothing' case
superComplexFunction Nothing = pure 42
-- but the 'Just' case is super complicated, so we leave it as 'todo' for now
superComplexFunction (Just a) = todo

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 the base 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 solution

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! and unimplemented! macros

rendered docs

rendered docs without expanded Examples rendered docs with expanded Examples

some more screenshots

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:

  1. accessibility and visibility as well as usefulness in general (least overhead, least tooling issues, least possibility of divergence, etc.) will be greatest if this is in base
  2. and most importantly: I still want this is Prelude eventually; this seems to be only possible when something has been in base 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 the

These implementations include many useful improvements to the function described above, which is really awesome.

The name of the module is justified as follows:

Note: 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:

MangoIV commented 2 months ago

There is already a standalone package on ekmett/placeholder. It just needs to be published on hackage.

Bodigrim commented 2 months ago

Additionally, adding the -fdefer-typed-holes flag has one (minor?major?) drawback: I can now no longer use typed holes as an error alongside with typed holes as a warning.

This. That's why I never considered typed holes as an alternative for todo, I'm an ardent adept of hole-driven development, so suppressing the warning is not an option for me. I feel that holes are much more widerly used for HDD than for -fdefer-typed-holes.

Bodigrim commented 2 months ago

@MangoIV thanks for the proposal, I greatly appreciate all work and effort you put into it.

...it's quite a good testimony of how cumbersome any substantial improvements, many of which are even somewhat universally agreed on, will be, just for the reason of them creating a lot of churn in the ecosystem. Mind that this is no criticism but instead a pledge for a more glorious future after the base split.

I'm not quite sure what you refer by "the base split" and how it would help to evolve base faster. If anything, splitting ghc-internal is to make base more stable by excluding the volatile GHC component.


While the fact that CLC votes against vox populi might be perceived by some as worrying, I strongly believe that it's not. Let me remind that everyone wishing to affect CLC decisions is most welcome to nominate themselves at the next CLC elections (which is January 2025) and help us together make the world a better place to write Haskell.

tomjaguarpaw commented 1 month ago

There is already a standalone package on ekmett/placeholder. It just needs to be published on hackage.

It has now been published: https://hackage.haskell.org/package/placeholder

Thanks to @MangoIV and @ekmett for making this idea into a package