purescript-debug icon indicating copy to clipboard operation
purescript-debug copied to clipboard

Possible API enhancements?

Open Trequetrum opened this issue 3 years ago • 2 comments

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?

Trequetrum avatar Mar 19 '21 21:03 Trequetrum

I can't say predicated spying is something I've particularly needed, I just ignore the spys 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 * _)

?

garyb avatar Mar 25 '21 13:03 garyb

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

Trequetrum avatar Mar 26 '21 17:03 Trequetrum