-
Notifications
You must be signed in to change notification settings - Fork 52
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Plumb rich failure types #1116
Plumb rich failure types #1116
Conversation
b06f623
to
b7d297b
Compare
src/Swarm/TUI/Model/Failure.hs
Outdated
|
||
data Asset = Achievement | Data | History | Save | ||
data PathLoadFailure | ||
= PathLoadFailure FilePath LoadingFailure |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you curry error constructor? 🙁
As far as I see, you do not use the PathLoadFailure
for anything else. The SystemFailure
in question is already named AssetNotLoaded
so what do you get by introducing this extra step?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The SystemFailure
type has an Asset
member, and it was unclear in certain low-level functions which asset is relevant. However, in the higher-level functions, the context becomes clear, and then I am able to use withExceptT
to supplement the error with the Asset
argument.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kostmo I only see the getData[Dir|File]Safe
functions. Why do this hard, when you can simply pass them the AssetData
type?
- left (AssetNotLoaded $ Data AppAsset) <$> getDataDirSafe "."
+ getDataDirSafe AppAsset "."
This also makes more sense, because no Asset
other than AssetData
is in the data
directory.
@kostmo thanks for working on this, once you are done I can rework logging to use these types. 👍 |
98f9001
to
6d62892
Compare
6d62892
to
5773db5
Compare
src/Swarm/Failure/Render.hs
Outdated
@@ -0,0 +1,37 @@ | |||
{-# LANGUAGE OverloadedStrings #-} | |||
|
|||
module Swarm.Failure.Render where |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was just commenting on a better place for the module to be, but I agree, it can/should probably just go in the Failure
module itself.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this case I had intended to use quoting functions from Swarm.Util
in the rendering code. And up until 75669d7, Swarm.Util
depended on types in the Failure
module, causing a circular dependency. So I did the work of splitting the modules, but later decided I didn't need the quoting functions after all.
However, after doing that splitting work, I'm not particularly inclined to merge Swarm.Failure
and Swarm.Failure.Render
back into a single module again---IMO, there is no downside to the types and the rendering functions being in separate modules, even if there is no technical requirement to be so. I've found it to be a common step in the evolution of a module: auxiliary functions that had been self-contained with the data definitions eventually needing to pull in an external dependency, requiring the data to move to a separate "types" module to break a circular reference. It may very well become necessary again for Failure
rendering at some point.
It fact, for data definitions that pervade the codebase, it is convenient for changes to that data's rendering functions not to cause the whole project to recompile. The only use case I've observed that demands co-locating data and their "methods" (to borrow OOP parlance) in the same module is smart constructors. My stance has been that the partitioning of Haskell code into modules is largely arbitrary, but in general I prefer smaller modules, and even splitting them pre-emptively.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kostmo that is inconsistent with the code base style though.
Rendering widgets - sure.
But pure functions that are critical to usability of the type (ParseError
and prettyParseError
) can absolutely go into the same module and they do everywhere else in Swarm.
Which language puts pure functions on data in different module? In C++ and Python, I have always seen the functions that are basically “methods” in the same module.
Regarding dependencies, your reasoning is backwards. The Util module should simply not import anything else. It has to be on the bottom of dependency graph. We should try to move stuff from it (as you did) instead of importing modules in it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Finally from a practical standpoint this is premature and silly.
We have modules 2000+loc and argue here for splitting a 100loc module in a novel way, based on OOP best practise.
The future split could go another way though - split failures based on which part of game or UI produced it.
Whoever adds more code to failures in the future can decide which split fits better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As a side note, the smart constructor use case is a good point, but it does not apply here.
We will be adding failure types and their pretty printing at the same time, not tweaking the pretty printing function. This is not about "rendering a widget" - I am not going to change the colour of exceptions tomorrow and recompile the whole code base because Text
does not have a colour Attr
that I could change.
What I am trying to point out is that failure data type and format are tightly linked, think of it like C++ e.what()
method instead of failureWidgetWithAttr
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kostmo I like how you split the modules 👍 Please just move them as I commented.
Some of this will be easier once Cabal enforces it on split libraries, but doing that is a lot of not-very-fun work, so I would appreciate it if you made it easier for me.
I can not wait to have it done and get to more enjoyable things like rewriting the logging. 😅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good to me!
38ec0e0
to
1254f79
Compare
@xsebek, moved the new modules under |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kostmo I think this is good enough to go. 👍
Please read my comment on PathLoadFailure
though - it seems to me you are unnecessarily making the types harder to use.
I can not understand the urge to create separate "Render" modules, but I guess I can live with one extra folder and a file. I will just have to sort the folders and files alphabetically and not "folders first" so it is less annoying. 😒
1254f79
to
2318036
Compare
towards #866 NOTE: #1116 should be merged first so that the schema change of save files is less disruptive. ## Examples Different criteria can have their own best score: ![image](https://user-images.githubusercontent.com/261693/219904496-fcd23ca0-b208-43e1-afc6-188acfe327cf.png) All criteria share the same single best score: ![image](https://user-images.githubusercontent.com/261693/219904553-abe3011c-41b0-469c-b34d-95d84b91697a.png) ## Behavior notes * As currently designed, the code size will only be scored if the the player has specified their code **before** the scenario begins. Furthermore, any input into the REPL will invalidate code size scoring for the current game. * Because of this, the only way to score code so far is with a command-line argument of `--run` or `--autoplay`. However, #1010 shall implement code size scoring when a scenario is launched from the UI. * In the "best scores" display, if multiple "best score" criteria were all from the same game, they will be consolidated. If all criteria are for the same game, the criteria labels will be omitted. * The code size metrics will not be displayed if a "best score" was not obtained via `--run`. ## Caveats ### `run` command Currently, the code entailed in a `run "somescript.sw"` command is not transitively included, so using `run` make the code size score meaningless. ## Testing ### Unit tests Run the subset of unit tests: scripts/run-tests.sh --test-arguments '--pattern "Tests.Precedence"' ### Manual integration tests First, reset the score: rm -f ~/.local/share/swarm/saves/Tutorials_grab.yaml Saving the following to `grab-soln.sw`: ``` move; move; grab; turn back; move; turn back; move; move; grab; turn back; move; turn back; move; move; grab; turn back; move; turn back; move; move; grab; turn back; move; turn back; move; move; grab; turn back; move; turn back; move; move; grab; ``` Run as follows: scripts/play.sh --scenario Tutorials/grab.yaml --run grab-soln.sw This should establish a record for code size. Then, play the Grab tutorial and immediately paste and run this in the REPL: move; move; grab; move; grab; move; grab; move; grab; move; grab; move; grab; This solution is faster in terms of time, but should not displace the code-length record, since no code length should be recorded from a REPL solution.
closes #1115
This PR makes use of the rich types defined in #982 and converts many functions of
Has (Throw Text)
type toExceptT
.It also logs a warning instead of crashing with terminal failure if "save" info cannot be loaded, which will be important for #974 where the save file schema changes.