Skip to content

Instantly share code, notes, and snippets.

@duangsuse
Last active March 12, 2024 12:44
Show Gist options
  • Save duangsuse/8fa4ae8c627e5c3c6044522a84ccebf4 to your computer and use it in GitHub Desktop.
Save duangsuse/8fa4ae8c627e5c3c6044522a84ccebf4 to your computer and use it in GitHub Desktop.
compile-typer, 泛型,Visitor. 懒得喂给GPT了,因为知道要协商很久。在语法低冗余前,这些比DSL还虚的还是少设计的好。从库历史的角度看,复杂理论的“美”绝对是昙花一现的。

请写一个 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)
}

频道上的吐槽

写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做不到的,因为代码+数据=双指针

函数值比struct的好处

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)

受C数据类型的影响

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(替换) 功能也不一定完整

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment