com.codepoetics:mtuples

Sonatype helps open source projects to set up Maven repositories on https://oss.sonatype.org/

GroupId

GroupId

com.codepoetics
ArtifactId

ArtifactId

mtuples
Last Version

Last Version

0.2
Release Date

Release Date

Type

Type

jar
Description

Description

Sonatype helps open source projects to set up Maven repositories on https://oss.sonatype.org/
Source Code Management

Source Code Management

http://github.com/poetix/mtuples

Download mtuples

How to add to project

<!-- https://jarcasting.com/artifacts/com.codepoetics/mtuples/ -->
<dependency>
    <groupId>com.codepoetics</groupId>
    <artifactId>mtuples</artifactId>
    <version>0.2</version>
</dependency>
// https://jarcasting.com/artifacts/com.codepoetics/mtuples/
implementation 'com.codepoetics:mtuples:0.2'
// https://jarcasting.com/artifacts/com.codepoetics/mtuples/
implementation ("com.codepoetics:mtuples:0.2")
'com.codepoetics:mtuples:jar:0.2'
<dependency org="com.codepoetics" name="mtuples" rev="0.2">
  <artifact name="mtuples" type="jar" />
</dependency>
@Grapes(
@Grab(group='com.codepoetics', module='mtuples', version='0.2')
)
libraryDependencies += "com.codepoetics" % "mtuples" % "0.2"
[com.codepoetics/mtuples "0.2"]

Dependencies

test (3)

Group / Artifact Type Version
junit : junit jar 4.11
org.hamcrest : hamcrest-library jar 1.3
org.mockito : mockito-all jar 1.9.5

Project Modules

There are no modules declared in this project.

MTuples

Very lightweight record types for Java 8.

Maven

<dependency>
    <groupId>com.codepoetics</groupId>
    <artifactId>mtuples</artifactId>
    <version>0.2</version>
</dependency>

FAQ

Knowing you, this is yet another attempt at doing something like Scala's case classes in Java.

That's right. MTuples are immutable collections of values, with a strongly-typed protocol for creating new tuples and reading values out of existing tuples. They have sensible equals and hashCode and toString implementations.

OK, so what's the trick this time?

An MTuple<T> is an array of Object[], together with a java.lang.reflection.Method belonging to the type T that can receive that array as its argument list.

To read the values of an MTuple<T>, you pass it an instance of T and it calls the method on that instance, passing in the Object[] array.

Like this (notice that we're supplying a lambda as our implementation of Person):

public interface Person {

    static MTuple<Person> build(Consumer<Person> buildWith) {
        return MTupleBuilder.build(Person.class, buildWith);
    }

    void with(String name, int age);
}

public void readValues() {
    MTuple<Person> person = Person.build(p -> p.with("Theodor", 41));

    person.accept((name, age) ->
        System.out.println(name + " is " + age + " years old."))
}

That looks weird.

It is a bit weird. The build syntax maybe needs some explaining. MTupleBuilder creates a proxy implementing T, and passes it to the Consumer<T> you pass in. You call a method on the proxy in the Consumer<T>, and the method call gets recorded and turned into an MTuple<T> which the builder then hands back to you. It's a bit origami-like. But the next bit's worse.

Go on.

Suppose we wanted to read a single value from the MTuple<T>. We could do this:

AtomicReference<String> theName = new AtomicReference<>();
MTuple<Person> person = Person.build(p -> p.with("Theodor", 41));

person.accept((name, age) -> theName.set(name))

String name = theName.get();

But who wants to do that? There's a better way, which is to define an Extractor<T, V> which looks like this:

Extractor<Person, String> theName = result -> (name, age) -> result.accept(name);

String name = person.extract(theName);

Because the with method on Person returns void, we can't return a value directly from the lambda we pass in. So instead we pass a Function<Consumer<V>, T> to the extract method. It gives us a Consumer<V> (which we've called result here), to which we supply the value we want to return as a result; we give it an implementation of Person which it then calls with the MTuple<Person>'s arguments. Our lambda has to call result.accept to "send" a value back out to the caller.

Under the hood, it's using an AtomicReference<V> just like in the previous code snippet.

That is sick.

Yes, but you get used to it surprisingly quickly.

Anyway, all of this is very nice, but what about polymorphism? Scala's case classes do pattern matching - can we do something like that?

Well...

public interface Message {
    static MTuple<Message> build(Consumer<Message> buildWith) {
        return MTupleBuilder.build(Message.class, buildWith);
    }

    void itemCreated(String id, String name);
    void itemDeleted(String id);

    // Anonymous classes act like pattern matchers...
    Extractor<Message, String> id = result -> new Message() {
        @Override
        public void itemCreated(String id, String name) {
            result.accept(id);
        }

        @Override
        public void itemDeleted(String id) {
            result.accept(id);
        }
    };
}

@Test
public void multipleMethods() {
    MTuple<Message> createdMessage = Message.build(m -> m.itemCreated("123", "Foo"));
    MTuple<Message> deletedMessage = Message.build(m -> m.itemDeleted("123"));

    assertThat(createdMessage.extract(Message.id), equalTo(deletedMessage.extract(Message.id)));
}

I'm not talking to you any more. Go away.

Fine. Go and spend more time with your beans...

Versions

Version
0.2