Functions in Java

Prior to the introduction of Lambda Expressions feature in version 8, Java had long been known as a purely object-oriented programming language. "Everything is an Object" is the philosophy deep in the language design. Objects are entities with identity, states and behaviors, which essentially combines data and function. Java models almost everything with objects. The conceptual consistency makes Java easy to understand for beginners, but it doesn't fit every problem well.

More universal than object, data and function are two concepts found in all programming languages. Functions are simulated with interfaces in Java. When you do need a function, you have to define a class which implements the interface. The following code snippet demonstrates the Java way of simulating a function of type T -> R with a generic interface.

  1. interface F1<T, R> {
  2. R apply(T arg);
  3. }

Every time you need a function, you have to either explicitly or anonymously define a class to implement the interface. It causes a bunch of syntactic noise around the core feature code which you really need to write. Every once in a while, we see complains about this in the community.

  1. // Java 6
  2. // option 1: named class
  3. class AgePredicate implements F1<Employee, Boolean> {
  4. public Boolean apply(Employee employee) {
  5. return employee.getAge() > 35; // business logic
  6. }
  7. }
  8. employees.filter(new AgePredicate());
  9. // option 2: anonymous class
  10. employees.filter(new F1<Employee, Boolean>() {
  11. public Boolean apply(Employee employee) {
  12. return employee.getAge() > 35; // business logic
  13. }
  14. });

With the advent of lambda expression feature in Java 8, life is much easier. Java 8 still uses functional interfaces to represent functions, but there's no need to define a class any more:

  1. // Java 8
  2. employees.filter(employee -> employee.getAge() > 35);

Lambda != Functional Programming

Programmers are lazy, they don't like writing boilerplate code, they love conciseness. However, some programmers may have the illusion that functional programming is all about lambda expressions. This is simply not correct, there're more stuff in functional programming than lambda expressions.

There're 2 things that shape functional programming:

1. Higher-order function: functions as the first class citizen;

2. Functional composition: the unique way of composing small functions into a larger one.

Lambda expressions can be considered as a syntactic sugar to create functions concisely, but it's not indispensable. The key is you really understand the nature of function and how functions work together.

This article just wanted to impress you that functional programming has always been there in Java since the introduction of Generics feature in version 5. Most of our programmers just overlooked it.

Let's get started with a simple question:

map is a famous higher-order function which maps a transform function f of type T -> R over a list of type [T] (alias of List<T>) and returns a list of type [R]. For example, map x -> x * x over [1, 2, 3] will yield [1, 4, 9].

In terms of API design, there can be different options, so the question is which one in the following forms is the best in general?

1. map(x -> x * x, [1, 2, 3])
2. map([1, 2, 3], x -> x * x)
3. [1, 2, 3].map(x -> x * x)

The first 2 forms are both in functional style, they only differ in parameter order. The third form is in OO style.

However, although they look similar, when it comes to API design, we should always prefer the first form, only choose other forms when you really have special reasons. If you don't understand the reason now, don't worry, I'll explain it later.

Currying

Currying is an extremely important feature of functional programming. Currying means breaking a function with many arguments into a series of functions that each takes one argument and ultimately produce the same result as the original function.

Only a few programming languages support Currying at the language level. Haskell and Scala fall into this category, see currying in Haskell and currying in Scala for details. Many other modern languages supporting lambda expression (such as C#, Python and JavaScript) don't support Currying natively.

Let's get a sense of Currying with a Haskell code. Say we define a function add as below:

  1. -- Haskell
  2. add :: Int -> Int -> Int
  3. add x y = x + y

The type of add is Int -> Int -> Int meaning it's a function which takes 2 Int arguments and returns an Int value. I hope the audience can get used to the Haskell form of function signature. It's a better form than the Java counterpart int add(int, int).

The interesting thing is what would happen if we don't feed all the arguments at once to add? Let's do an experiment in GHCi:

  1. // Haskell (GHCi)
  2. > :t add
  3. add :: Int -> Int -> Int
  4. > :t add 2
  5. add 2 :: Int -> Int
  6. > :t add 2 3
  7. add 2 3 :: Int

The result shows: 1) the type of add if Int -> Int -> Int; 2) the type of add 2 is Int -> Int; 3) the type of `add 2 3 is Int. This explains why we prefer the arrow form of function signature, because it's intuitive. Originally you have a type Int -> Int -> Int, after feeding one Int, you get Int -> Int, after feeding another Int, you get Int.

Be aware that similar things will not work in C#, JavaScript or Python. Let's take a look at JavaScript for example:

  1. // JavaScript
  2. function add(x, y) {
  3. return x + y;
  4. }
  5. add(2); // add(2) => add(2, undefined) => NaN

add(2) doesn't return a function as what we got in Haskell, instead it automatically uses undefined as the second argument. That means Currying is not a feature of the JavaScript language.

Usually, you will need to do something like this to get the curried version of add:

  1. // JavaScript
  2. function add() {
  3. return function(x) { return function(y) { x + y; } }
  4. }
  5. add; // function of type object -> object -> object
  6. add(2); // function of type object -> object
  7. add(2)(3); // value 5

Java is no better than JavaScript on this point, but we are able to come up with a workaround. In the following snippet, F2<T1, T2, R> stands for a curried function of type T1 -> T2 -> R:

  1. // Java 6
  2. /** Function of type T1 -> T2 -> R */
  3. public abstract class F2<T1, T2, R> extends F1<T1, F1<T2, R>> {
  4. /** Subclasses override this method to implement the function */
  5. public abstract R apply(T1 arg1, T2 arg2);
  6. /** Partial application */
  7. public final F1<T2, R> apply(final T1 arg1) {
  8. return new F1<T2, R>() {
  9. @Override public R apply(T2 arg2) {
  10. return F2.this.apply(arg1, arg2);
  11. };
  12. };
  13. }
  14. }

Just put definitions of F1<T, R>, F2<T1, T2, R> ... in a library, then we can define curried functions as follows:

  1. // Java 6
  2. class Strings {
  3. /** Curried form of times(int, String) */
  4. public static F2<Integer, String, String> times() {
  5. return new F2<Integer, String, String>() {
  6. @Override String apply(Integer n, String str) {
  7. times(n, str);
  8. }
  9. };
  10. }
  11. public static String times(int n, String str) {
  12. ... // implementation
  13. }
  14. }

Then we can use it as:

  1. F2<Integer, String, String> nTimes = times();
  2. F1<String, String> threeTimes = nTimes.apply(3);
  3. threeTimes.apply("foo"); // "foofoofoo"

In Java 8, things get easier, you don't need to write the curried version manually. With the method reference feature, we can come up with a reusable function to convert a non-curried function into a curried one. :

  1. // Java 8
  2. F2<Integer, String, String> times = Currying.<Integer, String, String>curry(Strings::times);
  3. assertEquals("abcabcabc", times.apply(3).apply("abc"));

More details here.

Functional Composition

All the things we did in the the previous section is to get the capability of partial application. Why partial application matters? Let's look at a problem:

Given a string of words separated by spaces, e.g. "I love programming in Java", write a function String convert(String input) to: 1) reverse the order of words; 2) convert the words to upper case; 3) join the words by underscore. For example, convert("I love programming in Java") will yield "JAVA_IN_PROGRAMMING_LOVE_I".

In Java 6, we can actually do like this:

  1. // Java 6
  2. String convert(String str) {
  3. // _ composes multiple functions into one function
  4. F1<String, String> f = _(split(" "), Lists.<String>reverse(), map(toUpperCase()), join("_"));
  5. return f.apply(str);
  6. }

More details here.

The point here is the unique way of composing a bunch of small functions into a larger one.

Every programming language has its way to composition, they only differ in how. We call the style above functional composition. One of the biggest differences between functional composition and OO composition is that we don't even need to mention the data in functional composition, you can just partially apply the curried functions and compose then together. It's also called Point-Free style.

In functional composition, we need to pay special attention to the type of values and functions. For example, the type of compose function _ is (T -> U) -> (U -> R) -> (T -> R) meaning given a function f1 of type T -> U and a function f2 of type U -> R, it will return a function of type T -> R. So the restriction here is the return type of f1 is the parameter type of f2. If it's not satisfied, the compiler and IDE will warn you. Let's look at the example, since we want to have convert of type String -> String, the type of split(" ") must be String -> T where T can be anything, then since it's connected to Lists.<String>reverse(), here T is forced to be List<String>.

The following diagrams depicts the type of partially applied functions and composed functions:


Functional composition also depends on the way you define the types. Recall the 3 forms of map:

1. map(x -> x * x, [1, 2, 3])
2. map([1, 2, 3], x -> x * x)
3. [1, 2, 3].map(x -> x * x)

In general, we always prefer the first form which puts data at the end of the parameter list. One important reason for this is easier to partially apply the function and compose it with other functions. I'm not saying the second form doesn't work, if you have an existing function say f :: D -> T -> R which puts data at the beginning, just use flip(f) to turn the type into T -> D -> R, the implementation will not be changed.

In the next section, I'll tell another important reason for preferring the first form.

Functional Programming without Lambda - Part 1 Functional Composition的更多相关文章

  1. Functional Programming without Lambda - Part 2 Lifting, Functor, Monad

    Lifting Now, let's review map from another perspective. map :: (T -> R) -> [T] -> [R] accep ...

  2. Java 中的函数式编程(Functional Programming):Lambda 初识

    Java 8 发布带来的一个主要特性就是对函数式编程的支持. 而 Lambda 表达式就是一个新的并且很重要的一个概念. 它提供了一个简单并且很简洁的编码方式. 首先从几个简单的 Lambda 表达式 ...

  3. [Functional Programming Monad] Combine Stateful Computations Using Composition

    We explore a means to represent the combination of our stateful computations using familiar composit ...

  4. [Functional Programming] Rewrite a reducer with functional state ADT

    For example we have a feature reducer like this: // selectCard :: String -> Action String export ...

  5. Python Lambda & Functional Programming

    Python Lambda & Functional Programming 函数式编程 匿名函数 纯函数 高阶函数 # higher-order functions def apply_tw ...

  6. Beginning Scala study note(4) Functional Programming in Scala

    1. Functional programming treats computation as the evaluation of mathematical and avoids state and ...

  7. Functional programming

    In computer science, functional programming is a programming paradigm, a style of building the struc ...

  8. 关于函数式编程(Functional Programming)

    初学函数式编程,相信很多程序员兄弟们对于这个名字熟悉又陌生.函数,对于程序员来说并不陌生,编程对于程序员来说也并不陌生,但是函数式编程语言(Functional Programming languag ...

  9. Monad (functional programming)

    In functional programming, a monad is a design pattern that defines how functions, actions, inputs, ...

随机推荐

  1. ORACLE中常见SET指令

    1         SET TIMING ON 说明:显示SQL语句的运行时间.默认值为OFF. 在SQLPLUS中使用,时间精确到0.01秒.也就是10毫秒. 在PL/SQL DEVELOPER 中 ...

  2. #英文#品读中国城市个性——最好的和最坏的&当东方遇到西方

    冒险家的乐园 a playground of risk 实现发财梦 realize one's dreams of wealth 道德沦丧,堕落 moral deprivation 租界 foreig ...

  3. 【CentOS】LAMP相关3

    调优,安全如果是运维一个网站,PHP搭建的话,可能会出现500的错误,白页怎么去排查呢,今天就涉及到这方面的东西 http://blog.csdn.net/bsi_l4/article/details ...

  4. python tkinter

    1. 在python3中使用 import tkinter 异常:no module named _tkinter apt-get install python-tk

  5. MVC中RenderBody的工作原理

    A)什么是RenderBody  根据MSDN的解释(http://msdn.microsoft.com/query/dev10.query?appId=Dev10IDEF1&l=EN-US& ...

  6. 可变参数列表与printf()函数的实现

    问题 当我们刚开始学习C语言的时候,就接触到printf()函数,可是当时"道行"不深或许不够细心留意,又或者我们理所当然地认为库函数规定这样就是这样,没有发现这个函数与普通的函数 ...

  7. 如何理解typedef void (*pfun)(void)

    问题: 在刚接触typedef void (*pfun)(void) 这个结构的时候,存在疑惑,为什么typedef后只有一"块"东西,而不是两"块"东西呢?那 ...

  8. 【BZOJ】4001: [TJOI2015]概率论

    题意 求节点数为\(n\)的有根树期望的叶子结点数.(\(n \le 10^9\)) 分析 神题就打表找规律.. 题解 方案数就是卡特兰数,$h_0=1, h_n = \sum_{i=0}^{n-1} ...

  9. CSS Tip

    硬件加速 CSS will-change 属性

  10. 基于synchronized 或 ReadWriteLock实现 简单缓存机制

    package cn.xxx.xxx; import java.util.HashMap; import java.util.Map; import java.util.concurrent.lock ...