purescript-debug
purescript-debug copied to clipboard
Possible API enhancements?
Spy with a Predicate
Currently, Debug has spyWith, which is fantastic for formatting what you're spying on.
spyWith :: forall a b. DebugWarning => String -> (a -> b) -> a -> a
I think it would be nice to augment this to allow for a predicate that decides if a value is interesting enough to be printed
spyWithMaybe :: forall a b. DebugWarning => String -> (a -> Maybe b) -> a -> a
or perhaps
spyWithPred :: forall a b. DebugWarning => String -> (a -> Boolean) -> (a -> b) -> a -> a
The strength of having such functionality is that you have a quick and dirty way to see how only a subset of some output is created. For example: "I'm surprised that this functions maps some of my 10,000 generated inputs to values < 100. Is my function/algorithm wrong or are there actually values for which this output makes sense?"
Without it, you have to break this functionality out and filter the results of some intermediate value. It might be nice to have a quick and dirty way to grab some examples and reason about those individually.
Point-free spying
This is really an argument for aesthetic.
If you enhance spy (or make a new function) that takes a function instead of a value, then it can seamlessly compose with other functions, though you do make writing out a value less inviting.
Right now something like:
weirdZero :: Number -> Number
weirdZero =
(_ + 5.0) >>>
(_ / 5.0) >>>
spy "from 100" (100.0 - _) >>>
(0.0 * _)
Will attempt to print a partially applied function and then return that partially applied function. Which doesn't break anything, but isn't desirable either.
To have this work the way you'd want, you'd have to re-write it like this:
weirdZero :: Number -> Number
weirdZero =
(_ + 5.0) >>>
(_ / 5.0) >>>
\n -> spy "from 100" $ (100.0 - _) n >>>
(0.0 * _)
or this:
weirdZero :: Number -> Number
weirdZero =
(_ + 5.0) >>>
(_ / 5.0) >>>
(spy "from 100" <<< (100.0 - _)) >>>
(0.0 * _)
Instead, you can take a function and its argument as arguments.
newSpy :: forall a b. DebugWarning => String -> (a -> b) -> a -> b
newSpyWith :: forall a b c. DebugWarning => String -> (b -> Maybe c) -> (a -> b) -> a -> b
Now you can write it either way
weirdZero :: Number -> Number
weirdZero =
(_ + 5.0) >>>
(_ / 5.0) >>>
newSpy "from 100" (100.0 - _) >>>
(0.0 * _)
weirdZero :: Number -> Number
weirdZero =
(_ + 5.0) >>>
(_ / 5.0) >>>
\n -> newSpy "from 100" (100.0 - _) n >>>
(0.0 * _)
which I think looks much cleaner in the first case and doesn't need $ in the second case since newSpy String
now has the same type signature as apply. In most use-cases, you can write newSpy "some string" in most expressions and get something intelligible back.
The drawback is if you want newSpy to print a value, it gets worse as you'd still have to feed it the identity function.
identity :: forall a. a -> a
identity x = x
weirdZero :: Number -> Number
weirdZero =
(_ + 5.0) >>>
(_ / 5.0) >>>
\n -> newSpy "from 100" identity $ (100.0 - _) n >>>
(0.0 * _)
I rarely need to print a value, it's usually the result of some function. Maybe that's different for others?
I can't say predicated spying is something I've particularly needed, I just ignore the spy
s that aren't relevant :smile: but I can see that it makes sense. I think the Maybe
version seems like the better option, since it's one function that can work both for the with
case and a simple predicate by using guard
to produce the Maybe
.
I'm not sure I understand your second point though. The purpose of spy
over trace
is that it's already for logging in the middle of expressions, so I'm not sure why the function/point free aspect is an issue - why wouldn't you just write something like
weirdZero :: Number -> Number
weirdZero =
(_ + 5.0) >>>
(_ / 5.0) >>>
(100.0 - _) >>>
spy "from 100" >>>
(0.0 * _)
?
Yeah, I think you're right about both points.
The second isn't really about being point-free the more that I think about it.
I just like being able to shove "spy String
" in-front of any function quickly without adding any addition syntax. No $
or >>>
for example. That being said, it doesn't really change anything and it's pretty straight forward to implement myself if I'm THAT keen on avoiding the extra keystroke :)
newSpy :: forall a b. String -> (a -> b) -> a -> b
newSpy tag fn = fn >>> spy tag