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

技术相干订阅~
另外有 throws 闲杂频道 @dsuset
转载频道 @dsusep
极小可能会有批评zf的消息 如有不适可退出
suse小站(面向运气编程): https://WOJS.org/#/
Download Telegram
#KJS 草,这个 gradle 还支持 webpackTask... 和 binaries {executable()}
duangsuse::Echo
唉,现在看这个 Ruiko.kt (原版是一 F# 函数式大佬的) 还是搞不懂它拿 MutList 实现 lr 是什么个原理,看这架势大概要理解编辑撤销的 stack 指针和为何用 Set 吧。(估计理解了以后又会失望,索性不费这时间) 不过现在的我可一点也不想顶礼膜拜,因为俺明白了俺就是个农民,农民靠拿出切实可用、啥人都看得懂的玩意过活,不是靠智商和“设计模式”,唉。 俺设计的 ParserKt, 起名字都不往函数式、逻辑数学上靠, 是 Seq,Decide,Repeat(asXXX(), p),…
对于感兴趣文本解析领域,和解析组合子之金科绿玉的观众可以去

https://github.com/orangeduck/mpc

看看,一个 C 的半生成器式解析组合子。它提供的基元组合子命名可以说完美列举了本门『教师爷』定下的名词。🧐

不过值得一提的是虽然是 C ,这个库的 many (repeat) 组合子也采用了 Fold 模型(不过是 (*)(int no, **xs) 鹅)来归纳,真是工程界英雄所见略同啊😓,反正我没见函数式框架有过(不少函数式的连他们祖师爷的 unparse 操作都虚无,反而爱强调自己在努力优化性能诶)

突然感觉有些函数式者有时在自相矛盾,明明一定要用 F# 写,却还要其中最“高性能”的框架…… 不是自虐么
duangsuse::Echo
对于感兴趣文本解析领域,和解析组合子之金科绿玉的观众可以去 https://github.com/orangeduck/mpc 看看,一个 C 的半生成器式解析组合子。它提供的基元组合子命名可以说完美列举了本门『教师爷』定下的名词。🧐 不过值得一提的是虽然是 C ,这个库的 many (repeat) 组合子也采用了 Fold 模型(不过是 (*)(int no, **xs) 鹅)来归纳,真是工程界英雄所见略同啊😓,反正我没见函数式框架有过(不少函数式的连他们祖师爷的 unparse 操作都虚无,反而爱强调自己在努力优化性能诶)…
唉,这位的文风也很清晰,而且他居然会为附加功能的缺失说 Sorry !

看着看着就莫名觉得很感慨😳, ParserKt 所谓之「不制造问题」是有多深刻啊……

几乎所有的,哪怕函数式解析器框架都支持 Backtracing, ParserKt 刻意只让 peek(1) ,即便现在也醒目地和新 peek(n) 划清界地(这也是 "Decide" 这个名称的由来,因为它只能判1字符😂),却可以让用户清晰地利用 Piped.concat 解决 Name|Name '('{Expr}[',']')' 的歧义消除(而不是先读个 Name(Args) ,然后发现没有调用的括号😱, backtrace 折返回去再读一遍 Name ,分配多余的 String)。

我之前都想不到,仅仅一个 Backtrace 就能引出多少问题:除了 buffer, position 栈维护至少占 80 行,竟然还有 FSParserc 专门新建 "memorize parser"(cache bktrace) 来优化(等于记忆 Input String 和其position),要不然就得合并混淆两种不同的语法,而 PKT 竟然如此轻松就靠定义式规避了?!😳

多少框架不支持左递归,多少框架对用户写出文法的顺序和范围自动做处理避免冲突、Bison 把左递归和右递归区别特化,新手根本不懂其中区别,但对其源代码,那支持左递归的部分又有谁看得懂?

PKT 支持了 Piped.leftRec ,竟然只是靠一个 LeftRec.lastResult 对象和其创建函数的“it:Pattern 给出上一次的结果;默认为 notParsed” 直至未解析返回 lastResult ,逻辑这么简单自然以至于我怀疑是错的……

几乎所有函数式框架都用 then/and 甚至半懂不懂的“数学”中缀来定义顺序解析,部分连 or/otherwise/alt/<|> 都作为中缀,为此不惜用链表甚至在构造时自动铺平数据对象(当然,用 Monad 而该这么做的除外),可 PKT 却刻意避免在主要结构上用 infix 记法,只允许普通到不能再普通的 vararg 调用,不考虑这些问题,却带来了编写和阅读时的便利性。

大家都争相效仿“正统函数式parserc”的命名:zeroOrOne(被PKT替换的原 optional), many(被废弃的 Repeat.Many), many1, manyTill(被废弃的原 Until), skipMany, skipMany1, sepBy, sepBy1, sepEndBy, sepEndBy1; skipWhile, take, takeWhile, any, char, not, string, asciiCI(被 TriePattern 替换的 stringNocase), oneOf, noneOf ,为原/否命题(oneOf/anyOf & noneOf)和可空重复起“不同”的 English 化名字, PKT 却仅保留 item/elementIn/satisfy 和其 not. 形式,只有 anyChar/noChar 一对特例。

懂得最小化设计的函数式解析器框架,为优化性能也过不了“缓存 backtrace 结果”这一关,它们陷入思维定势,没有意识到主要是 alt stringP 使用了这一特性,不知道可以换用 Trie 树从根本上解决这一问题。
而它们的子解析器结果 "Reply" "Result<I,T,E>" 用起来冗余贼多 , ParserKt 却拿 Kotlin 的 Nullability 操作和 Exception 替换了臃肿的 union 结构,比起“正统” runParser 又懂得用 TopPattern 以完整的函数式内联+面向对象形式提供设计法;性能与可读性,二者可得而兼。

并不是一开始我就懂要这么设计,也并不是我从 Haskell 里抄来了这个设计,都得感谢 Kotlin ——它对 JVM null 问题前无古人的灵活处理真可谓化腐朽为神奇,而这个库也绝对不负 "Kt" 的名字。

更进一步的框架,有的开始支持内部处理空格跳过,甚至独立出 Token 流

可 PKT 早已步过了泛型“词条”对象流的阶段,认识到旧时最初创建 tokenizer/parser 区分的原因,也就是关键点——跳过空格注释而保留必要空白(如文本量里的),为此只建立了 SkipWhite.Lexer/Feed 嵌套流,只添加一个非阻塞解析的状态机子组件,在保持高效数据流同时实现这个目标

看看实在是过于草生,高层建模让程序和数据直接对应,允许程序和程序自由组合竟然能构造出这样的效果?

还有,真是很难想象,函数式也有为实用性妥协的时候……

从阅读代码到实现逻辑、性能优化, Parser 涉及多少细节,可能陷入多少舍本逐末的片面追求, PKT 靠着否定一切、重构一切的莽撞,以及对定义式、低内存分配、傻瓜也能读懂的贪婪,硬生生一个坑也没掉进去,不愧是函数式交过程式生的杂种

比起导致问题,再解决问题,
直接让用户改正不好的使用习惯,让问题直接不存在…… 还有,让答案也变得更简单。

我想,这就是我在编程上一直在追求的东西吧。 #Kotlin #statement #learn
https://github.com/search?p=2&q=parser+combinator&type=Repositories

这么多人/组织写过,可是我真的蛮想让 ParserKt 变成最强解析器框架的😒

旧版当然没做到,下一次我要搞个大新闻

反正现在打包好的的 ParserKt 易读性和对实际问题的接近已经秒杀前 2 页的框架了(Kotlin better-parse 在第三页,就凭它的 old-school tokenizer style 和 and/or/skip 表现力, 下版秒杀

毕竟这个项目的最终目标是实现社会主义现代化和中华民族伟大复兴 "Data representation is never a problem" #Kotlin
https://github.com/inhabitedtype/angstrom/blob/master/examples/rFC2616.ml#L51 说句实在话, lift 在“函数式编程”(其实应该叫“数学式编程”,因为它从不考虑领域外的易懂性😂)里是个被用滥了的模糊词汇,本来我还打算起 TypeCast/NumberLift 这种名字,现在想想为何不用 NumberWiden 这种更明确的名字?看来即便有长度对仗,不该起的名字还是要规避。
duangsuse::Echo
https://github.com/inhabitedtype/angstrom/blob/master/examples/rFC2616.ml#L51 说句实在话, lift 在“函数式编程”(其实应该叫“数学式编程”,因为它从不考虑领域外的易懂性😂)里是个被用滥了的模糊词汇,本来我还打算起 TypeCast/NumberLift 这种名字,现在想想为何不用 NumberWiden 这种更明确的名字?看来即便有长度对仗,不该起的名字还是要规避。
说起来,编译器的 NumWiden 完全可以写在 TypeCast 里的,毕竟基本上就取优先级、统一类型两个操作(如果是 kotlin.Number 上,就多俩操作:化为类型、(关于类型的)应用计算,毕竟 Number 是不能+-*/ 😂

想来我当时重写 @mivik233 的 kamet, 也重用了他的数值提升 when (dst) (type) 写法,只是加了 foldSign 代码至少可以看,实在是思维定式,本来写个 general 的 signess 判定再 maxBy { it.byteSize } 就好的。
duangsuse::Echo
https://github.com/inhabitedtype/angstrom/blob/master/examples/rFC2616.ml#L51 说句实在话, lift 在“函数式编程”(其实应该叫“数学式编程”,因为它从不考虑领域外的易懂性😂)里是个被用滥了的模糊词汇,本来我还打算起 TypeCast/NumberLift 这种名字,现在想想为何不用 NumberWiden 这种更明确的名字?看来即便有长度对仗,不该起的名字还是要规避。
上面的代码看起来干净清爽,别被它的风格骗了,其实这洋洋洒洒 20 行就定义了一个 4 行的结构:

EOL "\r\n"
HRequest HMethod URL Ver
HResponse Ver Digits {!EOL}
HHeader Name':' {!EOL}

别琢磨用 2 空格还是 4 空格、等号长表达式换行与否,琢磨错了。
即便看起来再清爽,缺乏表现力的做法,代码越多越无聊;但表现力强的代码不一样,因为你能看出它是啥玩意,保持好奇心。
duangsuse::Echo
唉,这位的文风也很清晰,而且他居然会为附加功能的缺失说 Sorry ! 看着看着就莫名觉得很感慨😳, ParserKt 所谓之「不制造问题」是有多深刻啊…… 几乎所有的,哪怕函数式解析器框架都支持 Backtracing, ParserKt 刻意只让 peek(1) ,即便现在也醒目地和新 peek(n) 划清界地(这也是 "Decide" 这个名称的由来,因为它只能判1字符😂),却可以让用户清晰地利用 Piped.concat 解决 Name|Name '('{Expr}[',']')' 的歧义消除(而不是先读个…
https://epsil.github.io/gll/#continuation-passing-style-section

#FP #scheme #parser 想了解 continuation-passing-style (没有 return 如何编程?)的大佬们可以看看这人的文章,我觉得相当好。实用性,王垠那几十行代码不就是 CPS 优化吗。

照例个人观点:
0. 上文定义了 success/failure 的 union ,以及 (successed val rest) failure ,还有添加回调的 (bind p f),基本是 (match (p s) [(success v rest) (+ v 1)] [failure failure]) 这么用的。
实现的解析器不支持流,支持 substring 。传递方式是 backtrace (比如 (string "abc") 在 (seq) 里成功则 (cons "abc" (cont "")) 失败就只是 failure 单值,所以要利用 memo 函数)

1. PKT 是不需要这种“优化”的(顶多比过程式慢 50倍的纯函数式框架要用),因为我们的 Seq 明白一解析器失败不考虑后面就 return notParsed ,不需要玩 p1(p2(p3 { })) 这种耗栈的游戏。Kotlin 不支持 CPS 优化,编程也不是智商测试。

2. CPS 也是有一定价值的,虽然它会损失一定性能,但能够拿到调用者的句柄(比如在有 Decide 的模式里,就可以后继操作遍历所有分支了,或者进行异步回调"thunk"函数)。和非 CPS 一样可作为 suspend fun 协程

3. 即便这篇文章相对易懂 #Lisp #Racket ,我不建议大家认真用 Racket ,原因是括号的表现力不够

比如文中 (let (result (apply op args)) (entry (mcons args result)) (set! alist (mcons entry alist)) (m=map)一大堆括号,有没有注意到 (let (a v) expr) 只是为可读性而加的,量定义可以内联…… 只是表达 alist = args to op(args) : alist 甚至 alist[args] = result 的意思呢(这例还是SICP里的呢,多余命名量本身就可能意味着语言性能缺失😢,比如『文言文』里甲乙丙丁一大堆OK么)…… 函数式那么多年修成正果了,开始从“无副作用”往“看起来像过程式”靠,草生(* ̄m ̄)

4. 从解决实际问题而言我觉得 ParserKt 更贴近,但这个文章所创建的解析组合子用更少的代码定义了更广义的实现方法,非常有意思(最后也用左递归和 regex 创建了计算器,缓存和穷举最长匹配问题如此有意思以至于我开始可惜PKT不用处理它了😳),而且也易懂的讲解了 CPS/trampoline 以及“穷举所有可能结果”的正统函数式思路 #Learn
#linux #windows #wsl #sysadmin

一个 npm/vue 项目,试了 wsl(2),失败后虚拟机+ vbox(vmware) shared dir, IDEA source depoly, samba, nfs(失败) 四种方案,最终选择了 samba

为了在主机和虚拟间共享项目文件夹, VBox 的实现性能太低而且不支持软连接(npm install 要炸)、 IDEA 的代码部署可以全量上传下载,但是 sync 时也有点慢、 Samba 配置要禁用 unix extensions 并启用 follow symlinks, wide links 只是偶尔卡、NFS 在 Windows 下文件无法重命名,而 Linux 下则显示来自奇怪的用户,并且依然卡……

个人观点:没用过 Qt 以外的跨平台,不过我是从来只在本地测试的……
《总结一下我在“跨平台开发”上进行过的尝试》
https://www.mokeyjay.com/archives/2840
Forwarded from 新蛤社
满分作文生成器,包含🐸
http://beautyyu.top/full-mark-composition-generator/
#Telegram 看到 comment 了 🤔
#web #Haha #tools 纯 markdown 式 HTML 🌚
https://book.wy-lang.org/ #web #js
说一下这个索引的生成方法。

val category by helem()
val book by helem()

fun generate() {
for (i, c) in chapters.map(::generateChapter).withIndex() {
category.appendChild(element("div", withClass("chapter-ref"),
element("img", withDefault(), chapterIcons[i])), c.firstChild/*h1*/.innerText)
book.appendChild(c)
}
}

其实是
function generate() {
var i=0; for (let c of [...chapters.map(generateChapter)]) {
category.appendChild(<div class="chapter-ref">c.firstChild.innerText<img src=icons[i]></img></div>)
book.appendChild(c)
i += 1
}
}
当然也可以写成 onChapter 的形式:
function onChapter(i, c) {
categories.appendChild(/**/)
}
function generate() {
var i=0; for (let c in /**/) {
book.appendChild(c); onChapter(i, c); i += 1
}
}


欸我写这个干什么呢……
duangsuse::Echo
关于 Kamet 里 val/var 和 let 的区别,本来我的意思是要除掉 let 的,但它的语义也的确不同 最开始, Kamet 只有栈上局部变量 var 和 const var (即后来的 val) 后来 Mivik 可能是发现 fun add1(n:Int) { val res = n+1; return res } 完全不需要实际栈上分配而可以内联,于是又新加入了 let 看起来 var / val 和 let 是各司其职、其区分无可厚非,我相信这种做法仍是不好看的,并且 val 应该默认具有…
现在想来 Mivik 必须区分 val/let 的问题,除了局部 struct/array 分配的必要性,大概是对 (xs.size-1) inline 还是局部分配计算&引用的问题。

这有关于 inline 开销——无论是先算完存下来(就必须在栈上分配),还是每次去算都好,该取最优化的结果;譬如 xs.size 是 struct(named product type) 上的解指针,完全可以内联,而 x+1*2/3 这种复杂计算显然不应该到处内联;但开销除了计算量也是关于引用处的分数,譬如 val size = xs.size 解指针计算量小,但如果有许多引用则还是作变量缓存比较好。

和函数的 inline 不同,因为 inline val lastIndex get() = size-1 是绝对的内联表达式,不会存栈帧局部分配。

选择让用户决定无可厚非,但不利于代码的可移植性和语义泛用性,而且这样容易引起麻烦—— let 和 val 后面都可以跟任何表达式,但 let user = User() 接着 user.name = "wtf" 是有效的,但应该是不可行的;这会降低语言的一致性和安全性。

其实 kamet 的处境也很麻烦——全部栈上吧,不知道 LLVM 是否会有优化(目前只已知 mem2reg 的 alloca 寄存器分配优化),不想分配多余栈空间
全部 IR 节点内联吧,如上例不科学;而自动选择是内联还是缓存变量, LLVM 貌似没提供(只有函数级别的内联开销,不适用)

所以就只好分出了 val/let ,当然我个人是拼命不会用这种做法的(我大概就会做成有 1 层计算 a.name 式的就内联,否则就局部分配的形式... 很反智,不知道有没有更好的方法