请写一个 type inference ,正确判别以下表达式
toAST.run {
`+`(+1, +1)
`+`(+"Hi", +"World")
say(+"Good"); say(Str)
}
toAST.runCatching {
`+`(+1, +"World")
`+`(+true); `+`(+1)
say(say(+"void"))
F(1){x-> x[0](Str); x[1](`+`(x[0], +1)) }
//f=(A:Str)=>A+1
//编译器只对函数体内调用赋值执行断言,(x:Int,y:Str) 是{dict}的写法而已
}
toAST.run {
`+`(+1, `+`(+2, +3))
F(1){x-> x[0] (3); x[1](`+`(x[0], +1)) }
F(1){x-> x[1](Var(Box,x[0])) }(Int)
F(1){x-> Var(Box,x[1])(x[0]); }(Var(Box,Int))
// ([T])=>T 和 (Str)=> 检查程序相同
}
必须基于以下AST 仔细思考
data F(n:Int, arg:(List<VT>)->Unit) {//arg+1 ret(T)
invoke(*arg:List<VT>): T
var next:F?//+1 overload
}
data T(override var type:Class?): VT {
invoke(:Any)=if(a is VT) type!!.isAssignableFrom(t.tryFit(type)).or("call")
else tryFit(T(a::class.java)).isInstance(a).or("lit")
tryFit(t)=type?:let{ type=t;t }
}
data Var(type,inout=0, vararg v:T): VT {
invoke(:VT)=// 0逐位完全相等,1允许v(out vt) 2允许vt(in v)
}
object toAST {
//Str,Int=T()
//Box=T(null)
//say: F(A,B), Str(A), B(null)
//`+`: F(2){x-> Int(x[0]); Int(x[1]); Int(x[2]) } or F(2){Str;Str}
}
//其中参考了
fun Bool.or(:Str)=let{if(!it)println("bad $s"); it}
fun Any.unaryPlus()=T(this)
sealed May2<A,B> {
data A(:A)
data B(:B)
way(A:(A)->R, B:(B)->R)
}
- 类型/泛型推理和查询Prolog数据库没有任何区别:
print(Str,R)
里参数返回等同于局部val,(obj as T).k 即查表"k":k(T,R)
- https://suijiyun.github.io/FOC.github.io/2022/04/07/blog-06/ 理论基础,
- 四则解析与闭包可看 https://t.me/dsuse/19097, https://t.me/dsuse/19092 (都没提到typer 只有比JSON还low的ADTs)
- https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html 是js里switch"type":"ID" 的静态化,意味着没有if()class{}宏
- https://jkchao.github.io/typescript-book-chinese/compiler/checker.html 23k行ts代码 另外
- 一种双拟合(unify两次) 系统能避免指定List,实际上它会按依赖图(SAT)做广度搜索。当然这和sympy(SMT)无关
- https://firecodelab.com/blog/subtype-inference-by-example-part-4-the-typechecker-core/#:~:text=值和用
写java,C++ 这些的时候有种错觉: public 是🔘列表符号 分号和各种不执行的标注也完全喧宾夺主了 以上的 data{} 也有这种感觉。 对于重复率如此高的元组定义,为何不物以类聚呢?
还好好搜了一下各种中文blog 没找到好的内容,草 明明各种大学都有开编译原理依照 static typed 做compiler前端的是吃白饭的么? 都在教一些又过时又get不到重点的东西一样
这kt编译期显然不是古早的 Matcher() 那个级别 使用Var()来收集类型信息是 https://tomstu.art/hello-declarative-world 里就有的 unification ,但那个支持dfs,比如解X+Y=1。kt可以直接拟合(fit),也不需要 a=b,b=c, a=1=c 的传递性
现在的React也开始习惯于「变量作为值」了,不过用Signal()深赋值取代diff的还是去年,发展得挺慢
为了简洁就避免使用 单例Visitor(本质是做一次fn.bind常量)和链表式全局域 obj.k 这种语法,看着也像受检查的 obj[k] ,不过obj的类型若非final,传obj相当于传入了一堆函数值,这是静态类型特有的"多态" 同样是代码+bind双指针,OOP组合性比FP差。FP因为太紧凑不被职人习惯。
动苏我对OOP和FP的理解,其实要从bash谈起。 作为只能调用函数、组合管道forif偶尔$((算术))的脚本语言,对IT不可谓不具代表性
为什么不能用这类0门槛的命令范式编程?为了复用。 编程,是规范记录自己想过的解法,更是自顶向下解构出可复用项目,逐一摘抄缝合的艺术。
fn --参数 输入数组
组合时只能靠调用链,输入输出(str,bytes文件)严重同质化,没有顺序参数和文档,泛化力差 一致性低。好比只有list而无tuple参数,缺乏Int,RegExp,Date 等规范概念(Lisp也是如此),每个函数里都要做序列化。
反正,运维就是为单次用默认值,调用多个同类对象设计的
不过bash里已经有了"上文对象"的概念,git.clone(depth=1, *url)
就很有语感,而这就是OOP和FP区别的关键!
clone()的输入不能是fn的输出,这个节点图问题Lisp已经用嵌套和let解决了,但FP还附加了闭包对象: add=a=>(b=>a+b)
这个函数藏住了1个参数,这也是只有全局表的bash或C做不到的,因为代码+数据=双指针
OOP稍微笨点,它认为(add(1))(2)既然藏了东西,堆分配必须new 函数名要大写,因为class Add(a:Int) 的数据量固定,吃它的函数应归于相同类型 这使 x.fn()=fn(x) 有了查表依据,同时允许你AOP出数据更多的类,添加或修改旧操作。 此为封装&继承
这样的坏处,是难以学js在List,Map里直接查函数,算式被绑死在struct上名字也难想(于是有了设计模式..)。 但凡是要filter{}.map{} ,都得把 abstract fun
当作函数值来传递
而且,is-a 关系太严苛。Go就完全抛弃了override 使用弱类型
当然,在有大量函数需要默认值,或者说编译优化时,OOP胜过FP。 此为抽象&子类型多态
多态在js可以用 switch(o.type||e.tagName)
来模拟。这种定义式的case规则,可以被Visitor模式多次执行
sealed Sum { //代数数据类型(不可扩展的父类)
data Num(lit:Int)
data `+`(expr:Sum)
}
object SumVis<R=Int> {
fun se(e:Num)=e.lit
fun se(e:`+`)=se(e.expr)+1
}
object SumToStrVis<R=Str>
Sum.run{`+`(Num(0))}
.let{SumVis.se(it)}==1
不过,Sum 的多态子类,本质上只是做了一次Num.bind(0)柯里化,直接运算会更直白 这也是为什么FP复用比OOP更具实用性
trait Sum<R> {
- Num(lit:Int): R
- `+`(expr:R):R
}
SumToInt.run{`+`(Num(0))} //也就是在parse()时就传入遍历器。对象树不就是为了遍历编辑吗?
大部分没有空检查的(弱)类型里都支持 null, nil, arg undefined 值,这种参数或返回必须被报错或替换
Optional 显然是更好的(异步)错误处理模式,它能和Promise无缝衔接:
await fetch(url).as(x=>x.text()).or("bad web")
prompt("age").or(no=>42)
as(Ok, ()=>{throw "转换出错"})
at(nums, x=>x>0) ?? 42
这些都是基于可选链而非try-catch的方法
另外, 选出分数大于 60 的数据的 id board.as(x=> x.score>60? x.id : null)
CLR里泛型能是值类型,但JVM泛型擦除为Obj(int tag+void*) kt是做了一个自动装拆箱,这样局部/数组和List<>里就无差异 jvm的代码体积会小,因为C#是用动态生成template<> 的方法new泛型class,它知道sizeof才不需要解指针啊(对象类似 int tag;union{Number})
freeze() 的运行时检查好比内存用后清零,没意义,认为能池化从而加速更是捕风捉影。反而为创建"不改变数据容器"的副本经常有CoW开销
用户名、邮箱地址等检查,本来是只有load/dump() 时涉及的带if类型,如果不能在函数开头自动检测,不含元数据,newtype 限制的意义不大。
如果数据容器必须是""那样的值,就总是要写 a=append(a,item) 来做简单的push。像XML那样对人对机器都不友好 滥用Mutable容器会导致代码意图模糊,使用 buildList{}.asMut() 这样的方法才是灵活合理的
目前 kt,scala,rs,py 里声明tuple(record)的方法都不够简洁,copy(替换) 功能也不一定完整