duangsuse::Echo
583 subscribers
4.12K photos
118 videos
579 files
6.13K links
import this:
美而不丑、明而不暗、短而不凡、长而不乱,扁平不宽,读而后码,行之天下,勿托地上天国。
异常勿吞,难过勿过,叹一真理。效率是很重要,盲目最是低效。
简明是可靠的先验,不是可靠的祭品。
知其变,守其恒,为天下式;穷其变,知不穷,得地上势。知变守恒却穷变知新,我认真理,我不认真。

技术相干订阅~
另外有 throws 闲杂频道 @dsuset
转载频道 @dsusep
极小可能会有批评zf的消息 如有不适可退出
suse小站(面向运气编程): https://WOJS.org/#/
Download Telegram
#PL 晚上散步的时候忽然回想到部分绝句设计细节🤔

绝句亮点特性:
逗号表示法、中缀否定、记法、中缀链、人称文法

顺序:
事/回 (回重写、回交)

分支控制流:
若/判 (「判你」 是第二人称)

循环控制流:
重复/对 (「对…里的……」 是第三人称)
停下、略过

第二人称还包括 且/或链
第三人称还包括 中缀链

绝句的 label 定义,对 block 是 ,[] 对控制结构是 对[]
引用则是 [^]

值绑定模式:
常/变/量

只有 量 可以类型推导, 常/变 直接跟后面写类型。

真假 字
字节 短数 数 长数
短实 实

类/物/例
储物 例物 况物 标物
扩物 内物 抽象物

公开 族内 私下 内部

抽象/实现 开放/终定/覆写 记法
晚成
尾递归 断续
#PL 🌚 我们只听说过 COBOL/SNBOL/SmallTalk 和 Ada/Fortran 什么的,我是只知道 Lua 和 Sol
#learn #PL #CS 首先引出问题,什么是操作符链(中缀链)
1+2*3; 1*2+3 ,它们可以被简单理解为以 +-*/ 运算符(操作符)分割的单项
但其实不然,用形式化文法(以『左递归』)应当是这样:
Atom = [0-9]* | "("Expr")" | "-"Expr
Expr1 = Atom "*"/"/" Atom
Expr = Expr1 "+"/"-" Expr1
上面,我们定义了『表达式』和『基元(Atom)』两项,我们以 Expr, Expr1 区分了加减法、乘除法的优先级

但本质上,表达式是由基元和『中缀算符(infix operator)』构成(即便 OOP 的 o.x() 访问也可以是如此),这样更为清晰易懂,也无需使用“左递归”(P = P "x")。
Lua 的解析器会同时处理 unary 一元(如取负)和 binary 二元运算,不过我们认为一元运算和括号表达式就是 Atom ,无需参与运算符链(从而,此链代表了表达式)。

中缀链式结构只是它的形式,并不是它的内涵;所以为了按顺序执行计算,我们需要构造出树形结构—二叉树(之后的求值一般是左右 中(算符ID)访问, LRD)
1+2*3 = (+ 1 (* 2 3))
1*2+3 = (+ (* 1 2) 3)
注意乘法是先算的(离根节点远),而且这个结构还很迷,即便它只是 Expr = "("op Expr Expr")" | Num 的形式而已(所以说算法很复杂)

首先讨论数据建模,我们在算法里必须考虑算符ID(String) 和结合优先级信息(因为有类似 1^(2^3) 的右结合,可以建模为 Pair<Int, Int> 左右优先级、或 Assoc(prec:Int, isRAssoc:Boolean) )

这里选择左右优先级,优点是节约计算,而且如果要 preety print ,检查是否需加括号比较方便
为代码复用可以建立一个抽象类,待实现 readAtom/readInfix/getAssoc/join(op, a, b) 操作

抽象降低过程:
1. readInfix 返回 String, 然后隔离出 getAssocInfo(op: String): Int 信息,可以用符号或奇偶存储左右结合性(这是为了保证频繁调用操作便于定义、无抽象、低计算开销),接着 getAssoc 将其算为 Pair<Int,Int>
2. 不把 getAssocInfo 所存储的 String-Int 映射放在此类中,直接让 readInfix 返回 Pair<String, Int> ;节约一个 Map 对象。
3. 移除 getAssoc, readInfix 直接返回 Triple, 包含算符ID及其左右优先级, 为了方便创建再提供 createAssoc { mapOf("+" to it++, "^" to it.r) } 这样的辅助对象(本来如果不用 Map, getAssocInfo 是动态计算,不该做这样高开销操作,现在不存在选择了)

分步降低的目的是在尽可能不影响可读性的前提下减少计算和内存分配,提升性能。

在上图里:

版本1 是目前 ParserKt 在用的版本(除了不支持右结合外完全一致,我之前没有探索其他方法)
版本2 是更直白的版本1 ,没有用莫名其妙的 null (开始我这么写是因为被 LLVM Cookbook 的代码辣到眼睛了,害怕写出一丁点类似的代码……)
版本3 是类似之前 "ADT Stack" (用 LinkedList 替换系统栈)重写测试的版本,也是 Lua 5.1 的读取算法

三者都不好理解。
1+(2*3); (1*2)+3 的结合必须依靠栈,而输入永远是从左往右(L-R) 的,所以 1+2*3 必须要递归一层, 1*2+3 却一层也不用
这个问题本身也只关于相邻两算符的“操作数争夺”,相等优先级 (a+b)+c 左结合即可,没有偏序传递性之类花哨的逻辑
我们靠递归调用 infixChain() 来做到算法的“加括号”要素,那么“结合”要素的呢?

其实,以上算法结合性的本质都是在 zipWithNext ,长这样:
var a = initial
while (a != null) {
val b = next()
op(a, b)
a = b
}
它的递归形式(例为寻找流里的 a, -a ):
fun Iterator<Int>.someOp(a: Int): Int {
val b = next()
return if (a+b == 0) a else someOp(a = b)
}

我们看看上面的 infixChain2
fun infixChain2(base: Atom, op1: Op): Atom {
val rhs = scanAtom() // (·b)
val op2 = scanInfix() ?: return op1.join(base, rhs) // (a·b)
return when {
op1 <= op2 -> infixChain2(op1.join(base, rhs), op1 = op2) // (1*2)+3
op1 > op2 -> op1.join(base, infixChain2(rhs, op1 = op2)) // 1+(2*3)
else -> impossible()
}
}

很明白了,如果是 *+, 直接照 base=`*`.join(1, 2) 放到新符的左边继续递归,等 op1==(+) 时读不出 op2, 结果 (1*2)+3
如果是 +*, 就会有一层 `+`.join() 等待递归 op1==(*), 同样在 op2 == null 开始归纳,得 1+ (2*3)

有没有可能优化呢?比如移除 base 参数?
可是没有 base 参数如何把 (1*2)+3(1*2) 组织到 +3 上?可以的。

稍有常识的人就能看出, 在 (op1 <= op2) 也就是先结合的时候,我们的“递归”实际上等于重写参数,并没有创建新栈帧(可以理解为“部分”尾递归,或者说“部分”CPS(靠调用延续执行) 😂)。
infixChain3 就是用循环替代了无递归的部分,不过写得比较不优雅(把它理解为处理 a*b^c 这种优先级升序情况的处理器就可以了,而且优先级链其实也只是参考两项,从左至右 reduce { acc, it -> op(id, acc, it) } 而已)

现在我们看看 Lua 是怎么写的
op = getbinopr(ls->t.token);
while (op != OPR_NOBINOPR && priority[op].left > limit) {
nextop = subexpr(ls, &v2, priority[op].right);
op = nextop;
}
return op;
注意 prec_op2 > prec_op1(limit) 的情况会继续递归读取 (a op2 b),否则会级联返回到合适层
比如 1*2+3 的情况,看起来在 rec(0) rec(*)| op=(1*2) 时直接返回,实际上它只返回到 rec(0) 的层次, op=(+) 继续读取(……怎么这么像缩进块的读取,原来标准做法是这样么)

code = "a 1 b 2 c 3 d 3 e 2 f".split(" ")
def read(xz):
x = next(xz)
return (x, int(x) if x.isdigit() else None)
codez = iter(code)
n = 0
def subexpr(prec_base = 0):
global n
print(" "*n, "subexpr", prec_base, end=": ")
item = read(codez)
if item[1] == None: print(item[0]); item = read(codez)
while item[1] != None and item[1] > prec_base:
try: n+=2; item = subexpr(item[1])
except StopIteration: return item
finally: n-=2
return item

 subexpr 0: a
subexpr 1: b
subexpr 2: c
subexpr 3: d
subexpr 3: e
subexpr 2: f
subexpr 2:
=> ('2', 2)

这种方法只能在直接输出线性结构时使用,它在调用栈里保留树结构信息,但其实它与 zipWithNext 版是等效的。
即便有 1*2^3-4 这种优先级步降不为1 的情况,直接生成 (1*(2^3)) -4 也是正确的。 Lua 递归做法的实质是通过比较基优先级,直接把树(其中包括类似链表而在后序遍历中被铺平的 (a+b)+c )遍历代码生成写到解析时,等于一次性完成生成和后序遍历(不必模仿,这样也有只可保留线性结构的缺点)

不过还有种用新版 abstract class Recursion 的更优雅的方法,待会发一下
#PL #CS https://github.com/duangsuse-valid-projects/Share/tree/master/Others/CommentBot/vertxBusGen#模板语言的语法解析器
Templator : Tests
好不容易实现完了这门 模板语言,不得不用非 Feed 结构实现 peek(2) ,感觉真是难受死了…… 非得处理完所有输入,没有无 hasNext/nextToken 的流式结构
为了实现这个还得有 expect() lastToken 的函数,感觉勉勉强强糊弄了吧,还是不优雅,但模板语言能用
oop1.lua
1.2 KB
#Lua #oop #PL 🤔刚才体验写标准Android之前又把 LuaOOP 默写了一遍,构造器调用子类优先了,重写后果然感觉优化了不少(也提升了效率编程自信心)
#Java #groove #pl #gui 🤔挺喜欢这个的。 PKT 新版也打算支持 ASTNode 对应 SourceLocRange . 目前 TkGUI 还没有专门的 Table 控件,只能用 TreeWidget ,我还是爱 DOM 式封装
dnaugsuz
那个…… 提几个建议 1.不要再起 n:str 了,红姐也有这种习惯,换 s:str 或 name:str 吧,激进一点也可以叫 sKey 同理激进命名也可以前置 mainf 的 f, 并精确为 fpOrigin 2.retrun tuple 的时候可以加括号;局部赋值 unpack 时也可以 3.Entry.__lt__(other:str) 序运算符解析了其上 value ,也可以在 ctor 里缓存下 4.s.split(' ',limit=1) 可以抽提下 5.我知道 write_main 里面你…
#statement #dev #pl 补充一句,我用的不是匈牙利命名法(这个名字本身怎么那么狭隘……匈牙利人常用么)

匈牙利命名法是 property(常量/类属性/可变量)+简写type+描述

我的命名法是在长期重构和编程中自创的,大小写分词(camel/snake)基于 Kotlin 的规范,但在特定子程序(涉及物理量/同名不同形式量 比较多的)会有更清晰的模式,甚至可以替代类型声名,我管它叫『物理命名法』

匈牙利命名法重视类型前缀,物理命名法却重视类型本身——因为很多作用域范围内同类型只会有一个值,或一个 1:N 相关值出现,所以除了类型简写,描述往往省略

它有4种模式:
单/多个量: x, xs, xz(Iterator)
嵌套级简写: evt=ev.target, ek=e.key
量化: nX=xs.size, kX=xs.keyOf(x)
时序/编号: v, v1/oldV; t0, t1

类型简写:
i,j,n,m: Int
w,h,l: Int|Double
s: String
c: Char
op: Function
p: (T) -> Boolean
ex: Exception
d: Map
e: Map.Entry|Element
k: K
x,v: T
f: File; fp: FilePath
ev: Event
#js #DontKnow 原型链:(感谢 @JackWorks 提供相关信息)

访问语法都是动态解析的,比如 x.prop
x["prop"]
就是
x.[[Get]]("prop", x)

ES 里一共有五种不同的 [[Get]] 实现,分别是
- 普通对象 [规范]
- Argument 对象(你们今天应该不会用到了)
- 类数组对象(数组的特殊行为就在这里)
- 模块对象(import * as Mod 里的 Mod
- Proxy 对象(reflect proxy 全 delegate)

此外, Object.getOwnPropertyDescriptor(o, k) 可以获取可配置 enumerable, writeable 等属性的配置对象
Object.[get/set]PrototypeOf(o)
o.__proto__ 是它的「超类虚表」

[[Get]] 过程的 Receiver (第二参数)很重要,如果没有这个 Receiver,基于原型链的 OOP 其实是做不起来的

原来是往 proto 上找属性!
这就解释了 Array.prototype.map 之类的东西

parent = { method() { return this; } }
child = {}; child.__proto__ = parent;
child.a = 1; child.method(); // 返回自身
最简单的解释是, Receiver 就是属性访问的时候最上层的那个对象,会被当成 this 用。
因为在这个算法里你可以看到,Receiver 是跟着递归一路传递下去的

原来是 o["RESOLVE"](o.prototype, "attr", receiver=o) !(当然,肯定是先查本地,然后才查 prototype
本地如果有就不会查 prototype 了

明白了,和之前写的 LuaOOP 很像,都是层叠属性查找

“ 大佬能交换下原型链相关知识吗
之前看加 Mixin 好像是说把 prototype 除了哪两个属性的什么全部复制一下
#Python#Ruby 的情况我都了解, Py 是 mro() 链查询, A.wtfa.wtf 都是往 class A 找成员,后者实质是 type(a).wtf(a) 所以得到 bound method ,而直接 A.wtf 就是 bind 操作
@staticmethod 直接不收 self ,不需要 bound 所以可以在类和实例上用

https://paste.ubuntu.com/p/tJv7QpSjGt/ liuil-util 的 #TypeScript mixin.ts 重写
#dev 物理命名法 (前缀?) 涵义😋
只有写一段代码重复写到无聊但从不写冗余代码的人能理解这种做法的意义。
实际一点的活用见上
a b : paired item
c: char
d: step/dict
e: element; ev: event; ex: exception
f: math func/file; fp: file path
g: graphics
h: height
i: index
j: 2nd dim index
k: ratio/key
l: distance
m: 2nd dim size
n: dim size/count
o: simple object; op: subroutine closure
p: predicate
q:
r: degree
s: str/stream
t: target/text
u:
v: value (of k)
w: width
x y z: left top pos/list item
0: base or elder element
1: new or stop value
duangsuse::Echo
#ai #code https://jsbin.com/divoxufajo/edit?js,output 可以试玩一下AI编程的质量😒 。全程没4句话,全网找不到类似代码,是 #Bing 从算法原文据位运算知识翻译的。 人家就是复现算法原文,也比工业界写的强。 什么叫 🐮🍺 ,就是大家实现同1个算法,结果AI不仅快,而且简明,而且每一根毛都看得清算法原文 #game AI generate 还能缝合棋盘 puzzle (UX有bug, 请按 Hint 键体验. 这个键也是AI实现的 pong, gpt2
#statement
人能活几十年,计算机界也有70岁, 对技术怎么就这么短视😒。 过几年再看看,会觉得吵的是毫无收获,你新写的东西真的是唯一的吗?世界上甚至有人实用过。也有无数种视角你没做过,这并不能作为什么谈资
自己做CS,科学,搞得跟信教了一样,主动提AI,主动骂狗shit ,你拿什么荔枝?
#ce #reveng #sb https://tttttt.me/Javaer/895034
哦,刚刚想起来 跳转Block 的层级也是可以做混淆的,但这是 #security 领域的东西,一般人只会用加壳脱壳扫内存,这种技术根本没有普及,业界倾向于选择无法软破解的DRM

obfuscator, 比如,把所有 if else 块变成 while() switch(flag) 的格式,就能让反汇编或retdec 很难读懂。 如果用动态分析找到Block的跳转集散点做记录,就可以反混淆
我就想,正向工程,尚没有什么复制不了的业务功能,何况需要ELF文件才能开展的RE呢? 好好写定义式的专业框架、C-FFI 不香吗
觉得低能我当然理解,“安全论坛”base族不过是入门教程,但这只是我涉足领域的10% ,触类旁通罢了
我是从问题创作自己的解法, 而不是知道问题和“正确的”解法、或知道哪类解法该在什么对线里拿来炫耀 ;那是对编程的玷污🙄

用比上不足比下有余的心态看待技术,真的…… 比你强的大老牛一堆, 比你菜的js框架一堆,挣钱嘛,不寒碜

无非你是会冷门领域,然后 #AI 提供的搜索结果能涉足你领域一些“菜鸟技能”,让你要拼命掩饰它是种威胁了。

但那些根本不是“技术”,只是别个领域里前人做过,而你碰巧重做了。对你而言似是其非、表达不出,勉强在test上通过了,故自以很厉害,
但究其原理就那样,前人的正确错误都犯到,连冗杂都不是原创。

AI只是暴露了这苗头,AI只是向你证明了没有信息差时,那些堆砌就根本不是“技术”;你们硬要喷别人蠢。它就是蠢,但未来就是能干死“只有你懂”的伪门槛。 你可以不信,但最好停止狭技居奇,看看pancake的r2利用好了多少“蠢”领域的技术吧? 强如Haskell又有几个pandoc ?
要是所有程序员早设计好二进制可视化和RE的IDE插件 等API与脚本化,许多难题甚至都不会出现。编译器。呵呵。
我爱旧知,但我更爱真理
我爱算法,但我更敢组合

连API和DSL都写不好的程序员 在用XX范式里百年未变的概念,写模式匹配->更单调语言 的重构器,自以掌握了所有编程里的变与不变,看透了虚表多态、符号解析、跳转回填、闭包和可暂停化、流控图/Val引用、片段地址和二进制、typing、ptrace/mmap、Locks等深奥模型,其实连母语 都讲不利索,能称为设计者吗🙄。未知生,焉知死。总结不出自己做了啥学了啥,不是个人肉copilot吗?

#PL 人这天才病,可真得养好再上网吧。天才,就要有自己的话语体系?笑料。
大家都写列表处理,都琢磨图求解图重构,就你 NodeGraph<E= CFG_Block> 高人一等,那就永远别做技术交流、别做有工程意义的工具,术语内卷、套路通胀,让这个领域自生自灭吧。

ref: 开头 - 开头1
被骂了code
导致误会的disclaimer
Deleted Account:
天天手撮LLVM pass這叫寫不了
我寫編譯器的時候你在幹嘛?
沒意思,天天打稻草人,一句話幾個 非形式謬誤(论证素材虚构/不相干)
(😅原来AST walker+IRBuilder 是啥了不起的技术
我的反思 - 我导致被骂的观点
👎3
duangsuse::Echo
#statement 人能活几十年,计算机界也有70岁, 对技术怎么就这么短视😒。 过几年再看看,会觉得吵的是毫无收获,你新写的东西真的是唯一的吗?世界上甚至有人实用过。也有无数种视角你没做过,这并不能作为什么谈资 自己做CS,科学,搞得跟信教了一样,主动提AI,主动骂狗shit ,你拿什么荔枝? #ce #reveng #sb https://tttttt.me/Javaer/895034 哦,刚刚想起来 跳转Block 的层级也是可以做混淆的,但这是 #security 领域的东西,一般人只会用加壳脱壳扫内…
#ai 这个多少有点恶心了, #PL 人都这么抽象的吗?🙄
#zhihu 回答大意:
GPT不能说是编程语言,因为套路和准确度都不够统一 也没有生态和社区,但可以帮更多人更简短的完成任务,所以说是最好也没有错。

《程序员的心理疾病》
☝️文中提到的礼貌问题GPT全解决了,所以帮人用好它的人,就成了被口诛笔伐的“蠢货”

唉,不论是非只论善恶,也难怪中国的PL人都要出去留学呢

没点素质,真润出去的PL大佬哪会在意这点细节?多是一群连parser和IR都没构造过、OOP和trait多态都不知咋实现的愤青,看到自己模式匹配和English terminology 的技能被AI“抢”了,就破防到跨领域无论据追捕

#cs 的学生成千上万,但文化程度这么低的也是第二次见 👏😓
1