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. 将jsp页面转化为图片或pdf(一)

    在项目中遇见了将jsp页面转化为pdf的问题,试过itext,但是itext需要标准的html代码,我的页面中的一些属性是itext所不识别的,所以努力了一段时间后就放弃了,后来发现htmlutil抓 ...

  2. Python 学习第十八天 js 正则及其它前端知识

    一,js 正则表达式 test 判断制度串是否符合规定的正则 (1)定义正则表达式匹配规则         js 中定义正则表达式为rep=/\d+/,两个//之间为正则模式 (2)rep.test( ...

  3. [NHibernate]查看NHibernate生成的SQL语句

    最近接触到一个用Spring.Net结合NHIbernate的项目,第一次使用,有很多配置,数据操作一旦出问题,很难找到原因,那么如何查看NHibernate发送给数据库的SQL语句呢? 当然我们可以 ...

  4. BZOJ 2342 & manachar+最优性剪枝

    题意: 求最长回文串,串的两边都是回文串. Solution: manachar预处理然后暴力找... Code: #include <iostream> #include <cst ...

  5. 浅谈我对 jQuery 的了解

    总述 0 获取 jQuery 对象 1 对象跳转 2 方法调用 3 常用API 4 $(…); 5 jQuery 对象获取 6 Data 相关方法 7 选择器 8 基本的过滤器 9 内容过滤选择器 1 ...

  6. NOI 题库 9272 题解

    9272   偶数个数字3 描述 在所有的N位数中,有多少个数中有偶数个数字3? 输入 一行给出数字N,N<=1000 输出 如题 样例输入 2 样例输出 73 Solution : 令f ( ...

  7. 中文字符匹配js正则表达式

    普遍使用的正则是[\u4e00-\u9fa5],但这个范围并不完整.例如:  /[\u4e00-\u9fa5]/.test( '⻏' ) // 测试部首⻏,返回false    根据Unicode 5 ...

  8. MySQL中EXPLAIN命令详解

    explain显示了mysql如何使用索引来处理select语句以及连接表.可以帮助选择更好的索引和写出更优化的查询语句. 使用方法,在select语句前加上explain就可以了: 如: expla ...

  9. C#窗体中读取修改xml文件

    由于之前没有操作过xml文件,尤其是在窗体中操作xml,脑子一直转不动,而且很抵制去做这个功能,终于还是突破了自己通过查询资料完成了这个功能,在此记录一下自己的成果. 功能说明:程序中存在的xml文件 ...

  10. win32进程名查找进程PID

    1. #include <Psapi.h> #pragma comment(lib, "Psapi.lib") DWORD GetProcIDFromName(LPCT ...