fluent interfaces

428 views
Skip to first unread message

Mick Killianey

unread,
Dec 14, 2010, 2:59:07 PM12/14/10
to golang-nuts
I've been writing some utilities of my own to make testing easier, and
I've been modeling those classes on the hamcrest library.

Typical hamcrest code looks something like this:
AssertThat(8, Is(AllOf(GreaterThan(5),LessThan(18),Not(Prime())))

So qualified imports present a naming challenge: even a short package
name breaks up the flow:
import h "hamcrest"
h.AssertThat(8,
h.Is(h.AllOf(h.GreaterThan(5),h.LessThan(18),h.Not(h.Prime())))

I considered loading them all into the file scope in one fell swoop,
but I want to be very careful not to pollute the namespace, so the
simplest solution I can think of is to import packages with exactly
one symbol each:
import (
"hamcrest"
. "hamcrest/allof" // makes the AllOf matcher available in
this file
. "hamcrest/is" // the Is matcher
. "hamcrest/lessthan" // the LessThan matcher
...etc...

Is anyone else working on Go code with a fluent interface? Have you
discovered any particularly good tricks for this?

Bill Burdick

unread,
Dec 14, 2010, 3:10:53 PM12/14/10
to Mick Killianey, golang-nuts
You can load the functions and variables you want into locally defined variables, like this:

var AssertThat = h.AssertThat
var Is = h.Is
...


Bill

Esko Luontola

unread,
Dec 14, 2010, 3:36:49 PM12/14/10
to golang-nuts
In GoSpec (https://github.com/orfjackal/gospec) I use importing to the
current scope. The assertion syntax looks like this:
https://github.com/orfjackal/gospec/blob/gospec-1.3.4/examples/expectation_syntax_test.go

Russ Cox

unread,
Dec 14, 2010, 5:08:00 PM12/14/10
to Mick Killianey, golang-nuts
>    AssertThat(8, Is(AllOf(GreaterThan(5),LessThan(18),Not(Prime())))

Maybe the problem is that this is not a "fluent interface".
There are no dots.

Oh, and by the way, Go has an expression syntax
that might just do the trick:

assert(x >= 5 && x < 18 && !prime(x))

Russ

Bill Burdick

unread,
Dec 14, 2010, 5:34:20 PM12/14/10
to r...@golang.org, Mick Killianey, golang-nuts
It looks to me like Hamcrest is a declarative testing library and the example Mick gave looks like a valid Hamcrest expression.  The Go expression isn't declarative and probably won't work for what he wants -- I think his real goal is to make a Hamcrest Go binding, not write a fluent interface (despite the subject line).  The JUnit docs refer to Hamcrest as "fluent" and Hamcrest is included in the FluentJava project.  Fowler gives this example of a fluent interface on the page where he coins the term, http://martinfowler.com/bliki/FluentInterface.html,

mock.expects(once()).method("m").with( or(stringContains("hello"), stringContains("howdy")) );

You can see that Fowler's use of stringContains() and or() are similar to the Hamcrest LessThan() and AllOf() expressions in Mick's example, although Fowler's example also chains methods.


Bill

chris dollin

unread,
Dec 15, 2010, 1:27:49 AM12/15/10
to r...@golang.org, Mick Killianey, golang-nuts

The failure messages won't be so readable, though.

Chris

--
Chris "allusive" Dollin

Esko Luontola

unread,
Dec 15, 2010, 1:33:05 AM12/15/10
to golang-nuts
Quoted from http://martinfowler.com/bliki/FluentInterface.html

"Probably the most important thing to notice about this style is that
the intent is to do something along the lines of an internal
DomainSpecificLanguage. Indeed this is why we chose the term 'fluent'
to describe it, in many ways the two terms are synonyms. The API is
primarily designed to be readable and to flow. The price of this
fluency is more effort, both in thinking and in the API construction
itself. The simple API of constructor, setter, and addition methods is
much easier to write. Coming up with a nice fluent API requires a good
bit of thought."

"I've also noticed a common misconception - many people seem to equate
fluent interfaces with Method Chaining. Certainly chaining is a common
technique to use with fluent interfaces, but true fluency is much more
than that."


Designing a fluent requires knowing all syntax tricks of the language
and using them to your advantage to achieve a nice syntax. So fluent
interfaces will be different depending on the language. For example
fluent Ruby or Scala will look very much different from fluent Java or
Go.

Esko Luontola

unread,
Dec 15, 2010, 1:35:08 AM12/15/10
to golang-nuts
On Dec 15, 12:34 am, Bill Burdick <bill.burd...@gmail.com> wrote:
> Fowler gives this
> example of a fluent interface on the page where he coins the term,http://martinfowler.com/bliki/FluentInterface.html,
>
> mock.expects(once()).method("m").with(
> or(stringContains("hello"), stringContains("howdy")) );
>
> You can see that Fowler's use of stringContains() and or() are similar to
> the Hamcrest LessThan() and AllOf() expressions in Mick's example, although
> Fowler's example also chains methods.

Those _are_ methods from Hamcrest. The Hamcrest library was originally
extracted JMock.

Mikael Tillenius

unread,
Dec 15, 2010, 4:44:06 AM12/15/10
to golang-nuts
One possibility would be to allow multiple local imports in some form,
e.g.

{
import . "hamcrest"
AssertThat(8, Is(AllOf(GreaterThan(5),LessThan(18),Not(Prime())))
}

Nothing from hamcrest would be visible outside the curly braces.
Suggestions on shorter and better syntax are welcome.

/Mikael

Mick Killianey

unread,
Dec 15, 2010, 5:59:59 AM12/15/10
to golang-nuts
On Dec 14, 10:08 pm, Russ Cox <r...@golang.org> wrote:
> >    AssertThat(8, Is(AllOf(GreaterThan(5),LessThan(18),Not(Prime())))
>
> Maybe the problem is that this is not a "fluent interface".
> There are no dots.

As Esko points out elsewhere in this thread, not only is this a fluent
interface...it's the *original* fluent interface.

> Oh, and by the way, Go has an expression syntax
> that might just do the trick:
>
>     assert(x >= 5 && x < 18 && !prime(x))

See Chris Dollin's response re: better error messages, but as to your
sarcasm, that raises an interesting point.

An obstacle to the adoption of fluent APIs in Go will be that many of
the core libraries thus far have been written by people with an
extremely terse style. Fluent interfaces are a major style clash for
"tersers"...a clash that isn't endemic to Go (the language), but has
everything to do with personal tastes.

Broadly speaking, the "tersers" arrived first, but the "fluents" have
started to move in. I'm very curious to see whether they work
together or set up separate camps on the island...err...I mean, in the
language.

> Russ

Russ Cox

unread,
Dec 15, 2010, 8:53:56 AM12/15/10
to Mick Killianey, golang-nuts
>>     assert(x >= 5 && x < 18 && !prime(x))
>
> See Chris Dollin's response re: better error messages, but as to your
> sarcasm, that raises an interesting point.

I wasn't being sarcastic. I don't see the point
of creating a separate syntax for writing expressions
when Go already has a rich syntax for writing expressions.
To automate error messages? I can write error messages
myself and then not have to learn (and continually extend)
some other testing language that isn't Go. I'd rather write
my tests and my code in the same language.

> An obstacle to the adoption of fluent APIs in Go will be that many of
> the core libraries thus far have been written by people with an
> extremely terse style.  Fluent interfaces are a major style clash for
> "tersers"...a clash that isn't endemic to Go (the language), but has
> everything to do with personal tastes.

No. It has everything to do with writing Go versus
writing some other language mapped to Go function calls.

> Broadly speaking, the "tersers" arrived first, but the "fluents" have
> started to move in.  I'm very curious to see whether they work
> together or set up separate camps on the island...err...I mean, in the
> language.

It's interesting that the fluents only move in on existing languages.
Where is the programming language written entirely in this
"fluent" manner, and why don't the fluents all use that one?
I suspect it is because you can't get any real work done,
only testing.

Russ

chris dollin

unread,
Dec 15, 2010, 9:27:39 AM12/15/10
to r...@golang.org, Mick Killianey, golang-nuts
On 15 December 2010 13:53, Russ Cox <r...@golang.org> wrote:
>>>     assert(x >= 5 && x < 18 && !prime(x))
>>
>> See Chris Dollin's response re: better error messages, but as to your
>> sarcasm, that raises an interesting point.
>
> I wasn't being sarcastic.  I don't see the point
> of creating a separate syntax for writing expressions
> when Go already has a rich syntax for writing expressions.
> To automate error messages?  I can write error messages
> myself

Why, so can I, or so can any man --

> and then not have to learn (and continually extend)
> some other testing language that isn't Go.

It's just function calls; it's not a new language. It's a stylised way
of using the existing language, which pretty much any application
is.

As for the messages, while it's true that you can write [1]

if x < 5 {
bother( "expected", x, "to be less than 5" )
} else if x >= 18 {
bother( "expected", x, "to be less than 18" )
} else if prime(x) {
bother( "expected", x, "to be composite" )
}

I'd rather write something more like, if not exactly like:

AssertThat(8, Is(AllOf(GreaterThan(5),LessThan(18),Not(Prime())))

because the 17th time I wrote the former my head would
explode and my fingers start to smoke.

Chris

[1] `bother` being assumed to print its arguments nicely and quite the test.

--
Chris "allusive" Dollin

chris dollin

unread,
Dec 15, 2010, 9:45:52 AM12/15/10
to r...@golang.org, Mick Killianey, golang-nuts
On 15 December 2010 14:27, chris dollin <ehog....@googlemail.com> wrote:

> because the 17th time I wrote

something like!

> the former my head would explode and my fingers start to smoke.

Chris

--
Chris "allusive" Dollin

Russ Cox

unread,
Dec 15, 2010, 9:56:15 AM12/15/10
to chris dollin, Mick Killianey, golang-nuts
>    if x < 5 {
>        bother( "expected", x, "to be less than 5" )
>    } else if x >= 18 {
>        bother( "expected", x, "to be less than 18" )
>    } else if prime(x) {
>        bother( "expected", x, "to be composite" )
>    }

I'd have written

if x < 5 || x >= 18 || prime(x) {
t.Fatalf("invalid x: %d", x)
}

but this is not a common kind of check in a test.
I certainly have never written 17 such checks
in my life.

Can you point to places in actual Go tests
in the distribution where the so-called fluent
style would help? It seems like a solution in
search of a problem.

As far as I can tell, fluent coding is quite the misnomer.
It ignores the surrounding language instead of actually
using it idiomatically and, well, fluently.

Russ

chris dollin

unread,
Dec 15, 2010, 10:34:02 AM12/15/10
to r...@golang.org, Mick Killianey, golang-nuts
On 15 December 2010 14:56, Russ Cox <r...@golang.org> wrote:
>>    if x < 5 {
>>        bother( "expected", x, "to be less than 5" )
>>    } else if x >= 18 {
>>        bother( "expected", x, "to be less than 18" )
>>    } else if prime(x) {
>>        bother( "expected", x, "to be composite" )
>>    }
>
> I'd have written
>
>    if x < 5 || x >= 18 || prime(x) {
>        t.Fatalf("invalid x: %d", x)
>    }

Thereby not saying what test failed (yes, I know I can look
at the code and work it out -- I'd rather have the machine tell
me what it already knows).

> but this is not a common kind of check in a test.
> I certainly have never written 17 such checks
> in my life.

17 such AllOfs, nor me either. But at the smaller scale,
in JUnit tests I routinely use assertEquals (and a local
assertDiffers) rather than AssertTrue (or AssertFalse)
because of the better error messages, routinely create
assertWhatever methods when there's a meaningful
Whatever predicate that applies for the values in question,
and will happily write functions over the different Whatevers
(which amounts to being able to write the "fluent" expressions
in question).

> Can you point to places in actual Go tests
> in the distribution where the so-called fluent
> style would help?

No, but that only proves my current ignorance of the contents
of the Go tests.

> As far as I can tell, fluent coding is quite the misnomer.
> It ignores the surrounding language instead of actually
> using it idiomatically and, well, fluently.

I didn't (and possibly still don't) know what "fluent coding" is;
the Hamcrest-style being discussed is just, well, declarative-
style code written to be explicit about what it means, as far as I
can see. There's a bunch of library functions and you use them
to write things -- that some of them parallel existing language
structures is hardly surprising.

Bill Burdick

unread,
Dec 15, 2010, 12:02:36 PM12/15/10
to r...@golang.org, chris dollin, Mick Killianey, golang-nuts
So you don't think Hamcrest is a valid approach to testing, even though you haven't used that technique yourself.  It's OK for you to have that position, but it doesn't help Mick solve his problem.  Personally, I try to refrain from saying "you shouldn't do it that way" about things that aren't in my experience base -- it's too close to "640K should be enough for anyone."


Bill

Mick Killianey

unread,
Dec 15, 2010, 2:45:42 PM12/15/10
to golang-nuts


On Dec 15, 1:53 pm, Russ Cox <r...@golang.org> wrote:
> >>     assert(x >= 5 && x < 18 && !prime(x))
>
> > See Chris Dollin's response re: better error messages, but as to your
> > sarcasm, that raises an interesting point.
>
> I wasn't being sarcastic.  I don't see the point
> of creating a separate syntax for writing expressions
> when Go already has a rich syntax for writing expressions.
> To automate error messages?  I can write error messages
> myself and then not have to learn (and continually extend)
> some other testing language that isn't Go.  I'd rather write
> my tests and my code in the same language.
>
> > An obstacle to the adoption of fluent APIs in Go will be that many of
> > the core libraries thus far have been written by people with an
> > extremely terse style.  Fluent interfaces are a major style clash for
> > "tersers"...a clash that isn't endemic to Go (the language), but has
> > everything to do with personal tastes.
>
> No.  It has everything to do with writing Go versus
> writing some other language mapped to Go function calls.

If that's what you really think, then I would suggest that Go is
larger than your vision of it.

> > Broadly speaking, the "tersers" arrived first, but the "fluents" have
> > started to move in.  I'm very curious to see whether they work
> > together or set up separate camps on the island...err...I mean, in the
> > language.
>
> It's interesting that the fluents only move in on existing languages.
> Where is the programming language written entirely in this
> "fluent" manner, and why don't the fluents all use that one?
> I suspect it is because you can't get any real work done,
> only testing.

Your intuition is correct that fluent interfaces are particularly
useful when writing tests.

Your opinion that writing tests isn't real work is uninformed.

When incorrect code puts valuable things at risk, the amount of test
code far outstrips the amount of production code. (My experience has
been 5:1 test:production.) I've never worked at NASA, but I expect
that when a piece of software takes two years to reach its target, you
write a *lot* of tests to make sure nothing ruins a multi-billion
dollar project.

So if you see the "fluents" moving in, it's because they *are* doing
real work...they're using the appropriate tools to make sure your
child's/spouse's/parent's life-support system at the hospital never,
ever fails.

If you don't see so many fluent languages in the early stages of
language development, perhaps that's because there are very few
mission-critical consequences when things go wrong. Who cares if you
break a prime number sieve?

> Russ

Mick Killianey

unread,
Dec 15, 2010, 3:14:17 PM12/15/10
to golang-nuts
Whoops...clicked send prematurely.

I should also add to this that I think that the Go team (and the Go
community) has done some really wonderful work and I'm very much
enjoying their young creation. So if I seem to want to pick up the
ball and run with it, that's not because I don't want to play, or
because I think that the Go team is going in the *wrong* direction.
I'm just speaking up because I'm aware that my needs and wants take me
in a different direction, and I think highly enough of Go that I'd be
chuffed to have it with me as I go my merry way. Yay, Go!

While I'm passing out the kudos, a big shoutout to gocode and
goclipse...two much-appreciated tools that really complement each
other.

Cheers!

> > Russ

Bill Burdick

unread,
Dec 15, 2010, 3:19:55 PM12/15/10
to Mick Killianey, golang-nuts
I agree -- Russ, you rock, along with everyone else on the team!  I really love what you guys are doing.


Bill
Reply all
Reply to author
Forward
0 new messages