当前位置: 首页 > 科技观察

使用TypeScript实现斐波那契数列

时间:2023-03-15 16:24:36 科技观察

前几天在知乎看到一篇文章,使用TypeScript类型运算实现一个中国象棋程序:woc边看边看,TypeScript不就是类型系统吗?我该如何实施国际象棋?,感觉自己找到了新大陆,然后克隆了老大的代码,在本地“运行”,不过只能带引号运行,因为TS是动态推导类型,只需要安装相关插件,鼠标悬停。查看结果。看到这个神奇的魔法,我检查了它的原因。图灵完备性这是第一个接触到的概念。维基百科是这样定义的:一个计算系统可以计算出任何图灵可计算的函数,称为图灵完备性(或图灵完备性)。或者任何可以模拟通用图灵机的系统。可计算函数大致理解为“人们可以计算的问题”,现在C++、JAVA等几乎所有的编程语言都是图灵完备的。关于Turing-completeness是一个更大的问题,比较通俗的解释可以是SeewhatisTuringcomplete?知道这个答案很有意思。https://www.zhihu.com/question/20115374/answer/288346717还学习了一种有趣的编程语言——UrbanMüller于1993年发明的Brainfuck语言,感受一下它是如何打印HelloWorld的。++++++++++initializecounter(cell#0)to10[uselooptoset70/100/30/10>++++++++add7tocell#1>++++++++++add10tocell#2>+++add3tocell#3>+add1tocell#4<<<<-decrementcounter(cell#0)]>++.print'H'>+.print'e'+++++++.print'l'.print'l'+++.print'o'>++.print''<<++++++++++++++++.print'W'>.print'o'是的,你没有看错,左边是代码,右边是注释,这里是单句执行图,大家可以感受下:http://fatiherikli.github.io/brainfuck-visualizer/上面的纸带代表内存情况,然后左右移动加减减,最后输出Helloworld!。在TypeScript仓库的一个issue中,也讨论过TypeScript的图灵完备,作者还分享了一个判断是不是素数的代码,也很有意思。TypeScript相关知识为了实现《使用TypeScript实现斐波那契数列》一文的标题,需要先学习相关知识点。泛型如果你之前学过C++或Java之类的语言,你一定对泛型不陌生。泛型允许我们在不指定参数类型的情况下定义函数参数,而是使用占位符。确定传递的类型。举个简单的例子,实现两个元素的相加,如果受限于TypeScript,即使是同样的逻辑也要写多次。函数addTwoNumber(a:number,b:number):number{returna+b;}functionaddTwoNumberString(a:string,b:string):string{returna+b;}addTwoNumber(1,3)addTwoNumberString('1','2');否则只能是anyScript,类型验证就彻底没了。functionaddTwoNumber(a:any,b:any):any{returna+b;}addTwoNumber(1,3)addTwoNumber('1','2');addTwoNumber('1',2);//有则不报错is泛型不仅可以实现逻辑复用,还可以验证类型。functionaddTwoNumber(a:T,b:T):T{returna+b;//这个需要转换,否则会报Operator'+'cannotbeappliedtotypes'T'and'T'.ts(2365)}addTwoNumber(1,3);addTwoNumber("1","3");addTwoNumber('1',2);//当然有强制使用泛型的嫌疑,但是可以大致理解Genericsworkjustfine,哈哈。在上面的情况下,使用TS的重载会更好。在类型typeTS中,除了基本类型number、string、number[]等,特殊的地方1、abc、true也可以算作一个单一的类型。1的类型是1,当然也属于number。最重要的是,TS允许我们定义新的类型,我们也可以通过泛型变量进行类型操作,然后生成新的类型。举几个例子:typeType1=Textendsnumber?number:string;typeType2=Type1<2121>;//此时Type2等价于numbertypeType3=Type1<{}>;//此时Type3等价到stringexstends和以下?构成一个三元表达式。如果extends之前的类型可以赋值给extends之后的类型,则判断表达式为真,否则为假。因为单个数字也是一种类型,所以我们可以判断传入的T是否等于某个数字。typeType4=Textends1?true:false;typeType5=Type4<2121>;//此时Type5相当于false类型typeType6=Type4<1>;//此时Type6相当于true类型,你可以在这里仔细体会,很重要。后面写斐波那契数列的时候,一不小心就会涉及到,因为我们是在操纵类型之间的运算,这和平时的编程有很大的不同。一句话,每个类型都可以看成是一个函数,传入的泛型是一个函数参数,也是一个类型,最后返回一个新的类型。Type(type,type)=>typeinferinfer表示extends条件语句中要推断的类型变量。简单理解,就是我们要判断某个类型是否扩展了某个“结构”,但是不知道结构中参数的类型。这时候我们写一个inferR(R只是一个占位符,随便取个名字都可以),在类型推导的时候,R是当前类型的真实参数类型。比如:我们判断T是否是{a:XXX,b:XXX}的类型,因为T是泛型,我们不知道T中a是什么类型,所以可以使用infer来放置。传入具体类型时,可以得到a是什么类型。typeFoo=Textends{a:inferU;b:inferU}?U:never;typeT1=Foo<{a:string;b:string}>;//T1类型为字符串typeT2=Foo<{a:string;b:number}>;//T2的类型是string|number斐波那契数列斐波那契数列就不多解释了,先用js实现一个斐波那契数列。functionFibonacci(n){if(n===1){return1;}if(n===2){return1;}returnFibonacci(n-1)+Fibonacci(n-2);最关键的递归不用担心,TS支持类型的递归定义。我们先写一个粗略的原型。有意思的是Chess直接用中文定义类型,这里也是直接用中文。之前总结的一句话,这里会加深。每种类型都可以看作是一个函数。传入的泛型是函数参数,也是类型。最后,返回一个新类型。我们首先定义“Fibonacci”类型。泛型就是传入一个数字(这里的数字算是一个类型),先判断传入的类型是1还是2,然后直接返回1的类型。typeFibonacci=somenumberextends1?1:somenumberextends2?1:addition>,Fibonacci>>>;:Addition>,Fibonacci>>>;上面需要注意的是<>里面的extends是限制传入的类型,和外面的extends不同。我们还需要定义一个“加法”类型和一个“减法”类型。加法是两种类型相加,但是类型系统不支持数字直接相加。1+2不能直接相加。这是一个技巧。数组类型和js中的数组一样,也有length属性,返回具体的数字类型,也支持扩展运算符。例如:typeA=[any,any,any]typeB=A["length"]//B是3种类型所以我们可以把数字转成数组,操作数组,再通过length把数组转回数字。先写一个获取对应数组的Type,这里需要递归。type获取长度=array["length"];type转为数组<某数extendsnumber,对应数组extendsany[]=[]//默认赋空数组,外部调用时不需要传入>=getlength<对应数组>extends一定数量//长度是否等于要求的长度?对应数组//如果长度等于要求的,则返回:转换为数组<某个数,[任意,...对应数组]>;//否则,再向数组中添加一个元素,并然后递归调用:converttoarray;//否则,向数组中添加另一个元素,然后递归调用添加方法,对于数组的Type,很容易写。我们只需要将两个数转换成对应的数组,将两个数组连接起来,连接后最后返回数组的长度即可。输入加法<一些数字A扩展数字,一些数字B扩展数字>=获取长度<[...转换为数组<一些数字A>,...转换为数组<一些数字B>]>;然后定义减一的方法,即数组的长度减1。这里使用了infer,同样需要对数组进行解构。typearrayminusone<某个数组类型extendsany[]>=((...parameter:某个数组类型)=>any)extends(splitanelement:any,...therestofthearray:infertherest数组类型的)=>任何?剩余数组类型:[];我们定义一个函数类型,通过函数参数的解构,剩下的数组少了一个元素。有了“数组减一”的类型,数“减一”就是水到渠成的事了。将数字转换为对应的数组,将数组减去一个元素,再还原为数字。typeminusone=getlength>>;整个代码出来了:typeFibonacci=somenumberextends1?1:someNumberextends2?1:Addition>,Fibonacci>>>;type得到length=array["length"];type转换为array=getlength扩展一个数?对应array:转换为array;addtype<某数A扩展数,某数B扩展数>=获取长度<[...转换为数组<某数A>,...转换为数组<某数B>]>;typearrayminusone<某个数组类型extendsany[]>=((...parameter:acertainarraytype)=>any)extends(deleteanelement:any,...therestofthearray:推断数组的其余部分Type)=>any?剩余数组类型:[];typeminusone=getlength>>;//测试typeFibonaccieight=Fibonacci<8>看结果:但是直到Fibonacci11,是因为递归层次太深gg。这里主要是体验TS的图灵完备性,不考虑优化。总结通过写这个例子,对TS的类型有了更深入的了解,但是平时开发肯定不会这样做,主要是为了写玩哈哈,毕竟写个简单的斐波那契数列这么麻烦,引用LinusTorvalds的自传结束,只是为了好玩!