ParserKt 已经在设计上准备好进行第一次重写,这次重写会包含以下内容:
+ 针对 BulkFeeder 什么的;lineNumber 只能是 LF/CR/CRLF 中的一种,不可能在 Feeder 层面同时兼容
+ 对类似 ParsingFeeder、TreeRangeMap 的结构,开放一些辅助方法的覆写以提升代码重用性
+ 提供 reduceOr 和 leftrec 特性(左递归文法支持)
+ 针对基于 Buffer 的 MarkReset,它的 stack 必须是 lazily evaluated [
+ takeWhile 和 dropWhile 会被重命名为 takeTerminate; skipTerminate,FeederOpt 里的 takeUntilIn, skipWhileIn 重命名。
+ Folder 架构会有更多实例,比如
基本的原子解析器会被定义为内联的
+ 会加入很多扩展的内联解析修饰 (pre, effect, …)
+ Box<T> (就是可空版的 Maybe)会被正式包装好,也包括 map/flatMap 操作
+ 针对 BulkFeeder 什么的;lineNumber 只能是 LF/CR/CRLF 中的一种,不可能在 Feeder 层面同时兼容
+ 对类似 ParsingFeeder、TreeRangeMap 的结构,开放一些辅助方法的覆写以提升代码重用性
+ 提供 reduceOr 和 leftrec 特性(左递归文法支持)
+ 针对基于 Buffer 的 MarkReset,它的 stack 必须是 lazily evaluated [
newBuffer()],这意味着 Parser.tryRun(Feeder) 的子解析器如果没有实际上 consume() 一些项目,就不会有调用架构器的开销+ takeWhile 和 dropWhile 会被重命名为 takeTerminate; skipTerminate,FeederOpt 里的 takeUntilIn, skipWhileIn 重命名。
+ Folder 架构会有更多实例,比如
asHist(), asCount()
+ 很多原子解析器都会被重命名: char(_), anyChar, charSatisfies, charIn, charseq…基本的原子解析器会被定义为内联的
+ 会加入很多扩展的内联解析修饰 (pre, effect, …)
+ Box<T> (就是可空版的 Maybe)会被正式包装好,也包括 map/flatMap 操作
在我证明了它可以解析 JSON 和 Kotlin 之后,我就会抛弃现在叫 jison 的 ParserKt;完全重写 ParserKt
新的库会更加模块化、更简单,并且会实现这次没有实现的 source map 和
新的库会更加模块化、更简单,并且会实现这次没有实现的 source map 和
clamDown 镇定解析策略。老版本的 ParserKt 不会维持多久的,它有太多的问题;尽管有不少代码都是好的,但是依然需要彻底脱胎重构、去除冗杂和莫名其妙的设计
#Haskell 里定义类型的一些方式:
当然,一般它是和 Record 架解构同用的
这是代数数据类型 (Algebraic Data Types)
这样的 Lambda 看起来是这样…… 当然也可以
(Generalized Algebraic DataTypes,也就是「广义」的代数数据类型)
它有两个架构器
最基本的情况,GADT 允许你直接写明架构器们的类型,当然这对类型系统是有用的,一般用来实现EDSL(Embedded Domain Specific Language)的AST(Abstract Syntax Tree),不过是受更强类型检查的那种。
(Scala)
它的意思是,T 只是用来确定 Link 里那个 T,和后面它的子类架构器的各种 T 无关:
所以我们的
如果不呢?
因为没有限制你也不可以保证
实际上
这就是 GADT 的作用,当然在 Kotlin 的类型系统里是不存在这类问题的,除非你要
以上描述会比
https://blog.hoshino9.org/2019/07/26/how-to-create-a-wonderful-type.html
的直白一些,用的概念也都是 Haskell 原生的(数据、数据架构器),而不是从 C++ 系套过来方便理解的(枚举…)
因为一方面,我觉得你把『类型』视为它『数据实例』的集合,然后取交(&,inersection)并(|,union)都不会的话,也就不要学 Haskell 了……
另一方面,从引文也可以看到如果要用 C++ 那一套的话,你会像数学一样弄出一大堆
甚至你还可以弄出『带参数且只有一个值和一个case的枚举类型』……
说实话,这就是基本编程思想问题而已:是用
当然 Haskell 里的是最直白的,Haskell 的类型就是单纯的类型…… 不要把你用
type Ints = [Int] — type synonym
newtype User = User (String, Int) — newtype
newtype 就是「new」了的「type synonym」,exactly one constructor, exactly one field.当然,一般它是和 Record 架解构同用的
newtype User = User { name :: String, age :: Int }
不要把它和 Monad 扯上关系。data User = User String Int | Monkey Int — data declaration 这是代数数据类型 (Algebraic Data Types)
id (User name age) = User name age解构等式定义,等号后面访问到的 name 是 User 的第一个 field
(name :: String)、age :: Int
(\(User no yo) -> (no, yo)) :: User -> (String, Int) 这样的 Lambda 看起来是这样…… 当然也可以
let (no, yo) = user in … 解构data Link a where这是 GADT (-XGADTs)
Cons :: a -> Link a -> Link a
Nil :: Link a
(Generalized Algebraic DataTypes,也就是「广义」的代数数据类型)
它有两个架构器
(Cons h tail) 和 Nil (对面向对象来说就是子类型的架构器……)最基本的情况,GADT 允许你直接写明架构器们的类型,当然这对类型系统是有用的,一般用来实现EDSL(Embedded Domain Specific Language)的AST(Abstract Syntax Tree),不过是受更强类型检查的那种。
data Link a = Cons a (Link a) | Nil对于这种情况 (Kotlin)
sealed class Link<out T> {
data class Cons(val head: T, val tail: Link<T>): Link<T>()
object Nil: Link<Nothing>()
} (Scala)
trait Link[+T]不过这里我们没直接用 GADT 们独有的特性,只是说,
case class Cons[T](x: T) extends Link[T]
case object Nil extends Link[Nothing]
Link[T] 的这个 type variable T
被人称为「phantom type variable」(dummy 的 typevar)它的意思是,T 只是用来确定 Link 里那个 T,和后面它的子类架构器的各种 T 无关:
{-# LANGUAGE GADTs #-}
data Ast a where
I :: Int -> Ast Int
P :: Bool -> Ast Bool
And :: Ast a -> Ast a -> Ast a
(差点忍不住手痒把 type InfixCons t = t -> t -> t 抽提了…… Kotlin 里面可没有 Higher Kinds, type constructors 这么方便的东西啊,只能参数化多态)所以我们的
(I 1) :: Ast Int,但 (And (I 1) (I 2)) 也是 Ast Int……(跑如果不呢?
data Ast a = I Int | P Bool | And (Ast a) (Ast a)
And :: forall a. Ast a -> Ast a -> Ast a (P True, I 2) 是什么?如果把他们取一个并集(union) 你会发现他们都是 forall a. Ast a…… 这个该死的 "unbound" 类型变量 a
And (P True) (I 2) :: Ast a 于是,你就可以得到一个完全无关的 Ast 类型因为没有限制你也不可以保证
And (P p0) (I x) 不出现,Haskell 的 type checker 也不能保证(定义解构函数的时候)实际上
(P True) :: (Ast String) 都可以,因为你 :type P 会发现它只是 forall a. P :: Bool -> Ast a,这是属于比较骚操作的情况了。这就是 GADT 的作用,当然在 Kotlin 的类型系统里是不存在这类问题的,除非你要
Cons<T>(…): Link<out Nothing>() 以上描述会比
https://blog.hoshino9.org/2019/07/26/how-to-create-a-wonderful-type.html
的直白一些,用的概念也都是 Haskell 原生的(数据、数据架构器),而不是从 C++ 系套过来方便理解的(枚举…)
因为一方面,我觉得你把『类型』视为它『数据实例』的集合,然后取交(&,inersection)并(|,union)都不会的话,也就不要学 Haskell 了……
另一方面,从引文也可以看到如果要用 C++ 那一套的话,你会像数学一样弄出一大堆
XX类型 这种带修饰的名词…… 比如『枚举数据类型』、『只有一个值的枚举类型』、『带有参数的枚举类型』……甚至你还可以弄出『带参数且只有一个值和一个case的枚举类型』……
说实话,这就是基本编程思想问题而已:是用
Desc(x: String?) 还是 Desc(x: String) | NoDesc?我觉得必须看情况来择一而取。当然 Haskell 里的是最直白的,Haskell 的类型就是单纯的类型…… 不要把你用
data 定义的那一套当作『类型』…… 因为 Haskell 还有很多种『类型』足够打脸……blog.hoshino9.org
Haskell 如何打造爆款数据类型
数据类型是 Haskell 中的重要知识之一这篇文章讲教你从一无所知到随手打造爆款数据类型
duangsuse::Echo
#Haskell 里定义类型的一些方式: type Ints = [Int] — type synonym newtype User = User (String, Int) — newtype newtype 就是「new」了的「type synonym」,exactly one constructor, exactly one field. 当然,一般它是和 Record 架解构同用的 newtype User = User { name :: String, age :: Int } 不要把它和…
https://blog.hoshino9.org/2019/08/25/just-dependent-type.html
这是hoshi9对这个问题的解释,这篇文章他把Phantom type翻译为『幻影类型』
然后他还讲了 Data Kinds / Type Families 这些依赖类型的东西(这 Kotlin 就没法写了,因为它只是多态而没有 Kinds),这个我之前是不知道的,不过讲得很好也没必要再说一遍了。
这是hoshi9对这个问题的解释,这篇文章他把Phantom type翻译为『幻影类型』
然后他还讲了 Data Kinds / Type Families 这些依赖类型的东西(这 Kotlin 就没法写了,因为它只是多态而没有 Kinds),这个我之前是不知道的,不过讲得很好也没必要再说一遍了。
blog.hoshino9.org
浅谈 GADT 与 Dependent Type
这篇文章将会带你接触 GADT 和 Dependent Type 的奇妙世界
星野(我看了一点简单的日语,所以知道 hoshi ほし是繁星的意思,虽然冰封说是叫什么)大佬写的东西还真不错,很容易理解
虽然 Haskell 嘛,尤其是一些名字上的东西需要注意到,并且逐渐去熟悉,要不然看不懂在写什么的(自然语言也是一样嘛)
不过比起写 Monad,我觉得还是先写点工程能用的东西好一些。
用 Monad Transformers 真的是把 PLT 的问题都算法化了,可是如果要用 Haskell 写,当然 Haskell 比 Kotlin 高级啊,可是代码就难看很多
毕竟写什么东西用什么语言的
而现在我打算写的东西暂时碰不上 Haskell
我还是更想在一些所有人都能弄懂的地方创新,毕竟他们写的算法上多一些(虽然我现在算法上也不是设计不出……何况有Algorithms这本书作资源呢)
可是我觉得,他们还不够简单…… 还不够友好……
就是这样,暂时继续用 Kotlin 写一个简单的小东西再配上友好的接口,而不是 Scala/Haskell 加上 Dependent Types 解决许多工程不重视但是 PLT 重视的问题。
虽然 Haskell 嘛,尤其是一些名字上的东西需要注意到,并且逐渐去熟悉,要不然看不懂在写什么的(自然语言也是一样嘛)
不过比起写 Monad,我觉得还是先写点工程能用的东西好一些。
用 Monad Transformers 真的是把 PLT 的问题都算法化了,可是如果要用 Haskell 写,当然 Haskell 比 Kotlin 高级啊,可是代码就难看很多
毕竟写什么东西用什么语言的
而现在我打算写的东西暂时碰不上 Haskell
我还是更想在一些所有人都能弄懂的地方创新,毕竟他们写的算法上多一些(虽然我现在算法上也不是设计不出……何况有Algorithms这本书作资源呢)
可是我觉得,他们还不够简单…… 还不够友好……
就是这样,暂时继续用 Kotlin 写一个简单的小东西再配上友好的接口,而不是 Scala/Haskell 加上 Dependent Types 解决许多工程不重视但是 PLT 重视的问题。
『盒子(不能直接取出内部物品的)』的比喻对范畴论来说虽然很不正确(因为盒子只是有封闭律和结合律、identity的图),但你会发现没有这个『盒子』的理解和举例,你根本不会理解 Functor, Applicative, Monad, State, 甚至只是 Identity/Maybe 那一套;即便别人解释得再简单、再可视化也不行,所以说知识到底还是要『讲』出重点的。
好比我开始想Dijkstra算法基本思路的时候,肯定要问『为什么每次要选余下节点里最小的更新权重,如果选中位数、极大(Max)值,甚至干脆随便选会怎么样?』这个问题,要不然你肯定无法理解算法需要输入数据满足的性质和它做的断言、不会知道为什么不能有『负权边』。
— 因为它做了一个断言:『任何可能是某点前驱的节点,其距离绝对小于该点』,这是为了在不连续的搜索过程中保证从『当前搜索路径』一直能够得到等价的新源点、保证一个点的距离被传递前它的所有可能路径均已经求得最短,所以不能有负权边。
可见对知识的第一印象是很重要的,错的第一印象看起来会导致谬误,但是没有能让人理解的第一印象就没有进步。
所以比起老是对『知识不准确』患得患失,不如踏踏实实先学一个不恰当的比喻,然后在以后的实践和学习中慢慢加深自己的理解,才可以做到大团圆的结局。
好比我开始想Dijkstra算法基本思路的时候,肯定要问『为什么每次要选余下节点里最小的更新权重,如果选中位数、极大(Max)值,甚至干脆随便选会怎么样?』这个问题,要不然你肯定无法理解算法需要输入数据满足的性质和它做的断言、不会知道为什么不能有『负权边』。
— 因为它做了一个断言:『任何可能是某点前驱的节点,其距离绝对小于该点』,这是为了在不连续的搜索过程中保证从『当前搜索路径』一直能够得到等价的新源点、保证一个点的距离被传递前它的所有可能路径均已经求得最短,所以不能有负权边。
可见对知识的第一印象是很重要的,错的第一印象看起来会导致谬误,但是没有能让人理解的第一印象就没有进步。
所以比起老是对『知识不准确』患得患失,不如踏踏实实先学一个不恰当的比喻,然后在以后的实践和学习中慢慢加深自己的理解,才可以做到大团圆的结局。
Forwarded from dnaugsuz
也不是,就是可以
比如说
然后直接按照 BasicBlock 扫描就可以提取出基本结构了……
BasicBlock(0)
[0] = 0 — 需要利用一个抽象的执行栈来分析,这里省略了很多
[1] = 1.upTo(10)
[2] = [1].iterator()
BasicBlock(9)
ifnot [2].hasNext() — 回填,或者干脆直接去扫描……
[0]++
println([0])
succ=//上面的BasicBlock(9) — 如果指令指针往回指了(< currentIPtr),就是说产生了循环
BasicBlock(16)
return
当然 BasicBlock(16) 是不应该存在的(因为它只有一个可能的前驱:iptr=15),这只是个例子,实际实现的时候肯定要不断把那些指令序列切来切去的…… 也麻烦
BasicBlock(ipOffset) 然后可以各种类扫描器算法分析折叠出表达式,然后递归扫出控制流比如说
var a = 0可以翻译成(我手翻的,好累啊
for (i in 1..10) {
a++
println(a)
}
main [stack=2, locals=3]:
ipush 0; istore 0
ipush 1
ipush 10
invoke Int.upTo
astore 1
aload 1
invoke IntRange.iterator
astore 2
hasNext$1:
aload 2
invoke IntIterator.hasNext
br.not hasNext$1out
inc 0
iload 0
invoke println
goto hasNext$1
hasNext$1out:
return 然后直接按照 BasicBlock 扫描就可以提取出基本结构了……
BasicBlock(0)
[0] = 0 — 需要利用一个抽象的执行栈来分析,这里省略了很多
[1] = 1.upTo(10)
[2] = [1].iterator()
BasicBlock(9)
ifnot [2].hasNext() — 回填,或者干脆直接去扫描……
[0]++
println([0])
succ=//上面的BasicBlock(9) — 如果指令指针往回指了(< currentIPtr),就是说产生了循环
BasicBlock(16)
return
当然 BasicBlock(16) 是不应该存在的(因为它只有一个可能的前驱:iptr=15),这只是个例子,实际实现的时候肯定要不断把那些指令序列切来切去的…… 也麻烦
Forwarded from dnaugsuz
有没有人和我一样觉得,对应用程序编程而言,好的方法实现是不需要加注释的…… 🌚???
Forwarded from 永久封存 | Yuuta 台 | 😷 #Pray4Wuhan (Yuuta ⠀)
阅读早年 Dir 代码的我:wdnmd 这方法干什么的啊..(论不加注释的危害。
说你呢,Telegram Android。
说你呢,Telegram Android。
Forwarded from dnaugsuz
多用用 Ctrl+Alt+M,注释起在名字里。
"丑逼VIP"
fun killFuckingUser() {
users.filter(::isFucking).forEach(::见机行事) //当然也可以用partition+forEach,不过基本需要引入val
//所以我才没用expression body
}
fun isFucking(u: User) = u.gender == Gender.Boy && u.face = Face.Ugly //just4fun
fun 见机行事(u: User) = when {
数据库满了 -> db.remove(u)
else -> warn(u,
)只是一个例子
}
inline val 数据库满了: Boolean get() = db.isFull
Forwarded from dnaugsuz
如果一个算法很复杂,加注释也没用。
在确保已经使用了尽可能易于理解的命名方式、尽可能自然的表达风格后,还需要外部文档。
真像JDK一样,对类似 LinkedList, TreeSet 这样弄大片的『文档』嵌在代码里事无巨细都详细得跟蔡徐鲲打篮球没什么两样,我不觉得有什么好看的,尽管JDK是为了javadoc网页方便。
在确保已经使用了尽可能易于理解的命名方式、尽可能自然的表达风格后,还需要外部文档。
真像JDK一样,对类似 LinkedList, TreeSet 这样弄大片的『文档』嵌在代码里事无巨细都详细得跟蔡徐鲲打篮球没什么两样,我不觉得有什么好看的,尽管JDK是为了javadoc网页方便。
// 但是我没用 stl 的 map<k,v>
template<typename T>
class UnionFind {
private:
T* up; unsigned* size;
T* findFamily(T self)
{ while (up[self] != self) self = up[self]; return self; }
public:
UnionFind(size_t n): up(new T[n]), size(new unsigned[n]) {
for (int i=0; i<n; i++) { up[i]=i; size[i]=0; }
}
boolean connects(T a, T b) { return findFamily(a) == findFamily(b); }
void unite(T a, T b) {
auto pa = findFamily(a), pb = findFamily(b);
if (pa == pb) return;
if (size[pa] < size[pb]) up[pa] = pb, size[pb] += size[pa];
else up[pb] = pa, size[pa] += size[pb];
}
} 带权重的合并树查找(相等关系传递)
#include <bits/stdc++.h>修复版本
template<typename T>
class UnionFind {
private:
T* up; unsigned* size;
size_t family(T self)
{ while (up[self] != self) self = up[self]; return self; }
public:
UnionFind(size_t n): up(new T[n]), size(new unsigned[n]) {
for (int i=0; i<n; i++) { up[i]=i; size[i]=1; }
}
bool connects(T a, T b) { return family(a) == family(b); }
void connect(T a, T b) {
auto pa = family(a), pb = family(b);
if (pa == pb) return;
if (size[pa] < size[pb])
up[pa] = pb, size[pb] += size[pa];
else up[pb] = pa, size[pa] += size[pb];
}
};
using namespace std;
const size_t N = 1000;
int main() { int a, b;
UnionFind<int> uf(N);
while (cin >> a >> b) {
if (uf.connects(a, b)) continue;
uf.connect(a, b); cout <<a<<" "<<b<<endl;
}
}
#include <map>当然也可以用 std::map,不过要实现的话必须定义一个 map 子类型……
#include <cstddef>
template<typename T>
class UnionFind {
private:
template<typename A>
using M = std::map<T, A>;
M<T> up; M<unsigned> size;
T *family(T self) {
while (up[self] != self) self = up[self];
return &up[self];
}
~UnionFind() { delete up, size; }
public:
explicit UnionFind(size_t n): up(), size() {
for (int i=0; i<n; i++) up[i]=i, size[i]=1;
}
bool connects(T a, T b) { return family(a) == family(b); }
void connect(T a, T b) {
auto fa = family(a), fb = family(b);
if (fa == fb) return;
auto pa = *fa, pb = *fb;
if (size[pa] < size[pb])
up[pa] = pb, size[pb] += size[pa];
else up[pb] = pa, size[pa] += size[pb];
}
};
#include <iostream>
using namespace std;
const size_t N = 1000;
int main() { int a, b;
auto uf = new UnionFind<int>(N);
while (cin >> a >> b) {
if (uf->connects(a, b)) continue;
uf->connect(a, b); cout <<a<<" "<<b<<endl;
}
}