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

技术相干订阅~
另外有 throws 闲杂频道 @dsuset
转载频道 @dsusep
极小可能会有批评zf的消息 如有不适可退出
suse小站(面向运气编程): https://WOJS.org/#/
Download Telegram
duangsuse::Echo
就抽象和算法基本结构来说,不太谈语法(主要靠你的中文语感)提及绝句的大部分构造。 绝句是一门面向对象的编程语言(准确的说也是函数式的),它支持类型推导、静态类型、可空性、带控制流闭包,基本上可以说是 Kotlin 的扩展。 绝句的语言表达层次从小到大,依次是「常」、「量」、「言」、「句」、「段」、「块」、「体」、「构」、「件」 其中,「言」就是表达式(expression)、「句」就是语句(statement)、「段」就是(LLVM 的概念)标准块(basic block)、「体」就是类似方法体、…
常参/量/变参 定义(别人称为声明)是语句
(解量)赋值都是语句
判…[(值/属/在)],…… {以上情况皆,……} 否则,……
注意 判p { 是真、是假,都一样()、回。 } 应该是有效的
是表达式
判断,……
{以上情况皆,……}
否则,…… 是语句
若…,……否则,…… 是表达式
若…,…… 是语句
尝试,……
{接迎……(成…)?,……}
是表达式
带上 终焉,…… 就是语句了
尝试将…成Name,…… 接迎~…… 是表达式
带上 终焉,…… 依然是语句
即便可以方便一点,绝句也没引入 Java 的 union type(交集类型),来用在 multi-catch 的上面
理由是这很麻烦(而且我不知道有多大用,可能最多也就三四个 catch case,对绝句的逗号文法和函数式风格手工 union 一个最近的 type 好像也没啥),可能在未来的版本会加入。

重复当…,…… 是语句
重复先,…… 当…时 是语句

问题:如果我要
引记法……「~s」
重复先,
说("emmm... ");睡(5s)
当时间的秒数小 60 时

怎么办?
答案很简单,利用标识符强行合法化(绝句称为『』名字
重复先,说("emmm... ")、睡(5s)。
当『时间』的秒数小60时

对…里的NameOrTuple,……
是语句
对…里…的NameOrTuple,…… 是语句

回/抛下/停下/略过 都是语句
不过抛下不能带 label


重写(……) 是语句
一般而言,重写都是带名字重写的(named application)

绝句里的记法「…」的事…… 全都是中缀方法
中缀方法都是左结合的,优先级没有前缀高,但是有后缀高
这是为了让 它以a始 这样的话能够正常表达,显然比 它对r中值 (它对r.中值, 错的)更自然
一时间无法列出优先级,大部分优先级可以参照 Kotlin
包括一些关键字的软硬,也都可以取 Kotlin 的风格参照

然后中缀上还有「你」文法

你x大a或小b,表达式 (x > a || x < b) 第二人称且或—定参
你x且大a、b,表达式 (x > a && x > b) 第二人称且或—定操作

以上简写适用「且」「或」

你某人,名字。 — 某人令做,我的名字。
第二人称域文法
当然,尽管绝句没有,但接收者还是纳入了类型标记的
::show is (Int) -> String
::read is (String) -> Int
文::成文属 文.()文
绝句按照类似 Java 的「包」划分命名空间,和 Java 有一样的要求:包 某包.某子包某件 必须在 某包/某子包/某件.jue 路径下
包 绝句 是被特殊处理的,不过没有 package-info
和 Java 一样,绝句也可以在任何情况带命名空间引用某个构件

引 绝句.标物.平台
引全 绝句.集合
引全 绝句.环境的
引 绝句.环境的输入 -- 就是 import static
引 绝句.额联 藏(说)
引 绝句.区间 (长数域、短数域 成 小罐数域)

区别是绝句利用「引」文法来动态修改自己的语法
引记法 绝句.额联 藏「~前」
引记法 绝句.额联.扩符 「成」

不过,「藏」是不能藏掉原来就有的东西的(比如数::前

绝句也可以使用 partial application,「方法引用」语法当然不可以使用 partial application,不过任何「事」都可以被部分应用:打(_, 好人卡)(这人)
绝句里接收者不算参数(所以没有 Kotlin 一样直接的 extension fun/var,只有「扩物」)。

绝句和 Kotlin 一样,「事」可以被带名字的参调用,且有 optional arguments,语法与 Kotlin 无二。
Smart cast 也是实现时必须考虑的事情(同时也是实现 无常<R> 的必需品)。

所有 @……“标物” 都是修饰符,可以被应用在直接构件上,绝句也有类似 Java APT 的「标物处理器」。

标物的位置标签: 件、属性、取者、置者、置者参、类型接收者、代理(似乎要改变?)

绝句在未来还会引入「模板」这种语法
目前保护的关键字有:「模板」、「待例」、「实际」、「模块」、「断续」、「回交」、「性质推导」

语言特性:「对何皆有之」(rank-N type)、「模板」、「具名模板」、「单线程逻辑」(然后就可以针对串行逻辑 smart cast)、「此即值」(也可以用于实现 lazy)、「无例之物」(只该在标准库里用)

原来我还把「类实例」也作为额外特性设计的(而且还有「和之类型」、「类定界」、「前缀记法」emmm),后来它是主特性了
类定界说白了就是复辟 Java 的 ?extends, ?super。我当时的理由是教学目的和「检查器内部使用方便」,可后来我一想就想出了一点不带所谓 wildcard 的型变泛型类型检查,于是就作罢了。
「T定超A」「T定输A」,如果你想定义交集类型(intersection type)还可以用「T定属A属B」…… 还有什么「T定超A超B」……emmmmmm

https://www.cs.cornell.edu/andru/javaspec/1.1Update.html #Java 找到一个资料
duangsuse::Echo
绝句按照类似 Java 的「包」划分命名空间,和 Java 有一样的要求:包 某包.某子包 的 某件 必须在 某包/某子包/某件.jue 路径下 包 绝句 是被特殊处理的,不过没有 package-info 和 Java 一样,绝句也可以在任何情况带命名空间引用某个构件 引 绝句.标物.平台 引全 绝句.集合 引全 绝句.环境的 引 绝句.环境的输入 -- 就是 import static 引 绝句.额联 藏(说) 引 绝句.区间 (长数域、短数域 成 小罐数域) 区别是绝句利用「引」文法来动态修改自己的语法…
运算符:

前、后 (dec/inc)

+(加)、-(减)、*(乘)、/(除)、%(取余)
-~(取负)

大(>)、小(<)
不大(<=)、不小(>=) — 这一行括号里的表示法仅参考用
汉语里就不要用小于号、小于等于了。
取负的 -~ 只是注释,绝句不存在前缀记法,只有前缀算符

&(且) |(或) !~(取非)
(异) — 只有真假类型有
绝句的且或非逻辑都是短路计算的,也就是说 假 & 不可能, 真 | 不可能
我相信,既然绝句已经改了这么多了,不会有人觉得让 (&) 官复原职很奇怪
第一眼看了或许会觉得对短路语义强调得不明显,多看几次就顺眼了。

== (是) ===(即是)
是就是 equals,即是就是全等
!=(不是) !==(不即是) — 这一行是不依赖语言内定义的「事」定义的
Null 的操作符们:
(?.) ?. ?的 ?去
(!!) !!
(?:) 空则
空则,……
(不空) — 就是「不是空」的意思了

(在)(不在) —in
(属)(不属) —is
(作) — as
(试作) — as?
..(到) (rangeTo)

以上(中缀、算符的事……)都可以作为中缀使用,不过「非/取负」是不能作为前缀使用的……


一些内部的东西:

默认导入的一些操作符
— 位运算
包 绝句.额联

扩物(我: 数) 为
记法「位」「~位非」
“位左移、位右移、位直右移”
“位交、位并、位异” “位非”

扩物(我: 长数) 为……

特别的元方法:
代为取(仅取): 值者
代为置(取置、值者): 效果
取项(针: 引数): E
置项(针: 引数, 新值: E)
用(……) 就是 Kotlin 的 invoke(...)


因为绝句有存储建模的原因,就不必弄什么先增后增的了。
var i = 0
println(i++)
println(++i)
<=>
引记法 绝句.额联.扩符 (令并置为)
变数 i=零
说(i顺便,它令置为「后」。);说(i令并置为,它后。)


说(i令为,它后。顺便(i去::置为))


说句题外话,这是给那些执迷不悟的人用的,绝句利用二维文法本来已经可以足够好看足够短地实现什么先增啊、后减啊的,你还是要用这种带副作用的算符,那我就无话可梭了。

对何<项>皆有
物 切片迭代器(量 列: 切片<项>、私下的 变引数 针=0): 迭代子<项> 为
实现的事 下移(): 项 为
回列[针] 顺便,针令置为「后」。 “{它后}/引数::后”
— 实现的事 下移(): 项 = 回列[针 顺便,它令置为「后」。]
“是何苦呢?”

看了这些例子你肯定发现了关于「,」文法的一个细节:没有嵌套的
针 顺便,它令置为,它后。 这种
更不会来段
若p,若q,f。。 这种了

首先 「」 文法是绝句的 sectioning (Haskell
「后」实际上的意思是 { 它后 }, 当然这是因为「后」本身是一种后缀运算符,而它前面的东西缺了。「后后」、「后+1」也是可以的
「+2+3」的意思是 { x -> x+2+3 }
「2+_」的意思是 { y -> 2+y }
「+」的意思是 { x, y -> x+y }
不过「+a+」这种是不行的,中缀链只能缺少主语(就是中缀链前面的东西),或者它就是某种记法/运算符、甚至可在右边使用 _ 文法。
虽然绝句实际上也能在标准库里定义这些加法啊减法的,我觉得还是作为语言内部钦定的文法强一些。

对后缀和中缀记法的处理是不完全一致的,不过都是按照这个原则来。
上面的 () 写的是不对的,绝句同时支持全角括号和半角括号,只是要匹配才行。

至于「,」表示法的三种简记呢,首先我想谈谈它是怎么设计出来的

大概就是我在考虑 某文件=打开("emmm","rw") 这样的调用时
我觉得逗号似乎是不应该用于这个地方
其实绝句程序设计语言在开始设计前,我在写一个『绝句的Java版本』,就是可以这么写的:

/* import 了一堆被我称为名词的单例,实际上他们都该说是所谓的访问器对象。 */
小.的(燕子); 穿.着(花.的(衣));
又(年, 年).的(春天).是会(来(这里));
我.问(燕子); 为何(你, 来);
它(燕子).说(我.们.的(春天).是最(美丽.的()));

— 觉得上面的没有多大用或者让我「show code」?我有一个实用一点的例子,虽然 show code 是不可能的。
把(这人.的(朋友们).中(所有(名字.以("abc").起始().的()))).都拿去(用户表::删掉);

从(1).数到(100, (它) -> {
若((它/2).为零(), 向(终端).写("偶数"))
.否则(向(终端).写("奇数"));
});

是不是有点眼熟了?某些 Java 框架都喜欢这么干,不过据我了解某个叫 Arqullian wrap 的框架做得很难看…… 或者说被写得很难看很空。
Consumer<?> 不可能 = (Object _) -> { throw new ImpossibleException(); };
判断(他.的(名字),
倘若(它.是("蔡徐鲲"), 向(它).致敬()),
倘若(它.是(在(iKun.映射到(IKun::名字)).中()), 向(终端)::写),
否则(不可能));

当然,仅仅这样是不够的,所以我的方法是枚举了一些常见的介词(p)/连接词/助词/补语/位置(f)数量(b)/副词(d),然后在需要使用的特定子类实现里补上动名词(比如实现什么『把字句』『以子句』『位置数量定语』『从字句』),然后就可以利用 this 调用链和函数式方法以中文语序写程序。

不过,上面的代码虽然对中文来说比较自然了,绝句里还是更进一步:
判他的名字,
是"蔡徐鲲",我致敬(它)
在 iKun映至「的名字」“其实不该每次去投至”,输出写它
否则,不可能。


嘛,毕竟 「判」的分支后面也可加句号,也可不加。
引记法 ……「以为」
引记法 ……「~是傻逼」
引记法 ……「唾弃」
块实现加了句号后就不需要后面的 [去]了,但是不能加 (.), (?.),(?去) (?的) 也还是不能少
对[某事]iKun中滤出「属年轻人」里我@某事以为 我是傻逼的你,我唾弃你。
emmm
不对…… 我没用逗号记法
确保(1令为,它成文。令为,"+"接它。是"+1")

小燕子(nr),(,)穿(zg)花衣(n)
把(p)这(r)人(n)的(uj)朋友(n)们(k)中(f)所有(b)名字(n)以(p)abc(?)起始(v)的(uj)都(d)拿(v) 去(v)让(v)用户表(n)删掉(v)

看着这样的风格,我忽然明白了 sodayo 原来不该用逗号切分这种弱智的参数列表,因为中文是拿顿号分割不同意群的!!!
后来绝句就有了逗号文法,这也是我对绝句最引以为豪(当然也是喜欢,有时候也最讨厌)的一个语言特质 🙊

不过这个逗号文法,开始只是类似 Python 一样起始任何语句「块」的,但是简写也很重要啊(比如,我不可能把 若用户的钱数少于商品的价格,回。 写到两行里,而且第二行还只有一个「回」!)

这三种特化的文法,就是逗号段/单控制流或表达式简记/语句们 and 控制流简记,当然还有一个用于传递块参数的「逗号块」没计。
duangsuse::Echo
运算符: 前、后 (dec/inc) +(加)、-(减)、*(乘)、/(除)、%(取余) -~(取负) 大(>)、小(<) 不大(<=)、不小(>=) — 这一行括号里的表示法仅参考用 汉语里就不要用小于号、小于等于了。 取负的 -~ 只是注释,绝句不存在前缀记法,只有前缀算符 &(且) |(或) !~(取非) (异) — 只有真假类型有 绝句的且或非逻辑都是短路计算的,也就是说 假 & 不可能, 真 | 不可能 我相信,既然绝句已经改了这么多了,不会有人觉得让 (&) 官复原职很奇怪 第一眼看了…
然后再谈一些比较贴近语言本身的「物」:

物 数值 为……
物 真假 为……
别名属 是否 = 真假
物 字节 属数值 为……
物 短数 属数值 为……
物 字儿 为……
物 数 属数值 为……
物 长数 属数值 为……
物 浮数 属数值 为……
物 实数 属数值 为……
物 文 为……
别名属 计数 = 数
@内部 别名属 正计数 = 计数
别名属 引数 = 数

「文」和 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) +(加)、-(减)、*(乘)、/(除)、%(取余) -~(取负) 大(>)、小(<) 不大(<=)、不小(>=) — 这一行括号里的表示法仅参考用 汉语里就不要用小于号、小于等于了。 取负的 -~ 只是注释,绝句不存在前缀记法,只有前缀算符 &(且) |(或) !~(取非) (异) — 只有真假类型有 绝句的且或非逻辑都是短路计算的,也就是说 假 & 不可能, 真 | 不可能 我相信,既然绝句已经改了这么多了,不会有人觉得让 (&) 官复原职很奇怪 第一眼看了…
简单的说,逗号文法就是这四种情况:

+ 直接表达式(组)
一行(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
可能不确定的暂时不弄
总感觉最近思路像是被限制了一样,不灵活
#Low #China #Huawei 华为真还不如王垠,王垠都比华为实干(比率上)
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 都有的优化,不信你试试:
  id=`uuidgen`
echo 'class CostFold { String ing() { if (true) return "+"; else return "-"; } }'>/tmp/${id}.java
javac /tmp/${id}.java -d /tmp
javap -cp /tmp -c CostFold
辣鸡 Javac 不接受非 Java 扩展名的输入!
+ 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
ldc 2; ldc 1; isum // 当然这里用的是逆记法,因为弹(pop)出来的是最后压(push)上去的,尽管我们希望 (1) 先被求值。
imul
零地址一般就是说 JVM, Ruby YARV, CLR 这种以 last-in-first-out(LIFO)「栈」 传递参数的架构
(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/0p-code,之前我在 USTC 的 CE share 里看到了 PL/0 实践教程的文档,好像是被撤下了?
Forwarded from duangsuse Throws

既然我都谈到 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。
Forwarded from duangsuse Throws
不少知识和信息,除了从书上看到我还从其他爱好者的资料里看到。
USTC ce 系学生的一些分享不知道为什么不见了,我很遗憾,因为那些资料也都很方便。
不过我给大家一个建议:比起直接从理解『解决方式』来理解问题,更应该从理解问题本身来理解问题的解决方式。

对任何复用库的开发者而言
,设计时要以使用者的角度思考;对任何使用者而言,想理解问题要先自己设计一个问题的解决方案,然后从编写者的视角再看别人的解决方案。
或许用户的思维是幼稚的,可你不必总当用户。当你实践理论的时候,它们才真正到了你的脑子里。

如果你对知识进行了彻底的分析和思考,且你脑海中生成的概念已经与生硬的文本之间没有很强的相似性,我们就认为这个知识是被理解的。

彻底的分析和非平凡的变换,是获得真知的标志性特征。

(大意)据说这是冰封哥的一个朋友说的。emmm……

我也相信,先理解问题还是先理解解法,是创造与重复之间最根本上的鸿沟。
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 都没起新项目,那是因为都拿去研究报告学习了。
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) 就可以了)
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 的 package…… 都不利于代码的简洁性,我是好好想了一会的,其实未必没有办法引入『导出』文法,代价是,必须去掉包声明,不然不好看

……还是留着吧,毕竟 Java 系的不是 Haskell 的风格
绝句还不是绝句Script 呢。

不过我觉得可以给「包」声明一个扩展:

— 数域.jue
包 绝句.区间,
物,节域、短数域、数域、长数域、
浮数域、实数域。

来说明这个文件『公开』定义实现了的接口,但是 公开的物 节域 这个公开修饰符不能省去,不然会使语法不一致不美观
等等……好像默认就是公开 emmm
这将有利于程序员对定义繁多的文件快速建立第一印象
binarie.zip
2.6 KB
我再也不想写 Trie 树了!
第一次尝试就是非常失败的
眼高手低的最佳实例系统(指网络小说圈玩梗)