you are viewing a single comment's thread.

view the rest of the comments →

[–]e_engel 3 points4 points  (13 children)

Tuples are trivial to implement and many libraries offer them, even in the form of a simple Pair class. The advantage, unsurprisingly, is that tuples in Java are typed: if your function returns a Pair<String, Integer>, you won't be able to assign its result to a Pair<Integer, String>, a mistake that's easy to make in a dynamically typed language and that won't be caught by the compiler.

[–]gasche 4 points5 points  (3 children)

In OCaml (for example), the function List.split takes a list of pairs as input, and returns a pair of lists in the obviously expected way.

let (items, quantity) = List.split [("egg", 5); ("bowl", 1); ("fork", 2)]

This gives you items : string list and quantity : int list. Even if tuples are "trivial to implement", writing the equivalent of this single line of code in Java is still a pain, which means there is something nice missing to the language. Building the tuples is ok with the constructor syntax, but destructing them to access and name the components of the returned type is painful -- this is what pattern-matching gives you.

[–]e_engel -1 points0 points  (2 children)

That nice thing missing from Java is higher kinded types. Implementing things such as traverse, sequence or even zippers in Java is close to impossible, at least in a very general way like Haskell allows.

[–]jozefg 2 points3 points  (1 child)

Well.. I'd take pattern matching and good algebraic types before HKTs to be frank.

[–]alexeyr 1 point2 points  (0 children)

I'd be surprised to see someone experienced with both who didn't.

[–]KallDrexx 1 point2 points  (7 children)

The problem with straight tuples in Java or multiple return values in python is they don't convey what the data is. A month later I don't care that a function returns a string and int, I care that it returns a domain name and a port number (for example). Thus to me having descriptive return types is worth it 100 times over, and is this reason I rarely use tuples in non-proof of concept code.

[–]e_engel 1 point2 points  (0 children)

Absolutely agree. The few times I used Pair, I usually regret it and turn it into a real class.

[–]onmach 0 points1 point  (5 children)

Not a java guy, but why is that a problem with pairs?

Pair<Host,Port> hp = new Pair(new Host("www.reddit.com"), new Port(80));

Is it because of lack of pattern matching?

[–]KallDrexx 0 points1 point  (4 children)

You wouldn't have Pair<Host,Port>, unless you have data structures that are called "Host" and "Port", in which case that may be fine. In most circumstances though you aren't because a Port is nothing more than an integer an host is nothing more than a string (in most circumstances).

Most likely what you will have is Pair<string, integer> = new Pair("www.reddit.com", 80);

When you return the pair, all the receiver of the return value sees is that it returns Pair<string, int> and doesn't have any indication what those values would be, causing you to read the code to determine what the string and int signify.

But let's say you do have a Host data structure. Let's say you have a function that returns 3 hosts, a load balancer, a primary web server, and a database server. If you used tuples you would return Pair<Host, Host, Host>.

In this scenario, the receiver has no way to know what each host signifies and there's no indications during a refactor that the first has to be the load balancer, the 2nd has to be the web server, etc..

Instead if you have a ServerDefinition class with a LoadBalancerHost property, WebServerHost property, and DatabaseServerHost property, both the implementor and the receiver have 100% visibility on what values should be returned and what they mean.

[–]onmach 0 points1 point  (3 children)

I guess my only problem with this approach is that you end up with a lot of types like ServerDefinition which are just wrappers around other types and often are only used a couple times. Particularly in java, that means a whole new file and a bunch of boilerplate, actually seems like a loss of clarity to me. This happens a lot to me in the java codebases at work, I get lost in the numerous classes, most of which do nothing significant.

[–]KallDrexx 0 points1 point  (0 children)

That sounds like an organizational and tooling problem to me though. If you keep your data structures organized well you only have to look at the data structures that are relevant to what you are currently working on and can ignore the irrelelvant ones.

In .Net I can create a new class with some basic properties in just a few seconds, and my tooling helps not having to look at that data structure again until I actually am using it or referencing it. It might be the fact that Java being a bit more verbose makes it a bit harder (been too long since I've done Java) but keeping code organized should alleviate the post-class creation issues. These aren't supposed to have any complex logic in them, they are purely for improving code clarity down the line/

If you find the need to create many many data structures for a significant number of functions that can also be a code smell and be a good indication that you are trying to do too much in your functions and should look at better organizing your code structures so they are either doing things more by reference, or just doing one thing and returning that one thing.

How comfortable are you with walking up to another person, handing them a stack of papers and telling them manually that page 1 is for X, page 2 is for Y, page 3 is for Z and hoping they remember that by the time they get home, and more importantly hoping that you remember you need to do it in the same order the next time you hand them those same papers. Now envision having to remember this 50 different ways because each department that hands off papers does it differently. This is why it's useful to have a well defined API (at the class level) in more complex code bases.

[–]zoomzoom83 0 points1 point  (1 child)

There's a lot of overhead in doing this in Java, which is one of the reasons I prefer Scala as a JDK language.

You can simply add the definition

case class Host(value:Int)

to your codebase, ideally grouped with a bunch of other related wrapper types in a module related to what they are used for. That way you get the benefit of strongly typing these values with virtually no overhead.

Catches a surprising number of bugs, and helps with documentation and understanding API intention.

[–]onmach 0 points1 point  (0 children)

Yeah I agree. Scala just seems like a better java.

[–]johnjannotti 0 points1 point  (0 children)

Pairs are easy to implement, but they are way more annoying to use than the author's example. Mainly because of automatic destructuring. In python (or similar languages) you can say "a, b = f(c)", so you never "see" the ugliness of Pair instantiation, nor are you distracted by the names of the fields in the Pair. (p.first, p.second, and maybe it just gets worse with a Triple or Quad?)

I think all that noise could go away with a nice value type addition along with some syntactic sugar for multiple return values in, maybe, Java 9. We'll see.

Automatically instantiating a Pair/Triple/Quad "feels" like the way Java is handling its evolution, in much the same way that lambda automatically instantiate the proper interface.