7

I've been reading about the OO 'fluent interface' approach in Java, JavaScript and Scala and I like the look of it, but have been struggling to see how to reconcile it with a more type-based/functional approach in Scala.

To give a very specific example of what I mean: I've written an API client which can be invoked like this:

val response = MyTargetApi.get("orders", 24)

The return value from get() is a Tuple3 type called RestfulResponse, as defined in my package object:

// 1. Return code
// 2. Response headers
// 2. Response body (Option)
type RestfulResponse = (Int, List[String], Option[String])

This works fine - and I don't really want to sacrifice the functional simplicity of a tuple return value - but I would like to extend the library with various 'fluent' method calls, perhaps something like this:

val response = MyTargetApi.get("customers", 55).throwIfError()
// Or perhaps:
MyTargetApi.get("orders", 24).debugPrint(verbose=true)

How can I combine the functional simplicity of get() returning a typed tuple (or similar) with the ability to add more 'fluent' capabilities to my API?

Community
  • 1
  • 1
Alex Dean
  • 15,575
  • 13
  • 63
  • 74
  • This "fluent interface" approach...doesn't look very friendly towards statically-typed languages. Although I'm sure with some wrapping code and inheritance you could probably do it with Scala, it just won't be as type safe as having `getOrders` and `getCustomers` separately, instead of `get("orders")` and `get("customers")` using the same `get` method. – Dan Burton Dec 30 '11 at 16:59
  • Thanks Dan - but I wouldn't worry too much about the `get("slug", id)` syntax - this isn't really what my question is about. In any case there's another more typesafe mode in the library which looks like `MyTargetApi.orders.get(id)` – Alex Dean Dec 30 '11 at 17:10
  • Personally I think you should offer a more representative example of some fluent code and exactly which bit you think is not functional. At the moment, it just appears from your question that you don't really know what fluent means – oxbow_lakes Dec 30 '11 at 17:28
  • Why not use `Either`? http://stackoverflow.com/questions/1193333/using-either-to-process-failures-in-scala-code – earldouglas Dec 30 '11 at 19:05
  • Thanks **James** - `Either` is an interesting idea. It's something I'll be using elsewhere in my library, because an `orders.get()` response once marshalled will be `Either[ErrorRepresentation, OrderRepresentation]` – Alex Dean Dec 31 '11 at 10:53
  • 1
    You might want to have a look at Tony Morris' presentations on `Reader` and `Writer` as well – oxbow_lakes Dec 31 '11 at 10:57
  • Thanks **oxbow_lakes**, I will take a look, links: [Writer monad example](http://blog.tmorris.net/the-writer-monad-using-scala-example/), [Reader monad introduction](http://blog.tmorris.net/configuration-without-the-bugs-and-gymnastics/) – Alex Dean Dec 31 '11 at 11:35
  • @DanBurton There's nothing wrong with fluent interface and statically typed languages. It's just the example that sucks at implementing it. – Daniel C. Sobral Jan 02 '12 at 18:33

3 Answers3

8

It seems you are dealing with a client side API of a rest style communication. Your get method seems to be what triggers the actual request/response cycle. It looks like you'd have to deal with this:

  • properties of the transport (like credentials, debug level, error handling)
  • providing data for the input (your id and type of record (order or customer)
  • doing something with the results

I think for the properties of the transport, you can put some of it into the constructor of the MyTargetApi object, but you can also create a query object that will store those for a single query and can be set in a fluent way using a query() method:

MyTargetApi.query().debugPrint(verbose=true).throwIfError()

This would return some stateful Query object that stores the value for log level, error handling. For providing the data for the input, you can also use the query object to set those values but instead of returning your response return a QueryResult:

class Query {
  def debugPrint(verbose: Boolean): this.type = { _verbose = verbose; this }
  def throwIfError(): this.type = { ... }
  def get(tpe: String, id: Int): QueryResult[RestfulResponse] =
    new QueryResult[RestfulResponse] {
       def run(): RestfulResponse = // code to make rest call goes here
    }
}

trait QueryResult[A] { self =>
  def map[B](f: (A) => B): QueryResult[B] = new QueryResult[B] {
    def run(): B = f(self.run())
  }
  def flatMap[B](f: (A) => QueryResult[B]) = new QueryResult[B] {
    def run(): B = f(self.run()).run()
  }
  def run(): A
}

Then to eventually get the results you call run. So at the end of the day you can call it like this:

MyTargetApi.query()
  .debugPrint(verbose=true)
  .throwIfError()
  .get("customers", 22)
  .map(resp => resp._3.map(_.length)) // body
  .run()

Which should be a verbose request that will error out on issue, retrieve the customers with id 22, keep the body and get its length as an Option[Int].

The idea is that you can use map to define computations on a result you do not yet have. If we add flatMap to it, then you could also combine two computations from two different queries.

huynhjl
  • 41,520
  • 14
  • 105
  • 158
  • 1
    Wow - huge thanks **huynhjl** for wading through the poor phrasing of my question and putting this answer together. It's hugely helpful - it shows how to define a fluent interface which can return my `RestfulResponse` type or via `map` can apply a further computation and return that. Can I confirm that the original `query()` method you mention is part of `MyTargetApi` and simply returns a `new Query()` object? Also would it be too much to ask to see the `flatMap` definition? Thanks again! – Alex Dean Dec 31 '11 at 10:50
  • 1
    @AlexDean, I added `flatMap`. Yes `query()` would be part of your original `MyTargetApi` and return a new `Query` object. Please, use my answer only for some ideas. I also invite you to look at http://engineering.foursquare.com/2011/01/21/rogue-a-type-safe-scala-dsl-for-querying-mongodb/ and in general any ORM and nosql interface or wrapper written for Scala for more inspiration. – huynhjl Jan 01 '12 at 04:11
  • Many thanks **huynhjl**. I've been using the [Squeryl source](https://github.com/max-l/Squeryl/tree/master/src/main/scala/org/squeryl) for inspiration, but I will definitely check out Rogue and some of the other ORMs/NoSQL tools too... – Alex Dean Jan 01 '12 at 20:22
3

To be honest, I think it sounds like you need to feel your way around a little more because the example is not obviously functional, nor particularly fluent. It seems you might be mixing up fluency with not-idempotent in the sense that your debugPrint method is presumably performing I/O and the throwIfError is throwing exceptions. Is that what you mean?

If you are referring to whether a stateful builder is functional, the answer is "not in the purest sense". However, note that a builder does not have to be stateful.

case class Person(name: String, age: Int)

Firstly; this can be created using named parameters:

Person(name="Oxbow", age=36)

Or, a stateless builder:

object Person {
  def withName(name: String) 
    = new { def andAge(age: Int) = new Person(name, age) } 
}

Hey presto:

scala> Person withName "Oxbow" andAge 36

As to your use of untyped strings to define the query you are making; this is poor form in a statically-typed language. What is more, there is no need:

sealed trait Query
case object orders extends Query

def get(query: Query): Result

Hey presto:

api get orders

Although, I think this is a bad idea - you shouldn't have a single method which can give you back notionally completely different types of results


To conclude: I personally think there is no reason whatsoever that fluency and functional cannot mix, since functional just indicates the lack of mutable state and the strong preference for idempotent functions to perform your logic in.

Here's one for you:

args.map(_.toInt)

args map toInt

I would argue that the second is more fluent. It's possible if you define:

val toInt = (_ : String).toInt

That is; if you define a function. I find functions and fluency mix very well in Scala.

oxbow_lakes
  • 133,303
  • 56
  • 317
  • 449
  • Hi **oxbow_lakes** - many many thanks for taking the time to put this answer together. You were right - the phrasing of my question was very poor, apologies for all the confusion. I agree that functional and fluent are completely compatible - and thank you for the examples around stateful and stateless builders. On the untyped strings for `get()`ing HTTP resources - I agree, that was a bad idea, I'm going to remove that capability from the API (or at least mark it `unsafe`, Haskell-style). – Alex Dean Dec 31 '11 at 11:05
0

You could try having get() return a wrapper object that might look something like this

type RestfulResponse = (Int, List[String], Option[String])

class ResponseWrapper(private rr: RestfulResponse /* and maybe some flags as additional arguments, or something? */) {

    def get : RestfulResponse = rr

    def throwIfError : RestfulResponse = {
        // Throw your exception if you detect an error
        rr    // And return the response if you didn't detect an error
    }

    def debugPrint(verbose: Boolean, /* whatever other parameters you had in mind */) {
        // All of your debugging printing logic
    }

    // Any and all other methods that you want this API response to be able to execute

}

Basically, this allows you to put your response into a contain that has all of these nice methods that you want, and, if you simply want to get the wrapped response, you can just call the wrapper's get() method.

Of course, the downside of this is that you will need to change your API a bit, if that's worrisome to you at all. Well... you could probably avoid needing to change your API, actually, if you, instead, created an implicit conversion from RestfulResponse to ResponseWrapper and vice versa. That's something worth considering.

Destin
  • 1,194
  • 2
  • 10
  • 12
  • And in what way is this the "functional style" which the OP was asking for? You are throwing errors and performing I/O – oxbow_lakes Dec 30 '11 at 17:21
  • 1
    @oxbox_lakes You're correct; it's not functional, but most practical programming has to make compromises as far as functionalism goes. I think this is the best solution for doing what he's asking about. I, personally, would recommend changing the API, but that's not my call to make. If he wants to throw errors and perform I/O, who am I to say he shouldn't? – Destin Dec 30 '11 at 18:04
  • But it was specifically his question: "how does fluent and functional mix?". Which you have in no way answered. – oxbow_lakes Dec 30 '11 at 18:14
  • I think you're perhaps reading too far into some (maybe) poor word choice on the asker's part—or maybe I am. From my interpretation, he's asking how he can return a tuple and make function calls on the tuple, as per this quote: "I don't really want to sacrifice the functional simplicity of a tuple return value - but I would like to extend the library with various 'fluent' method calls". – Destin Dec 30 '11 at 18:20
  • Hi **Destin** - big thanks for taking the time to put this together. I think your answer started from my specific painpoint, whereas **oxbow_lakes** started from my (very poor) phrasing. So you were both right :-) An implicit conversion from `RestfulResponse` to a more feature-rich `RestfulWrapper` was something I vaguely had in mind so it was helpful to see it suggested in your answer. I agree throwing errors and performing I/O isn't particularly functional, but then my library is interacting with a third-party web service over HTTP, so it's inherently non-idempotent to begin with. – Alex Dean Dec 31 '11 at 11:24