函数式编程之-F#类型系统

在深入到函数式编程思想之前,了解函数式独有的类型是非常有必要的。函数式类型跟OO语言中的数据结构截然不同,这也导致使用函数式编程语言来解决问题的思路跟OO的思路有明显的区别。

什么是类型?类型在编程语言中有什么作用呢?一般来说,类型有两个作用:

  1. 首先当你对某个数据声明类型后,就拥有了编译时的检查,换句话说,你可以认为类型充当了“编译时的单元测试”;
  2. 类型系统可以让你建立一种模型,用来表达真实世界中的模型;

Tuple type

元组是函数式编程语言中的常用类型,同时在.NET 4.0中被引入到了C#中。但是在C#中几乎不太会用到这种类型,但是在函数式编程语言中却随处可见。
例如在C#中这样使用元组:

var s = new Tuple<int, int>(1, 2);
var fist = s.Item1;

在F#中

let t = 1,2
let first = fst t //提取第一个元素
let second =snd t //提取第二个元素

let f s = t //通过解构提取元素

Record type

Record type是F#中最常用的类型。经常被用来对领域建模(Domain modeling)。例如定义一个矩形数据结构:

type Rect = {
    Left: float32
    Top: float32
    Width: float32
    Height: float32
}

这看起来跟OO语言中对class的定义是很相似的,好比在某个class中声明了一组属性。使用起来也简单:

let rc = {
    Left = 10.0f;
    Top = 10.0f;
    Width = 200.0f;
    Height = 200.0f
}

你可以把它当做一个简单的class,但是从定义和使用都能看出来Record type更加简单和直接一些。并且我们并没有在Record type里设计一些方法,这根class有本质的区别。
Record type还支持“复制一个现有记录并进行一些修改”:

let rc2 ={ rc with Left = rc.Left + 100.0f }

C#中的class是没有这种能力的,你不得不显示复制所有属性。
另外Record type自动实现了equal操作符:

type Name = { First:string ; Last:string}

let jim = { First ="Jim"; Last = "Dan"}
let jim2 = {First =  "Jim"; Last = "Dan"}
let isSame = jim = jim2  //true

使用Record type来建立领域模型

考虑下面的Contact领域模型:

type Contact = {
    FirstName: string;
    MiddleName: string;
    LastName: string;
    EmailAddress: string;
    Address1: string;
    Address2: string;
    City: string;
    State: string;
    Zip: string;
}

如果你把它当做一个class也是可行的,实际上在OO语言里我们也经常设计这样的class。这样的模型定义犯了三个错误:

  1. 没有把相关一组类型组合起来,例如FirstName, MiddleName, LastName。这三个类型共同组成了Name,我们的模型并没有体现出这样的设计。
  2. EmailAddress真的是一个string吗?他能有效的表达Email这样的Domain吗?Email分有效和无效,他拥有自己的规则,并不是所有的字符串都是Email,string这样的类型无法表达Email的Domain含义。
  3. 在F#中没有null,如果你认为某个类型可能为空,就应该设计为option类型,例如MiddleName,他应该是string option。
    还记得前面的章节我们说函数式编程的核心思想是组合,组合不但体现在函数之间的组合,类型也是可组合的:
type PersonalName = {
    FirstName: string;
    MiddleName: string option;
    LastName: string;
}

type EmailContactInfo = {
    EmailAddress: string;
    IsEmailVerified: bool;
}

type PostalAddress = {
    Address1: string;
    Address2: string;
    City: string;
    State: string;
    Zip: string;
}

type PostalContactInfo = {
    Address: PostalAddress;
    IsAddressValid: bool;
}

type Contact = {
    Name: PersonalName;
    EmailContactInfo: EmailContactInfo;
    PostalContactInfo: PostalContactInfo;
}

Descriminated Unions type

中文翻译过来叫做可区分联合,这种类型试图来为不同的选项进行建模,所以你可以把他理解为选项类型
举个例子:“现在温度是多少?“
如何对现在的温度建模?你问的是摄氏度呢还是华氏度呢?如果是摄氏度即38°,如果单位是华氏度,则为100.4°。

type Temperature =
    | F of float
    | C of int 

let tempNow = C(30)
let tempNow2 = F(100.4)

只有一个选项的类型:

type EmailAddress = EmailAddress of string
let email = "a" |> EmailAddress
let emails = ["a"; "b"; "c"] |> List.map EmailAddress

使用 Descriminated Unions type来建立领域模型

选项类型在F#是非常常用的领域模型建模类型,比如设计一个关于支付的模型,在OO语言中,你可能会这样做:

interface IPaymentMethod { }

class Cash : IPaymentMethod { }

class Cheque: IPaymentMethod { }

class Card : IPaymentMethod { }
...

在函数式语言中利用选项类型可以轻松搞定:

type PaymentMethod =
   | Cash
   | Cheque of ChequeNumber
   | Card of CardType * CardNumber

OO思想中通过抽象接口和定义派生类来实现这个模型。函数式语言则利用选项类型把模型核心内容通过尽可能少的代码展现出来。
此时你也许会有所疑虑,在OO的设计中,每种支付方式都是一个独立的实现,所以每种支付方式的具体行为就可以设计在具体的实现中,例如Payment的过程。不同的支付方式显然需要不同的支付过程,这种设计在OO中的好处是显而易见的。
选项类型中,似乎把三种不同的支付方式揉在了一块,那么每种支付方式的支付过程这种行为怎么实现呢?答案是模式匹配,我们将在下节介绍。

原文地址:https://www.cnblogs.com/xiandnc/p/9382139.html

时间: 2024-08-28 10:44:19

函数式编程之-F#类型系统的相关文章

函数式编程初窥F#(二)

大量讲解函数式编程语言的书籍最终都会用Fuctor,Monad,Monoids,范畴论等各种词汇吓退命令式语言玩家,所以我试图避开这些问题,揭开这些复杂词汇带来的具有实战意义的成果.另外我会尽量使用C#语言来描述函数式编程思想,因为C#某些语法和特性来自于函数式语言的启发,但C#终究并不是正统的函数式语言,随着对话题的深入,我们不可避免要使用F#来描述某些特性.所以本文会对F#的基础语法做一些简单了解,本文并不是一个纯粹的F#教程,只会提及必要的F#语法,最终循序渐进穿插在整个文章中. 函数是一

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

本文译自:Functional Programming for JavaScript People 和大多数人一样,我在几个月前听到了很多关于函数式编程的东西,不过并没有更深入的了解.于我而言,可能只是一个流行词罢了.从那时起,我开始更深地了解函数式编程并且我觉得应该为那些总能听到它但不知道究竟是什么的新人做一点事情. 谈及函数式编程,你可能会想到它们:Haskell 和 Lisp,以及很多关于哪个更好的讨论.尽管它们都是函数式语言,不过的确有很大的不同,可以说各有各的卖点.在文章的结尾处,我希

傻瓜函数式编程

傻瓜函数式编程 FP 说明 转载,github.com/justinyhuang 2006年6月19日,星期一 开篇 我们这些码农做事都是很拖拉的.每天例行报到后,先来点咖啡,看看邮件还有RSS订阅的文章.然后翻翻新闻还有那些技术网站上的更新,再过一遍编程论坛口水区里那些无聊的论战.最后从头把这些再看一次以免错过什么精彩的内容.然后就可以吃午饭了.饭饱过后,回来盯着IDE发一会呆,再看看邮箱,再去搞杯咖啡.光阴似箭,可以回家了-- (在被众人鄙视之前)我唯一想说的是,在这些拖拉的日子里总会时不时

【转】 为什么说面向对象编程和函数式编程都有问题

我不理解为什么人们会对面向对象编程和函数式编程做无休无止的争论.就好象这类问题已经超越了人类智力极限,所以你可以几个世纪的这样讨论下去.经过这些年对编程语言的研究,我已经清楚的看到了问题的答案,所以,我经常的发现,人们对这些问题做的都是一些抓不住要领.无意义的争论. 简言之,不论是面向对象编程还是函数式编程,如果你走了极端,那都是错误的.面向对象编程的极端是一切都是对象(纯面向对象).函数式编程的极端是纯函数式编程语言. 面向对象编程的问题 面向对象的问题在于它对"对象"的定义,它试图

函数式编程-将Monad(单子)融入Swift

前言 近期又开始折腾起Haskell,掉进这个深坑恐怕很难再爬上来了.在不断深入了解Haskell的各种概念以及使用它们去解决实际问题的时候,我会试想着将这些概念移植到Swift中.函数式编程范式的很多概念在Swift等主打面向对象范式的语言中就像各种设计模式一样,优雅地帮助我们构建好整个项目,促使我们的代码更加的美观优雅.安全可靠. 本篇文章为"函数式编程"系列中的第二篇,我主要说下Monad的一些小概念,以及试图将Monad融入Swift中来让其为我们的实际工程项目作出贡献. 关于

Python学习笔记八:文件操作(续),文件编码与解码,函数,递归,函数式编程介绍,高阶函数

文件操作(续) 获得文件句柄位置,f.tell(),从0开始,按字符数计数 f.read(5),读取5个字符 返回文件句柄到某位置,f.seek(0) 文件在编辑过程中改变编码,f.detech() 获取文件编码,f.encoding() 获取文件在内存中的编号,f.fileno() 获取文件终端类型(tty.打印机等),f.isatty() 获取文件名,f.name() 判断文件句柄是否可移动(tty等不可移动),f.seekable() 判断文件是否可读,f.readable() 判断文件是

PYTHON修饰器的函数式编程

转自:http://coolshell.cn/articles/11265.html Python修饰器的函数式编程 Python的修饰器的英文名叫Decorator,当你看到这个英文名的时候,你可能会把其跟Design Pattern里的Decorator搞混了,其实这是完全不同的两个东西.虽然好像,他们要干的事都很相似--都是想要对一个已有的模块做一些"修饰工作",所谓修饰工作就是想给现有的模块加上一些小装饰(一些小功能,这些小功能可能好多模块都会用到),但又不让这个小装饰(小功能

[Java] 函数式编程相关概念 - 笔记1

Java 8 引入了 lambda 表达式,以及函数式编程风格.在了解函数式编程过程中,做了些笔记,摘录于本文. 嵌套函数( Nested Function ) 1. 嵌套函数,是指在另一个函数里面定义的一个函数.外层的函数,这里简称为外层函数. 2. 函数的嵌套可以是多层嵌套.嵌套函数可以看到其全部的外层函数的非局部变量.在实际程序中,嵌套的层数一般很少.下面是一个三层嵌套的例子, innerOfInner 也可以访问在 outer 函数体重定义的变量 x . function outer()

函数式编程--函数和闭包

函数式编程就是一种抽象程度很高的编程范式.(Python允许使用变量,不是纯函数式编程语言) 函数式编程的特点:函数可以赋给变量,所以,可作为参数传递,可作为返回值返回. 一个最简单的高阶函数: 1 def add(x, y, f): 2 return f(x) + f(y) add(-5, 6, abs) , abs 作为参数传入 add 函数中 ,又作为参数返回 作为参数 一个列表运算的例子:lst = range(5) 加法:只需 for 循环遍历 lst ,依次相加,返回 amout 1