DZone
Java Zone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
  • Refcardz
  • Trend Reports
  • Webinars
  • Zones
  • |
    • Agile
    • AI
    • Big Data
    • Cloud
    • Database
    • DevOps
    • Integration
    • IoT
    • Java
    • Microservices
    • Open Source
    • Performance
    • Security
    • Web Dev
DZone > Java Zone > What Is Applicative? Basic Theory for Java Developers

What Is Applicative? Basic Theory for Java Developers

Are you a Java developer who wants to know the theory behind Applicatives? Here you will find a step-by-step tutorial that will help you understand them.

Bartłomiej Żyliński user avatar by
Bartłomiej Żyliński
CORE ·
Jan. 20, 22 · Java Zone · Analysis
Like (6)
Save
Tweet
6.49K Views

Join the DZone community and get the full member experience.

Join For Free

Applicative is just another concept similar in meaning and history to Functors and Monads. I have covered these two in my previous articles, and I think it is finally time to start closing this little series about the most commonly known functional abstractions. Besides explaining some details and theory, I will implement a simple Applicative. I will also use Optional, hopefully one last time, to show what advantages Applicatives give us.

The source code for this article is available in GitHub repository.

Why Should We Care About Applicatives?

First of all, Applicatives are the intermediate construct between Functors and Monads. They are more powerful than Functors but less powerful than Monads. Applicatives are also great for performing various context-free computations like parsers or traversable instances.

Additionally, all Applicatives are Functors, which may make implementing Applicatives easier if you have some experience with Functors. Thanks to this relation, Applicatives can make the whole journey from Functors to Monads easier, playing the role of a bridge between both concepts.

Moreover, they are usually easier to write than Monads. In more functional languages like Scala or Haskell, Applicatives tend to have more instances than Monads.

What Is An Applicative?

An Applicative Functor, or Applicative in short, is a mostly functional programming concept. It was introduced in 2008 by Conor McBride and Ross Paterson in their paper Applicative programming with effects. Their indirect counterpart in category theory is known as lax monoidal functors. Additionally, please keep in mind that all Applicatives are Functors. Such a relation will have significant implications when we start discussing Applicative Laws and methods — it mostly implies that all Laws required by Functors have to be also met by Applicatives.

In the world of software, the main focus of Applicatives is, similarly to Monads, to wrap values in a particular context and then perform operations - to be exact, operations wrapped in the same context as the value. Contrary to Monads, Applicatives do not allow chain operations in the same ways as Monads do, with the output of one operation being the input of the other. Unfortunately, it is not so easily implementable in Java, so we basically end up with such an ability anyway. Furthermore, unlike Functors, Applicatives allow us to sequence our computations.

Applicative Laws

As both previously described data types, Applicatives also have laws to fulfill. In fact, Applicatives have the highest number of such laws, namely: Identity, Homomorphism, Interchange, Composition. In my opinion, Applicative Laws are also the hardest to understand at first sight, especially Homomorphism and Interchange, among all the three data types I described.

Classically, a few assumptions before we start:

  • f is a function mapping from type T to type R
  • g is a function mapping from type R to type U
  1. Identity
    Applying the identity function to a value wrapped with pure should always return an unchanged value.

    Java
     
    Applicative<Integer> identity = Applicative.pure(x).apply(Applicative.pure(Function.identity()));
    boolean identityLawFulfilled = identity.valueEquals(Applicative.pure(x));
  2. Homomorphism
    Applying a wrapped function to a wrapped value should give the same result as applying the function to the value and then wrapping the result with the usage of pure.

    Java
     
    Applicative<String> leftSide = Applicative.pure(x).apply(Applicative.pure(f));
    Applicative<String> rightSide = Applicative.pure(f.apply(x));
    boolean homomorphismLawFulfilled = leftSide.valueEquals(rightSide);
  3. Interchange
    Applying the wrapped function f to a wrapped value should be the same as applying the wrapped function which supplies the value as an argument to another function, to the wrapped function f

    Java
     
    // As far as I can tell it is as close, to original meaning of this Law, as possible in Java
    Applicative<String> interchangeLeftSide = Applicative.pure(x).apply(Applicative.pure(f));
    Supplier<Integer> supplier = () -> x;
    Function<Supplier<Integer>, String> tmp = i -> f.apply(i.get());
    Applicative<String> interchangeRightSide = Applicative.pure(supplier).apply(Applicative.pure(tmp));
    boolean interchangeLawFulfilled = interchangeLeftSide.valueEquals(interchangeRightSide);
  4. Composition
    Applying the wrapped function f and then the wrapped function g should give the same results as applying wrapped functions composition of f and g together

    Java
     
    // As far as I can tell it should be in line with what is expected from this Law
    Applicative<Long> compositionLeftSide = Applicative.pure(x).apply(Applicative.pure(f)).apply(Applicative.pure(g));
    Applicative<Long> compositionRightSide = Applicative.pure(x).apply(Applicative.pure(f.andThen(g)));
    boolean compositionLawFulfilled = compositionLeftSide.valueEquals(compositionRightSide);

Additionally, because the Applicative is an extension of a Functor, your instance should fulfill both laws from the Functor: Identity and Composition. Fortunately, both laws imposed by the definition of Functor are already among four laws of Applicative, so they should be fulfilled by the Applicative definition itself.

Now that you know what laws you have to fulfill, I can start talking about what exactly you need to implement your Applicative.

Creating an Applicative

  1. What We Need To Implement The Applicative

    The first thing you will need is parameterized type A<T>. The parameterized type is the cornerstone of all data types similar in structure to Applicatives, Monads and Functors. Moreover, you will need two methods:

    • apply (or ap) responsible for performing operations. Here you pass a function already wrapped in our context and that operates on the value in our context. This method should have the following signature M<U> (M<T -> U>).
    • pure which is used to wrap your value and has the following signature M<T>(T).

    Additionally, because all Applicatives are Functors, you get a map method with signature M<R> (T -> R) by definition.

    Be Aware:

    There is a second, probably more common, equivalent for Applicative in such a case. Instead of the apply method, we have a product method with signature M<(T, U)>(M<T>, M<U>). Such Applicatives must obey different Laws, namely: Associativity, Left Identity and Right Identity. Their descriptions and examples are included in the GitHub repository.

    Knowing what one needs to make an Applicative, I can start implementing one.

  2. Implementing an Applicative

    Java
     
    public final class OptionalApplicative<T> implements Functor<T> {
    
        private final Optional<T> value;
    
        private OptionalApplicative(T value) {
            this.value = Optional.of(value);
        }
    
        private OptionalApplicative() {
            this.value = Optional.empty();
        }
    
        <U> OptionalApplicative<U> apply(OptionalApplicative<Function<T, U>> functionApplicative) {
            Optional<U> apply = functionApplicative.value.flatMap(value::map);
            return apply.map(OptionalApplicative::new).orElseGet(OptionalApplicative::new);
        }
    
        static <T> OptionalApplicative<T> pure(T value) {
            return new OptionalApplicative<>(value);
        }
    
        @Override
        public <R> Functor<R> map(Function<T, R> f) {
            return apply(pure(f));
        }
    
        // For sake of asserting in Example and LawsValidator
        public boolean valueEquals(Optional<T> s) {
            return value.equals(s);
        }
    
        public boolean valueEquals(OptionalApplicative<T> s) {
            return this.valueEquals(s.value);
        }
    }
    
    Above, you can see ready Applicative implementation, but why does it look the way it looks?
  3. Describing Applicative Implementation

    The base of this implementation is the parameterized class with the immutable field named "value", which is responsible for storing the value. Then, you can see private constructors that make it impossible to create an object in any other way than through a wrapping method – pure.

    Next come two methods unique for Applicatives - pure used for wrapping values in Applicative context and apply for using wrapped functions to wrapped values. Both are implemented using Optional and its methods, so they are fully null safe.

    Then you have a method map coming from the Functor, it is an “additional” method that may become useful if you are interested in performing some operation over the value inside the Applicative.

  4. Applicative Usage Example

    Let’s move on to presenting a simple example of Applicative usage along with a short description why it may be better than a non-applicative approach.

    Java
     
    public class Example {
    
        public static void main(String[] args) {
            int x = 2;
            Function<Integer, String> f = Object::toString;
            // Task: applying function wrapped in context to value inside that context.
    
            // Non-applicative
            Optional<Integer> ox = Optional.of(x);
            Optional<Function<Integer, String>> of = Optional.of(f);
            Optional<String> ofx = ox.flatMap(d -> of.map(h -> h.apply(d)));
            // One liner -> Optional.of(x).flatMap(d -> Optional.of(f).map(h -> h.apply(d)));
    
            // Applicative
            OptionalApplicative<Integer> ax = OptionalApplicative.pure(x);
            OptionalApplicative<Function<Integer, String>> af = OptionalApplicative.pure(f);
            OptionalApplicative<String> afx = ax.apply(af);
            // One liner -> OptionalApplicative.pure(x).apply(OptionalApplicative.pure(f));
    
            if (afx.valueEquals(ofx)) {
                System.out.println("Values inside wrappers are equal");
            } else {
                throw new RuntimeException("Values inside wrappers are not equal");
            }
        }
    }

    Above you can see for yourself the possible benefits given by Applicative abstraction over plain Optional-based approach. First of all, Applicative-based code is simpler and easier to understand than the Optional one, it does not require any complex things like embedded map calls. In fact, the user does not even know that they are using Applicative with Optional features, so I was able to provide better encapsulation. In my opinion, the pros of using such abstraction outbalance the cost of writing one.

Summing up

Despite being a concept of less power than Monads, Applicatives can be a great solution especially when you need to handle context-free computations. They are very useful when you have to write parsers or traversables. Additionally, being the intermediate concept between Functors and Monads, they can make your journey through category theory data types easier. Thank you for your time.

Category Theory Data Types in Java

If this piece of text was interesting, you may also be interested in other articles from my series about category theory data types in Java:

  1. Functors
  2. Monads

Applicatives FAQ

  1. What Is an Applicative?

    An Applicative Functor, or Applicative, is a functional programming concept, all Applicatives are instances of Functors.

  2. What Are Applicative Laws?

    Every Applicative instance has to satisfy four laws: Identity, Homomorphism, Interchange, and Composition

  3. What Do I Need to Implement an Applicative?

    To implement an Applicative, you need a parameterized type M<T> and two methods: pure and apply, the method ‘map’ is inherited from the Functor, so we get it by definition.

Java (programming language) Functor Monad (functional programming) Law (stochastic processes) Concept (generic programming)

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • How to Upload/Download a File To and From the Server
  • How to Configure Git in Eclipse IDE
  • Waterfall Vs. Agile Methodologies: Which Is Best For Project Management?
  • Stupid Things Orgs Do That Kill Productivity w/ Netflix, FloSports & Refactoring.club

Comments

Java Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • MVB Program
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends:

DZone.com is powered by 

AnswerHub logo