Lifting

Now, let's review map from another perspective. map :: (T -> R) -> [T] -> [R] accepts 2 parameters, a function f :: T -> R and a list list :: [T]. [T] is a generic type paramterized by T, it's not the same as T, but definitely shares some properties of T. So, an interesting interpretation of map(f) :: [T] -> [R] is that map turns a function of type T -> R into a function of [T] -> [R], this is called lifting.

Take the square function x -> x * x as an example, map(x -> x * x) turns the function on Int into a function on [Int]. Therefore, it makes sense to name map(x -> x * x) as squareForList. You can even simply name it as square, which is the overloaded version of square for [Int].

The concept of lifting is the key to understand the advanced abstractions in functional programming. Lifting allows you to reuse a function of type T -> R (or T1 -> T2 -> R ...) in the context of List, Maybe, Lazy, Promise, etc. That saves you the work to implement similar functions from scratch just for the context.

Let me explain why lifting matters by changing the string conversion problem in the previous chapter a bit. In the original problem, we got a function convert :: String -> String, what if the input string is not directly available, but asynchronously fetched from a web service? Do you want to chain the convert to the callback for the asynchronous HTTP response? You can use callback, but that makes you lose functional composition.

Just like map lifts a function on T into a function on [T], we just wanted to lift it to Promise<T>. Here Promise<T> stands for an asynchronously available value of type T. So, we'll introduce a function fmap :: (T -> R) -> Promise<T> -> Promise<R>, meaning fmap turns a function of type T -> R into a function of type Promise<T> -> Promise<R>. See the following example:

// Java 6
F1<String, String> convert = _(split(" "), reverse, map(toUpperCase), join("_"));
// fmap turns a function of type "T -> R" into a function of type "Promise<T> -> Promise<R>"
F1<Promise<String>, Promise<String>> convertForPromise = fmap(convert);
// await() blocks until the async result available
String result = convertForPromise.apply(promise(URL)).await();

More details here.

promise(URL) :: Promise<String> stands for a string value which will be available in the future. Calling await on the promise object will block until the string is available. fmap turns convert :: String -> String into convertForPromise :: Promise<String> -> Promise<String> which can work on a promise. By the way, if you like we can omit the convert function by inlining it as:

fmap(_(split(" "), reverse, map(toUpperCase), join("_")))

Functor

As I mentioned in the previous section, Promise, Maybe, List, Lazy, and so on are all contexts. The idea behind is the functional abstraction named Functor. In Java, a functor can be defined as follows:

interface class Functor<T> {
<R> Functor<R> fmap(F1<T, R> f);
}

then, Promise<T> will implement the fmap:

class Promise<T> implements Functor<T> {
<R> Promise<R> fmap(F1<T, R> f) {
...
}
}

But as I have said before, we are not in favor of the OO-style API design. A better way to define functor in Java is as follows:

public class Promises {
public static <T, R> F1<Promise<T>, Promise<R>> fmap(F1<T, R> f) {
return Promises.<T, R>fmap().apply(f);
}
}

It essentially means if we can define a function fmap to lift a function of type T -> R into a function of type Functor<T> -> Functor<R>, then Functor<T> is a functor. In addition, there're 2 properties named Functor Laws as the semantics constraints to ensure the type makes sense:

fmap id      = id
fmap (p . q) = (fmap p) . (fmap q)

Don't be scared, it's actually very simple. Just like we put the FILO constraint on the push and pop of the Stack type to make sure it behaves as what we want.

If you feel too abstract, take a look at the example of List<T> or Promise<T>. More often than not, your functor class satisfies the laws automatically. However, keep in mind that you may always want to test the functor laws for your functor class, just like you want to test FILO for a Stack implementation. See unit tests of Promise<T> for the functor laws here.

Monad

Lifting a function of type T -> R into a function of type Functor<T> -> Functor<R> allows us to reuse the existing functions in a different context, but sometimes the basic function we have is not as plain as toUpperCase :: String -> String. Let's look at the following problem:

Given 1) a function Promise<String> asyncGet(String url) which accepts an URL and returns a promise of the web page; 2) n hyperlinked web pages, the contents of one page is the URL of the next page, url1 -> page1 (url2) -> page2 (url3) -> page3 (url4) ... page_n (url1), please write a function Promise<String> asyncGetK(String url, int k) which starts from the url, goes forward by k steps, returns the page.

If what we have is a sync function String get(String url), that would be a simple loop like:

// Java 6
String getK(String url, int k) {
String page = url;
for (int i = 0; i < k; i++) {
page = get(page);
}
return page;
}

The point here is that the result of the previous get can be directly passed to the next get, because the type matches. In other words, we can compose multiple get functions together.

But since we only have asyncGet of type String -> Promise<String>, the result type Promise<String> of a previous asyncGet doesn't match the parameter type url :: String of the next asyncGet, we are unable to compose them together directly. So, we'd really like to lift asyncGet :: String -> Promise<String> into asyncGetPromise :: Promise<String> -> Promise<String> then it's composable.

The idea is great, but what would happen if we apply fmap to asyncGet. Since the type of fmap is (T -> R) -> Promise<T> -> Promise<R>, then the type of fmap(asyncGet) would be Promise<String> -> Promise<Promise<String>>. Ooops, that's too much! But if we have a join :: Promise<Promise<T>> -> Promise<T> to flatten a nested promise, then we will get _(fmap(asyncGet), join) :: Promise<String> -> Promise<String>. Combining fmap and join together, we get a function flatMap :: (T -> Promise<R>) -> Promise<T> -> Promise<R>, which is exactly what we want.

Being able to define a function fmap makes a type a Functor, likewise being able to define a function flatMap makes a type a Monad. Then the code would be like:

// Java 6
String getK(String url, int k) {
F1<Promise<String>, Promise<String>> asyncGetPromise = flatMap(asyncGet);
Promise<String> page = unit(url);
for (int i = 0; i < k; i++) {
page = asyncGetPromise(page);
}
return page.await();
}

It really shares the same structure as the sync code. That is isomorphic!

Functional Programming without Lambda - Part 2 Lifting, Functor, Monad的更多相关文章

  1. Functional Programming without Lambda - Part 1 Functional Composition

    Functions in Java Prior to the introduction of Lambda Expressions feature in version 8, Java had lon ...

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

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

  3. Python Lambda & Functional Programming

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

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

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

  5. Functional programming

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

  6. [Functional Programming] Function signature

    It is really important to understand function signature in functional programming. The the code exam ...

  7. JavaScript Functional Programming

    JavaScript Functional Programming JavaScript 函数式编程 anonymous function https://en.wikipedia.org/wiki/ ...

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

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

  9. a primary example for Functional programming in javascript

    background In pursuit of a real-world application, let’s say we need an e-commerce web applicationfo ...

随机推荐

  1. MySQL深度巡检

    1. IO层面检查   (1)IO检查   查看%util是否接近100%定位是哪个磁盘IO压力大 #iostat -x 1 10 (2)iotop定位负载来源进程 查看哪个PID占用IO最高 #io ...

  2. Linux 线程管理

    解析1 LINUX环境下多线程编程肯定会遇到需要条件变量的情况,此时必然要使用pthread_cond_wait()函数.但这个函数的执行过程比较难于理解. pthread_cond_wait()的工 ...

  3. 深入理解ConcurrentMap.putIfAbsent(key,value) 用法

    转自:http://blog.csdn.net/exceptional_derek/article/details/40384659 先看一段代码: public class Locale { pri ...

  4. HDFS操作

    HDFS操作 1.shell 1.1 创建目录 hadoop fs -mkdir 目录名(其中/为根目录) 1.2 遍历目录 hadoop fs -ls 目录名 1.3 删除目录 hadoop fs ...

  5. c语言文件读写操作总结

    C语言文件读写操作总结 C语言文件操作 一.标准文件的读写 1.文件的打开 fopen() 文件的打开操作表示将给用户指定的文件在内存分配一个FILE结构区,并将该结构的指针返回给用户程序,以后用户程 ...

  6. Spark性能优化-coalesce(n)

    有时用Spark 运行Job 的时候,输出可能会出现一些空或者小内容.这时重新将输出的Partition 进行重新调整,可以减少RDD中Patition的数目. 两种方式: 1. coalesce(n ...

  7. 4.4 多线程进阶篇<下>(NSOperation)

    本文并非最终版本,如有更新或更正会第一时间置顶,联系方式详见文末 如果觉得本文内容过长,请前往本人"简书" 本文源码 Demo 详见 Github https://github.c ...

  8. java的poi技术写Excel的Sheet

    在这之前写过关于java读,写Excel的blog如下: Excel转Html java的poi技术读,写Excel[2003-2007,2010] java的poi技术读取Excel[2003-20 ...

  9. maven整理——初步

    最近用到了maven,查找了很多资料,写这篇博文是为了记录maven的使用学习,也方便自己日后好查找. 在这里引用http://www.cnblogs.com/dcba1112/archive/201 ...

  10. JSON详解

    首先要知道JSON是一种轻量级的数据格式,不是一种编程语言,因此其他语言也可以使用. 一.JSON语法 JSON语法可以表现为下面三种值: 1.简单值:字符串.数值.布尔值和null,如"h ...