Forwarded from dnaugsuz
我在去吃饭的路上好好想过了,
对于 return/throw/break/continue 这样的跳转行为,实现成 Nothing 表达式的目的,无非就可能是这两种:
(1) 为了语法特性需要这么做
(2) 这么做对编译器实现方便或者对语言工具处理有奇效,且对语言本身影响不大
第二条先否决掉,Kotlin 绝对不至于需要什么奇效来加速语言实现的过程,因为它的人够多。
开始我考虑限定绝句的语法
(也就是
可是后来我发现,好像无论是 Kotlin 还是绝句都没有支持那么随便的在块里加 return/throw/break/continue 指令,只能以带标签的形式使用,况且我发现,其实在不在块里不都是拿来跳转的么……
所以这个问题实际上就不存在
存在的问题是:他们作为一定是
一般而言,对表达式的最佳实践是:值一定会被用到
所以
可是这类表达式的值总是被无视,更不要说一个一定没有值的表达式了,为什么?我想它们做成表达式不是为了在『需要值』的地方使用(比如上面的
最经典的例子就是
更恐怖的就是
只是因为,break 的值是 Nothing,而 Nothing 是 Boolean 的子类型,Nothing 是一切的子类型,所以它可以被放到几乎一切地方(即便在被重载 overload 的地方,因为实现类型不唯一所以无法这么搞)
总之,我想 Kotlin 这么做的原因,就是为了能在所有需要表达式的地方(而且,一定不是参数列表、接受者这种需求处)使用跳转,而这种手段只在表达式的求值情况不可被静态判断的时候才有实际意义(比如说
可是,我想这么做最常见的语法也大概就是
我觉得,绝句既然是不打算保持 Kotlin 一些不好的地方的,就不能支持这种一切皆表达式的手段。
绝句应当更明确地转而提供面向『逗号文法』的控制流快捷方式,比如:
还好绝句有逗号文法简记,Kotlin 是没有办法很方便的支持的(不过,逗号文法的意思其实也就是明确化)
绝句的『空则』关键字可以通过直接在后面加逗号的方式切换语义,Kotlin 要想换语义就得专门定义新
说起来我也是现在才知道,Kotlin 是门倾向表达式的语言
对于 return/throw/break/continue 这样的跳转行为,实现成 Nothing 表达式的目的,无非就可能是这两种:
(1) 为了语法特性需要这么做
(2) 这么做对编译器实现方便或者对语言工具处理有奇效,且对语言本身影响不大
第二条先否决掉,Kotlin 绝对不至于需要什么奇效来加速语言实现的过程,因为它的人够多。
开始我考虑限定绝句的语法
回/抛下/停下/略过 在『是逗号块』(也就是
用户里滤出,它在穷逼VIP里的。全都让(本群去::踢了); 或者中缀链简记文法 对用户里滤出「在穷逼VIP里的」,本群.踢了(它)。 这种,这里「里」「~里的」是一种记法,也就是自定义的 . 操作符或者二元操作符)可是后来我发现,好像无论是 Kotlin 还是绝句都没有支持那么随便的在块里加 return/throw/break/continue 指令,只能以带标签的形式使用,况且我发现,其实在不在块里不都是拿来跳转的么……
所以这个问题实际上就不存在
存在的问题是:他们作为一定是
Nothing 的表达式(如果是在分支里,就不一定了,不过实际上 is Nothing 这种判断是无意义的),是要做什么?一般而言,对表达式的最佳实践是:值一定会被用到
所以
fun Collection<E>.add(item: E): Boolean 这种大部分情况值都会被随便丢弃的 API 不是好 API。可是这类表达式的值总是被无视,更不要说一个一定没有值的表达式了,为什么?我想它们做成表达式不是为了在『需要值』的地方使用(比如上面的
listOf(continue, return, break, throw...) ),而仅仅是为了在可以填表达式的地方使用。最经典的例子就是
val mustAny: Int = (x as? Int) ?: return 这种了更恐怖的就是
do { listOf(1).isEmpty() || break; println() } while(false) 这种,利用 || && 运算的短路计算(就是惰性计算)来不使用 if 的 判断 continue……只是因为,break 的值是 Nothing,而 Nothing 是 Boolean 的子类型,Nothing 是一切的子类型,所以它可以被放到几乎一切地方(即便在被重载 overload 的地方,因为实现类型不唯一所以无法这么搞)
总之,我想 Kotlin 这么做的原因,就是为了能在所有需要表达式的地方(而且,一定不是参数列表、接受者这种需求处)使用跳转,而这种手段只在表达式的求值情况不可被静态判断的时候才有实际意义(比如说
?: 的情况)。可是,我想这么做最常见的语法也大概就是
x?.a ?: return; x?.b ?: throw Exception() 这种了,也就是 Elvis 表达式空判断+跳转。(其他的我相信都不会使用,更不要提那种很阴毒的以它作为函数/方法参数的手段了)我觉得,绝句既然是不打算保持 Kotlin 一些不好的地方的,就不能支持这种一切皆表达式的手段。
绝句应当更明确地转而提供面向『逗号文法』的控制流快捷方式,比如:
例物 喜爱程度 为
喜爱、很喜爱、非常喜爱、爱到爆炸 “emmm...”
公开的 “其实默认就是公开” 事 夸夸群() 为
晚成的量 话: 文
对用户里属人的你,话 置为判 我.度爱(你),
喜爱,"爱你!"
很喜爱,"永远给你${名字}打 call"
非常喜爱,"我不要你以为,我只要我以为"
爱到爆炸,"你的爸爸大中国"
以上情况皆,话令置为,它接"耶!"。 “只是个例子” 否则,说("emmm")、回。 “该填『不可能』”
说(话) 还好绝句有逗号文法简记,Kotlin 是没有办法很方便的支持的(不过,逗号文法的意思其实也就是明确化)
量 anyOrZero: 值 = x试作数 空则零
量 mustAny: 值 = x试作数 空则,回。 绝句的『空则』关键字可以通过直接在后面加逗号的方式切换语义,Kotlin 要想换语义就得专门定义新
?: 语法或者提供基于 {} 块的版本(而且好像还是表达式……),如果 Kotlin 要支持,所有绝句里用逗号文法切换语义的地方(when, ?:)都得加条分支,也难怪是作为表达式的……说起来我也是现在才知道,Kotlin 是门倾向表达式的语言
duangsuse::Echo
#life #dev 吃完饭后先收尾了 Doku,写 AXML 和 Java Class 的读写器。 唉,事情好多 不知道何时做得完
对日常脱稿拖稿的,Doku、Parser.kt(又要修改增加一堆)。
最近新加又有点难度的,Trace、kt4j、GeekSpec、GeekQuery、绝句Java类库(不是绝句后来才该有的程序设计语言)
有点绕的,Linked.List、Reflect helper、RangeMap、Trie、enumMap、基于scanNextOp只要一个参数的 infixChain、NestedList、JPanel Fibonacci sequence。
以后的,绝句解析器(至少依赖现在的 Parser.kt, infix parser)
绝句的实现虽然暂时不要(暂时是只计划翻译置 Java/Kotlin),不担保以后也不要 Doku 库的类文件读写器。
最近新加又有点难度的,Trace、kt4j、GeekSpec、GeekQuery、绝句Java类库(不是绝句后来才该有的程序设计语言)
有点绕的,Linked.List、Reflect helper、RangeMap、Trie、enumMap、基于scanNextOp只要一个参数的 infixChain、NestedList、JPanel Fibonacci sequence。
以后的,绝句解析器(至少依赖现在的 Parser.kt, infix parser)
绝句的实现虽然暂时不要(暂时是只计划翻译置 Java/Kotlin),不担保以后也不要 Doku 库的类文件读写器。
https://ice1000.org/2017/04-23-KotlinForEachBreakContinue.html
令做、全都 后面跟的都是块,一般都是异常捕获实现,除非我要内联???
尽管标准库的东西一般都强制内联,可是内联后就能够知道要跳转到哪里了?
不管内联与否都很容易做到块内返回,但返回到块外面的优化?
是不是应该用启发式往外找一层看是不是能用 break/continue?无法匹配再利用异常传递控制权?
大概只有建模好执行后才能准确的明白
令做,[断]🤔 我该怎么才能让这个东西,只生成基于 for iterator & break 的代码?
(0..20).全都,[续]
若它不小10,回[断]
说(它)
令做、全都 后面跟的都是块,一般都是异常捕获实现,除非我要内联???
尽管标准库的东西一般都强制内联,可是内联后就能够知道要跳转到哪里了?
令做,[断]
对0到20里的,
若它不小10,…… “跳转到哪里???它怎么知道?”
说(它) 不管内联与否都很容易做到块内返回,但返回到块外面的优化?
是不是应该用启发式往外找一层看是不是能用 break/continue?无法匹配再利用异常传递控制权?
令做,断:do {
对0到20里的,
若它不小10,break 断 “跳回[令做]块里”
说(它) } while (false);
……
大概只有建模好执行后才能准确的明白
#PL #PLT 为了方便大家了解一下,我在正式开始编写设计文档前会简单谈一下『绝句』程序设计语言的表达方式。
我不会提及任何设计逻辑、推导的东西,这对绝句来说应该是实现细则,只需要说「必须写明类型」、「能够智能转型」即可。
— 例子: 分二查找.jue —
— 快查集.jue —
— 基本词法
认为换行是 [\n\r] 里任意一个,但数行数的时候按 (\n?!<=\r|\r\n) 来数(也就是说,支持 Mac 和 Linux 式的 LF/CRLF 换行
文件开头可以是
空格:
可选的缩进文法(就是「为……」和「,……」文法)里会预先取缩进长度,在这种文法中空格更加受到重视。
注释:
整数值:
下划线不得连续出现
除非就是
当有负号时,数值部分不应为
普通记法:
十六进记法:
汉字记法:
零, 二, 四十一, 十, 十一, 一百, 一亿
这个是实现起来麻烦点的……
二进制记法:
浮点数值:
下划线不得连续出现
普通记法:
Exponent记法:
转义:
'\b', '.'
支持转义的一个字符
字符串:
名字:任何Kotlin里有效的标识符
空:
这里,准确的说对于绝句而言,词法是不能和语法分开谈论的。(因为它的解析过程不仅仅是上下文相关,而且还能够动态,虽然我在设计上很克制,只允许在导入块添加自定义关键字/二元/后缀运算符)
— 注意的一点是:绝句里没有一元的
本来我想弄好 Parser.kt 再说的,可既然现在写了就继续吧……
我不会提及任何设计逻辑、推导的东西,这对绝句来说应该是实现细则,只需要说「必须写明类型」、「能够智能转型」即可。
— 例子: 分二查找.jue —
“之所以叫『分二』,是因为绝句里以『二』开头也是表示数字的方法……”
“在上升序列 [自己] 中二分查找一个 [目标] 项目”
对何<项: 可比<项>>皆有
私下、尾递归的事 分二查找(自己: 行<项>、目标: 项、范围: 数域): 引数? 为
“即便函数没有『我』,绝句也把『我』视为关键字不允许赋值”
若范围.项数是一,回空。
“绝句“数”域默认和 Kotlin 一样是 a..b 全开区间;存项就是 begin <= end,用绝句说是 “始不大末”。”
“二分查找中,一般都是 begin!=end,所以不用 .存项 量(不过也不是不可以)”
量 中 = 范围的中值
判 我[中],
是目标,回中
大目标,重写(范围=范围.换末(中前))
小目标,重写(范围=范围.换始(中后))
对何<项>皆有
扩物(我: 行<项>) 为
事 分二查找(目标: 项、范围: 数域) = 分二查找.分二查找(我、目标、我的针域)
“本该名辅助函数为二分找,这里是为展示包命名空间” — 快查集.jue —
引 绝句.环境.读写 (输入、输出、扩文)
引记法 绝句.环境.读写.扩文 「~分」
引记法 绝句.集合.组 「带」
引记法 绝句.额联.括符 「以」
引记法 绝句.额联.括符 「读、写」
常 数 N = 一千 “不一定要加空格,如果不想写类型,用<常参>”
事 入口(变长 参行: 文 “组<文>”) 为
量 亲 = 一组<数>()
对0止N里的号,亲去取位置(号)置为号。
量 b: 数;量 c: 数
“看到的『位置』『置为』『读』都使用了绝句的 存储<T> 物”
重复当输入读b读c不空,
量 a = 亲[b]
若亲[c]是a,略过。
对亲带存储里的d亲、d亲值,若d亲是c,
d亲值置为a “d亲值 = a”
输出以' '分 写b写c — 基本词法
认为换行是 [\n\r] 里任意一个,但数行数的时候按 (\n?!<=\r|\r\n) 来数(也就是说,支持 Mac 和 Linux 式的 LF/CRLF 换行
文件开头可以是
^#!.*$, 这一行会被跳过空格:
[ \t\n\r\u{0x3000}] — 全角空格可选的缩进文法(就是「为……」和「,……」文法)里会预先取缩进长度,在这种文法中空格更加受到重视。
注释:
“” 可嵌套的『引号』注释--, — 直接行注释,以 [\n\r] 结尾整数值:
'-'? … 'L'?
默认『数(Int)』,后缀 'L' 以默认『长数(Long)』下划线不得连续出现
除非就是
"0",数值部分不得以 '0' 开头当有负号时,数值部分不应为
0, 否则编译器要提示普通记法:
[_0-9]+
100, 1_000, -1十六进记法:
'0x' [_0-9A-Fa-f]+
0xFF, 0xFF_FF, -0xFF汉字记法:
… '[' '节'/'短'/'长'/'浮'/'实' ']'零, 二, 四十一, 十, 十一, 一百, 一亿
这个是实现起来麻烦点的……
二进制记法:
'0b' [_0-1]+
0b100, 0b10_11, -0b1浮点数值:
'-'? … 'F'?
frac = '.' digits
默认『实数(Double)』,后缀 'F' 以默认『浮数(Float)』下划线不得连续出现
普通记法:
digits frac?
0.1, 0.23, 1.00_37Exponent记法:
digits frac? [Ee] '-'? digits
0.1e3, 0.1E23, 1.0e-23转义:
'\' [tbnr\]
'\u' hexDigit*4
若在 "" 中支持 " 和 $
字符:'\b', '.'
支持转义的一个字符
字符串:
char term = (anychar) ~('\'|term) / escape
常量字符串: '"' char('"')* '"'
内联字符串:'"' (char('"') ~'$'|'$' Name | '${'Expr'}')* '"'
长段文法:缩进块 <<长段 Name 为 名字:任何Kotlin里有效的标识符
'『' (anychar)+ '』'
真假:'真'/'假' 空:
'空' 这里,准确的说对于绝句而言,词法是不能和语法分开谈论的。(因为它的解析过程不仅仅是上下文相关,而且还能够动态,虽然我在设计上很克制,只允许在导入块添加自定义关键字/二元/后缀运算符)
— 注意的一点是:绝句里没有一元的
+, 你可以认为这是相对 Kotlin 的一个倒退,但我觉得,我讨厌 +0==-0 这种事情、觉得零不应该分什么正负,也讨厌把一些可以默认的东西显式写出来,即便这是正确且在理论上也优雅的。本来我想弄好 Parser.kt 再说的,可既然现在写了就继续吧……
duangsuse::Echo
#PL #PLT 为了方便大家了解一下,我在正式开始编写设计文档前会简单谈一下『绝句』程序设计语言的表达方式。 我不会提及任何设计逻辑、推导的东西,这对绝句来说应该是实现细则,只需要说「必须写明类型」、「能够智能转型」即可。 — 例子: 分二查找.jue — “之所以叫『分二』,是因为绝句里以『二』开头也是表示数字的方法……” “在上升序列 [自己] 中二分查找一个 [目标] 项目” 对何<项: 可比<项>>皆有 私下、尾递归的事 分二查找(自己: 行<项>、目标: 项、范围: 数域): 引数? 为 …
关于汉字数值怎么读这个问题,我这里提供一个算法:
读取类似 「十一」「三百三十一」「一千零一」 这种数值表示法,可以被认为是解析类似这种模式:
「五十千」的前面是小于千的(十)
所以,他们表示的意思是 (一/五十)*千
然后还可以有:
这种情况,我们解析为
解析的时候我们除了使用递归,也可以利用重写参数或者直接用循环结构的方法,不过用循环就有点不好了
一般来说是不直接用左递归的(因为对初学者不友好,比如 JSON 的
这里的
num(it.L < L) * L + num(it.L = L.dec())?
num(it.L < L) * L + '零'num(it.L < L)?
然后继续输出剩下的,依然是(从下面的位置)找最大可选的单位,比如千->万,如果没有就不输出这个单位、继续直到无单位(1)(或者说,零也行)为止。
(比如,对一亿这个数值你能知道它 >= 亿、然后你先写 它/亿, 继续输出 它%亿)
考虑到效率建议在一个降序排列的链表上找。
突然发现 enumMap 是不存在的东西,即便有真型参也无法解决(因为不能访问
这就是绝句扫描汉字数值的方法。
fun <A, B> Iterable<Pair<A, B>>.toMap() = this.fold(HashMap<A, B>(10)) { m, vi -> val (v, i) = vi; m[v] = i; m }
// 上面的函数其实 Kotlin 标准库也有
val 汉位至数: Map<Char, Int> = "零一二三四五六七八九".withIndex().map { vk -> vk.value to vk.index }.toMap() 读取类似 「十一」「三百三十一」「一千零一」 这种数值表示法,可以被认为是解析类似这种模式:
(小某阶的表示)*某阶单位+(小某阶一位的表示)?「一千」的前面是小于千的(无单位)
(小某阶的表示)*某阶单位+零(小某阶的表示)?
「五十千」的前面是小于千的(十)
所以,他们表示的意思是 (一/五十)*千
然后还可以有:
五十千二百零五 这种情况,我们解析为
[5*(十)*(千)] + 2*(百) + 5也就是 5*10*1000 + 2*100 + 5,等于 50205。
解析的时候我们除了使用递归,也可以利用重写参数或者直接用循环结构的方法,不过用循环就有点不好了
一般来说是不直接用左递归的(因为对初学者不友好,比如 JSON 的
elements = element / element ',' elements 不如 elements = [{ element }] 清晰)这里的
[] 里包括的项目可选、{} 里包括的项目重复多次num(it.L < L) * L + num(it.L = L.dec())?
num(it.L < L) * L + '零'num(it.L < L)?
enum class 汉数单位(val 表值: Int) {
无(1),
十(10), 百(100), 千(1000), 万(10_000),
亿(100_000_000) /* 十万、百万、千万可以被正确处理 */
}
val base = 汉位至数.size
fun <E> Iterator<E>.partialIterable() = Iterable<E> { this }
tailrec fun Iterator<Char>.读汉数(已读: Int, 左单位: 汉数单位): Int {
var 新读 = 已读 + partialIterable().takeWhile { it in 汉位至数 }.fold(0) { x, ac -> ac*base + x }
val 单位 = 汉至位数.valueOf(tryNext() ?: 汉数单位.无)
// 如果 L > L0,「一^百^千^万」,该乘
if (单位 >= 左单位) 读汉数(新读*单位, 单位)
else 读汉数(已读+新读*单位, 单位) // 表达式也可以简化
// 如果 L < L0,「一十^二」,该加
// 都是伪代码,因为 Iterable 接口不足以用于写解析器
}
至于显示 (toString) 的算法,也就是循环,找最大可选的单位算出刚才那个乘上的个数然后继续输出剩下的,依然是(从下面的位置)找最大可选的单位,比如千->万,如果没有就不输出这个单位、继续直到无单位(1)(或者说,零也行)为止。
(比如,对一亿这个数值你能知道它 >= 亿、然后你先写 它/亿, 继续输出 它%亿)
考虑到效率建议在一个降序排列的链表上找。
突然发现 enumMap 是不存在的东西,即便有真型参也无法解决(因为不能访问
companion object),况且有更好的方法 valueOf(String),真美。 🤔这就是绝句扫描汉字数值的方法。
duangsuse::Echo
#PL #PLT 为了方便大家了解一下,我在正式开始编写设计文档前会简单谈一下『绝句』程序设计语言的表达方式。 我不会提及任何设计逻辑、推导的东西,这对绝句来说应该是实现细则,只需要说「必须写明类型」、「能够智能转型」即可。 — 例子: 分二查找.jue — “之所以叫『分二』,是因为绝句里以『二』开头也是表示数字的方法……” “在上升序列 [自己] 中二分查找一个 [目标] 项目” 对何<项: 可比<项>>皆有 私下、尾递归的事 分二查找(自己: 行<项>、目标: 项、范围: 数域): 引数? 为 …
就抽象和算法基本结构来说,不太谈语法(主要靠你的中文语感)提及绝句的大部分构造。
绝句是一门面向对象的编程语言(准确的说也是函数式的),它支持类型推导、静态类型、可空性、带控制流闭包,基本上可以说是 Kotlin 的扩展。
绝句的语言表达层次从小到大,依次是「常」、「量」、「言」、「句」、「段」、「块」、「体」、「构」、「件」
其中,「言」就是表达式(expression)、「句」就是语句(statement)、「段」就是(LLVM 的概念)标准块(basic block)、「体」就是类似方法体、量/变参定义的「取者/置者」这种东西
「构」就是所有「大」的语言构件(都是 Java APT 能够访问到的),比如「类」、「物」、「事」、「量」,「件」就是最基础的编译单元,一个文件。
绝句的对象数据依赖建模层次从小到大,依次是
其中,「类」的含义并非
绝句把『类』设计「物」的并非是因儿戏,有成语叫『物以类聚』,尽管从 Java 1.1 的时候都有所谓的「接口」,
一些使用其他风格多态系统的语言也不把类型上『操作』的规范叫「接口」,绝句要把
绝句改名了「类」,也是因为绝句有「类例」这种特性的原因(绝句不支持 sum type(union type) 的重要因素)
为了语言的一致性不能继续叫「接口」。
绝句的『接口』也允许
绝句的所有一级(可跨体访问的)「构」都可以设置访问限制,限制从小到大依次是
一般而言,「为……」「,……」都能够被替换为
绝句所谓的『记法』是一种语法扩展方式,也就是自定义关键字和中/后缀的结合
解析器利用
绝句的 「
类(
物(
物里可用第一人称文法
物也可以有同名多态的
物的架构器
「属」算是绝句(语言里称)的类型,有
「事」是类似方法的构造,
(
参数有 (
「量」、「变参」都是类似 Java field/property 的构造
量可以有 getter
没有定义 getter/setter 的属性是类field 属性,这种属性可以修饰上
实现/覆盖的属性的类型可以自动推导、类field 属性也可以。
常参和变参去掉「参」即可填上一个类型,取消自动推导。
如果要引用架构器,使用
对于所有可以面向对象继承的构件(物),都可以指定覆写可能性:
对于一个物,默认是
一个「物」的构造器默认和这个类有相同的开放性
物可以是
继承列表里,
任何类型后加
如果覆盖的目标是抽象的,则必须用
绝句的参数化类型可以被应用到 物/事/量/变参,是这么表示的:
至于
当然真型参是不能限制什么类、例物、class、struct 什么的
绝句是一门面向对象的编程语言(准确的说也是函数式的),它支持类型推导、静态类型、可空性、带控制流闭包,基本上可以说是 Kotlin 的扩展。
绝句的语言表达层次从小到大,依次是「常」、「量」、「言」、「句」、「段」、「块」、「体」、「构」、「件」
其中,「言」就是表达式(expression)、「句」就是语句(statement)、「段」就是(LLVM 的概念)标准块(basic block)、「体」就是类似方法体、量/变参定义的「取者/置者」这种东西
「构」就是所有「大」的语言构件(都是 Java APT 能够访问到的),比如「类」、「物」、「事」、「量」,「件」就是最基础的编译单元,一个文件。
绝句的对象数据依赖建模层次从小到大,依次是
「常参(常*)」、「量」、「变参(变*)」、「例」、「物」、「类」
绝句的『方法』『函数』都叫做「事」。其中,「类」的含义并非
class,它算是 Java/Kotlin 的 interface,「物」才是 class 的意思。绝句把『类』设计「物」的并非是因儿戏,有成语叫『物以类聚』,尽管从 Java 1.1 的时候都有所谓的「接口」,
一些使用其他风格多态系统的语言也不把类型上『操作』的规范叫「接口」,绝句要把
class 级以下降低一个级别也是有例可援的。绝句改名了「类」,也是因为绝句有「类例」这种特性的原因(绝句不支持 sum type(union type) 的重要因素)
为了语言的一致性不能继续叫「接口」。
绝句的『接口』也允许
default 实现,不过这种「类」被称为「混合类」绝句的所有一级(可跨体访问的)「构」都可以设置访问限制,限制从小到大依次是
「公开」、「族内」、「内部」、「私下」
对应 Kotlin 的 public/protected/internal/private
绝句支持可选的二维文法(缩进带语义),只有「为」「,」可能开启一个新缩进块。一般而言,「为……」「,……」都能够被替换为
{ …… } 文法,比如 类 老鼠 为…… 可以写成 类 老鼠 {……}
用户.存一,…… 也可写成 用户.存一 {……}
绝句还支持所谓的『逗号文法』,也就是对各种代码块/组合语句的写法,存在三种简写情况,过会会提及。绝句所谓的『记法』是一种语法扩展方式,也就是自定义关键字和中/后缀的结合
解析器利用
引…… 章节里的信息 引记法……「某中缀」「~某后缀」
引记法…… 藏「某中缀」「~某后缀」
就可以在解析输入的时候自动分词结合表达式了绝句的 「
的/去」 就是 (.) 点运算符的同类(语法也基本一样),不过「的」只能访问属性、「去」只能访问「事」类 可销毁 为「类」、「物」、「例」都是类似 Java class 的构造,他们代表了面向对象的『抽象』『封装』『继承』『[部分]多态』,有
事 销毁了() -- 也可叫『毁掉』
抽象的 物 很吵的东西(量 名字: 文): 可销毁 为
抽象的量 销毁消息: 文
实现的事 销毁了() = 说("$名字: 销毁消息 [毁。]")
抽象的 物 安静的东西(量 名字: 文): 可销毁 为 -- 非最佳实践,这里只作示例
实现的是 销毁了() = 说("('$名字' 被毁了。)")
例 老鼠: 属很吵的东西("🐁") 为
实现的量 销毁消息 = "吱吱!"
例 蟑螂: 属安静的东西("哔~")
例 小例子: 终端应用“一个混合类” 为
实现的事 入口(变长 参行: 文) = 老鼠去销毁了() 顺便,蟑螂去销毁了()。
类(
混合类)物(
内联、抽象)(扩物、内物、内扩物、储物、例物、标物)(类例)物里可用第一人称文法
「我」(this)和「亲」(super),Mixin 时需要可以使用 亲<超类名>
物里可以有初始化器(「初,……」),它在自动生成的初始化器后执行物也可以有同名多态的
「伴生例」
当然,例的定义体也是一种物,所以它也可以有初始化器 伴生例 emmm { 初,加载库("a")。 } 物的架构器
「造于(……)」可以明写 对何<甲、乙>皆有物 对儿 公开的造于(a: 甲、b: 乙): 贰(a, b) ,当然也可以有很多架构器、调用 this() 架构器,与 Kotlin 无异「属」算是绝句(语言里称)的类型,有
别名属 文 = java.lang.String 这种(typealias),规则和 Kotlin 一样,可以对何……皆有、不能别名『绝句.额联.断止』类型。「事」是类似方法的构造,
(
尾递归(「重写」文法)、中缀、算符、外部、内联)参数有 (
[语者:对何皆有之]「对何<…>皆有」、变长、[函数参数]跨嵌/不嵌)「量」、「变参」都是类似 Java field/property 的构造
量可以有 getter
「取者,……」
变参是量,且必须有 setter「置者,……」
它们都可以使用代理: 量『绝句』: 诗 代者,诗(唐人 王维)云,……
代理使用了绝句的反射特性 事 代为置(委托者: 绝句.镜射.取置, 新值: 值者)…… 没有定义 getter/setter 的属性是类field 属性,这种属性可以修饰上
「晚成」 (lateinit),当然「晚成」也可以修饰局部变量实现/覆盖的属性的类型可以自动推导、类field 属性也可以。
「常参」只能直接定义(声明+赋值),它的值必须是编译期可知的(@纯 的事 或字面常量)常参和变参去掉「参」即可填上一个类型,取消自动推导。
常参 N1 = 一千 作 数
常数 N = 一千
「的」和「去」都是 Kotlin 里 (.) 的语义,不过「的」只能用于访问数据、「去」只能用于访问行为。(好像又说了一遍如果要引用架构器,使用
「::名字」,如果引用实例上的方法,必须使用「实例去::操作」。对于所有可以面向对象继承的构件(物),都可以指定覆写可能性:
「开放」「终定」
类的成员不能是「终定」的(混入类也不可以)对于一个物,默认是
「终定(final)」,抽象的成员都是
开放的。一个「物」的构造器默认和这个类有相同的开放性
物可以是
「密封」的,密闭的类只能在定义它的文件里有子类实现。「抽象」可以使成员定义不包含实际体,「类」里的一切都是抽象的、「混合类」里可以标记抽象、「抽象」的 物 里也可以标记抽象继承列表里,
「属」后是真正的亲物(超类),除此之外可以实现任意数目的类。任何类型后加
「代者」(造于 里可访问的变量)都可以使用 delegate pattern如果覆盖的目标是抽象的,则必须用
「实现」覆盖、否则必须用「覆写」覆盖绝句的参数化类型可以被应用到 物/事/量/变参,是这么表示的:
对何<T1, T2, T3, ……>皆有……
对何<T1, T2, T3, ……>(T1: I1, I2, … T2: …, ……)皆有……
对何<值者>(值者: 值, 可拷贝)皆有事 又同一(这值: 值者): 值者 = 这值去拷贝() 对应 Kotlin 的 where……对何<值者: 值>皆有事 同一(这值: 值者): 值者 = 这值 对应 Kotlin 的 type parameter upper-bound对何<出项>皆有
物 行(初始大小: 计数) 为……
对何<项>皆有
物 动行……
别名属 啥玩意一行 = 行<*>
对何<入值>皆有
类 Consumer 为
事 accept(value: 入值): 效果 “返回效果的可以不写”
<入…/出…>对应声明处型变(生产/消费位置模型),或者说类型参数的型变性。* 就是 Kotlin 的星型投影,没区别(而且绝句里的类型顶是值?、底类型也是断止,和 Kotlin 差不多)「真型参」前缀可以实化内联的物/事,当然属性也可以多态,不过依赖给「我」用类型参数的扩物……至于
存储<T> 是如何实现,且看下面://!语者 此即值
包 绝句.额联
对何<值者>皆有
内联的类 存储<真型参 值者>: 值者 为
算符的事 此即值(): 值者
算符的事 置为(新值: 值者): 效果
包 绝句.集合
对何<项>皆有
物 组 …… 为
记法「取」
记法「取」的事 存储(针: 引数): 存储<项> 为
回例: 存储<项> 为
实现的事 此即值() = 我@组[针]
实现的事 置为(新值: 值者) { 我@组[针] = 新值 }
当然真型参是不能限制什么类、例物、class、struct 什么的
duangsuse::Echo
就抽象和算法基本结构来说,不太谈语法(主要靠你的中文语感)提及绝句的大部分构造。 绝句是一门面向对象的编程语言(准确的说也是函数式的),它支持类型推导、静态类型、可空性、带控制流闭包,基本上可以说是 Kotlin 的扩展。 绝句的语言表达层次从小到大,依次是「常」、「量」、「言」、「句」、「段」、「块」、「体」、「构」、「件」 其中,「言」就是表达式(expression)、「句」就是语句(statement)、「段」就是(LLVM 的概念)标准块(basic block)、「体」就是类似方法体、…
控制流,块的表示上:
隐式标签和 Kotlin 一样
控制流操作都不是表达式,不过允许「,回/抛下/停下/略过。」「,……、回/抛下/停下/略过。」简记
返回可以有标签:
其他(停下、略过)只能在一个块里使用
当然,
,[标签]
{ [标签] x, y -> …… } — 当然也是支持 (→) 连字文法的,就如同对 — 注释的支持一样隐式标签和 Kotlin 一样
控制流操作都不是表达式,不过允许「,回/抛下/停下/略过。」「,……、回/抛下/停下/略过。」简记
返回可以有标签:
回[标签] 其他(停下、略过)只能在一个块里使用
引记法……「里有」 — 要不然你只能用 「枪在包里」这叫做同一个块
对楼里的家,
对家里的你,
若包里有枪,我去报告(警察、你)、略过[楼]。 “快跑啊!”
楼.对每一, “绝句是不支持「,」块参数命名的,只能叫「它」”这不能说是同一个块,所以「略过/停下」都不能使用,当然「抛下」带标签也是没有意义的
它.对每一, — 人
若它的包里有枪,略过……。 “你想略过哪里?”
“当然,也可以回[楼]”
当然,
@[件]标记 绝句的标物也是可以使用这种「对象修饰文法」的,也就是 Kotlin 的 @file:标记
题外话,其实上面的代码本该这么写对楼里的家,对家里的这人,
若这人的包里有枪,去报告(警察、这人)、略过[楼]。
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 有一样的要求:包
和 Java 一样,绝句也可以在任何情况带命名空间引用某个构件
绝句也可以使用 partial application,「方法引用」语法当然不可以使用 partial application,不过任何「事」都可以被部分应用:
绝句和 Kotlin 一样,「事」可以被带名字的参调用,且有 optional arguments,语法与 Kotlin 无二。
Smart cast 也是实现时必须考虑的事情(同时也是实现
所有
标物的位置标签: 件、属性、取者、置者、置者参、类型接收者、代理(似乎要改变?)
绝句在未来还会引入「模板」这种语法
目前保护的关键字有:「模板」、「待例」、「实际」、「模块」、「断续」、「回交」、「性质推导」
语言特性:「对何皆有之」(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 找到一个资料
某包.某子包 的 某件 必须在 某包/某子包/某件.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?
以上
一些内部的东西:
默认导入的一些操作符
— 位运算
说句题外话,这是给那些执迷不悟的人用的,绝句利用二维文法本来已经可以足够好看足够短地实现什么先增啊、后减啊的,你还是要用这种带副作用的算符,那我就无话可梭了。
更不会来段
首先 「」 文法是绝句的 sectioning (Haskell)
「后」实际上的意思是
「+2+3」的意思是
「+」的意思是
虽然绝句实际上也能在标准库里定义这些加法啊减法的,我觉得还是作为语言内部钦定的文法强一些。
对后缀和中缀记法的处理是不完全一致的,不过都是按照这个原则来。
上面的
至于「,」表示法的三种简记呢,首先我想谈谈它是怎么设计出来的
大概就是我在考虑
我觉得逗号似乎是不应该用于这个地方
其实绝句程序设计语言在开始设计前,我在写一个『绝句的Java版本』,就是可以这么写的:
不过,上面的代码虽然对中文来说比较自然了,绝句里还是更进一步:
不对…… 我没用逗号记法
后来绝句就有了逗号文法,这也是我对绝句最引以为豪(当然也是喜欢,有时候也最讨厌)的一个语言特质 🙊
不过这个逗号文法,开始只是类似 Python 一样起始任何语句「块」的,但是简写也很重要啊(比如,我不可能把
这三种特化的文法,就是逗号段/单控制流或表达式简记/语句们 and 控制流简记,当然还有一个用于传递块参数的「逗号块」没计。
前、后 (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)看着这样的风格,我忽然明白了 sodayo 原来不该用逗号切分这种弱智的参数列表,因为中文是拿顿号分割不同意群的!!!
把(p)这(r)人(n)的(uj)朋友(n)们(k)中(f)所有(b)名字(n)以(p)abc(?)起始(v)的(uj)都(d)拿(v) 去(v)让(v)用户表(n)删掉(v)
后来绝句就有了逗号文法,这也是我对绝句最引以为豪(当然也是喜欢,有时候也最讨厌)的一个语言特质 🙊
不过这个逗号文法,开始只是类似 Python 一样起始任何语句「块」的,但是简写也很重要啊(比如,我不可能把
若用户的钱数少于商品的价格,回。 写到两行里,而且第二行还只有一个「回」!)这三种特化的文法,就是逗号段/单控制流或表达式简记/语句们 and 控制流简记,当然还有一个用于传递块参数的「逗号块」没计。
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。