深入理解函数式编程之functor

在函数式编程中,函子(functor)可以说是一个很基础的概念。
当然了,还有一个更基础的概念是函数(function)。函数是每一个编程语言都会涉及到的概念,它实际上表达的是一个对象到另一个对象的映射关系。请原谅我在这里使用了对象一词,因为作为一个Java程序员,对象可以说是最熟悉的概念。
上面的一个对象,指的是函数的输入参数;而另外一个对象,则是指函数的返回参数;映射关系指的是函数。
比如:

public int add1(int someNumber)
{
    return someNumber + 1;
}

这就是一个典型的函数。
函数处理的是对象,而函子则处理的是范畴。所谓“范畴”,从编程的角度来理解,我们可以理解成高阶对象。
那么,什么是“高阶对象”?
实际上,就是更为复杂的对象,或者类型。
比如:
一个int类型的对象是一个简单对象,那么一个int数组则是一个更为高阶的对象。
一个IM类型对象是一个简单对象。

class Communication
{
    private int type;
}
class IM extends Communication
{
    private String code;
}
class Address
{
    private String provinceId;
    private String cityId;
    private String district;
    private String detail;
}
class Employee
{
    private int emplNo;
    private String firstName;
    private String lastName;
    private Communication communication;
    private Address addresse;
}

那么,一个Employee对象就是另外一种更为高阶的对象,或类型,注意:为了减少代码量,省略了get和set方法。
前面说了,函数add1是对简单的int对象做映射;那么对应的函子则是对int数组做映射,具体说来就是对int数组的每一个元素做加1的映射。
说到这里,我们可以想到Java8的Stream对象可以做这样的映射。对了,Stream对象就是一个函子,它实现函子功能的函数就是map函数。
因此,所有的函子必须实现map函数。
像下面的例子,就是Stream作为函子的一个典型应用:

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
list.stream().map(i -> i + 1).forEach(System.out::println);

所谓函子,就是把一个作用于简单对象的函数,应用到一个高阶对象的过程。比如,上例中的i + 1函数,本来是作用于int类型对象的,现在我们通过map方法,把它应用到List<Integer>对象上,所以,Stream就是一个典型的函子。
重要的事情说三遍,下面再以图例的形式来说说如何从函数到函子的:
我们把简单对象称之为“值”:

简单对象
简单对象

下面的图例表示简单对象和函数之间的关系:
简单对象与函数
简单对象与函数

高阶对象好比是一个打包了的对象:
高阶对象
高阶对象

对于高阶对象,不能直接应用函数:
高阶对象与函数
高阶对象与函数

我们要定义一个新的运算-map来解决在高阶对象上使用函数的问题:
高阶对象与函子
高阶对象与函子

这个实现了map方法的类,我们就称之为函子。
所谓的map方法,就是将高阶对象打开,取出里面的简单对象,然后再对简单对象应用map方法传递过来的函数,再把应用函数后的结果又包装成高阶对象,最后将这个高阶对象返回。
我们都知道,对于上例中的int[]对象,我们可以通过使用Stream对象来构建函子,可以完成一系列的map运算,得到我们想要的结果,在这里就不再详细阐述。
下面,我们来看看上面的Employee对象,如何应用到函子。
首先,我们需要创建一个函子接口和它的子类:

public interface Functor<T, F extends Functor<?, ?>> {
    <R> F map(Function<T, R> f);
}

public class CommonFunctor<T> implements Functor<T, CommonFunctor<?>> {
    private T value;
    public CommonFunctor(T value) {
        this.value = value;
    }
    @Override
    public <R> CommonFunctor<R> map(Function<T, R> f) {
        final R result = f.apply(value);
        return new CommonFunctor<>(result);
    }
    public T getValue() {
        return value;
    }
}

我们再次可以看到,所谓的函子,就是实现了map方法的对象。在map方法中,很重要的是,必须在map方法体内包装函子对象,然后返回,如return new CommonFunctor<>(result);,这是函子(functor)和单子(monad)的最大区别。单子,我们后续会讲到。
最后,看看我们如何使用这个函子的:

IM qq = new IM();
qq.setType(1);
qq.setCode("123223");

Address address = new Address();
address.setProvince("浙江");
address.setCity("杭州");
address.setDistrict("西湖");
address.setDetail("西湖国家广告产业园");

Employee zhangsan = new Employee();
zhangsan.setEmplNo(23);
zhangsan.setFirstName("张");
zhangsan.setLastName("三");
zhangsan.setCommunication(qq);
zhangsan.setAddresse(address);

以上是初始化一些数据,用来测试,下面是如何使用函子的代码:

CommonFunctor<Employee> employeeCommonFunctor = new CommonFunctor<>(zhangsan);

通过函子求该员工的全名:

String fullName = employeeCommonFunctor.map(employee -> employee.getFirstName()+employee.getLastName()).getValue();

通过函子求该员工的全地址:

String fullAddress = employeeCommonFunctor.map(Employee::getAddress).map(addr -> addr.getProvince()+addr.getCity()+addr.getDistrict()+addr.getDetail()).getValue();

这是函子的一些基本用法,我们在Stream类的用法中可以深刻的感受到。
在后面的章节,我们将陆续讲讲函子在传统的设计模式中是如何使用的,敬请关注,谢谢!


参考文献:图解 Monad-阮一峰

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容