从0发现Lisp
一起来画画
假如说我们有一些画画的能力。这里使用JS来举个例子
drawPoint({x: 0, y: 1}, 'yellow')
drawLine({x: 0, y: 0}, {x: 1, y: 1}, 'blue')
drawCircle(point, radius, 'red')
rotate(shape, 90)
...
我们可以画一个点,画一条线,可以画圆,可以将图形旋转90度。
新的挑战
一般而言我们的这些能力我们都希望可以复用起来,不能开发一次就丢了,那就太浪费开发小哥哥的头发了。我们希望支持配置化,这意味着我们希望读取一些配置然后把不同的参数给不同的函数去执行,并得到一个结果。我们有个文件输入流,我们假定他叫stream
。
stream.on('data',data =>{
//TODO
})
Eval
选择JS来举例子是因为JS可以使用eval,如果我们想偷懒的话可以直接使用eval来满足我们的需求。
stream.on('data',data =>{
eval(data)
})
我们只要在配置里面写上"drawLine({x: 0, y: 0}, {x: 1, y: 1}, 'red')"
就可以画一条线了。 但是。。。只要稍微有点经验的程序员就会建议你不要这么做,因为你不知道data里面具体是什么,这么做就相当于让你的系统在配置面前裸奔,天晓得他会调用什么方法,会发生什么。
看来我们不能用eval了,但是我们还是需要解决我们的问题,我们想让用户可以自定义到底要怎么画画(毕竟要赋能业务嘛 🐶)
简单的想法
我们可以用JSON来做一个简单的配置,配置好要执行的函数,这个函数对应的参数是什么就可以了呀。假定我们设计好我们的配置格式如下:
{
instructions: [
{ functionName: "drawLine", args: [{ x: 0, y: 0 }, { x: 1, y: 1 }, "blue"] },
];
}
我们拿到配置之后要做的就是将其翻译成对drawLine({x: 0, y: 0}, {x: 1, y: 1},"blue")
的调用就可以了。 感觉还是挺简单的。
stream.on('data',instruction =>{
const fns = {
drawLine: drawLine,
...
};
data.instructions.forEach((ins) => fns[ins.functionName](...ins.args));
})
easy!!
稍微简化一下
因为每条指令的开始都是函数名,然后都是参数,所以我们其实并不需要指出哪个是函数名,哪个是参数,我们可以将我们的配置简化如下:
{
instructions: [["drawLine", { x: 0, y: 0 }, { x: 1, y: 1 }, "blue"]],
}
我们将我们的配置从一个json对象变成了一个数组。instructions 是一个包含指令数组的数组,一条指令的第一个对象一定是函数名,剩下的是函数的参数。修改一下我们的代码:
stream.on('data',instruction =>{
const fns = {
drawLine: drawLine,
...
};
data.instructions.forEach(([fName, ...args]) => fns[fName](...args));
})
好了,我们的drawLine
又可以欢快地跑起来了。