Functional Programming without Lambda - Part 2 Lifting, Functor, Monad
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 functionPromise<String> asyncGetK(String url, int k)
which starts from theurl
, goes forward byk
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的更多相关文章
- 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 ...
- Java 中的函数式编程(Functional Programming):Lambda 初识
Java 8 发布带来的一个主要特性就是对函数式编程的支持. 而 Lambda 表达式就是一个新的并且很重要的一个概念. 它提供了一个简单并且很简洁的编码方式. 首先从几个简单的 Lambda 表达式 ...
- Python Lambda & Functional Programming
Python Lambda & Functional Programming 函数式编程 匿名函数 纯函数 高阶函数 # higher-order functions def apply_tw ...
- 关于函数式编程(Functional Programming)
初学函数式编程,相信很多程序员兄弟们对于这个名字熟悉又陌生.函数,对于程序员来说并不陌生,编程对于程序员来说也并不陌生,但是函数式编程语言(Functional Programming languag ...
- Functional programming
In computer science, functional programming is a programming paradigm, a style of building the struc ...
- [Functional Programming] Function signature
It is really important to understand function signature in functional programming. The the code exam ...
- JavaScript Functional Programming
JavaScript Functional Programming JavaScript 函数式编程 anonymous function https://en.wikipedia.org/wiki/ ...
- Beginning Scala study note(4) Functional Programming in Scala
1. Functional programming treats computation as the evaluation of mathematical and avoids state and ...
- 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 ...
随机推荐
- Learn Git and GitHub without any code!
What is GitHub? GitHub is a code hosting platform for version control and collaboration.代码托管平台. repo ...
- SQL Server 2012 新特性
--Concat示例 ,null,'RTM') --Format实例 DECLARE @d DATETIME = GETDATE(); SELECT FORMAT( @d, 'd', 'en-US' ...
- 【Apache RocketMQ】RocketMQ捐赠给Apache那些鲜为人知的故事-转自阿里中间件
序言 今年的双十一对阿里巴巴中间件消息团队来说,注定是个不平凡的日子.在这一天,稳定性小组重点攻克的低延迟存储解决方案成功地经受住了大考.整个大促期间,99.996%的延迟落在了10ms以内,极个别由 ...
- <HTML>菜鸟入门基础须知
将持续更新-- 一,基础常用标签and属性 既然要学习这门知识,那必须得先知道这是什么能做什么,HTML:是一种超文本标记语言,什么意思呢,我拆开看一下,超(超链接)文本(犹如TXT)标记(改变成自己 ...
- 【转】UML图与软件开发过程那点关系
首先,软工文档, 软工文档,也就是计划,设计,描述,使用软件的一些文件,它最大的特点就是固定不变,用来给不同的人和计算机来阅读.在期间,文档起到了桥梁的作用,看这张图很形象: 在这里在看一下国家统一规 ...
- 【统计学习】主成分分析PCA(Princple Component Analysis)从原理到实现
[引言]--PCA降维的作用 面对海量的.多维(可能有成百上千维)的数据,我们应该如何高效去除某些维度间相关的信息,保留对我们"有用"的信息,这是个问题. PCA给出了我们一种解决 ...
- hack
1.Firefox @-moz-document url-prefix() { .selector { property: value; } }上面是仅仅被Firefox浏览器识别的写法,具体如: @ ...
- 【原】iOS学习之事件处理的原理
在iOS学习23之事件处理中,小编详细的介绍了事件处理,在这里小编叙述一下它的相关原理 1.UITouch对象 在触摸事件的处理方法中都会有一个存放着UITouch对象的集合,这个参数有什么用呢? ( ...
- 忽略this的后果
昨天在做一个简单的遮罩功能,说简单不如说是繁琐的好,主要是因为一个页面中有将近十几个,只不过是功能是一样的,要将一段文字遮盖住,文字的内容是不确定的,也就是跟着内容的高度变化而改变遮罩层的高度.了解了 ...
- linux platform设备与驱动
struct platform_driver { int (*probe)(struct platform_device *); int (*remove)(struct platform_devic ...