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. #英文#品读中国城市个性——秦汉雄风&和祖先在一起

    妨碍 interfere with 仇恨 hatred ​坍塌 collapse 专制君主 autocratic dictator 排除异己 suppress opposition 被逼到绝望边缘 b ...

  2. python 学习第二十一天,django知识(三)

    一,django的url路由系统总结 1,url(/index/,函数或者类) 2,url(/index/(\d+), 函数或者类) 3,url(/index/(?P<nid>\d+),函 ...

  3. [算法总结]partition (quicksort)

    private int partition(int[] nums, int lo, int hi) { if (lo >= hi) { return lo; } int i = lo; int ...

  4. 调整Kali Linux的锁屏时间

    调整Kali Linux的锁屏时间   锁屏是保护隐私的一种重要机制.当用户不操作电脑一段时间后,系统会进入锁屏状态.用户需要输入口令,才能重新进入系统.避免因为操作人员离开电脑后,被其他人员利用现有 ...

  5. 基于dubbo构建分布式项目与服务模块

      关于分布式服务架构的背景和需求可查阅http://dubbo.io/.不同于传统的单工程项目,本文主要学习如何通过maven和dubbo将构建分布项目以及服务模块,下面直接开始. 创建项目以及模块 ...

  6. $event 获取对象

    用Angular给元素添加事件时获取可以用 $event 传递当前触发的事件的元素对象 页面上可以这样写 <img ng-src="" alt="" ng ...

  7. fly bird

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  8. 【NEUQACM OJ】1017: 平面切割(特别版)

    1017: 平面切割(特别版) 题目描述 我们要求的是n条闪电型折线分割平面的最大数目.比如,一条闪电型折线可以将平面分成两部分,两条最多可以将平面分成12部分,三条最多可将平面分成31部分,四条最多 ...

  9. BZOJ3197 & 组合乱搞

    Description    求\[\sum_{i = 1}^{n}i^m m^i , m \leq 1000 \] 的值.Solution    From Miskcoo's Space:      ...

  10. mac新手的烦恼

    最近新换了mac,我换mac并非自愿.无论mac的性能有多好,我依然讨厌使用appstore下载软件的感觉,尤其在我一遍又一遍忘记自己的appID的时候.无奈我的thinkpad常常死机,最近又常倒腾 ...