MercuryTechnologies / ghciwatch

Load a GHCi session for a Haskell project and reload it when source files change
https://mercurytechnologies.github.io/ghciwatch/
MIT License
111 stars 11 forks source link

Toggle reinterpreting modules to evaluate private top-level binds #319

Open theGhostJW opened 2 months ago

theGhostJW commented 2 months ago

Describe the feature you’d like to be implemented

Currently if I have evals in place like this:

-- $> hello
hello :: IO ()
hello = T.putStrLn "Hello"

-- $> world
world :: IO ()
world = T.putStrLn "World"

and I kick off ghciwatch

ghciwatch \
  --log-filter info \
  --before-startup-shell 'hpack --force' \
  --restart-glob package.yaml \
  --command 'cabal repl --repl-no-load' \
  --watch src --watch test --watch examples --watch testIntegration \
  --allow-eval --clear --backtrace full

after making a change to the file i get this:

• examples/WebDriverDemo.hs:185:7: hello
[1 of 1] Compiling WebDriverDemo    ( examples/WebDriverDemo.hs, interpreted )
Ok, one module loaded.
Hello
• examples/WebDriverDemo.hs:189:7: world
[1 of 1] Compiling WebDriverDemo    ( examples/WebDriverDemo.hs, interpreted )
Ok, one module loaded.
World

It seems ghci is being reloaded once for prior to each evaluation even though only one change has been made to the file.

It would be great if ghciwatch only reloaded once so I got this (quicker and less noisy):

• examples/WebDriverDemo.hs:185:7: hello
[1 of 1] Compiling WebDriverDemo    ( examples/WebDriverDemo.hs, interpreted )
Ok, one module loaded.
Hello
World

List alternatives to the feature and their pros and cons

No response

Additional context

Current behaviour is slower and noisier than that requested. This is particularly true if you temporarily living with multiple warnings (such as undefined somewhere in code) in this part of the dev cycle.

9999years commented 2 months ago

I think this might be unavoidable because interpreting the module is needed to access its top-level binds for the eval feature:

https://github.com/MercuryTechnologies/ghciwatch/blob/main/src/ghci/stdin.rs#L138-L168

Try some of these commands locally and let me know if you can find a way to get it to avoid recompiling the module though?

theGhostJW commented 2 months ago

As far as running commands locally goes, this is definitely how ghcid works:

the following is a log from a project I am working on prior to switching to ghciwatch:

Note the warnings etc are logged once first then all the eval results:

ghcid --command 'cabal repl' pyrethrum --allow-eval --clear --no-height-limit '-o ghcid.log' 

Loading cabal repl pyrethrum ...
Warning: The package list for 'hackage.haskell.org' is 82 days old.
Run 'cabal update' to get the latest list of available packages.
Resolving dependencies...
Build profile: -w ghc-9.8.2 -O0
In order, the following will be built (use -v for more details):
 - pyrethrum-0.1.0.0 (interactive) (lib) (configuration changed)
Configuring library for pyrethrum-0.1.0.0...
Preprocessing library for pyrethrum-0.1.0.0...
GHCi, version 9.8.2: https://www.haskell.org/ghc/  :? for help
[ 1 of 36] Compiling ArbitraryIOTest  ( test/ArbitraryIOTest.hs, interpreted )
[ 2 of 36] Compiling AuxFiles         ( src/AuxFiles.hs, interpreted )

..... and many more

134 | mkFixture = \case
    | ^^^^^^^^^
examples/PyrethrumBase.hs:141:1-6: warning: [GHC-40910] [-Wunused-top-binds]
    Defined but not used: ‘mkHook’
    |
141 | mkHook = \case
    | ^^^^^^
examples/PyrethrumBase.hs:155:1-6: warning: [GHC-40910] [-Wunused-top-binds]
    Defined but not used: ‘mkNode’
    |
155 | mkNode = \case
    | ^^^^^^
examples/PyrethrumBase.hs:165:1-9: warning: [GHC-40910] [-Wunused-top-binds]
    Defined but not used: ‘mkTestRun’
    |
165 | mkTestRun tr = mkNode <$> tr
    | ^^^^^^^^^
examples/PyrethrumDemoTest.hs:6:17-19: warning: [GHC-38856] [-Wunused-imports]
    The import of ‘Out’ from module ‘DSL.Out’ is redundant
  |
6 | import DSL.Out (Out, out)
  |                 ^^^
examples/PyrethrumDemoTest.hs:7:24-27: warning: [GHC-38856] [-Wunused-imports]
    The import of ‘:>’ from module ‘Effectful’ is redundant
  |
7 | import Effectful (Eff, (:>))
  |                        ^^^^
test/SuiteRuntimePropTest.hs:(26,1)-(35,2): warning: [GHC-38856] [-Wunused-imports]
    The import of ‘Verbose, Verbose’
    from module ‘Test.Tasty.Falsify’ is redundant
   |
26 | import Test.Tasty.Falsify (
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^...

..... and many more

/home/theghostjw/pyrethrum/pyrethrum/test/SuiteRuntimeTest.hs:14:1
$> unit_simple_pass

/home/theghostjw/pyrethrum/pyrethrum/test/SuiteRuntimeTest.hs:18:1
$> unit_simple_fail

/home/theghostjw/pyrethrum/pyrethrum/test/SuiteRuntimeTest.hs:22:1
$> unit_nested_pass_fail

/home/theghostjw/pyrethrum/pyrethrum/test/SuiteRuntimeTest.hs:86:1
$> unit_nested_threaded_chk_thread_count

/home/theghostjw/pyrethrum/pyrethrum/test/SuiteRuntimeTest.hs:157:1
$> unit_empty_thread_hooks

/home/theghostjw/pyrethrum/pyrethrum/test/SuiteRuntimeTest.hs:188:1
$> unit_pass_prob_pregen

/home/theghostjw/pyrethrum/pyrethrum/test/SuiteRuntimeTest.hs:192:1
$> unit_pass_prob_no_pregen

/home/theghostjw/pyrethrum/pyrethrum/test/SuiteRuntimeTest.hs:209:1
$> unit_prop_test_fail_template_wrong_result

/home/theghostjw/pyrethrum/pyrethrum/test/SuiteRuntimeTest.hs:236:1
$> unit_prop_fail_each_after_out_of_order

/home/theghostjw/pyrethrum/pyrethrum/test/SuiteRuntimeTest.hs:254:1
$> unit_prop_fail_each_after_out_of_order1

/home/theghostjw/pyrethrum/pyrethrum/test/SuiteRuntimeTest.hs:272:1
$> unit_prop_fail_each_after

/home/theghostjw/pyrethrum/pyrethrum/test/SuiteRuntimeTest.hs:297:1
$> unit_missing_setup

/home/theghostjw/pyrethrum/pyrethrum/test/SuiteRuntimeTest.hs:311:1
$> unit_wrong_result

/home/theghostjw/pyrethrum/pyrethrum/test/SuiteRuntimeTest.hs:343:1
$> unit_fail_wrong_counts
unit_fail_wrong_counts

/home/theghostjw/pyrethrum/pyrethrum/test/SuiteRuntimeTest.hs:374:1
$> unit_missing_hooks

/home/theghostjw/pyrethrum/pyrethrum/test/SuiteRuntimeTest.hs:402:1
$> unit_wrong_failure_path

/home/theghostjw/pyrethrum/pyrethrum/test/SuiteRuntimeTest.hs:436:1
$> unit_once_failure_missing

example code is here: https://github.com/theGhostJW/pyrethrum/blob/ghcid-play/test/SuiteRuntimeTest.hs

This is just a branch of a reasonably big project. I can cut this down to a minimal example if that would be useful

9999years commented 2 months ago

ghcid doesn't let you access unexported top-level bindings in eval comments, so it doesn't have to reinterpret the modules under inspection. Maybe I could have a toggle for this?

theGhostJW commented 2 months ago

So if I understand you correctly you are saying that the behviour of ghcid comes with an extra limitation in that "private" functions are not able to be evaluated. Its not a limitation I've noticed before because I usually develop in fully open modules and then restrict the exports when the api of the module is bedded down. By that time I'm done with this type of stubbing anyway.

If this is the case I think a toggle would be awesome. Different users will prefer different behaviour at different stages.