framework icon indicating copy to clipboard operation
framework copied to clipboard

Explore additional Box combinators and hierarchical improvements

Open Shadowfiend opened this issue 8 years ago • 13 comments
trafficstars

A few possibilities:

  • Add collect to Box. This can be useful for PartialFunction-based combined filter+map operations on boxes as they are on collections.
  • Add some sort of mapFailure that behaves similarly to map but only runs with a Failure. This would allow running a function only when the box is a Failure, without having to do a match that has to deal with the other three box states.
  • Restructure the Box hierarchy a little to allow a type that indicates “only Empty or Full” and another that indicates “only Failure or Full”, for example.
  • Add logging helpers for Box if possible (may need to be in lift-util).

For the last one, an untested proposal I posted to gitter a while back:

sealed trait Box[+T]
sealed trait PresenceBox[+T] extends Box[T]
sealed trait TryBox[+T] extends Box[T]
sealed abstract class EmptyBox extends Box[Nothing] // Empty or Failure
case class Failure(msg: String, exception: PresenceBox[Throwable], chain: PresenceBox[Failure]) extends EmptyBox with TryBox[Nothing]
case object Empty extends EmptyBox with PresenceBox[Nothing]
case class Full[T](item: T) extends TryBox[T] with PresenceBox[T]

Shadowfiend avatar May 22 '17 18:05 Shadowfiend

Can I take this up?

Bhashit avatar May 25 '17 10:05 Bhashit

Sure, happy to see any attempts. My example above forgot to have Empty extend PresenceBox.

Shadowfiend avatar May 25 '17 10:05 Shadowfiend

Alright.

Also, what kind of logging helpers are you looking for? Any approximate examples or a rough idea?

Bhashit avatar May 25 '17 10:05 Bhashit

Oh, logging helper wise I actually just want to port a bunch of stuff we have at Elemica into Lift. So I've more or less already figured this out, if that's okay… This was definitely a brain dump for myself, sorry.

Shadowfiend avatar May 25 '17 13:05 Shadowfiend

Alright. I'll leave that part to you then. :smiley:

Bhashit avatar May 25 '17 14:05 Bhashit

Another thought on the hierarchy:

sealed trait ParamFailableBox[+T, A] extends Box[T]

case class ParamFailure[A](..., param: A) extends ParamFailableBox[Nothing, A]
case class Full[T](item: T) extends TryBox[T] with PresenceBox[T] with ParamFailableBox[T, Nothing]

Not 100% sure that bit'll work, but it allows you to declare that something expects only a Full and a ParamFailure with a particular type to be produced.

Shadowfiend avatar May 25 '17 19:05 Shadowfiend

Looks good. That will be helpful.

One thing I wanted to make sure was what you are expecting from mapFailure. For ex. here's what I thought: using mapFailure will be an optional stage in the map/flatMap/filter pipeline, which will only get applied if the Box was a Failure. Otherwise, that stage will simply be skipped and the maps/flatMaps down the line will still be applied.

The final result will still be a Box. If the Box is indeed a failure, the operation will be applied and any further map/flatMap etc. will be skipped.

In other words: mapFailure is a way to transform your failure if it happens, otherwise go ahead with the rest of the operations as if there was no mapFailure call in the middle.

I haven't thought this through completely yet. But, is that how you saw it?

Bhashit avatar May 26 '17 05:05 Bhashit

Yes, I think that was my overall thinking. Since I jotted down some older ideas floating in my head I'm not certain that's the case though… A sample use case might be to transform between one set of ParamFailures that a service A produces to the set that service B produces, so that a consumer of service B only needs to know about its error codes.

Shadowfiend avatar May 26 '17 19:05 Shadowfiend

Alright. I think I'll create a small WIP PR soon. Maybe you can comment on relevant parts, whether or not it's what you expected and I'll make the necessary changes. Hopefully that won't be too much trouble for you. But do let me know if that doesn't work for you.

Bhashit avatar May 27 '17 05:05 Bhashit

Heh, figured out another one here, which is flipping an EmptyBox to a Full. This is useful for error handling, where sometimes you want something that will take an errored box and turn it into a Full of a different kind (e.g., a CSS selector transform). One could imagine:

  def myRenderer = {
    ".error*" #> user.logFailure("Data fetch went sideways").flip({
      case Empty => "Failed to find data."
      case ParamFailure(_, _, _, userError: String) => s"Error: $userError"
      case Failure(internalMessage, _, _) => s"Internal error: $internalMessage"
    }) &
    ".name *" #> user.map(_.name) &
    ".age *" #> user.map(_.age)
  }

Don't know that flip is the right term of course ;) This could look like, for EmptyBox:

  def flip[T](handler: (EmptyBox)=>T): PresenceBox[T] = {
    Full(handler(this))
  }

And then Full would always return Empty. Thoughts? (I used PresenceBox from the initial bit of this issue…)

Shadowfiend avatar Jun 29 '17 02:06 Shadowfiend

Looks good. I can see it being useful. flip sounds like a good name, since it turns empty to full and full to empty. Any further combinators after flip would only work if the initial result was an EmptyBox. If the user needed to do something for user.logFailure("Data fetch went sideways") being full, it would have to be done separately, and that does seem appropriate for this kind of a use case.

Bhashit avatar Jun 29 '17 02:06 Bhashit

Does a similar flipFailure method sound useful? We are already adding a collectFailure method as well. We could end up with too may combinators though.

Also, I think we will get conflicts when there's a need to merge my branch with your box-hierarchy branch. I'll try to push some final changes and then it can be merged into yours if things look appropriate maybe? Or if you want, I can do the merge and modify things as needed with the new hierarchy. Adding tests and so on.

Bhashit avatar Jul 10 '17 05:07 Bhashit

No, I think flip handles flipFailure just fine---it takes an EmptyBox, but if you want to pattern match inside it you can. I guess it won't let you only flip a Failure… I would say it's worth waiting to see if that's a material use-case in practice though.

Shadowfiend avatar Jul 22 '17 20:07 Shadowfiend