duangsuse::Echo
运算符: 前、后 (dec/inc) +(加)、-(减)、*(乘)、/(除)、%(取余) -~(取负) 大(>)、小(<) 不大(<=)、不小(>=) — 这一行括号里的表示法仅参考用 汉语里就不要用小于号、小于等于了。 取负的 -~ 只是注释,绝句不存在前缀记法,只有前缀算符 &(且) |(或) !~(取非) (异) — 只有真假类型有 绝句的且或非逻辑都是短路计算的,也就是说 假 & 不可能, 真 | 不可能 我相信,既然绝句已经改了这么多了,不会有人觉得让 (&) 官复原职很奇怪 第一眼看了…
然后再谈一些比较贴近语言本身的「物」:
然后是集合
不过绝句里认为,只要是可以用于装一个以上值的东西都叫做集合。
按照从小到大的顺序,
他们都默认是只输出数据(不可变)的,
类似
元组一直提供到
之所以提供了一元元组,是为了对称,当然绝不会提供类似 Rust 和 Haskell 的元零(无意义)。
对元组来说,他们不需要去兼容某一种属(也是应该的,不然和数组就没啥区别了)以提供通用 API
操作上,
isEmpty, isNotEmpty 被译为
和 Kotlin 一样,对 Java 里的原生类型(primitive) (ZBSIJFDC, boolean, byte, short, int, long, float, double, char) 也提供了拆箱的组们,比如
然后是函数
绝句提供对 SAM 方法的自动实现,无论是块、方法引用(实例方法、架构器、自由方法也即未绑定到实例的方法、partial application 的结果)
一般认为函数是有N个输出一个输出的动作
没有输入和输出的函数被绝句称为
提供
之后的函数变量是无法表示的,这是一个限制,我不知道 Scala 是多少,好像有跑到二十?
然后是阿猫阿狗
一般来说,如果可能且仅可能有一种失败,绝句标准库的封装不会使用
断止还是一个『无例之物』
比如
令做(run)、令为(let)、令置为、顺手(also,
有若(takeIf)
成文(toString) 成码(hashCode)
滤出(filter)、映至(map)
存一(find+不空)、存仅一(single)、皆是(all)、皆非(none)
我承诺 50% 兼容 Kotlin, 60% 兼容 Java :P……
100% 不兼容烂代码
物 数值 为……
物 真假 为……
别名属 是否 = 真假
物 字节 属数值 为……
物 短数 属数值 为……
物 字儿 为……
物 数 属数值 为……
物 长数 属数值 为……
物 浮数 属数值 为……
物 实数 属数值 为……
物 文 为……
别名属 计数 = 数
@内部 别名属 正计数 = 计数
别名属 引数 = 数
「文」和 Kotlin, Java 里的 String 一样,是不可继承的,其他类型也和 Kotlin 里一样是终定的。是否一般是用作可变数据的然后是集合
不过绝句里认为,只要是可以用于装一个以上值的东西都叫做集合。
按照从小到大的顺序,
元一<A>..., 组<T>, 行<E>, 行列<E>, 表<K, V>, 集<E>, 合<E>, 迭<E> 他们都默认是只输出数据(不可变)的,
类似
动元一<A>..., 动组<T>, …… 这种是可变的元组一直提供到
元十。之所以提供了一元元组,是为了对称,当然绝不会提供类似 Rust 和 Haskell 的元零(无意义)。
对元组来说,他们不需要去兼容某一种属(也是应该的,不然和数组就没啥区别了)以提供通用 API
操作上,
isEmpty, isNotEmpty 被译为
无项、存项
size 被译为项数
lastIndex 被译为引末(对应的是引始)和 Kotlin 一样,对 Java 里的原生类型(primitive) (ZBSIJFDC, boolean, byte, short, int, long, float, double, char) 也提供了拆箱的组们,比如
长数动组、短数动组 然后是函数
绝句提供对 SAM 方法的自动实现,无论是块、方法引用(实例方法、架构器、自由方法也即未绑定到实例的方法、partial application 的结果)
一般认为函数是有N个输出一个输出的动作
没有输入和输出的函数被绝句称为
『副作用』,当然是我的叫法,实际可能是 事元零<效果>
没有输出的函数通通输出提供
事元零<R>
事元一<T1, R>
事元二<T1, T2, R>
……直到事元十二,其实本来应该用模板写的,可绝句目前还没机会加模板特性之后的函数变量是无法表示的,这是一个限制,我不知道 Scala 是多少,好像有跑到二十?
然后是阿猫阿狗
作用 (就是 kotlin.Unit)可抛
可闭(毕竟绝句是有 try-with-resources 的)不常(就是 java.lang.Exception)
无常<R> (这个就是绝句版本的 Checked Exception,你若不想检查可和 Rust 一样直接 unwrap_ok(),噢不是 作决常, 当然还有 smart cast)一般来说,如果可能且仅可能有一种失败,绝句标准库的封装不会使用
无常<R>, 而是用 R? 这样的断止、决常 (无常的唯一两个子类)断止还是一个『无例之物』
终定的物 断止 私下造于(): 无常<*>
它不能被拿来判断也不能被拿来转型,因为 x属断止; x作断止 是常量判断存储<T> 就是可以被赋值的东西,编译器能够在取可写属性、变参的时候能够自动将它实例到存储以方便使用比如
储物 用户(量 名字: 文、量 男: 真假、变文 bio)—
量 某君 = 用户("duangsuse", 男=真, "……")
某君的名字 ”字符串 duangsuse“
某君的名字置为"$某君的名字+1" -- 但是你不能写 "$某君.名字+1" 的
“^ 自动转型为存储<文>”
某君的名字 ”字符串 duangsuse+1“
令做(run)、令为(let)、令置为、顺手(also,
(T)效果)、交给(支持 (T)* 的 also)有若(takeIf)
成文(toString) 成码(hashCode)
滤出(filter)、映至(map)
存一(find+不空)、存仅一(single)、皆是(all)、皆非(none)
我承诺 50% 兼容 Kotlin, 60% 兼容 Java :P……
100% 不兼容烂代码
duangsuse::Echo
运算符: 前、后 (dec/inc) +(加)、-(减)、*(乘)、/(除)、%(取余) -~(取负) 大(>)、小(<) 不大(<=)、不小(>=) — 这一行括号里的表示法仅参考用 汉语里就不要用小于号、小于等于了。 取负的 -~ 只是注释,绝句不存在前缀记法,只有前缀算符 &(且) |(或) !~(取非) (异) — 只有真假类型有 绝句的且或非逻辑都是短路计算的,也就是说 假 & 不可能, 真 | 不可能 我相信,既然绝句已经改了这么多了,不会有人觉得让 (&) 官复原职很奇怪 第一眼看了…
简单的说,逗号文法就是这四种情况:
+ 直接表达式(组)
+ 逗号嵌套链
+ 逗号取调链
boxA令为,它去取值()。使用()
+ 后面不能跟
+ 如果都没有但后面跟了名字,默认是
如果后面直接跟名字,
比如说:
这一点不用中文编程还看不出来啊woc
控制流简记是针对表达式的情况,即便
对表达式组的最后一项也一样。
+ 直接表达式(组)
一行(1,2,3,4)去滤出「大一」.每个,说(它)。 — 这里的 说(它) 是直接表达式对输入行里 检查(你)是 否 的,无效集合记它、回。— 这是「组」和控制流简记
-- 若无效集存项,抛下输入无效(无效集合)。+ 嵌套段
若你的名字不空,这是嵌套段,就是最一般的情况
说(你的名字)
+ 逗号嵌套链
若p,若q,嵌套链「,」的右边只接受嵌套链或嵌套段。
emmmmm()
若p0,若p1,若p2,p0 p1 的收的都叫嵌套链,最后一个是嵌套段。
这一行()
+ 逗号取调链
boxA令为,它去取值()。使用()
boxA.let { it.get() }.use()
逗号取调链就是,在使用直接表达式作为块参数的时候,+ 后面不能跟
./?.
+ 后面可以跟 的/去 + 如果都没有但后面跟了名字,默认是
(.)
用一般的描述方法,就是说后面必须跟汉语访问符,不能跟英语访问符。如果后面直接跟名字,
。号并作 (.) 号用比如说:
宫水家.找,她:她的名字是"三叶"。手机上的最近聊天记录.数,它的发送者是泷。有若「大五十」 空则,抛下沃日("emmmm")。
唉,我看逗号块还是允许利用:指定默认的 it 吧,毕竟这是中文,把人称作「它」实在是太过分了……这一点不用中文编程还看不出来啊woc
控制流简记是针对表达式的情况,即便
回/抛下/停下/略过是语句,它也可以取而代之,对表达式组的最后一项也一样。
考虑一下先弄结束 Dokuss,然后 Trie 线索树和 TreeRangeMap,然后一些脚本语言需要用的可扩展反射库,然后 ParserKt 的 stream/fold 修改和完整的 infix parser mixin
可能不确定的暂时不弄
总感觉最近思路像是被限制了一样,不灵活
可能不确定的暂时不弄
总感觉最近思路像是被限制了一样,不灵活
Forwarded from duangsuse Throws
真的很替华为惋惜,虽然程序设计语言领域的冰封哥支持华为并且称有多为 CE 的朋友在开始工作了
可我觉得原型是很重要的,如果一个东西很久连个基本的框架都没有,我们是不是可以认为,它的可维护性很差?模块化程度太低???
可我觉得原型是很重要的,如果一个东西很久连个基本的框架都没有,我们是不是可以认为,它的可维护性很差?模块化程度太低???
Forwarded from duangsuse Throws
#China #Huawei #PL #ce 打住,据说冰封哥和蛤为方舟编译系统没半毛钱关系,不过我是在 Q 群里看到他说有研发方舟的朋友的。
顺道给各位没听说过的科普一下,常见的编译优化(这里不局限于 JVM 平台,而且我和 JVM 的实现也没太大关系,尽管我会偶尔思考一下线性指令序列的控制流和高级控制结构的关系)
constant-propagation & constant-folding, dead-code-elimation, inlining, instcombine(当然也包括 jmp 指令的), loop-unrolling, loop-reversal, loop-inversing, loop-invariant-code-motion(LICM), tail-call-optimization(TCO), , global-value-numbering(GVN), common-subexpression-elimation
有些过于直白的优化就不用看了,当然也不必看。(你自己应该会写)
+ cp/cf 系可怜的 Javac 都有的优化,不信你试试:
+ dce 我学过基于 BFS(breath-first search) 对 LLVM 的 Value ADCE 的,不过再高级一点的输入 ADCE(激进 DCE) 也就是走一遍到集合 {used} 然后取补、删。(苏格拉底都问过学生同类问题:苹果林里你不走一遍是不知道哪颗苹果树绝世而孤立的…… emmm怎么感觉有点不对?)
+ inline 就是 inline function 呗,很多优化的效果都依赖这个优化(外部到局部)
+ instcombine 不是标准的名字(LLVM的),比如合并赋值+一串jmp,就是 Lua 跳转链表
+ unroll 就是展开。reversal 和 inversing 的区别是,reversal 就是你从 N 数到零可以用类似 decrease-jumpIfZero(DJNZ) 这种指令(反之;<n;i++这种不行)、inversing 的意思是
知道循环为什么叫循环吗?因为
知道我们在生成
+ LICM 包含说的 loop-expression-hoisting.
高级一点的,比如 LLVM(low-level-virtual-machine) 的 Alias-analyzing (特指 C/C++ 这种
编译策略上(基本是JVM/CLR里有的),比如 JIT(just-in-time), AOT(ahead-of-time), on-stack replacement, hot-spot dection
JVM 上常见的,比如 de-reflexing, unboxing, range-check-elimation, null-check-elimation, branch-frequencey-prediction (从《深入理解Java虚拟机》书上精选的)
程序员自己要做 profile-guided optimization(PGO) 和算法选择上面的研究(这样学术一点的就得用到函数增长率,普通一点的比如我就会选择口碑好的emmm)。
不知道算了,可以自己到 wiki 上查,不过如果你能够默写 The Little Schemer (不要问我为什么总拿这本书说话,这里是刚好合适,一些优化不一定需要很厉害的算法) 的列表处理例子,基本上就可认为是有足够技术水平去实现了
—
既然我都说了这么多了,那再顺便提及一下『汇编』吧,虽然我一个弄软件的也不知道太多。
一般来说汇编还是要分 instruction structure architecture(ISA) 的,可以按地址归类为 0, 1, 2, 3 地址
举个例子,比如加法运算吧。
你可以设想一下,即便
如果你要设计一个温度计,同时支持摄氏度华氏度显式,所以你的
3 地址一般就是 dst, src1, src2
我们常说的汇编一般认为是当年个人机所用 x86 架构处理器,是3地址。
『汇编』很多时候都会有类似这样的指令:
ldc — 存储常量
ld, st — 读取/写入待操作的对象
mov — 读取和存储的 combine,也就是 copy
别和 "clone" 搞混了,clone 一般是对聚合量(product value)
jmp — 直接指定下一条指令的位置,一般用于循环
br — 分支,如果存储里是「真(truthy)」值
br.not — 分支,如果存储里「非真(falsy)」值
op — 以待操作的对象,实际执行操作
操作有可能有值,一般会被送到存储中(比如 JVM 里就是栈顶)
call — 调用,移交控制权给目标子程序并且待其完成。
alloc — 分配一定大小的栈帧本地存储
ret — (被调用者 callee)从自己的栈帧返回到 caller,本帧分配(alloca)的存储作废。
在有「调用栈层次」的机器里,一般 ld/st/mov 都会有一个所谓的「存储层次」。目的是,某个子程序可以访问调用者给自己的参数。
对应地,call 也会有一个所谓的层次参数指定新调用的层次。
这个过程需要依赖对调用栈
某些机器有「寄存器窗口」,它意味着每个 call 都能够得到自己的一组寄存器。
可以看看 Wiki 的 PL/0 和 p-code,之前我在 USTC 的 CE share 里看到了 PL/0 实践教程的文档,好像是被撤下了?
顺道给各位没听说过的科普一下,常见的编译优化(这里不局限于 JVM 平台,而且我和 JVM 的实现也没太大关系,尽管我会偶尔思考一下线性指令序列的控制流和高级控制结构的关系)
constant-propagation & constant-folding, dead-code-elimation, inlining, instcombine(当然也包括 jmp 指令的), loop-unrolling, loop-reversal, loop-inversing, loop-invariant-code-motion(LICM), tail-call-optimization(TCO), , global-value-numbering(GVN), common-subexpression-elimation
有些过于直白的优化就不用看了,当然也不必看。(你自己应该会写)
+ cp/cf 系可怜的 Javac 都有的优化,不信你试试:
id=`uuidgen`辣鸡 Javac 不接受非 Java 扩展名的输入!
echo 'class CostFold { String ing() { if (true) return "+"; else return "-"; } }'>/tmp/${id}.java
javac /tmp/${id}.java -d /tmp
javap -cp /tmp -c CostFold
+ dce 我学过基于 BFS(breath-first search) 对 LLVM 的 Value ADCE 的,不过再高级一点的输入 ADCE(激进 DCE) 也就是走一遍到集合 {used} 然后取补、删。(苏格拉底都问过学生同类问题:苹果林里你不走一遍是不知道哪颗苹果树绝世而孤立的…… emmm怎么感觉有点不对?)
+ inline 就是 inline function 呗,很多优化的效果都依赖这个优化(外部到局部)
+ instcombine 不是标准的名字(LLVM的),比如合并赋值+一串jmp,就是 Lua 跳转链表
+ unroll 就是展开。reversal 和 inversing 的区别是,reversal 就是你从 N 数到零可以用类似 decrease-jumpIfZero(DJNZ) 这种指令(反之;<n;i++这种不行)、inversing 的意思是
while-p: ...; br.not :while-broke<=>(可代换)
(your code)
jmp :while-p
while-broke: ...
br.not :while-broke就是
do-while-cont:
(your-code)
...; br :do-while-cont
while-broke: ...
while () {} 翻译成 if () do {} while (); 的意思了,根本区别在于 br 指令不必 jmp 后执行,据说在流水线上有好处。知道循环为什么叫循环吗?因为
jmp, br 指令可以随意重置指令指针,使得下条指令可能是jmp的前驱(或者说它可达这条jmp),扭转程序往下执行的趋势。知道我们在生成
br.not :broke 代码的时候为什么能够知道 while-broke 的位置/偏移吗?可以用回填啊,Lua 的跳转链表也解决了回填问题+ LICM 包含说的 loop-expression-hoisting.
高级一点的,比如 LLVM(low-level-virtual-machine) 的 Alias-analyzing (特指 C/C++ 这种
union 弄不清指针到底是什么的语言, 当然 C++ 默认策略也不能直接做到 exact memory management), Vectorization: SLP(superword-level-parallelism) 和 loop vectorization编译策略上(基本是JVM/CLR里有的),比如 JIT(just-in-time), AOT(ahead-of-time), on-stack replacement, hot-spot dection
JVM 上常见的,比如 de-reflexing, unboxing, range-check-elimation, null-check-elimation, branch-frequencey-prediction (从《深入理解Java虚拟机》书上精选的)
程序员自己要做 profile-guided optimization(PGO) 和算法选择上面的研究(这样学术一点的就得用到函数增长率,普通一点的比如我就会选择口碑好的emmm)。
不知道算了,可以自己到 wiki 上查,不过如果你能够默写 The Little Schemer (不要问我为什么总拿这本书说话,这里是刚好合适,一些优化不一定需要很厉害的算法) 的列表处理例子,基本上就可认为是有足够技术水平去实现了
—
既然我都说了这么多了,那再顺便提及一下『汇编』吧,虽然我一个弄软件的也不知道太多。
一般来说汇编还是要分 instruction structure architecture(ISA) 的,可以按地址归类为 0, 1, 2, 3 地址
举个例子,比如加法运算吧。
(1+2) * 3 (=9)
ldc 3零地址一般就是说 JVM, Ruby YARV, CLR 这种以 last-in-first-out(LIFO)「栈」 传递参数的架构
ldc 2; ldc 1; isum // 当然这里用的是逆记法,因为弹(pop)出来的是最后压(push)上去的,尽管我们希望 (1) 先被求值。
imul
(accumlator) plus 1; (accumlator) plus 2; (accumlator) times 9
1地址现在很不常见,早期可能在还没 programmable-logical-device(PLD) 的时候繁荣过一时你可以设想一下,即便
(1+2)*3 还是 1+(2*3) 都没问题(因为只是计算结合顺序变了),如果我们是要 a = a + f(a) 照这种法子要怎么办?如果你要设计一个温度计,同时支持摄氏度华氏度显式,所以你的
f(x) 是算偏差量的函数,这时程序你又该怎么写?效率又怎么样?add A, 1; add A, 2; mul A, 9
2 地址现在很常见了,就是 reduced-instruction-set-computers(RISC) 的芯片们,比方说你 armv7/v8 的手机,当然 hard-float(hf) 与否无关。3 地址一般就是 dst, src1, src2
add A, A, 1; add A, A, 2; mul A, A, 9
当然我举的例子在 Java 里貌似都是常量表达式?我们常说的汇编一般认为是当年个人机所用 x86 架构处理器,是3地址。
『汇编』很多时候都会有类似这样的指令:
ldc — 存储常量
ld, st — 读取/写入待操作的对象
mov — 读取和存储的 combine,也就是 copy
别和 "clone" 搞混了,clone 一般是对聚合量(product value)
jmp — 直接指定下一条指令的位置,一般用于循环
br — 分支,如果存储里是「真(truthy)」值
br.not — 分支,如果存储里「非真(falsy)」值
op — 以待操作的对象,实际执行操作
操作有可能有值,一般会被送到存储中(比如 JVM 里就是栈顶)
call — 调用,移交控制权给目标子程序并且待其完成。
alloc — 分配一定大小的栈帧本地存储
ret — (被调用者 callee)从自己的栈帧返回到 caller,本帧分配(alloca)的存储作废。
在有「调用栈层次」的机器里,一般 ld/st/mov 都会有一个所谓的「存储层次」。目的是,某个子程序可以访问调用者给自己的参数。
对应地,call 也会有一个所谓的层次参数指定新调用的层次。
这个过程需要依赖对调用栈
(底,顶) 或言 (b, t) 的维护。某些机器有「寄存器窗口」,它意味着每个 call 都能够得到自己的一组寄存器。
可以看看 Wiki 的 PL/0 和 p-code,之前我在 USTC 的 CE share 里看到了 PL/0 实践教程的文档,好像是被撤下了?
Wikipedia
Propagation constant
complex measure of the attenuation (real part) and phase angle (imaginary part) along the path travelled by a plane wave
Forwarded from duangsuse Throws
—
既然我都谈到 JVM 了,那我就再科普一些 GC 的基本理念,顺路推隔壁 USTC 的公开资料(虽然我没时间看了)
(好像没有公开资料了?)
garbage-collector(GC) 呢…… 就是自动内存管理,有条件的可以了解一下 boehm gc 什么的,比较知名
GC 就是让人把存储视作对象的东西,有时候我们需要这么做:
经典的计算机是面向存储、面向处理过程的,可是编程是面向对象(你也可以说是面向函数组合或者 continuation-passing-style, CPS)的,人的开发效率和机的执行速度,天生水火不容。
有个笑话说,冯·诺伊曼怒骂过程序员,“你怎么能用 assembler 呢?你知道它浪费了多少 CPU cycle 吗???”
鱼和熊掌不可得兼,所以要有权衡和取舍。曾经(比如 62 年前 1957, Fortran刚刚发明的时候)的时候,CPU们的速度还很慢,于是程序员就成了冤大头,不得不和一堆与机器过分相关的细节死肝,来描述自己的逻辑和计算。
因为那时候计算机的应用也就是那个『超算』『集群(cluster)』的程度,程序员也没有享受计算机周到服务的那个底气,机器的时间金贵啊!
21 世纪就不一样了,程序员不仅仅是『喜新厌旧』连 assembler 都看不上了,而且还用上了 compiler,甚至 virtual machine,这一个一个跑一秒钟都「浪费」 cycle 到能让老冯跪在地上哭啊!😭
可是程序员依然用得不亦乐乎,甚至还弄了 type checker, linter, inline docgen, sanitizer, code generator, build system, package manager,还IDE、自动补全、智能感知、浏览定位、代码分析,还测试全覆盖、DevOps、云计算。当然所有人也都是一样,因为计算机的算力上去了,不怕浪费一点宝贵的,应该拿去做科学计算的 CPU cycle 了。
如果能够避免悬垂指针或者说野指针(dangling pointer)、内存泄漏(memory leak),能够检测出指针溢出(pointer overflow)索引越界,其实由计算机处理更多工作也没什么,它闲嘛。
当然我这里不是说因为算力上去了你就可以不并行处理,编程时你就可以不做算法分析,不好好考虑怎么节省资源提高内存效率和执行速度。胡乱弄个纸张算法,这不是程序员该有的行为。
如果啥时候我们不需要记忆某些对象了,是不是就可以
之前我们说了
比如说,我们知道在 Java 里你只能访问静态、实例、局部变量(可能不准确,凑合着吧):
回收辣鸡就是对每个『对象』问一个问题:你有用么?
细化一点,就是在『某个时候』去问『我Boss的代码能够访问到你么?』,如果不能,『回收』这个对象。
当然对象也不一定得是所有的对象,比如,可以只是某个特定「区域」的对象。
这是要看时机的,比如
然后 execute 返回,我们就拿不到
如果啥都不做,『蔡徐鲲』就这么『泄漏(leak)』了,因为我们无法再见到它,它却偷偷躲在『片场』(内存池) 的某个角落里大跳《只因你太美》!
所以调用后的任何时候,我们问这三个对象(藏在
对象
(当然其实也是没有用的 (
对象
(所以这种会被放在 HotSpot JVM 的永久代或常量区,当然也有人叫方法区)
对象
我们刚才做的简单算法一般叫『标记-清除(mark-sweep)』算法,它也是 1958 年 Lisp 实现的第一个自动内存管理算法。
它是通过追踪调用栈(就是所有的『动态』变量)上的所有帧本地变量,BFS(广度优先搜索) 标记它们所依赖的对象做到的,换句话说,是为了保留根对象而搜索必须留下的对象、回收不必留的对象
为什么不直接搜索不必留的对象?看看上面的『苏格拉底的问题』…… 数学逻辑上的证明找数学家吧。
实现的细节有 bitmap marking (为 Unix-like 进程 CoW, copy-on-write 优化的).
不过这样不是不能优化,想想一个对象的依赖关系图 — 一个 Node 指向很多其他 Node,有些东西 — 我们称为 primitive,比如 int, float, double 这些 — 是图里最后的顶点
我们的问题是,假设有一个过程引用了
首先我们想,
然后某个依赖处消失,a 还是 x, y, z 的依赖数目都减1,为0时就可以回收,这就是 Rc(引用计数) 算法。
Rc 相对于 tracing 是一种『概括性』的算法,因为它不从GC roots追踪实际引用,只是(有损失地)概扩为某对象的被引次数。
Python和PHP 好像默认都是 Rc,不过他们是不会泄漏循环引用的,即便他们没有
欸我还有事的,怎么讲起这些不懂的领域了…… 😫
算了,这里我提及一些名词,请大家自己发动手指找资料……
Common-Language-Runtime(CLR), Mono project
弱代假说(weak-generation hypothesis)、分代垃圾回收
Exact memory management, pointer-type
Semi-space algorithm, Eden Heap, coping-GC, compat-GC
stop-the-world, incremental-GC, tri-color GC, GC write barrier
harmony_gc_source.pdf
停,这个 Harmony 不是华为的『鸿蒙操作系统』,是 Apache Haromny,一个曾经确实是不负众望过但是又因为 Oracle 的打压弄得 Java 世界都支离破碎的项目,当然这不怪它,全赖那个不思进取心胸狭隘的 Oracle。
既然我都谈到 JVM 了,那我就再科普一些 GC 的基本理念,顺路推隔壁 USTC 的公开资料(虽然我没时间看了)
(好像没有公开资料了?)
garbage-collector(GC) 呢…… 就是自动内存管理,有条件的可以了解一下 boehm gc 什么的,比较知名
GC 就是让人把存储视作对象的东西,有时候我们需要这么做:
Object[] memorizedThings = new Object[] {john, monkey, apple, grape, banana};
当然,以上例子按照面向对象封装建模可以建模成 class,按函数式闭包建模可以建模成 memorizedThings::get (这个闭包引用了 (Array<Object>)this, this 包括了对所有元素的引用)。经典的计算机是面向存储、面向处理过程的,可是编程是面向对象(你也可以说是面向函数组合或者 continuation-passing-style, CPS)的,人的开发效率和机的执行速度,天生水火不容。
有个笑话说,冯·诺伊曼怒骂过程序员,“你怎么能用 assembler 呢?你知道它浪费了多少 CPU cycle 吗???”
鱼和熊掌不可得兼,所以要有权衡和取舍。曾经(比如 62 年前 1957, Fortran刚刚发明的时候)的时候,CPU们的速度还很慢,于是程序员就成了冤大头,不得不和一堆与机器过分相关的细节死肝,来描述自己的逻辑和计算。
因为那时候计算机的应用也就是那个『超算』『集群(cluster)』的程度,程序员也没有享受计算机周到服务的那个底气,机器的时间金贵啊!
21 世纪就不一样了,程序员不仅仅是『喜新厌旧』连 assembler 都看不上了,而且还用上了 compiler,甚至 virtual machine,这一个一个跑一秒钟都「浪费」 cycle 到能让老冯跪在地上哭啊!😭
可是程序员依然用得不亦乐乎,甚至还弄了 type checker, linter, inline docgen, sanitizer, code generator, build system, package manager,还IDE、自动补全、智能感知、浏览定位、代码分析,还测试全覆盖、DevOps、云计算。当然所有人也都是一样,因为计算机的算力上去了,不怕浪费一点宝贵的,应该拿去做科学计算的 CPU cycle 了。
如果能够避免悬垂指针或者说野指针(dangling pointer)、内存泄漏(memory leak),能够检测出指针溢出(pointer overflow)索引越界,其实由计算机处理更多工作也没什么,它闲嘛。
当然我这里不是说因为算力上去了你就可以不并行处理,编程时你就可以不做算法分析,不好好考虑怎么节省资源提高内存效率和执行速度。胡乱弄个纸张算法,这不是程序员该有的行为。
如果啥时候我们不需要记忆某些对象了,是不是就可以
free 掉他们对应的存储?之前我们说了
call...alloc...ret 的「栈帧本地变量」,虽然这是一种分配方式,可也是清除了不需要的东西(虽然初衷不大相似)。比如说,我们知道在 Java 里你只能访问静态、实例、局部变量(可能不准确,凑合着吧):
import static java.lang.System.out;
public class GcRoots { public GcRoots(){}
public void execute() {
Object cxk = new Object() {String toString() {return "蔡徐鲲";}};
out.println(cxk);
}
String rootChicken = "🐔太美。";
static String ROOT_emmm = "emmm...";
}
回收辣鸡就是对每个『对象』问一个问题:你有用么?
细化一点,就是在『某个时候』去问『我Boss的代码能够访问到你么?』,如果不能,『回收』这个对象。
当然对象也不一定得是所有的对象,比如,可以只是某个特定「区域」的对象。
这是要看时机的,比如
GcRoots#execute 执行完成之前,cxk指向的那个东西,它就是有用的然后 execute 返回,我们就拿不到
new Object(){...} 的『蔡徐鲲』了,但它的分配还在,没消失!如果啥都不做,『蔡徐鲲』就这么『泄漏(leak)』了,因为我们无法再见到它,它却偷偷躲在『片场』(内存池) 的某个角落里
所以调用后的任何时候,我们问这三个对象(藏在
Object 引用后的):对象
rootChicken有用么?有用,因为我们为了调用 execute() 初始化了一个 GcRoots() 实例GcRoots play = new GcRoots();一个
play.execute();
cook(play.rootChicken);
this 存在时它的所有 field 都是有用的,因为它们可能通过这个 this 被访问。(当然其实也是没有用的 (
new GcRoots().execute()),有句话说得好,CLR 里一个对象可能在 constructor 被调用的时候,就已经 Finalize 了,这里为了保证准确性我刻意弄了个 cook 不是死代码)对象
ROOT_emmm有用么?它当然是有用的。实际上,类 GcRoots 被加载后的任何时候它都可能被访问,因为它是 GcRoots.ROOT_emmm。(所以这种会被放在 HotSpot JVM 的永久代或常量区,当然也有人叫方法区)
对象
cxk有用么?它已经没用了,所以它的分配可以被『回收』,接着用了。我们刚才做的简单算法一般叫『标记-清除(mark-sweep)』算法,它也是 1958 年 Lisp 实现的第一个自动内存管理算法。
它是通过追踪调用栈(就是所有的『动态』变量)上的所有帧本地变量,BFS(广度优先搜索) 标记它们所依赖的对象做到的,换句话说,是为了保留根对象而搜索必须留下的对象、回收不必留的对象
为什么不直接搜索不必留的对象?看看上面的『苏格拉底的问题』…… 数学逻辑上的证明找数学家吧。
实现的细节有 bitmap marking (为 Unix-like 进程 CoW, copy-on-write 优化的).
不过这样不是不能优化,想想一个对象的依赖关系图 — 一个 Node 指向很多其他 Node,有些东西 — 我们称为 primitive,比如 int, float, double 这些 — 是图里最后的顶点
Object x = 1, y = 2, z = 3; // 你需要在 Java 1.5 以上编译,autoboxing。
Object a = new Object[] { x, y, z };
Object b = new Object[] { x, y }; 我们的问题是,假设有一个过程引用了
a,在结束时我们怎么能知道 a 该不该被回收?首先我们想,
a作为「过程本身的局部变量」是一个「需求处」,但a依赖的对象不是它的「需求处」a 创建的时候就是作为某种需求处被依赖的,所以它有 1 个依赖然后某个依赖处消失,a 还是 x, y, z 的依赖数目都减1,为0时就可以回收,这就是 Rc(引用计数) 算法。
Rc 相对于 tracing 是一种『概括性』的算法,因为它不从GC roots追踪实际引用,只是(有损失地)概扩为某对象的被引次数。
Python和PHP 好像默认都是 Rc,不过他们是不会泄漏循环引用的,即便他们没有
java.lang.ref.WeakReference<T>. 也没 template<class T> class weak_ptr;, 据说这是因为他们有特殊的 GC module 处理。欸我还有事的,怎么讲起这些不懂的领域了…… 😫
算了,这里我提及一些名词,请大家自己发动手指找资料……
Common-Language-Runtime(CLR), Mono project
弱代假说(weak-generation hypothesis)、分代垃圾回收
Exact memory management, pointer-type
Semi-space algorithm, Eden Heap, coping-GC, compat-GC
stop-the-world, incremental-GC, tri-color GC, GC write barrier
harmony_gc_source.pdf
停,这个 Harmony 不是华为的『鸿蒙操作系统』,是 Apache Haromny,一个曾经确实是不负众望过但是又因为 Oracle 的打压弄得 Java 世界都支离破碎的项目,当然这不怪它,全赖那个不思进取心胸狭隘的 Oracle。
Wikipedia
Boehm garbage collector
garbage collector software library for C and C++
Forwarded from duangsuse Throws
不少知识和信息,除了从书上看到我还从其他爱好者的资料里看到。
USTC ce 系学生的一些分享不知道为什么不见了,我很遗憾,因为那些资料也都很方便。
不过我给大家一个建议:比起直接从理解『解决方式』来理解问题,更应该从理解问题本身来理解问题的解决方式。
对任何复用库的开发者而言,设计时要以使用者的角度思考;对任何使用者而言,想理解问题要先自己设计一个问题的解决方案,然后从编写者的视角再看别人的解决方案。
或许用户的思维是幼稚的,可你不必总当用户。当你实践理论的时候,它们才真正到了你的脑子里。
我也相信,先理解问题还是先理解解法,是创造与重复之间最根本上的鸿沟。
USTC ce 系学生的一些分享不知道为什么不见了,我很遗憾,因为那些资料也都很方便。
不过我给大家一个建议:比起直接从理解『解决方式』来理解问题,更应该从理解问题本身来理解问题的解决方式。
对任何复用库的开发者而言,设计时要以使用者的角度思考;对任何使用者而言,想理解问题要先自己设计一个问题的解决方案,然后从编写者的视角再看别人的解决方案。
或许用户的思维是幼稚的,可你不必总当用户。当你实践理论的时候,它们才真正到了你的脑子里。
如果你对知识进行了彻底的分析和思考,且你脑海中生成的概念已经与生硬的文本之间没有很强的相似性,我们就认为这个知识是被理解的。(大意)据说这是冰封哥的一个朋友说的。emmm……
彻底的分析和非平凡的变换,是获得真知的标志性特征。
我也相信,先理解问题还是先理解解法,是创造与重复之间最根本上的鸿沟。
GitHub
USTC-Resource/USTC-Course
:heart:中国科学技术大学课程资源. Contribute to USTC-Resource/USTC-Course development by creating an account on GitHub.
duangsuse Throws
真的很替华为惋惜,虽然程序设计语言领域的冰封哥支持华为并且称有多为 CE 的朋友在开始工作了 可我觉得原型是很重要的,如果一个东西很久连个基本的框架都没有,我们是不是可以认为,它的可维护性很差?模块化程度太低???
说句不漂亮的话,本频道的订阅者和频道主都菜炸了,因为(ice1000、thautwarm)他们都开始 fmap 顺序了,谈什么 lift 成参数成闭包、lambda calculus 模型, unify lambda,dependent type, linear type system、形式化文法, ANF,冰封问怎么实现 sum type 还说 tagged union 外居然能用 visitor,
还谈论普通递归下降(准确的说是递归比较构造式二元结合)外的二元解析算法,还要用两个 double-linked list、reverse-polish,区分左结合右结合自定义的情况,对别人发的论文评头论足。
除了群里两三个大佬外还有一些活宝(但是随便抓一个出来水平都比我高,而且都是CE/CS/Math的,基本功过硬),而且有类似 hoshi野这样的低龄dalao的存在。
这还是今年二月份的消息,我都舍不得跳过编译原理群的哪怕是一条消息,一条消息都比 USTC ce 的、华为所谓方舟编译组的、什么知识付费啊培训班啦好上十倍。
在 LLVM 里和 exception handling 相关的 IR 有: (1)call (2)invoke (3)resume (4)ladingpad,
可能在别的地方这是『大佬』们显示自己『超常之处』的地方,可在这群有人都把这问题当笑话答。『我选call。』,『真强,完美避过所有正确答案。』
说句题外话,虽然我不是很了解 exception handling 的实现(只知道 cxa throw 啊 unwrap, personality 什么的),把绝句(我会实现它的) 的 catch 起名『
这就是个单纯为了讨论技术而生的Q群,别的什么都不管,尽管我更喜欢Telegram但我没找到资源那么集中的群,可以说只有AndroidCN稍微谈那么一点。
我要说,Android开发的话题就是比CE/PLT/FP(functional programming)/math/logics的低级,肯定有人不认账、不过我还真是觉得只会Android开发是不好的,总该多学点。
现在的我似乎很奇怪为什么即便 Java 的 Annotation Processor 可以生成代码,许多 Android 开发者也不会写 Annotation processor,是因为它写起来麻烦?
即便它可以用来生成代码的话也是一样?
说实在话,我觉得和他们群里一比,王垠什么的都爆炸了,比算法比不过,比 PLT, TT 更比不过,他是清华的又怎么样?我要是他,博文里绝对发满了新学到和做出来的算法和找到的新问题。
冰封高二的时候弄的『动态(时序)作用域优化』,今年二月都开始大谈实现同构(isomorphism)了。
而且不止是谈,而拿 Agda 写过实际的代码,这一点上是可以说已经上天了,而且这还是去年的事情。
每一天他们都不一样,士别三日都要做好心理准备才能接受,光看他们好像做了什么是只能了解大概的动向的。
和我对入门者、大牛的感觉一样,虽然你们看冰封 GitHub 都没起新项目,那是因为都拿去研究报告学习了。
还谈论普通递归下降(准确的说是递归比较构造式二元结合)外的二元解析算法,还要用两个 double-linked list、reverse-polish,区分左结合右结合自定义的情况,对别人发的论文评头论足。
除了群里两三个大佬外还有一些活宝(但是随便抓一个出来水平都比我高,而且都是CE/CS/Math的,基本功过硬),而且有类似 hoshi野这样的低龄dalao的存在。
这还是今年二月份的消息,我都舍不得跳过编译原理群的哪怕是一条消息,一条消息都比 USTC ce 的、华为所谓方舟编译组的、什么知识付费啊培训班啦好上十倍。
在 LLVM 里和 exception handling 相关的 IR 有: (1)call (2)invoke (3)resume (4)ladingpad,
可能在别的地方这是『大佬』们显示自己『超常之处』的地方,可在这群有人都把这问题当笑话答。『我选call。』,『真强,完美避过所有正确答案。』
说句题外话,虽然我不是很了解 exception handling 的实现(只知道 cxa throw 啊 unwrap, personality 什么的),把绝句(我会实现它的) 的 catch 起名『
接迎』是这个原因。当然我知道这不是多高明这就是个单纯为了讨论技术而生的Q群,别的什么都不管,尽管我更喜欢Telegram但我没找到资源那么集中的群,可以说只有AndroidCN稍微谈那么一点。
我要说,Android开发的话题就是比CE/PLT/FP(functional programming)/math/logics的低级,肯定有人不认账、不过我还真是觉得只会Android开发是不好的,总该多学点。
现在的我似乎很奇怪为什么即便 Java 的 Annotation Processor 可以生成代码,许多 Android 开发者也不会写 Annotation processor,是因为它写起来麻烦?
即便它可以用来生成代码的话也是一样?
说实在话,我觉得和他们群里一比,王垠什么的都爆炸了,比算法比不过,比 PLT, TT 更比不过,他是清华的又怎么样?我要是他,博文里绝对发满了新学到和做出来的算法和找到的新问题。
冰封高二的时候弄的『动态(时序)作用域优化』,今年二月都开始大谈实现同构(isomorphism)了。
而且不止是谈,而拿 Agda 写过实际的代码,这一点上是可以说已经上天了,而且这还是去年的事情。
每一天他们都不一样,士别三日都要做好心理准备才能接受,光看他们好像做了什么是只能了解大概的动向的。
和我对入门者、大牛的感觉一样,虽然你们看冰封 GitHub 都没起新项目,那是因为都拿去研究报告学习了。
duangsuse::Echo
说句不漂亮的话,本频道的订阅者和频道主都菜炸了,因为(ice1000、thautwarm)他们都开始 fmap 顺序了,谈什么 lift 成参数成闭包、lambda calculus 模型, unify lambda,dependent type, linear type system、形式化文法, ANF,冰封问怎么实现 sum type 还说 tagged union 外居然能用 visitor, 还谈论普通递归下降(准确的说是递归比较构造式二元结合)外的二元解析算法,还要用两个 double-linked…
把二元操作直接 parse 成一个操作序列而不是 infix binary tree…… 嗯…… 直接能上后序遍历结果?以后我可以考虑一下
我现在是在用手写递归下降的栈算法做的,如果用显式栈或许会快一些,不过我不在乎(我又不是 parser compiler 的编写者,只是用 parsec(ombinator) 就可以了)
结合不结合我都是用优先级考虑的(Lua式左右优先级实现),按降序先结合可能 (+) 是 (precL=1, precR=2) 这就是 inifixl.
我现在是在用手写递归下降的栈算法做的,如果用显式栈或许会快一些,不过我不在乎(我又不是 parser compiler 的编写者,只是用 parsec(ombinator) 就可以了)
InfixTree scanNextInfix(InfixTree base); 结合不结合我都是用优先级考虑的(Lua式左右优先级实现),按降序先结合可能 (+) 是 (precL=1, precR=2) 这就是 inifixl.
(1+2)+3 => (+2):1 <(+3):2
(1+2)+3 => (+3):2> (+2):1
duangsuse::Echo
常参/量/变参 定义(别人称为声明)是语句 (解量)赋值都是语句 判…[(值/属/在)],…… {以上情况皆,……} 否则,…… 注意 判p { 是真、是假,都一样()、回。 } 应该是有效的 是表达式 判断,…… {以上情况皆,……} 否则,…… 是语句 若…,……否则,…… 是表达式 若…,…… 是语句 尝试,…… {接迎……(成…)?,……} 是表达式 带上 终焉,…… 就是语句了 尝试将…成Name,…… 接迎~…… 是表达式 带上 终焉,…… 依然是语句 即便可以方便一点,绝句也没引入…
看了某位大佬的 reley (一个类似 Haskell 编译到 Python VM Bytecode 的程序设计语言)
我觉得,其实 Java 和 Kotlin 的
……还是留着吧,毕竟 Java 系的不是 Haskell 的风格
绝句还不是绝句Script 呢。
不过我觉得可以给「包」声明一个扩展:
等等……好像默认就是公开 emmm
这将有利于程序员对定义繁多的文件快速建立第一印象
我觉得,其实 Java 和 Kotlin 的
package…… 都不利于代码的简洁性,我是好好想了一会的,其实未必没有办法引入『导出』文法,代价是,必须去掉包声明,不然不好看……还是留着吧,毕竟 Java 系的不是 Haskell 的风格
绝句还不是绝句Script 呢。
不过我觉得可以给「包」声明一个扩展:
— 数域.jue来说明这个文件『公开』定义实现了的接口,但是
包 绝句.区间,
物,节域、短数域、数域、长数域、
浮数域、实数域。
公开的物 节域 这个公开修饰符不能省去,不然会使语法不一致不美观等等……好像默认就是公开 emmm
这将有利于程序员对定义繁多的文件快速建立第一印象
平时总是觉得数学有点难,其实除了自己真的是数学不好以外,还是有其他原因的。
数学,或者至少是『中等』(也就是高中水平)程度的数学,是运行思维的『形式化』方法。(其实也没有那么形式化哈)
数学也有很多抽象模型:数、运算、结合性、数轴、区间、集合、对(pair)、未知项和关系、逆运算、直角座标、极座标、映射、命题逻辑、微积分……
数上面的很多运算,除了可以用一个个计算的纸面表达方式(比如,递等式、开根式、除法上位,减、乘法十进制移位叠加……),其实也可以用其他的思想去表达:
+ 加法就是重复 "1"
1 + 2 = 1 + (1+1)
我知道这十分不好理解,不过『0』『1』的确应该是最特殊的数字了(但和二进制无关),小数分数都不够特殊。
从 1 开始的加法,可以直接从『数羊』这个行为想。
+ 乘法就是重复 "+"
1 * 3 = 1
11 + 21 = (1+1+1... (*10)*1 +1) + (1+1+1... (*10)(*2) +1)
解类似『我们这每人都有 10 只羊,一共有多少只?』这种问题的时候可以使用。
「羊」就是羊,「小明」的羊和「小红」的羊其实没有区别,都是「羊」。
数学允许我们在这个抽象角度上忽视掉无关量来思索问题,但需要注意的是,数永远代表着实际的含义:羊、用户、一棵树的分叉数目……
所以也就出现了『有效』和『无效』的数以及描述数值的『表达式』。
当然,表达式也是可以『惰性计算』的,就是说你 1+2+3 你可以描述成 (1+2)+3 而不一定非得计算时求 3+3 等于几
求解『两个足球队A,B(分别是a人,b人的)一对一PK能赛几场』这种问题也可用到乘法的定义:
显然对于 A 队伍的每个人,都得和 B 对的每个人 PK
所以,我们把 A 的每个人视为 (1+1+1... *b),这里的 "1" 表示一次比赛,最后实际上是b被『重复』了a次。
就是
求解配对问题也很类似,不过要去掉自己和自己的配对:
这也是一种抽象:不管你是什么,我只知道你是 "1"
不管你是小明还是小红,我只知道为了求得所有配对,你要和除自己外(n-1)个数的人配对。
+ 求幂就是重复 "*"
解类似可以用到乘方运算的定义:重复了『指数』次的『底数』
比如说,国家实现「全面二胎」的理想情况是,每两个人都有两个宝宝(惊恐)
如果一个住了5对夫妻的豪宅,照这样下去3代,第三代多少人?(无论死活长幼,子代一定与外人结婚……)
5*(2**3) 得40人,其中 (2**3) 是8,算的是一个家庭三代产生的宝宝数目。
想象一下:
+ 第一代的夫妻有2个宝宝
+ 第一代产生的2人找到配偶后,每人也都有2个宝宝
+ 第三代的宝宝长大后也是一人两个宝宝
+ 2*2*2 = 2**3,就是这个逻辑,算的是三代后的宝宝数目
如果要算总人数可以用递推函数式表达:
其中b是每代娃娃数,c是此代总人数,gen是代数
当然是可以优化的
+ 求对数(logarithm)就是给定『乘』的并列次数求并列项
这个相对困难些,用处比如: log10 100 = 10,你
对数还应该提到 ln 和 e,不过我不知道是为什么。
+ 求方根(root)就是给定『乘』的并列项求并列次数,这不是太困难,因为可以直接除:
求最终解构的乘法数目(也就是可以除的数目),所以不困难
+ 除法(a / b)就是算 a 里出现了几次 b
比如:小明一家每天炖一只羊,请问:50只羊够他们炖几天?
+ 取模 (a mod b) 可以保证 (a mod b) 结果小于 b,如果不够除,则值是a。
它的「循环」(因为『除』是按『一块一块的b』来算的)性质偶尔被拿来作指针碰撞(移动啦……)
整除 (a mod b) == 0 的性质偶尔被用来作图
+ 小数就是把一切乘一个倍数,以某一种最小单位(而不是整数 "1")执行计算
+ 小数和分数都是可以互化的(不过有些分数只能化成无限位长的小数……)
不过分数是有一个基本性质: a/b = (a*c)/(b*c)
分子分母同时乘一个c,分数值不变。
举例:(9)班有10位同学,全班的同学一共有20只眼睛,每人都有两只眼睛(20/10,10位同学平分20只眼睛,正好一人一双)。
某个学校有10个班,每个班的情况都是这样,请问这个学校每个人有几只眼睛?
由分数的这个性质我们可以在不实际除法计算的情况下,得出这10个班级里也是一人2只眼睛。
这不是小学数学,同时,我想说:现在很多程序员真的是连小学数学的所谓『数学逻辑』『数学性』都败光了。
数学的优雅性和准确性,嗯哼?
老实想想,这些看似『低级』的解释方法,自己现在还记得几条?自己编程的时候又用到几条?
当然数学也是可以涉及除了表达式本来『结合顺序』外的基本逻辑的:
+ 函数可以是『分支函数』,实现了控制上的『判断』
+ 函数可以定义成递推式的形式,也就是递归『重复』
关于是否的确能够『循环计算』,当然是可以的,毕竟至少有应用序 Y 组合子(applicative order Y combinator)嘛
注意这里两个 \c... 是一样的(也自然是循环的『样子』喽)
稍微有点数学/逻辑学爱好的人一定知道逆波兰记法(reverse polish notation):
(1+3)*6 等价后缀(postfix)表示的
后缀表示我就不说什么了,一个 LIFO。(后压入的先弹出,换到Java字节码就是先求值push的最后pop,按此顺序『倒数第一参数最先求值』)
数学主要的缺点是在表示上:虽然一些人眼里数学的确是很『天才』(当然不天才也没什么过错)
数学的高冷风格(什么希腊字母啊……把「美」和「丑」、「简洁」和「冗长」、会和不会分得太开)反而是不好。
比如说,『年久失修的伦敦大桥终于塌了。』
用数学的话说就是『伦敦大桥塌了。』
这很简洁,可是也包含一点不那么令人满意的因素:
+ 一些数学者认为,『年久失修』这个信息是可以从『大家都会』的知识上下文里推导出来的,所以不需要写明。
并且当他们往下说的时候,已经默认你把『伦敦大桥』想成是『年久失修』的伦敦大桥,可是,万一有些人认为它只是『设计不当』,或者干脆笨一点,没想过『伦敦大桥』是什么样的,该怎么办?
这类人往往被数学者称为『IQ低』『理解力差』。
+ 『终于』可以删掉而完全不影响这句话的数学含义,可是它实际上是有作用的:表达了作者对此事件的情感。
虽然这一条你看不出它对下文有什么用处,可保不准缺了它,就会使得本来不多的相关表达更少了隐含的信息。
其实写不写明这是个仁者见仁的问题,实在是和『艺术是瞬间的还是永恒的』这个问题一样争论不休,自然是各有各的好处,所以我们说点别的。
一般来说,接触某个新问题的人都必须先大概知道为什么会有这个问题,以及解决这个问题的示例,这样才能更好的理解问题。
一上来就以一种
从这个角度看,《算法(C++)实现III》一书可以说是正面例子了 -- 讲算法先讲道理,全书内容信息依赖安排得当,与目标群体的原本知识系统相契合。
Robert Sedgewick 就真是字面意义上的『教授』,名副其实。
大部分人会觉得计算机科学是为计算机解决问题而生的,而数学是为人解决问题而生的。
其实好像一切都喜欢反着来:计算机科学看起来是更考虑人的感受,而数学则是半人半机。
数学,或者至少是『中等』(也就是高中水平)程度的数学,是运行思维的『形式化』方法。(其实也没有那么形式化哈)
数学也有很多抽象模型:数、运算、结合性、数轴、区间、集合、对(pair)、未知项和关系、逆运算、直角座标、极座标、映射、命题逻辑、微积分……
数上面的很多运算,除了可以用一个个计算的纸面表达方式(比如,递等式、开根式、除法上位,减、乘法十进制移位叠加……),其实也可以用其他的思想去表达:
+ 加法就是重复 "1"
1 + 2 = 1 + (1+1)
我知道这十分不好理解,不过『0』『1』的确应该是最特殊的数字了(但和二进制无关),小数分数都不够特殊。
从 1 开始的加法,可以直接从『数羊』这个行为想。
+ 乘法就是重复 "+"
1 * 3 = 1
11 + 21 = (1+1+1... (*10)*1 +1) + (1+1+1... (*10)(*2) +1)
解类似『我们这每人都有 10 只羊,一共有多少只?』这种问题的时候可以使用。
「羊」就是羊,「小明」的羊和「小红」的羊其实没有区别,都是「羊」。
数学允许我们在这个抽象角度上忽视掉无关量来思索问题,但需要注意的是,数永远代表着实际的含义:羊、用户、一棵树的分叉数目……
所以也就出现了『有效』和『无效』的数以及描述数值的『表达式』。
当然,表达式也是可以『惰性计算』的,就是说你 1+2+3 你可以描述成 (1+2)+3 而不一定非得计算时求 3+3 等于几
求解『两个足球队A,B(分别是a人,b人的)一对一PK能赛几场』这种问题也可用到乘法的定义:
显然对于 A 队伍的每个人,都得和 B 对的每个人 PK
所以,我们把 A 的每个人视为 (1+1+1... *b),这里的 "1" 表示一次比赛,最后实际上是b被『重复』了a次。
就是
solve(a, b) = a*b 了,或者说 solve = (*) 。求解配对问题也很类似,不过要去掉自己和自己的配对:
paircount-of(n) = n*(n-1)这也是一种抽象:不管你是什么,我只知道你是 "1"
不管你是小明还是小红,我只知道为了求得所有配对,你要和除自己外(n-1)个数的人配对。
+ 求幂就是重复 "*"
解类似可以用到乘方运算的定义:重复了『指数』次的『底数』
比如说,国家实现「全面二胎」的理想情况是,每两个人都有两个宝宝(惊恐)
如果一个住了5对夫妻的豪宅,照这样下去3代,第三代多少人?(无论死活长幼,子代一定与外人结婚……)
5*(2**3) 得40人,其中 (2**3) 是8,算的是一个家庭三代产生的宝宝数目。
想象一下:
+ 第一代的夫妻有2个宝宝
+ 第一代产生的2人找到配偶后,每人也都有2个宝宝
+ 第三代的宝宝长大后也是一人两个宝宝
+ 2*2*2 = 2**3,就是这个逻辑,算的是三代后的宝宝数目
如果要算总人数可以用递推函数式表达:
total-count(b, gen, c)|gen == 0 = c|else = c+total-count(b, gen-1, c*b)其中b是每代娃娃数,c是此代总人数,gen是代数
当然是可以优化的
+ 求对数(logarithm)就是给定『乘』的并列次数求并列项
这个相对困难些,用处比如: log10 100 = 10,你
floor(log10(x)) 就可以得到十进制位数了(二进制有 Striling 公式,不过也是可以用对数算)对数还应该提到 ln 和 e,不过我不知道是为什么。
+ 求方根(root)就是给定『乘』的并列项求并列次数,这不是太困难,因为可以直接除:
sqrt(2) = 1sqrt(n*2) = 1+sqrt(n)求最终解构的乘法数目(也就是可以除的数目),所以不困难
+ 除法(a / b)就是算 a 里出现了几次 b
比如:小明一家每天炖一只羊,请问:50只羊够他们炖几天?
+ 取模 (a mod b) 可以保证 (a mod b) 结果小于 b,如果不够除,则值是a。
它的「循环」(因为『除』是按『一块一块的b』来算的)性质偶尔被拿来作指针碰撞(移动啦……)
整除 (a mod b) == 0 的性质偶尔被用来作图
+ 小数就是把一切乘一个倍数,以某一种最小单位(而不是整数 "1")执行计算
+ 小数和分数都是可以互化的(不过有些分数只能化成无限位长的小数……)
不过分数是有一个基本性质: a/b = (a*c)/(b*c)
分子分母同时乘一个c,分数值不变。
举例:(9)班有10位同学,全班的同学一共有20只眼睛,每人都有两只眼睛(20/10,10位同学平分20只眼睛,正好一人一双)。
某个学校有10个班,每个班的情况都是这样,请问这个学校每个人有几只眼睛?
(20*10/10*10) = (20/10) = 2由分数的这个性质我们可以在不实际除法计算的情况下,得出这10个班级里也是一人2只眼睛。
这不是小学数学,同时,我想说:现在很多程序员真的是连小学数学的所谓『数学逻辑』『数学性』都败光了。
数学的优雅性和准确性,嗯哼?
老实想想,这些看似『低级』的解释方法,自己现在还记得几条?自己编程的时候又用到几条?
当然数学也是可以涉及除了表达式本来『结合顺序』外的基本逻辑的:
+ 函数可以是『分支函数』,实现了控制上的『判断』
+ 函数可以定义成递推式的形式,也就是递归『重复』
关于是否的确能够『循环计算』,当然是可以的,毕竟至少有应用序 Y 组合子(applicative order Y combinator)嘛
Y = \f. (\c. (f (c c))) (\c. (f (c c)))注意这里两个 \c... 是一样的(也自然是循环的『样子』喽)
稍微有点数学/逻辑学爱好的人一定知道逆波兰记法(reverse polish notation):
(1+3)*6 等价后缀(postfix)表示的
1 3 + 6 *后缀表示我就不说什么了,一个 LIFO。(后压入的先弹出,换到Java字节码就是先求值push的最后pop,按此顺序『倒数第一参数最先求值』)
数学主要的缺点是在表示上:虽然一些人眼里数学的确是很『天才』(当然不天才也没什么过错)
数学的高冷风格(什么希腊字母啊……把「美」和「丑」、「简洁」和「冗长」、会和不会分得太开)反而是不好。
比如说,『年久失修的伦敦大桥终于塌了。』
用数学的话说就是『伦敦大桥塌了。』
这很简洁,可是也包含一点不那么令人满意的因素:
+ 一些数学者认为,『年久失修』这个信息是可以从『大家都会』的知识上下文里推导出来的,所以不需要写明。
并且当他们往下说的时候,已经默认你把『伦敦大桥』想成是『年久失修』的伦敦大桥,可是,万一有些人认为它只是『设计不当』,或者干脆笨一点,没想过『伦敦大桥』是什么样的,该怎么办?
这类人往往被数学者称为『IQ低』『理解力差』。
+ 『终于』可以删掉而完全不影响这句话的数学含义,可是它实际上是有作用的:表达了作者对此事件的情感。
虽然这一条你看不出它对下文有什么用处,可保不准缺了它,就会使得本来不多的相关表达更少了隐含的信息。
其实写不写明这是个仁者见仁的问题,实在是和『艺术是瞬间的还是永恒的』这个问题一样争论不休,自然是各有各的好处,所以我们说点别的。
一般来说,接触某个新问题的人都必须先大概知道为什么会有这个问题,以及解决这个问题的示例,这样才能更好的理解问题。
一上来就以一种
a=1 => ab 式的语序(当然一个问题里还不止这一种)说定义,甚至弄出什么 若……对所有……且……当…… 这样连在一起分不清主次和句子结构的东西,那不用说自然是只会从自己的角度考虑问题的『聪明人』了。从这个角度看,《算法(C++)实现III》一书可以说是正面例子了 -- 讲算法先讲道理,全书内容信息依赖安排得当,与目标群体的原本知识系统相契合。
Robert Sedgewick 就真是字面意义上的『教授』,名副其实。
大部分人会觉得计算机科学是为计算机解决问题而生的,而数学是为人解决问题而生的。
其实好像一切都喜欢反着来:计算机科学看起来是更考虑人的感受,而数学则是半人半机。
为什么数学有时候反而应该是『更适合计算机阅读』?其实是因为数学想表达的东西太宽泛也太松散了,
数学家说数学有很强的逻辑性,其实让自然语言学家来看看:的确是很明确 -- 比如,p q 是一个『对(pair)』里两个项目的名字
这很准确,但这会使得我们不知道 p, q 里哪一个是『主语』、哪一个是『宾语』,即便实际上哪个都不是也哪个都是(因为它们的关系具有对称性(symmetric)),这才是数学嘛。
编程时我们一般认为从左到右,左边的是『宾语』(操作的对象,相等判断的变量) 比如
上面这个问题也出现在合并查找(union find)的算法里,如果引入逻辑上的传递性(a R b; b R c)而不止『数对』,就会很容易解决『没主语』的问题了,而且不会影响到逻辑的严密性。
其实不少数学问题在正常人看来都是描述『语序不当』甚至略微『成分残缺』,之所以觉得没有问题是习惯了而已,但习惯了也不能代表是最好的做法。
数学习惯的就是把一堆『特化』的东西(比如「逆命题」「简单命题」「复合命题」「分数」「小数」「无理数」「非负数」……)和没特化的大体混在一起,所以你得自己给他们归到树型,你得知道「XX命题」都是「命题」、「XX数」都是「数」…… 并且好好总结一下他们的特点和计算 -- 这不就是编程时高层抽象的多态吗?
如果只有看一个问题的能力,数学水平不容易提高,但是数学家也真是应该好好改正一下自己的说话水平了,如果不能以初学者的视角看问题,就找不到在表达上优化的方法。
计算机科学,它不像数学里,有些东西实际上是来自于数学者的『直觉』的,直觉这个东西往往很有用,但对传达知识来说,它会严重影响其他人对知识的理解(因为你「隐式」也就是「含糊」地引用了某些概念,比如什么「即得」啊「易证……」啊「只要证……」啦)。
许多数学书,比如你们的人教课本上,就会在某些问题上略过一些直白的解释,使得数学感不好的人保持半通不通只能靠做题找感觉的样子。(当然即便做了题考满分也未必代表彻底理解了)
计算机就像是什么都不会的婴儿,它很有执行能力,但完全是一片空白,所以我们不得不更加严密地处理『导入』问题(因为它和所有人一样,是有所不知的,最重要的是它比任何人都「蠢」,只会你教它的),仔细界定问题涉及到的计算、模型,这一点是许多数学问题从来不曾想的 -- 反正那一切都是数学,数学就是一堆纠缠在一起『密不可分』的模型概念。
甚至一个偏序理论,都或多或少地和几何纠缠在一起,问题得不到根本上的简化,自然对大部分人来说难以理解了。
所以计算机科学和数学对某个问题的解释,往往是数学的含糊一些,计算机科学的长不少;像是很多计算机会区分整数和浮点,而数学认为数可以是整数、分数、小数、非1自然数……,甚至是虚数)
同桌问我(一个app)为什么分苹果版和Android版,我反问他:
其实我也清楚这很不准确,但我觉得对一个问题,兴趣和简明的第一印象,远比定义是否准确更重要吧。
只是说一下看法而已。 #CS #Math
数学家说数学有很强的逻辑性,其实让自然语言学家来看看:的确是很明确 -- 比如,p q 是一个『对(pair)』里两个项目的名字
这很准确,但这会使得我们不知道 p, q 里哪一个是『主语』、哪一个是『宾语』,即便实际上哪个都不是也哪个都是(因为它们的关系具有对称性(symmetric)),这才是数学嘛。
编程时我们一般认为从左到右,左边的是『宾语』(操作的对象,相等判断的变量) 比如
[i++] == '\0' ,为什么我们不反着来?可是如果命名为 p q,理论是优雅了,写起来可是略微『美丽冻人』。上面这个问题也出现在合并查找(union find)的算法里,如果引入逻辑上的传递性(a R b; b R c)而不止『数对』,就会很容易解决『没主语』的问题了,而且不会影响到逻辑的严密性。
其实不少数学问题在正常人看来都是描述『语序不当』甚至略微『成分残缺』,之所以觉得没有问题是习惯了而已,但习惯了也不能代表是最好的做法。
数学习惯的就是把一堆『特化』的东西(比如「逆命题」「简单命题」「复合命题」「分数」「小数」「无理数」「非负数」……)和没特化的大体混在一起,所以你得自己给他们归到树型,你得知道「XX命题」都是「命题」、「XX数」都是「数」…… 并且好好总结一下他们的特点和计算 -- 这不就是编程时高层抽象的多态吗?
如果只有看一个问题的能力,数学水平不容易提高,但是数学家也真是应该好好改正一下自己的说话水平了,如果不能以初学者的视角看问题,就找不到在表达上优化的方法。
计算机科学,它不像数学里,有些东西实际上是来自于数学者的『直觉』的,直觉这个东西往往很有用,但对传达知识来说,它会严重影响其他人对知识的理解(因为你「隐式」也就是「含糊」地引用了某些概念,比如什么「即得」啊「易证……」啊「只要证……」啦)。
许多数学书,比如你们的人教课本上,就会在某些问题上略过一些直白的解释,使得数学感不好的人保持半通不通只能靠做题找感觉的样子。(当然即便做了题考满分也未必代表彻底理解了)
计算机就像是什么都不会的婴儿,它很有执行能力,但完全是一片空白,所以我们不得不更加严密地处理『导入』问题(因为它和所有人一样,是有所不知的,最重要的是它比任何人都「蠢」,只会你教它的),仔细界定问题涉及到的计算、模型,这一点是许多数学问题从来不曾想的 -- 反正那一切都是数学,数学就是一堆纠缠在一起『密不可分』的模型概念。
甚至一个偏序理论,都或多或少地和几何纠缠在一起,问题得不到根本上的简化,自然对大部分人来说难以理解了。
所以计算机科学和数学对某个问题的解释,往往是数学的含糊一些,计算机科学的长不少;像是很多计算机会区分整数和浮点,而数学认为数可以是整数、分数、小数、非1自然数……,甚至是虚数)
同桌问我(一个app)为什么分苹果版和Android版,我反问他:
猫能吃鱼,狗也可以吗?所以食物得分开给。
他又问『那为什么这个(web应用)不分了呢?』,我就回答:猫是动物,能叫;狗也是动物,也能叫,所以有时候不用分太开。
难道我要提到,Android和iOS本身「软件包『格式』不兼容」甚至「Android有libdvm.so,iOS没有」「基于JavaScript和DOM只需要他们都实现了这种(WWW)技术即可,和(原生)应用大不一样」这种技术细节?其实我也清楚这很不准确,但我觉得对一个问题,兴趣和简明的第一印象,远比定义是否准确更重要吧。
只是说一下看法而已。 #CS #Math
我可以很轻松写 Lisp 系的解释器,也可以同样轻松地写 JSON 解析器、计算器;但是 Trie 树实在是难写啊!怎么还分四种情况?还要把叶子上岔出分叉来?
可是如果我不写 Trie 树,就没法实现自定义的二元运算符了…… 因为没有线索树的话,扫描 a+b 这种就不知道该何时分 a,b 这些名字和加号了 emmmm 😂
我不敢用别人的实现,我觉得他们实现得肯定更复杂更难看,我只需要一个简单、递归的 API 就够了……
还以为能够写出带启发式合并的 Radix 树的
RangeMap 什么的也很绕啊…… 不过不像 Trie 树的泛型那么烧脑
可是如果我不写 Trie 树,就没法实现自定义的二元运算符了…… 因为没有线索树的话,扫描 a+b 这种就不知道该何时分 a,b 这些名字和加号了 emmmm 😂
我不敢用别人的实现,我觉得他们实现得肯定更复杂更难看,我只需要一个简单、递归的 API 就够了……
还以为能够写出带启发式合并的 Radix 树的
RangeMap 什么的也很绕啊…… 不过不像 Trie 树的泛型那么烧脑