众多语言都会设计Option类型,例如Java 8和Swift都设计了Optional类型。其实这种类型早就出现在了函数式语言中,在OCaml和Scala中叫Option,在Haskell中叫Maybe。Option类型是为了解决了什么样的问题呢?

null的局限性

你一定写过类似的C#代码:

public string GetCustomerName(int id)
{
if (id < 0) return null;
//....
}

这段代码有什么问题吗?null在这里代表了什么意思?是不是要表示不存在这样的Cusotmer?

Null在C#或者Java这类语言中表示未初始化的空引用。例如:

string input;

这时的input就是一个没有初始化空引用。

但是在上面的代码中,我们其实是想表达没有这样的Customer,不存在这样的CustomerName,而不是null,null没有类型,自然无法表达出不存在Name这样的领域模型含义。

可是在C#中我们似乎并没有其他选择,那就勉强用null来表达吧。

接下来你一定写过类似的代码:

var name = GetCustomerName(id);
var length = name.Length;

也许你一眼就看出了问题所在,上面的代码有可能会发生运行时的空引用异常。

是不是通过加上判空就能解决这个问题?且不说这个方案好不好,大家有没有想过作为一门静态强类型的语言,能不能让这样的错误发生在编译阶段?

使用C#定义Optional类型

假如我们能够定义一个这样的类型Optional,他能描述T或者是存在的,或者是不存在的。那么我们就有机会重新定义GetCustomerName的方法签名:

public Optional<string> GetCustomerName(int id)
{
//...
}

这个方法签名是自描述的,使用者从方法签名中就能得知CustomerName有可能是存在的,有可能是不存在的。如果我们还能通过技术手段强制开发者必须处理这两种情况,那么我们就有机会消除空引用异常。

实现一个简易版的Optional类型:

public class Optional<T>
{
private readonly bool _hasValue;
private readonly T _value; public Optional(T value, bool hasValue)
{
_value = value;
_hasValue = hasValue;
}
} public static class Optional
{
public static Optional<T> Some<T>(T value) =>
new Optional<T>(value, true); public static Optional<T> None<T>() =>
new Optional<T>(default(T), false);
}

有了Optional类型,就可以这样使用它了:

var s1 = Optional.Some("hello");
var s2 = Optional.None<string>();

重新定义GetCustomerName函数:

public Optional<string> GetCustomerName(int id)
{
if (id < 0) return Optional.None<string>();
//...
return Optional.Some("name");
}

看起来快要成功了,我们已经用自己定义的Optional类型完美的表达出了领域模型的含义。接下来的问题在于如何通过技术手段强制开发者处理存在或者不存在这两种情况。

截至目前,我们并没有在Optional中暴露T的属性,意味着开发者无法直接读取T的值:

var name = GetCustomerName(1);
//无法访问,因为name是Optional<string>类型,并没有Length属性
var length = name.Length;

此时如果在Optional类型中定义一个方法,他需要接受如何处理两种情况的函数:

public TResult Match<TResult>(Func<T, TResult> some, Func<TResult> none)
{
return _hasValue ? some(_value) : none();
}

开发者就可以这样读取Length:

 var name = GetCustomerName(1);
var length = name.Match(s => s.Length, () => 0);

Match方法接受两个lambda,第一个用来处理name存在的情况,第二个用来处理name不存在的情况。

至此,我们定义的Optional类型看起来改善了null带来的一些问题,不过此时的Optional还远远不够完善,请参考C#开源库Optional

F#中的Option类型

得益于F#强大的类型系统,定义Option类型只需要三行代码:

type Option<'a> =       // use a generic definition
| Some of 'a // valid value
| None // missing

上面的代码定义了两种情况:Some或者是None,当类型为Some时还包含了一个类型'a。这种能够描述情况A或者情况B的类型叫做可区分联合(Discriminated Unions),可区分联合是一种F#中非常有用的建模类型。在未来的章节将会详细描述函数式语言常用的数据类型。

类似于C# Optional类型,你可以使用类似的方法使用它:

let s1 = "abc"
let len1 = s1.Length let s2 = Option<string>.None
let len2 = s2.Length

上面的代码会出现编译错误,s2并不是string类型,他是Option类型,因此Option类型并没有Length这样的属性。如果你想访问Option里面包含的类型,你不得不使用模式匹配(Pattern Matching),模式匹配会强制你处理Option的两种情况。

let len2 = match s2 with
| Some s -> s.Length
| None -> 0

模式匹配会在后面的章节详细描述,此时的场景你可以参考上面C#中对Optional类型的用法。

再看一个使用模式匹配处理Option的例子:

let x = Some 99
let result = match x with
| Some i -> Some(i * 2)
| None -> None

如果此时忘记编写对任何一个分支的处理,编译器都会给予警告,提示你忘记了处理Option的另一种情况。

下一节将会描述模式匹配。

函数式编程之-拒绝空引用异常(Option类型)的更多相关文章

  1. Java8自定义函数式编程接口和便捷的引用类的构造器及方法

    什么是函数编程接口? 约束:抽象方法有且只有一个,即不能有多个抽象方法,在接口中覆写Object类中的public方法(如equal),不算是函数式接口的方法. 被@FunctionalInterfa ...

  2. Java函数式编程:一、函数式接口,lambda表达式和方法引用

    Java函数式编程 什么是函数式编程 通过整合现有代码来产生新的功能,而不是从零开始编写所有内容,由此我们会得到更加可靠的代码,并获得更高的效率 我们可以这样理解:面向对象编程抽象数据,函数式编程抽象 ...

  3. Java中的函数式编程(四)方法引用method reference

    写在前面 我们已经知道,lambda表达式是一个匿名函数,可以用lambda表达式来实现一个函数式接口.   很自然的,我们会想到类的方法也是函数,本质上和lambda表达式是一样的,那是否也可以用类 ...

  4. 给 JavaScript 开发者讲讲函数式编程

    本文译自:Functional Programming for JavaScript People 和大多数人一样,我在几个月前听到了很多关于函数式编程的东西,不过并没有更深入的了解.于我而言,可能只 ...

  5. Java8新特性:函数式编程

    1. 概述 函数式编程学习目的: 能够看懂公司里的代码 大数据量下处理集合效率更高 代码可读性高 消灭嵌套地狱 函数式编程思想: 面向对象思想需要关注用什么对象完成什么事情.而函数式编程思想就类似于我 ...

  6. 谈一谈Java8的函数式编程(二) --Java8中的流

    流与集合    众所周知,日常开发与操作中涉及到集合的操作相当频繁,而java中对于集合的操作又是相当麻烦.这里你可能就有疑问了,我感觉平常开发的时候操作集合时不麻烦呀?那下面我们从一个例子说起. 计 ...

  7. 翻译连载 | 附录 B: 谦虚的 Monad-《JavaScript轻量级函数式编程》 |《你不知道的JS》姊妹篇

    原文地址:Functional-Light-JS 原文作者:Kyle Simpson-<You-Dont-Know-JS>作者 关于译者:这是一个流淌着沪江血液的纯粹工程:认真,是 HTM ...

  8. C# 函数式编程及Monads.net库

    函数式编程中,一切皆为函数,这个函数一般不是类级别的,其可以保存在变量中,可以当做参数或返回值,是函数级别的抽象和重用,将函数作为可重用的基本模块,就像面向对象中一切皆为对象,把所有事物抽象为类,面向 ...

  9. 大数据技术之_16_Scala学习_04_函数式编程-基础+面向对象编程-基础

    第五章 函数式编程-基础5.1 函数式编程内容说明5.1.1 函数式编程内容5.1.2 函数式编程授课顺序5.2 函数式编程介绍5.2.1 几个概念的说明5.2.2 方法.函数.函数式编程和面向对象编 ...

随机推荐

  1. 山东省ACM多校联盟省赛个人训练第六场 poj 3335 D Rotating Scoreboard

    山东省ACM多校联盟省赛个人训练第六场 D Rotating Scoreboard https://vjudge.net/problem/POJ-3335 时间限制:C/C++ 1秒,其他语言2秒 空 ...

  2. Java实现图片的裁剪(包括透明背景)

    需求: 有一张位置大小的图片,现在需要根据这张原图得到指定尺寸的图片,且得到的图片要符合原先图片的比例,就是在原图的基础上等比例缩放得到图片后,在进行剪裁,这样保证得到的图片是原图的一部分,而不是将原 ...

  3. 将n个东西分成n1,n2,n3,n4,....nr 共 r组分给r个人有多少种分法。

    (n!/(n1! *n2! *n3!..nr!) )   * r!/( 同数量组A的数量! 同数量组B的数量!....) 比方20个东西分成2,2,,2,2   3,3,3,3 8组分给8个人有多少种 ...

  4. ceph删除pool提示(you must first set the mon_allow_pool_delete config option to true)解决办法

    现象: 1.在mon节点打开/etc/ceph/ceph.conf,增加以下 2.重启ceph-mon systemctl restart ceph-mon.target 3.删除pool [root ...

  5. 【洛谷P2584】【ZJOI2006】GameZ游戏排名系统题解

    [洛谷P2584][ZJOI2006]GameZ游戏排名系统题解 题目链接 题意: GameZ为他们最新推出的游戏开通了一个网站.世界各地的玩家都可以将自己的游戏得分上传到网站上.这样就可以看到自己在 ...

  6. IDEA 的黄线标注 取消

    用IDEA经常会遇到found duplicate code的问题,下面有黄线标注,严重影响强迫症患者的使用.下面就是如何取消的方法 在设置中把这个勾取消即可1 转载于:  https://blog. ...

  7. pytorch 损失函数

    pytorch损失函数: http://blog.csdn.net/zhangxb35/article/details/72464152?utm_source=itdadao&utm_medi ...

  8. linux中iptables配置文件及命令详解

    转自:https://www.cnblogs.com/itxiongwei/p/5871075.html iptables配置文件 直接改iptables配置就可以了:vim /etc/sysconf ...

  9. 28.TreeSet

    与HashSet是基于HashMap实现一样,TreeSet同样是基于TreeMap实现的.在前一篇中详细讲解了TreeMap实现机制,如果客官详细看了这篇博文或者对TreeMap有比较详细的了解,那 ...

  10. Linux时间戳转换成BCD码(转载)

    #include <stdio.h> #include <stdlib.h> #include <time.h> #include <math.h> / ...