Swift 和函数式编程的遗产

tryswift-rob-napier-swift-legacy-functional-programming
原文地址 by Rob Napier
译文地址 by Alex Tse

Rob Napier 开发过单子(Monad),Functor of Doom。 他接触过 map,flattened 和 lensed。他反复迭代尝试,提出了 Maybe 类型,今后他也将继续致力于对函数式编程的探索。
他从 Haskell 到 Church,可以确定:Swift 不是一个函数编程语言,如果硬把它们当成一回事反而会扰乱 Swift 和破坏 Cocoa。
然而,Swift 却已经从函数编程吸取了一些很不错的经验,虽然值类型可能不是当前所流行的,但它们显然是未来趋势。
Rob 研究着已经有数十年的函数语言是如何对 Swift 产生了影响,以及如何更好的使用这些特性,同时还要保持 Swift 的个性和 Cocoa 的友好,当然少不了要拥抱面向协议编程。


概述

Swift 面世一周后,我写了“Swift并不是一种函数语言”这篇博客。两年过去了,Swift 依旧不是函数语言。这篇文章讨论的是经历了数十载的函数编程,以更 Swifty 方式带到 Swift 里去。

什么是函数编程?

它是单子(monads),函子(functors),Haskell 这类优美的代码。

函数编程不是一种语言或语法,它是一种用来思考问题的方式。函数编程在 Monad 出来的前几十年就有了。 我们会用函数编程来思考如何解决问题,并将它们结构化的组织起来。

用 Swift 写一段非常简单的代码看看:

var persons: [Person] = []
for name in names {
    let person = Person(name: name)
    if person.isValid {
        persons.append(person)
    }
}

这是个实现了两件事的简单循环:用 name 实例 person,然后把有效的 person 插入到数组当中。如此简单的代码,还是做了不少工作。因为要知道这其中发生了什么,必须从头看到尾过一遍。而且你还需要思考,“这个数组是做什么的?”

这段代码其实还可以更简单。我们可以关注点分离(separate the concerns),把事情分开。

比如我们现在有两个循环,每个循环做少量的事。我们从每个简单的循环代码里面找到模式:创建一个数组,遍历一些值,在每个值上面做一些操作,最后把它们插入到另一个数组里。

当你有一些事需要重复执行多次,你可能需要类似一个 extract 函数 – Swift 就有这个函数,叫 “map”。Map 可以将一个列表的东西转换成另一个列表。要注意的是:Map 会包含所有有效或无效的 person 对象。这是个基于 names 的 persons 列表。因此我们不需要从头开始过一遍代码,我们要表达的已经在这里面。

let possiblePersons = names.map(Person.init)
let persons = possiblePersons.filter { $0.isValid }

另一个循环模式也很常见:叫“filter”。Filter 接受一个返回 bool 的闭包函数。Filter 适用这个例子,可以得到有效的 person。我们可以将 map 和 filter 放在一起,把 map 得到的 Person 列表放到 filter 函数当中。

我们从上面的7行代码减少到2行。此外,我们可以重组它们:把这些所有函数通过链式(chaining)放在一起处理。

它十分易读,我们一次就可以读完。

let persons = names
    .map(Person.init)
    .filter { $0.isValid }

这种方式值得好好学习,我们应该适应这种写法(在我看来这很Swift)。在这个例子里无需再写 for 循环。

函数式工具

在1977年,约翰·巴科斯(John Backus)(帮助发明了 FORTRAN 和 ALGOL)赢得了图灵奖,并发表了一场名叫“编程能否从 Von Neumann 风格中解放出来”的演讲,我喜欢这个标题。“Von Neumann 风格”指的就是 FORTRAN 和 ALGOL。

这次演讲是一场道歉,原因是他发明了 FORTRAN 和 ALGOL。他解释说,指令式编程是一步一步的改变一些状态,直到进入你想得到的最终状态。他说“函数”这并不代表今天所说的功能,他鼓励许多函数研究者去学习研究。

我对该演讲很感兴趣,因为有一些东西可以带给 Swift:我们如何将复杂的东西分解成简单的东西。使这些简单的东西变得通用,然后再使用一些规则(如代数)将它们粘合起来。

代数的规则是把东西堆放在一起,然后按条件分离,最后一起转换它们。我们可以想一个用来控制程序的规则,事实上我们已经做到了:将一个循环分解成两个更简单的循环,找到它们惯用的形式,然后用链式(chaining)链在一起。当 Haskell 开发者第一次接触 Swift 的时候,往往会觉得不爽,因为他们一直实践的是 Haskell 的理论。

就如所有函数式语言一样,Haskell 的基础构成单位就是函数。它们都有非常好的方法来组织函数。下面我创建了一个新函数,叫 sum,把 foldr 函数和 + 函数合在一起,并赋予其初始值为 0。你阅读时可能觉得不顺畅,但是一旦运用起来,它的魅力会把你折服。

let sum = foldr (+) 0
  sum [1..10]

你可以用在 Swift 上,但你会发现代码变得丑陋,又不能很好的运行,别用错了单位组合,Swift 可不是函数式。

Swift 的构成单位是类型。你可以用 Classes,structs,enums 和 protocols 来构建(compose)。我们通常会通过一个函数将两种类型结合在一起,使用更简单的方式构建它们。

Swift 另一种常见的构成是将类型添加上下文(context)。最常见的是可选(optionals)类型。可选(optional)是添加上下文的类型,这个上下文可能有值,可能没有。那么这个类型就包含了一个小小的信息:它是否存在?这就是上下文的意思。添加上下文比其他跟踪信息的方式更强大。

extension MyStruct<T>: Sequence {
    func makeIterator() -> AnyIterator<T> {
return ... }
}

我们可以跟踪查看一个值,判断它是不是整数,是否有值,是否为-1,如果是-1那这意味着是无效值。那么如果每个地方都写上这样的判断,你会发现代码开始变得丑陋起来,而且容易出错,甚至编译器也帮不到你。

如果我们把 int 加上可选类型,那么编译器就可以帮助你,这个 int 就有了这样的上下文:“有值,没值,我可以确保你不会忘记它”。如果你曾经使用过-1,而这又很容易忘记检查,那么你的程序就会走到你意想不到的地方。

// No value: magic value
let noValue = -1
let n = 0
if n != noValue { ... }

// No value: context
let n: Int? = 0
if let n = n { ... }

例子

让我们来看一个更复杂的例子。

func login(username: String, password: String,
           completion: (String?, Error?) -> Void)
login(username: "rob", password: "s3cret") {
    (token, error) in
    if let token = token {
        // success
    } else if let error = error {
// failure }
}

这是很常见的 API。我们有一个登录函数,需要用户名密码,同时还会回传一个 token 和一个可能会出现错误的信息。我认为我们可以把上下文(content)处理的更好。

第一个问题completion 回调。这里说的是一个 String,但这个字符串是什么?是一个 token。我可以加上一个 label,但这并没有用。在 Swift 中,label 不是类型。即时这样做了,它依旧是字符串,字符串可以表示很多东西。

Token 有一些规则:它可能是固定长度,或者不能为空。这些都可以用字符串表示,但都不能表示 token。我们想要 token 带有更多的上下文。它是有规则的,所以我们要使用这些规则。既然如此,我们可以给它更多的结构。这就是为什么称它为 struct

struct Token {
    let string: String
}

我把字符串加入到结构当中。这不会产生其他开销,也不会有任何额外内存的使用,但现在我可以把一些规则运用在这里。

在这就只能构建一些指定的字符串,使用扩展还可以创建一些其他字符串。更棒的是这可以添加所有类型,string,dictionary,array,int 等,统统没问题。

当有了这些类型,你就可以把它们添加到一个上下文当中,并在使用它们的地方控制它们。意味着你可以控制它们是什么意思。我不再需要 label 或添加标注(comment),因为这第一眼可以看出是 Token 类型,第一个参数就是令牌(token)

第二个问题是我们传入了一个用户名密码。在大部分的程序中,你总会把它们一起传入进去;但密码本身就很少被使用到。我想要创建一个规则,允许我用“and”把用户名和密码组合起来,所以我需要一个“and”类型。事实上我们已经有一个,那就是刚才用到的 struct。

“AND”类型(积)

struct Credential {
  var username: String
  var password: String
}

Struct 是“and”类型。Credential 是一个用户名密码。“and”类型常用的名字叫“积类型”(product type)。

我鼓励你大声的把看法说出来。例如:“credential 结构是一个用户名和密码。”是否有意义?如果没有意义,这或许是一个错误的类型,又或许是你构建错了。

func login(credential: Credential,
           completion: (Token?, Error?) -> Void)


let credential = Credential(username: "rob",
                            password: "s3cret")
login(credential: credential) { (token, error) in
    if let token = token {
        // success
    } else if let error = error {
        // failure
    }
}

现在我们可以把用户名密码换成 Credential:这也使得我们的验证函数变得更短,更清晰,还开辟了更多不错的可能:对 Credential 进行扩展,或者用其他类型来代替它们。或许我们想要一个 one0time 密码,一个访问令牌(access tokens),或者 Facebook、Google 等第三方登录。现在我不需要修改其他地方的代码,因为这里只需传一个凭证即可。

但这依旧有问题。我们已经传递了这个 (Token?, Error?) 元组(tuple) – 元组是“and”类型。它们都是匿名构造体。我们的意思是“这可能是一个 token?可能是一个 error”吗?所以有四种可能:都是,都不是,要么是 token,要么是 error。但任何场景下,只有其中两种可能性。如果我同时得到一个 token 和 error,怎么办?这是错误的情况吗?需要一个致命的错误码?需要忽略它吗?此时你需要思考一下才能针对性的测试。

“OR”类型(和)

问题是你不需要把所有情况都表示出来 – 你只需表达清楚这是 token 或是 error。那么是否有一个“or”类型提供我们使用吗?

enum Result<Value> {
    case success(Value)
    case failure(Error)
}

这是一个枚举(enum) – 枚举是“or”类型,而结构(struct)是“and”类型。“and”类型是“积类型”(product type),而“or”是“和类型”(sum type)。

func login(credential: Credential,
           completion: (Result<Token>) -> Void)
login(credential: credential) { result in
    switch result {
    case .success(let token): // success
    case .failure(let error): // failure
    }
}

我构建一个 result 类型。result 类型不是内建在 Swift 里,这真的让我很恼火。幸好它很容易构建。我们将赋予 value 更多上下文,从一个普通 value 变成有“登录成功”含义的 value。

我们这里的 error 也有了更多上下文,它更像处理失败情况的 error。如果返回像之前的结果,最终 token 会包含在里面,所有不可能发生的情况都需要编写一次测试。现在我们并不需要担心,因为已经不会发生这种情况了。我可不想为不存在的 bug 编写一个个测试。

我喜欢这个API。我通用一个 credential,它就会返回一个最终的 token。

总结

这是函数式编程真正的遗产,这些也应该带给 Swift:复杂的东西,可以分解成更小,更简单的东西。

我们可以为这些简单的东西找到一些通用的解决办法,并在程序有条理的使用一贯的规则将简单的东西放在一起。这能避免编译器不会出错,而且我认为在70年代的约翰·巴科斯(John Backus)也会认同这个观点。打破它,并重建(Break it down, build it up)。

Bootstrap 2.3.1 改动及文档

Bootstrap 2.3.1在前几天发布出来,主要是修复了2.3版本的一些Bug。而mdo和fat貌似对这次错误的出现表示很不愉快。下面是发布该版本的博客翻译和2.3.1最新文档,原文同样可以点击这里查看

Bootstrap 2.3.1

Bootstrap 2.3作为V3.0前的最后一个版本,就在刚才我们打了一个小补丁来解决一些“贴心”的JavaScript bugs。Bootstrap 3仍在开发,这个过程也相当不错。我们也将有更多的东西和大家分享。

在此之前,让我们来看看2.3.1有什么新的东西:

  • 修复了下拉菜单插件缺少事件的情况
  • 修复了提示框/提示工具委派的data-attr
  • 轮播可以更好的运作
  • 在makefile修正了jshint文献
  • 修复了在没有设置背景的情况下试图移除背景的错误

关于这次变动的更多细节,可以查看2.3.1 pull request

下载 Bootstrap 2.3.1 (主线最新的ZIP)

附注:除了这次的修改,以后或暂未发现的Bug只在3.0处理,或者说该版本已经没有太大问题。而这次遗留的问题是2.3版本发布后这几个星期让我们无法忽视,必须要做出修改。

<3,

@mdo and @fat

Bootstrap 2.3.1

点击进入Bootstrap 2.3.1文档中文翻译版

 

Bootstrap 2.3 文档 中文翻译

Bootstrap 2.3来了!该版本是V2的最后一个版本,所以下一版本会直接过渡到V3。

2.3的改动和未来3.0的一个变化可以查看我这篇博客

该版本的翻译跟上版本修改了一些错误或不足的地方,同时也跪求大家的指正,也希望和大家一起相互学习。

Bootstrap 2.3.0

点击进入Bootstrap 2.3.0文档中文翻译版

最后感谢@mdo@fat的一直努力,把最好的Bootstrap带给我们。

Bootstrap 2.3

下面是官方发布Bootstrap 2.3版本的博客文章,里面的内容包括了2.3版本的改动和3.0版本的变化。原博客文章可点击这里查看

Bootstrap 2.3

久等了朋友们。自我们推出新版本的Bootstrap已经过去了3个月。但不用担心,因为我们从未停止。经过无数次的延期,我们非常高兴地发布 Bootstrap 2.3

包含些什么

Bootstrap 2.3 包含了一些新功能,同样的也修复了一些bug和对文档进行了改进。下面的是重要的地方:

  • 库的变化:
    • 对于makefile和安装过程现在使用本地而非全局的依赖。所以现在开始容易许多了 — 只需运行 npm install
    • 升级至jQuery 1.9。其实是没有更改的需要,但我们的升级也要把最新版本的jQuery包含在里面。
    • 更改了changelog(更改记录),而不是简单的链接到一个wiki页面。
  • 新功能和一些改进:
    • 在轮播组件添加了指示器!
    • 在提示工具添加 container 选项。默认的选项依然是 insertAfter, 但现在你可以把提示工具插入(可选的)container参数指定的容器里面。
    • 提示框(popovers)现在是使用max-width 代替 width,从240px扩大到280px,并如果没有通过CSS设置 :empty 选择器将会自动隐藏标题。
    • 改进了提示工具边缘上的对齐方式 #6713
    • 改进了所有组件的<a>标签。 合并之后#6441,链接停悬状态现在适用于 :focus 状态。同样适用于按钮、导航、下拉菜单等等。
    • 添加了打印属性,在 screenprint 之间显示或隐藏内容。
    • 更新了各个input组件,让它们的行为更像默认的表单控件。添加了 display: inline-block;,改善了 margin-bottom,并且加入了 vertical-align: middle; 以配合 <input> 的样式。
    • 加入 .horizontal-three-colors() 渐变mixin (例子在CSS测试文件)。
    • 加入了 .text-left.text-center, 和 .text-right 属性,让对齐更加容易。
    • 添加了 @ms-viewport,让IE10在多画面(分屏)模式下也可以使用响应式。
  • 文档改变:
    • 添加了一个新的导航示例
    • 添加了一个带有固定导航的粘页脚(Sticky footer)的示例。

和以往一样,你可以在GitHub查看2.3.0 milestone2.3.0 pull request 的一个更加完整的列表。以上未被提及的问题,大多是对CSS轻微的改动和文档的错别字。

下载 Bootstrap 2.3.0 (主线最新的ZIP)

提示工具的注意事项

当我们发布了Bootstrap 2.2.2,我们改变了提示工具和提示框的插入方式。在默认情况使用insertAfter代替追加到<body>的方式。这种变化修复了z-index数量问题,并可以更加容易的控制和修改样式。

不幸的是,这也导致了一些错误的出现,也就是干扰了相邻的CSS选择器,破坏了input。我们并没有修改插入方式,而是加入了一个新的 container 选项。如果你遇到在 insertAfter 情况下不显示,那么在该选项设置最适合你使用的元素。

Bootstrap 3

正如我们以前所提到的,在专注开发3.0版本前,2.3版本是我们最后发布的一个版本。对于最新版本的情况,可跟进Bootstrap 3 pull request当然这里也有的一些“内幕”:

  • Bootstrap 3 将优先支持移动设备。
  • 没有单独的响应式CSS文件,现在整合到一个文件。
  • 放弃对IE7和Firefox 3.x(及以下版本)的支持。
  • 网格(栅格)已彻底修改,更容易使用,并在默认情况为流式布局。
  • 对话框现已支持响应式。
  • 不再支持子菜单。
  • 重新设计了轮播。
  • 重命名所有变量,现使用破折号分割代替驼峰命名。
  • 放弃了图像图标,用字体图标代替。
  • JavaScript事件也将加入命名空间。
  • 文档的改动 – 框架和基础CSS已被合并成一个单一的CSS页面。
  • 添加一个新的画廊页(gallery page)来展示更多更棒的Bootstrap的现实例子。
  • 和其他混乱的变化。

而这仅仅是一部分亮点。同样,进入pull request看最新变化,我们也将保持更新。可以通过任何形式进行反馈,可发表评论,或来到我们的Twitter。

<3,

@mdo and @fat

Bootstrap 中文翻译

Bootstrap,是GitHub最火爆的项目,是著名的前端开发框架之一。

个人认为Bootstrap比其他框架要好的原因之一是他有许多优秀的组件,可以帮助开发者快速开发一个网站(快到可以只导入Bootstrap的CSS代码,直接使用里面的属性,就可以实现一个简洁又灵活的网站)。而且还有许许多多第三方插件和样式的支持。同样的,Bootstrap也包含了响应式的功能。

目前翻译的Bootstrap版本是2.2.2,暂时也是最新版本。那时我翻译的时候还是2.2.1,快翻译完毕时才发现官方已经升级到了2.2.2,还好改动的不大,很快就修改完毕。但官方博客已经说正在规划3.0版本,现在也是可以下载到不完整的3.0版本,大家可以去他们的GitHub下载。

点击进入Bootstrap中文翻译版本 – 2.2.2

如果哪里翻译的不够好,或是有错,跪求指出批评。

最后感谢mdo和fat创作出如此优秀的框架。

[翻译]构建响应式布局

最近看了Zoe Mickley Gillenwater在2012年响应式设计峰会里面演讲PPT,里面谈到响应式设计的两个核心部分:流式布局和Media Queries。后面还有介绍如何解决在iOS横纵屏切换时产生的问题,和解决IE8甚至跟早版本的media query问题。

尝试翻译了一下,第一次做翻译,功底不深,如有地方错误或者翻译的不好,跪求指出批评。

翻译:构建响应式布局

原版:Building-Responsive-Layouts