Hacker News new | past | comments | ask | show | jobs | submit login

I just personally hate seeing a giant method chains in a code block.

How do i know how this call chain behaves in production? Where's all the logical exit branches from this for error recovery? Is this properly null checking if required?

I see this in python and javascript all the time. This style of programming is what i call 'happy path' programming. It only works properly with valid input.

Go forces the developer to explore non-happy path cases during development. This leads to java's 'check for null' boilerplate everywhere. The syntax is ugly however, personally believe that it leads to higher quality systems.




Learn the functional style, and it will look so much more simple.

>Where's all the logical exit branches from this for error recovery?

No exit; the error is carried on in the pipe until the end. Successive transformations simply have no effect, since the data is in error. The chain output type can be either the data you want, or an error.

> Is this properly null checking if required?

Stop using null's.

> This style of programming is what i call 'happy path' programming. It only works properly with valid input.

The unhappy path can be handled just as well, you merely have to incorporate it in the type of output your system can produce. In that way, errors are also a happy path.

> Go forces the developer to explore non-happy path cases during development. [...] The syntax is ugly however, personally believe that it leads to higher quality systems.

When I program in a language like Haskell, not exploring unhappy paths leads to code that doesn't even compile. Usually this leads to pretty good quality.


> How do i know how this call chain behaves in production?

> Where's all the logical exit branches from this for error recovery? Is this properly null checking if required? I see this in python and javascript all the time. This style of programming is what i call 'happy path' programming. It only works properly with valid input.

If this is done correctly this fluent pattern is usually a monad. This works really well with typed languages that have variant types that enforce pattern matching like Scala and Rust.

This pattern is actually far less error prone than imperative null checking (in my experience) as you have to to deal with the wrapped value but it can be equally tedious if the language isn't expressive.

An example of this syntax for Java can be seen in RxJava.


> Is this properly null checking if required?

Yes, because if you're typing the word null in your Scala code you're doing something wrong. Any Java library that might return null should always be wrapped in an Option, so that you're dealing with Some or None, and you never get an NPE.

    Option(myJavaLibraryFactoryMethodUsers()).flatMap(_.sortBy(- _.age).headOption)
...will get you an Option[User] of the oldest user. People who have written Scala for more than six months can immediately recognize what this line of code does, and it eliminates around 20 lines of equivalent Java (or Go?) code. This kind of thing rarely needs to be refactored.


Pedantic since you're just making a point, but maxBy would be the better performing choice though you'd have to do a non-empty check...or just a fold if you can stand a default on empty list.


Scala also forces the developer to explore non-happy path cases. But it does so with meaningful abstractions that don't force you to do it on every single line.

Scala lets you write your code in the style of 'happy path' programming:

    val result: MyError \/ ResultType = for {
        x <- f(input)
        y = g(x)
        z <- h(y)
    } yield (q(z,y))
Here `g(x)` is a pure function which never has errors. But f(input) and h(y) are impure functions which could return errors.

The error case is handled by `result` being `myError.left[ResultType]`.

To actually access `ResultType`, you use fold:

    result.fold(err => ..., r => success(r))
This makes it pretty hard to ignore the error case.


Scala carries some baggage from being on the JVM that makes this not-quite-true, but it mostly enforces all of those things in the type system. There's no null-checking required, and if something can fail, the type system should tell you.

e.g. your method signatures will read

doSomethingRisky(): Try[Result]

and to use that value you'll either have to carry that Try forward, or handle its failure cases.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: