On Functional Programming

This article was first published in A Brief Book of the Purpose of Poufu:https://www.jianshu.com/u/204...

1. Preface

One sunny afternoon, I watched the code migrate from day to day and saw this code:

Suddenly, I saw code like this:

    private void getTopicsDiskSizeForSomeBroker(int brokerID, AdminClient admin, Map<String, Long> topicsSizeMap) throws ExecutionException, InterruptedException {
        DescribeLogDirsResult ret = admin.describeLogDirs(Collections.singletonList(brokerID));
        Map<Integer, Map<String, DescribeLogDirsResponse.LogDirInfo>> tmp = ret.all().get();
        for (Map.Entry<Integer, Map<String, DescribeLogDirsResponse.LogDirInfo>> entry : tmp.entrySet()) {
            Map<String, DescribeLogDirsResponse.LogDirInfo> tmp1 = entry.getValue();
            for (Map.Entry<String, DescribeLogDirsResponse.LogDirInfo> entry1 : tmp1.entrySet()) {
                DescribeLogDirsResponse.LogDirInfo info = entry1.getValue();
                Map<TopicPartition, DescribeLogDirsResponse.ReplicaInfo> replicaInfoMap = info.replicaInfos;
                for (Map.Entry<TopicPartition, DescribeLogDirsResponse.ReplicaInfo> replicas : replicaInfoMap.entrySet()) {
                    String topic = replicas.getKey().topic();
                    Long topicSize = topicsSizeMap.get(topic);
                    if (topicSize == null) {
                        topicsSizeMap.put(topic, replicas.getValue().size);
                    } else {
                        topicsSizeMap.put(topic, replicas.getValue().size + topicSize);
                    }
                }
            }
        }
    }

Look at this code, I'm not good for the whole person!

First of all, the three nested for loops in the rocket style, and then the variable declaration statements. In order to iterate over them, we have to declare it once.

2. Use Stream

    public List<KafkaTopicInfoBO> getTopicDiskSize() {
        return getTopicPartitionReplicaInfo().entrySet().stream()
                .map(e -> new KafkaTopicInfoBO(e.getKey().topic(), e.getValue().size))
                .collect(Collectors.toList());

    }

    protected Map<TopicPartition, DescribeLogDirsResponse.ReplicaInfo> getTopicPartitionReplicaInfo() {
        Properties globalConf = zkConfigService.getProperties(ZkPathUtils.GLOBAL_CONFIG);
        Properties adminConfig = new Properties();
        adminConfig.setProperty(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, globalConf.getProperty((ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG)));
        AdminClient adminClient = AdminClient.create(adminConfig);
        List<String> brokerIds = zkConfigService.getChildByPath(kafkaIdsPath);
        return  brokerIds.stream()
                .map(Integer::valueOf)
                .map(Collections::singletonList)
                .map(adminClient::describeLogDirs)
                .map(DescribeLogDirsResult::all)
                .map(mapKafkaFuture -> {
                    try {
                        return mapKafkaFuture.get();
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                })
                .map(Map::values)
                .flatMap(Collection::stream)
                .map(Map::values)
                .flatMap(Collection::stream)
                .map(e -> e.replicaInfos)
                .map(Map::entrySet)
                .flatMap(Collection::stream)
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
    }

This seems a little better. But for those who are not familiar with functional programming, it is still a little difficult to understand the above code.

Next, let's briefly talk about functional programming.

3. What is functional programming?

3.1 Understand a sentence

It's like algebra from mathematics.

f(x)=5x^2+4x+3
g(x)=2f(x)+5=10x^2+8x+11
h(x)=f(x)+g(x)=15x^2+12x+14

Functional programming defines the relationship between input data and output data, which is actually a kind of mapping in mathematical expression. What is the relationship between input data and output data, which is used for function definition.

3.2 Intuitive Feeling: Using Code as an Example

public class Quint{
    public static void main (String args[]){
        for (int i=0; i<25; i++){
            System.out.println(i*i);
        }
    }
}
(println (take 25 (map (fn [x] (*x x) (range)))))

Simply explain the previous Lisp code:

  1. The range function returns an infinite list of integers starting at 0.
  2. The list is then passed into the map, calling anonymous functions with square values for each element in the list, resulting in an infinite number of lists containing square values.
  3. Pass the list into the take function, returning only the first 25
  4. println outputs access parameters

4. Use Kotlin to support better functional programming


    protected fun getTopicPartitionReplicaInfo(): Map<TopicPartition, DescribeLogDirsResponse.ReplicaInfo> {
        val globalConf = zkConfigService.getProperties(ZkPathUtils.GLOBAL_CONFIG)
        val adminConfig = Properties()
        adminConfig.setProperty(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, globalConf.getProperty(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG))
        val adminClient = AdminClient.create(adminConfig)
        val brokerIds = zkConfigService.getChildByPath(kafkaIdsPath)
        return brokerIds.stream()
                .mapToInt {
                    Integer.valueOf(it)
                }.let { intStream ->
                    adminClient.describeLogDirs(intStream.boxed().collect(Collectors.toList()))
                }.let { describeLogDirsResult ->
                    describeLogDirsResult.all()
                }.let { mapKafkaFutrue ->
                    mapKafkaFutrue.get()
                }.let { mapStream ->
                    mapStream.values
                }.let {
                    it.stream().map { e -> e.values }.flatMap { e -> e.stream() }.collect(Collectors.toList())
                }.flatMap {
                    it.replicaInfos.entries.toList()
                }.let { it ->
                    it.associateBy({ it.key }, { it.value })
                }
    }

The code looks pretty good. But Kotlin's keywords are easier to write. Let's look at the signatures of map functions in Java and let functions in Kotlin:

     * Returns a stream consisting of the results of applying the given
     * function to the elements of this stream.
     *
     * <p>This is an <a href="package-summary.html#StreamOps">intermediate
     * operation</a>.
     *
     * @param <R> The element type of the new stream
     * @param mapper a <a href="package-summary.html#NonInterference">non-interfering</a>,
     *               <a href="package-summary.html#Statelessness">stateless</a>
     *               function to apply to each element
     * @return the new stream
     */
    <R> Stream<R> map(Function<? super T, ? extends R> mapper);
/**
 * Calls the specified function [block] with `this` value as its argument and returns its result.
 *
 * For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#let).
 */
@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}

We can see that map s in Java are restricted to Stream API s, and Kotlin's let s are not.

At the same time, we can also feel that Kotlin is obviously better for functional programming support - in Kotlin, we can use a () to represent functions, while Java needs Interface to represent (in Java, objects are first-class citizens).

If the reader is interested, try Haskell or Lisp (Clojure in JVM). These are pure functional languages.

Similarly, Kotlin has many of these functions, called scoping functions. Here's a list of commonly used functions:

  • let
  • run
  • also
  • apply
  • takeIf
  • takeUnless
  • repeat

5. Summary

In The Way to Clean Architecture, there is a summary:

  • Structured programming is a restriction on the direct transfer of program control
  • Object-Oriented Programming is the Limitation of Indirect Transfer of Program Control
  • Functional programming is a restriction on the assignment of programs

If object-oriented programming is to abstract data, functional programming is to abstract behavior.

5.2 Three sets of functional programming:

  1. Map
  2. Reduce
  3. Filter

For example, bread and vegetable map s are shredded into hamburgers.

We can see that map and reduce don't care about input data, they only control, not business. Control describes how to do, and business description what to do.

In this article, we only see the image of map -- as mentioned above, every element in map convection operates.

Readers may ask what a let is. In this code example, let operates on the entire stream.

Simply put, Map & Reduce corresponds to the cycle we use in our daily life, while Filter corresponds to If

5.3 Advantages & Disadvantages

  • advantage

    1. Stateless
    2. Complication without injury
    3. There is no sequential problem with function execution
  • Inferiority

    1. Serious data replication

5.4 Application Scenarios

  • Python Decorator Mode
  • Event traceability: Does not record the final state, but records each event. When necessary, the current state is obtained by tracing (recalculating) the event.

    1. Database transaction log
    2. Version Controller
    3. Bitcoin

Keywords: Java Programming jvm Python

Added by jnutter on Sun, 11 Aug 2019 14:03:24 +0300