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. UWP Composition API - GroupListView(一)

    需求: 光看标题大家肯定不知道是什么东西,先上效果图: 这不就是ListView的Group效果吗?? 看上去是的.但是请听完需求.1.Group中的集合需要支持增量加载ISupportIncreme ...

  2. Redis - 常用命令详解

    1.远程连接redis服务器 # 用法:redis-cli [OPTIONS] [cmd [arg [arg ...]]] # -h <主机ip>,默认是127.0.0.1 # -p &l ...

  3. Git - 问题集

    1.If no other git process is currently running, this probably means a git process crashed in this re ...

  4. Dos命令查看端口占用及关闭进程

    1. 查看端口占用 在windows命令行窗口下执行: netstat -aon|findstr "8080" TCP 127.0.0.1:80 0.0.0.0:0 LISTENI ...

  5. 手持移动扫描终端 PDA移动开单系统-批发零售管理

    条码数据采集器通过扫描商品条码移动开单,实现便携式办公,伴随式销售,是我公司的一款最新便携式开单配套产品,采集器能通过WIFI无线局域网.GPRS互联网直接与主机连接,让公司业务人员能随时随地了解公司 ...

  6. Java面向对象之封装

     面向对象的三个特征:封装.继承和多态. Java开发的过程就是找合适的库对象使用,没有对象创建新对象.找对象,建立对象,使用对象并维护对象之间的关系. 类就是对现实生活中事物的描述,而对象就是这类事 ...

  7. HDU5838 Mountain(状压DP + 容斥原理)

    题目 Source http://acm.hdu.edu.cn/showproblem.php?pid=5838 Description Zhu found a map which is a N∗M ...

  8. CustomUI Direct3D9_Sample

    刚开始建这个项目的时候编译器报了很多Link2019的错误. 后来添加了一些lib文件才解决,参考    缺少.lib文件导致的Link2019 解决方案汇总 ==================== ...

  9. [BZOJ3751][NOIP2014] 解方程

    Description 已知多项式方程:a0+a1*x+a2*x^2+...+an*x^n=0 求这个方程在[1,m]内的整数解(n和m均为正整数).   Input 第一行包含2个整数n.m,每两个 ...

  10. JAVA多线程售票问题

    //定义一个类实现Runnable接口,定义一个需要同步的售票方法,然后重写run方法调用售票的sale方法 class SaleTicket implements Runnable{ private ...