Monday 30 April 2012

Validation without Scalaz

[2012-05-01 Update: added snippets showing case where check functions are in a collection.]
[2012-05-06 Update: links now point to new branch, 'simple' instead of 'master' one.]

So it's the desire to implement checks (that inputs are valid) in a more functional way (than by throwing exceptions) that's one of the main motivations for adopting Scalaz, it seems.

I've encountered evidence for this on at least a couple of occasions: first, at Chris Marshall's talk about practical uses of Scalaz in the financial industry at Skills Matter, and most recently, at Noel Welsh's Blue Eyes presentation at Scala Days 2012.

Having played around with the standard Scala library's scala.Either myself recently, after Noel's talk, I got to asking a few people why more use wasn't made of that.

Apparently, this is because Either can't be used in for comprehensions involving multiple generators (since it lacks a flatMap method), and because there's no support for accumulating bad results.

So I've since taken a closer look, and discovered that it can in fact be used in said for comprehensions, and that with the help of this twenty-odd line type-class, can also be used to accumulate bad results!

The challenge I set myself was to try to rewrite Chris's (very helpful) Nightclubs (Github) gist without using Scalaz. This I succeeded in doing, as shown here.

The trick is to use the Either's right: RightProjection method in for comprehensions. So this...


...becomes this:


And Chris's code for accumulate bad values...



...may be rewritten using an implicit method provided by the type class:


Finally, if the checks happen to be in a collection, this...


...becomes this:



Please help yourself to the type class, which is maintained in a Github project called scala-either-extras.

Comments welcome.

11 comments:

  1. Nice. Any way out of using scalaz is a Good Thing.

    ReplyDelete
  2. What to you makes scalaz so repulsive that you'd prefer to reinvent parts of it in a less expressive way than just using what is already there in scalaz?

    I can accept that all of the talk in scalaz of applicative functors , monoids and such can seem dense and inaccessible at first. However, if you give them a chance you'll realise that scalaz just defines a whole heap of design patterns, abstractions and data structures that apply beautifully to functional programming. That's it. It isn't trying to force scala people to become category theorists as all you really need to care about are a couple of interface properties of each of the category based structures to use them effectively. Surely a few extra interfaces and operations are not an insurmountable cognitive burden for a smart programmer that has just freed themself from the burden of oo and the conflation of computation and side effect? ;P

    Anyhow I'm not trying to be a troll here. I am merely curious as to why the thought of introducing some slightly dense math concepts into their programming toolbox is so repulsive. If it doesn't work for you and feels like it never will then that is fine, but if there is something that the scalaz community could do better to make it less scary then it would be good to know.

    ReplyDelete
    Replies
    1. Ben, I think the code speaks for itself - the version which uses Scalaz is just more complex. It's as simple as that. And it seems to be introducing new classes unnecessarily.

      If I get a simpler and expressive-enough result by writing a small amount of extra code myself, versus introducing a dependency on a vast edifice, that's what I'd prefer to do.

      Maybe the Scalaz version could have been written in a better way - I don't know. However, I'm sure there are other cases where Scalaz code would be a great fit.

      I'm all for using the sort of abstractions that Scalaz defines, but would prefer to avoid introducing external dependencies unless this is really warranted.

      So I can only point out this case where Scalaz doesn't appear to be the best bet. I'd have to spend a good while looking at Scalaz again in order to be of more help than that.

      Delete
    2. You're wrong, in layers. Do you care?

      Delete
    3. Your code compared to the scalaz code is definitely neater and simpler for that particular task; I can agree with that. However, in throwing away scalaz Applicatives and essentially rewriting them means that you can't reuse that pattern elsewhere. When you program in a functional style, lots of your data structures fall into the Applicative Functor and Monoid categories; so this becomes a really big deal.

      I don't know about you, but I would much prefer a little extra ugly but have consistency across my project than many specialised reinventions of the same thing everywhere. The way that scalaz uses implicits to emulate typeclasses makes extending scalaz to fit your types really easy and unobtrusive.

      What have you read about scalaz? Talks like this are a good start but unfortunately the scaladoc of scalaz doesn't help. The main reasons why I understand scalaz is because I've learned these patterns in haskell already and have watched a handful of talks about it.

      http://www.slideshare.net/oxbow_lakes/practical-scalaz

      Delete
    4. I have an answer to your question. It is testable. It is also hilarious by its implication. We should go to the pub.

      Delete
    5. Ben,
      > in throwing away scalaz Applicatives and essentially rewriting them

      I have only now (see next blog entry) added applicative-functor support.

      I know I'm only providing it for Either, but that alone seems to get you a long way for validation purposes.

      I went along to that very talk, then basically did as Chris suggests at the end (including reading Learn You a Haskell). Despite this, I suppose it's the 'purity' of using only the standard classes that appeals to me (you only need to name EitherExtras in your code).

      Delete
  3. Rob: Very nice. Anything that moves away from Scalaz is welcome.

    By the way, here is a tip to get Tony to stop trolling you: ask him to actually say something helpful instead of hand waving and you'll never hear from him again.

    ReplyDelete
  4. Here's the real Martin Odersky. I did not write this comment and I do not appreciate at all having my identity hijacked like that.

    ReplyDelete
  5. Here's what I was just writing. Sure I can package this in a monad and possibly use Either instead of Options to simplify it but I don't feel like it :)

    You can probably infer the missing bits, all the implicit conversions (from both Option and Boolean) etc...

    def doSomething(child: String) = Action { implicit request =>
    implicit val errCollector = new VError()
    (for (
    u <- auth orErr ("not authenticated");
    p <- session.get("pwd") orErr ("no pwd in session");
    c <- Users.findUserById(child);
    ok1 <- (child.lastName == u.lastName) orErr ("not your child, eh?")
    ) yield {
    Ok(views.html.user.edChildren(edprofileForm2.fill((t, n)), child, u))
    }) getOrElse Unauthorized("Oops - and the error is... " + errCollector.mkString)
    }

    I will probably tweak it until it looks like (transparent collector):

    (for (
    u <- auth orErr ("not authenticated");
    p <- session.get("pwd") orErr ("no pwd in session");
    c <- Users.findUserById(child);
    ok1 <- (child.lastName == u.lastName) orErr ("not your child, eh?")
    ) yield {
    Ok(views.html.user.edChildren(edprofileForm2.fill((t, n)), child, u))
    }) getOrElse {errors =>
    Unauthorized("Oops - and the error is... " + errors)
    }
    }


    cheers,
    Razie

    ReplyDelete
  6. Pretty frustrating to write the .right after each result in the for comprehension. Don't know what would be the downsides, but this works for me well:


    /**
    * Prevent writing '.right' each time after a validation result in a for comprehension
    */
    implicit def rightProjection [A,B] (either: Either[A,B]) = either.right

    ReplyDelete