How to resolve the algorithm Partial function application step by step in the Java programming language

Published on 12 May 2024 09:40 PM

How to resolve the algorithm Partial function application step by step in the Java programming language

Table of Contents

Problem Statement

Partial function application   is the ability to take a function of many parameters and apply arguments to some of the parameters to create a new function that needs only the application of the remaining arguments to produce the equivalent of applying all arguments to the original function. E.g:

Note that in the partial application of a parameter, (in the above case param1), other parameters are not explicitly mentioned. This is a recurring feature of partial function application.

Let's start with the solution:

Step by Step solution about How to resolve the algorithm Partial function application step by step in the Java programming language

The provided Java code showcases partial function application, a programming technique that allows you to create new functions by fixing one or more arguments of an existing function. It demonstrates this concept with a specific example of applying a function to an array of integers.

Interface and Method Definitions:

  • IntegerFunction: An interface representing a function that takes an integer as an argument and returns an integer.
  • SequenceFunction: An interface representing a function that takes an array of integers as an argument and returns an array of integers.

Original Function "fs":

  • static int[] fs(IntegerFunction f, int[] s): This method takes two arguments, an IntegerFunction and an array of integers, and applies the function to each element of the array. It returns the resulting array.

Currying the Function "fs":

  • static SequenceFunction fs(final IntegerFunction f): This method takes an IntegerFunction as an argument and returns a SequenceFunction. It creates a new function that applies the given IntegerFunction to each element of an input array. This currying is necessary for partial application.

Concrete Function Implementations:

  • f1: An IntegerFunction that multiplies an integer by 2.
  • f2: An IntegerFunction that squares an integer.

Partial Application Using Currying:

  • fsf1: A SequenceFunction that represents the partial application of fs with f1. It is created using fs(f1).
  • fsf2: A SequenceFunction that represents the partial application of fs with f2. It is created using fs(f2).

Main Method:

  • The main method initializes a 2D array sequences containing sets of integers.
  • It iterates over each array in sequences and applies fsf1 and fsf2 to it.
  • The results are printed to the console.

Understanding the Output:

The output shows the original array, the result of applying fsf1 (partial application of fs with f1), and the result of applying fsf2 (partial application of fs with f2) to each input array.

High-Level Explanation:

Partial application allows you to create new functions by fixing one or more arguments of an existing function. In this example, fsf1 and fsf2 are partially applied versions of fs with the f1 and f2 functions, respectively. This allows you to apply f1 and f2 to an array of integers with simpler syntax and improved code readability, as shown in the main method.

In another version, it contains a similar approach using Java 8 lambda expressions and the java.util.function package. The definition of the PartialApplication interface implements the BiFunction and provides a default method for partial application, which curries the function. The code then defines sample functions f1 and f2 and partially applies fs to them, creating fsf1 and fsf2 for convenient application to arrays.

Source code in the java programming language

import java.util.Arrays;

public class PartialApplication {
	interface IntegerFunction {
		int call(int arg);
	}

	// Original method fs(f, s).
	static int[] fs(IntegerFunction f, int[] s) {
		int[] r = new int[s.length];
		for (int i = 0; i < s.length; i++)
			r[i] = f.call(s[i]);
		return r;		
	}

	interface SequenceFunction {
		int[] call(int[] arg);
	}

	// Curried method fs(f).call(s),
	// necessary for partial application.
	static SequenceFunction fs(final IntegerFunction f) {
		return new SequenceFunction() {
			public int[] call(int[] s) {
				// Call original method.
				return fs(f, s);
			}
		};
	}

	static IntegerFunction f1 = new IntegerFunction() {
		public int call(int i) {
			return i * 2;
		}
	};

	static IntegerFunction f2 = new IntegerFunction() {
		public int call(int i) {
			return i * i;
		}
	};

	static SequenceFunction fsf1 = fs(f1); // Partial application.

	static SequenceFunction fsf2 = fs(f2);

	public static void main(String[] args) {
		int[][] sequences = {
			{ 0, 1, 2, 3 },
			{ 2, 4, 6, 8 },
		};

		for (int[] array : sequences) {
			System.out.printf(
			    "array: %s\n" +
			    "  fsf1(array): %s\n" +
			    "  fsf2(array): %s\n",
			    Arrays.toString(array),
			    Arrays.toString(fsf1.call(array)),
			    Arrays.toString(fsf2.call(array)));
		}
	}
}


import java.util.Arrays;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.IntUnaryOperator;
import java.util.function.UnaryOperator;
import java.util.stream.Stream;

@FunctionalInterface
public interface PartialApplication<INPUT1, INPUT2, OUTPUT> extends BiFunction<INPUT1, INPUT2, OUTPUT> {
  // Original method fs(f, s).
  public static int[] fs(IntUnaryOperator f, int[] s) {
    return Arrays.stream(s)
      .parallel()
      .map(f::applyAsInt)
      .toArray()
    ;
  }

  // Currying method f.apply(a).apply(b),
  // in lieu of f.apply(a, b),
  // necessary for partial application.
  public default Function<INPUT2, OUTPUT> apply(INPUT1 input1) {
    return input2 -> apply(input1, input2);
  }

  // Original method fs turned into a partially-applicable function.
  public static final PartialApplication<IntUnaryOperator, int[], int[]> fs = PartialApplication::fs;

  public static final IntUnaryOperator f1 = i -> i + i;

  public static final IntUnaryOperator f2 = i -> i * i;

  public static final UnaryOperator<int[]> fsf1 = fs.apply(f1)::apply; // Partial application.

  public static final UnaryOperator<int[]> fsf2 = fs.apply(f2)::apply;

  public static void main(String... args) {
    int[][] sequences = {
      {0, 1, 2, 3},
      {2, 4, 6, 8},
    };

    Arrays.stream(sequences)
      .parallel()
      .map(array ->
        Stream.of(
          array,
          fsf1.apply(array),
          fsf2.apply(array)
        )
          .parallel()
          .map(Arrays::toString)
          .toArray()
      )
      .map(array ->
        String.format(
          String.join("\n",
            "array: %s",
            "  fsf1(array): %s",
            "  fsf2(array): %s"
          ),
          array
        )
      )
      .forEachOrdered(System.out::println)
    ;
  }
}

  

You may also check:How to resolve the algorithm 100 doors step by step in the Wren programming language
You may also check:How to resolve the algorithm Literals/String step by step in the Vim Script programming language
You may also check:How to resolve the algorithm Ackermann function step by step in the SmileBASIC programming language
You may also check:How to resolve the algorithm Text processing/2 step by step in the J programming language
You may also check:How to resolve the algorithm Balanced brackets step by step in the Quackery programming language