lua工具库penlight--07函数编程(二)
列表压缩
列表压缩是以紧凑的方式通过指定的元素创建表。在 Python里,你可以说:
ls = [x for x in range(5)] # == [0,1,2,3,4]
在 Lua,使用pl.comprehension :
> C = require('pl.comprehension').new()
> = C ('x for x=1,10') ()
{1,2,3,4,5,6,7,8,9,10}
C是一个函数,它将一个列表字符串编译成一个函数。在这种情况下,该函数有没有参数。考虑一个字符串作为函数参数括号是冗余的,因此可以:
> = C 'x^2 for x=1,4' ()
{1,4,9,16}
> = C '{x,x^2} for x=1,4' ()
{{1,1},{2,4},{3,9},{4,16}}
注意该表达式可以是任何变量x的函数!
基本的语法到目前为止是 for ,可以是任何 Lua for理解的语句,也可以只是变量,在这种情况下的值将来自该参数的“语句”。在这里我强调,“语句”是可以带列表参数的函数:
> = C '2*x for x' {1,2,3}
> = C '2*x for x' {1,2,3}
{2,4,6}
> dbl = C '2*x for x'
> = dbl {10,20,30}
{20,40,60}
这里是稍微更加明确说明,1是的占位符参数,指传递给“语句”的第一个参数。
> = C '2*x for _,x in pairs(_1)' {10,20,30}
{20,40,60}
> = C '_1(x) for x'(tostring,{1,2,3,4})
{'1','2','3','4'}
当您想要一些收集结果的迭代器,如io.lines,此扩展的语法非常有用。这里“语句”创建一个函数,为文件中所有行创建一个表:
> f = io.open('array.lua')
> lines = C 'line for line in _1:lines()' (f)
> = #lines
118
许多函数都可以应用到“语句”的结果:
> = C 'min(x for x)' {1,44,0}
0
> = C 'max(x for x)' {1,44,0}
44
> = C 'sum(x for x)' {1,44,0}
45
(这些是相当于减少操作)。
可能在for的后面有条件,用来筛选输出。下面这条“语句”收集偶数列表:
> = C 'x for x if x % 2 == 0' {1,2,3,4,5}
{2,4}
可能有多个for部分:
> = C '{x,y} for x = 1,2 for y = 1,2' ()
{{1,1},{1,2},{2,1},{2,2}}
> = C '{x,y} for x for y' ({1,2},{10,20})
{{1,10},{1,20},{2,10},{2,20}}
这些“语句”很有用,特别是处理多个变量时,如果用其他的Penlight函数,不是那么轻易达到目的。
从函数创建函数
Lua 函数像任何其他值,当然你不能乘或添加它们(原文,of course you cannot multiply or add them.)。有意义的操作是函数组成compose,即函数调用链 (如(f * g)(x)是f(g(x)).)
> func = require 'pl.func'
> printf = func.compose(io.write,string.format)
> printf("hello %s\n",'world')
hello world
true
许多功能需要您将一个函数作为参数传递,如应用于所有值的序列或作为一个回调。经常,有用的函数具有错误的参数数目。所以有必要将传给函数的两个参数变为一个参数,一个绑定到一个给定值的额外参数。
部分应用的 有n 个参数的函数,并返回 n-1 个参数,它的第一个参数绑定到一些值:
> p2 = func.bind1(print,'start>')
> p2('hello',2)
start> hello 2
> ops = require 'pl.operator'
> = tablex.filter({1,-2,10,-1,2},bind1(ops.gt,0))
{-2,-1}
> tablex.filter({1,-2,10,-1,2},bind1(ops.le,0))
{1,10,2}
(译注:即有些函数是两个参数,但是调用者只能传递一个参数,于是需要一个bind1,绑定一个预定的值,这样传入的一个参数与这个预定值比较。)
不幸的是最后一个例子结果相反,因为bind1总是绑定的第一个参数 !(译注:0<x,结果都是比0大的)。此外不幸的是,之前我困惑 '扩充' 与 '部分应用',所以bind1的旧名称是curry— — 此别名仍然存在。
这是一种特殊的形式的函数参数绑定。这里是另一种方法说的打印示例:
> p2 = func.bind(print,'start>',func._1,func._2)
> p2('hello',2)
start> hello 2
这里1 和 2是占位符变量,分别对应于第一和第二个参数。
func 会让人分心,所以可以把 pl.func拉到本地上下文中。这里是筛选器的例子:
> utils.import 'pl.func'
> tablex.filter({1,-2,10,-1,2},bind(ops.gt, _1, 0))
{1,10,2}
(译注:即不用写func)
tablex.merge可以合并两个表。此示例演示绑定函数最后一个参数的用处。
> S1 = {john=27, jane=31, mary=24}
> S2 = {jane=31, jones=50}
> intersection = bind(tablex.merge, _1, _2, false)
> union = bind(tablex.merge, _1, _2, true)
> = intersection(S1,S2)
{jane=31}
> = union(S1,S2)
{mary=24,jane=31,john=27,jones=50}
当用bind 把print绑定时,我们得到有两个参数的函数,而我们真正想要的是使用 varargs ,就像print一样(译注:可变参数)。这是_0的作用:
> _DEBUG = true
> p = bind(print,'start>', _0)
return function (fn,_v1)
return function(...) return fn(_v1,...) end
end
(译注:上面的是bind后输出的结果,不是手工输入的)
> p(1,2,3,4,5)
start> 1 2 3 4 5
我已经开启全局DEBUG标志,以便打印出生成的函数来。它是实际的函数, 第一次调用绑定值的 v1到 ' start >'。
占位符表达式
在Penlight中的常见模式是把函数应用于一个表或序列的所有元素,如tablex.map或seq.filter。Lua 有匿名函数,虽然他们可能有点乏味:
> = tablex.map(function(x) return x*x end, {1,2,3,4})
{1,4,9,16}
,2,等等 (c + + 程序员将会认识到这从 Boost 库借鉴)
> utils.import 'pl.func'
> = tablex.map(_1*_1, {1,2,3,4})
{1,4,9,16}
可以生成的最多 5 个参数的函数。
> = tablex.map2(_1+_2,{1,2,3}, {10,20,30})
{11,22,33}
这些表达式可以使用任意函数,他们必须首先注册到函数编程库。func.register引入单个函数,func.import导入整个表的功能,如math.
> sin = register(math.sin)
> = tablex.map(sin(_1), {1,2,3,4})
{0.8414709848079,0.90929742682568,0.14112000805987,-0.75680249530793}
> import 'math'
> = tablex.map(cos(2*_1),{1,2,3,4})
{-0.41614683654714,-0.65364362086361,0.96017028665037,-0.14550003380861}
常见的操作是对调用一组对象的方法:
> = tablex.map(_1:sub(1,1), {'one','four','x'})
{'o','f','x'}
PEs 的操作有一些限制。例如, __len 元方法不能被普通的 Lua 表重写,因为我们需要定义一个特殊的函数来表达#_1':
> = tablex.map(Len(_1), {'one','four','x'})
{3,4,1}
同样对于比较运算符,不能比较不同的类型,必须表示为一个特殊的函数:
> = tablex.filter(Gt(_1,0), {1,-1,2,4,-3})
{1,2,4}
函数返回多个值是非常有用的。例如, tablex.pairmap期望函数返回键和值,并返回新值和键。
> = pairmap(Args(_2,_1:upper()),{fred=1,alice=2})
{ALICE=2,FRED=1}
PEs 不能包含nil值,因为 PE 函数参数表示为一个数组。相反,提供一个特别的值称为Nil。所以请用1:f(Nil,1) 而不是 1:f(nil,1).
占位符表达式,不能自动作为 Lua 函数的使用,必须将调用运算符重载构造函数调用如_1(1)。如果您想要强制一个 PE 要返回一个函数,请使用 func.I .
> = tablex.map(_1(10),{I(2*_1),I(_1*_1),I(_1+2)})
{20,100,12}
在这里我们考虑含有单个参数函数的表,然后用10调用它们。
用 PEs 的基本理念是用引号界定一个表达式,因此它不会立即进行计算,而是变成了一个以后可以应用到的某些参数的函数。基本机制是包裹值和占位符,这样普通的Lua 运算符有建立一个表达式树的效果。(这样你可以使用 PEs做符号代数,请参阅symbols.lua中的示例,testsym.lua演示了象征性分化的测试)。
规则是如果任何运算符有 PE 操作数,结果会被引号引起。有时我们需要显式引用的东西。例如,如果该元素值是set里的,我们会想传递一个必须返回 true的函数给筛选器。set[1]是的明显的表达式,但是它没有返回所需的结果,因为它直接计算结果,得到了nil。索引比二元运算相比,如加法 (set +1适当地使用引号),有些不同。所以有显式引用或包装操作的需求。这就是function干的事情, 在这种情况下,PE应该是 (set)[1]。这适用于函数,方便的替代办法是注册函数: (math.sin)(_1)。这就相当于使用lines' 方法:
for line in I(_(f):read()) do print(line) end
'类文件' 的对象都可以工作,只要有返回下一行的read方法。如果你有一个 LuaSocket 客户,被服务器逐行‘推’,那么_(s):receive ‘*l’将会创建一个迭代器用于接收输入。这些形式可以方便的调整您的数据流量,使它可以传递到pl.seq'的函数.
占位符表达式可以和序列的包装表达式混合。lexer.lua 将给我们双值标记,其中的第一个是类型,第二个是值。我们筛选出类型为 'iden',使用map提取实际值, 获取唯一值,最后将结果存到list中。
> str = 'for i=1,10 do for j = 1,10 do print(i,j) end end'
> = seq(lexer.lua(str)):filter('==','iden'):map(_2):unique():copy()
{i,print,j}
这看起来特别密集(我总是觉得不应该在一行程序解决一切!) ;关键是map的行为,它使用序列的两个值,因此_2返回的值部分。(因为filter在这里使用额外的参数,它只操作类型值。)
使用占位符表达式,有一些性能需求要考虑到。实例化一个 PE 需要构建和编译一个函数,这不是这种快速的操作。所以要获得最佳性能,从循环里分解出 PEs ;
local fn = I(_1:f() + _2:g())
for i = 1,n do
res[i] = tablex.map2(fn,first[i],second[i])
end
lua工具库penlight--07函数编程(二)的更多相关文章
- lua工具库penlight--06数据(二)
词法扫描 虽然 Lua 的字符串模式匹配是非常强大,但需要更强大的东西.pl.lexer.scan可以提供标记字符串,按标记机分类数字.字符串等. > lua -lpl Lua 5.1.4 C ...
- lua工具库penlight--01简介
lua的设计目标是嵌入式语言,所以和其它动态语言(如python.ruby)相比其自带的库缺少很多实用功能. 好在有lua社区有Penlight,为lua提供了许多强大的功能,接下来的几篇博客,我会简 ...
- lua工具库penlight--07函数编程(一)
函数编程 序列 Lua 迭代器 (最简单的形式) 是一个函数,可以多次调用返回一个或多个值.for in语句理解迭代器和循环,直到该函数将返回nil. Lua有标准的序列迭代器 (ipairs和pai ...
- lua工具库penlight--08额外的库(二)
执行一系列的参数 类型说明符也可以 是' ('MIN '..' MAX)' 的形式. local lapp = require 'pl.lapp' local args = lapp [[ Setti ...
- lua工具库penlight--02表和数组
类Python的List lua的优美之处在于把数组和关联数组都用table实现了(Python中叫list和dict,C++中叫vector和map). 一般我们把数字索引的table叫做list. ...
- lua工具库penlight--06数据(一)
这篇太长了,分了两部分.(这个是机器翻译之后我又校对了一下,以后的都这样,人工翻译太累了.) 读数据文件 首先考虑清楚,你的确需要一个自定义的文件读入器吗?如果是,你能确定有能力写好吗? 正确,稳健, ...
- lua工具库penlight--09技术选择
模块化和粒度 在理想的世界,一个程序应该只加载它需要的库.Penlight需要额外100 Kb 的字节码来工作.它是简单但却乏味要加载你需要什么: local data = require 'pl.d ...
- lua工具库penlight--08额外的库(一)
额外的库 在这一节中的库不再被认为是Penlight的核心部分,但在需要时,仍提供专门的功能. 简单的输入的模式 Lua 的字符串模式匹配是非常强大,通常您将不需要传统的正则表达式库.即便如此,有时 ...
- lua工具库penlight--04路径和目录
使用路径 程序不应该依赖于奇葩的系统,这样你的代码会难以阅读和移植.最糟糕的是硬编码的路径, windows和Unix的路径分隔符正好相反.最好使用path.join,它可以帮助你解决这个问题. pl ...
随机推荐
- php stdClass 的使用
原文:http://www.php.cn/php-weizijiaocheng-371767.html ------------------------------------------------ ...
- sed 命令编辑文本
1.sed 概述 sed 是一个非交互式文本编辑器.它能够对文本文件和标准输入进行编辑,标准输入能够是来自键盘输入.文件重定向.字符串.变量.甚至来自于管道文本. 2.sed工作流程简述 sed在处理 ...
- http协议中content-length 以及chunked编码分析
转载请注明出处 http://blog.csdn.net/yankai0219/article/details/8269922 0.序 1.http/1.1协议中与chunked编码的相关字段 1)E ...
- VC中关于预编译头文件的概括,以及无法打开预编译头文件的错误解决
1.预编译头文件 在VC中编译器一般都是以文件为单位进行编译,如果修改了工程中的一个文件,那么将导致所有文件都要从新编译,这样的编译将耗费很长时间.为了提高编译速度,将那些不常被修改,比较稳定,文件单 ...
- 轻量级UIImageView分类缓存 库 AsyncImageView 使用
轻量级UIImageView分类缓存 库 AsyncImageView 使用 一: AsyncImageView 主页:https://github.com/nicklockwood/AsyncIma ...
- IDEA20181.4破解
1.下载破解补丁: http://idea.lanyus.com/jar/JetbrainsIdesCrack-4.2-release-sha1-3323d5d0b82e716609808090d3d ...
- itext测试地址
http://demo.itextsupport.com/xmlworker/?snippetChooser=headers
- 不能使用控制器“XXXController”的单个实例处理多个请求。如果正在使用自定义控制器工厂,请确保它为每个请求创建该控制器的新实例。
原因:应用@{Html.RenderAction("aaa","XXX");}时路径路由和动作控制器不能是相同的,不然会的错. 比如:http://localh ...
- Java 的抽象特性:抽象类与接口深度解析
要点: 抽象类 接口 抽象类与接口的差别 一. 抽象 对于面向对象编程来说,抽象是它的四大特征之中的一个. 在Java中,能够通过两种形式来体现OOP的抽象:接口和抽象类. 接口和抽象类为我们提供了一 ...
- proc文件系统分析
来源: ChinaUnix博客 日期: 2008.01.03 11:46 (共有条评论) 我要评论 二 proc文件系统分析 根据前面的分析,我们可以基本确定对proc文件系统的分析步骤.我将按 ...