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

技术相干订阅~
另外有 throws 闲杂频道 @dsuset
转载频道 @dsusep
极小可能会有批评zf的消息 如有不适可退出
suse小站(面向运气编程): https://WOJS.org/#/
Download Telegram
有一个方法是写测试(持续开发持续测试持续集成…… 你可以利用面向对象多态 moke 模拟对象,或者是像 Aqullian 测试组那本 CEDJ Java 书一样,真实在本地自动部署测试环境执行测试),还有一个更好的方法是不写 bug,当然这个是那些欠打的 Haskell 程序员说的,他们连 syntax error 都不屑给你指出。

当然对我来说,我觉得任何应用程序都是不应该写测试的(因为他们不应该能够让你写错代码,无论是从工具的角度还是文档的角度);绝大部分算法和框架的测试都应该可以用 Haskell Test.QuickCheck 的方法来写。

文档懒得读可以尝试和作者从一个角度想问题,而不是被动接受别人的解释,比如:

当你看到 gradle 的 from 时,你会想到:我在 task 里用 from 是什么意思呢?task 是将多个文件汇编成一个文件,所以 from 是指定一个输入的意思
当你看到 task (type: ...), Task.dependsOn 的时候也会从使用对象工作流程本身的视角看问题
当你写复用库的时候,应该从用户的角度看问题,比如:你要定义一个 Collection<*>.isNotEmpty,是要把它作为扩展方法还是扩展属性?
此时,你发现这个 (getter) 方法是纯函数,它没有副作用,所以你定义 val Collection<*>.isNotEmpty: Boolean get() = size != 0
很多其他情况你考虑一些诸如『是不是提供 vararg?』『是不是用 Builder?』『这个 out 是什么意思?』的时候一样有效
Forwarded from Yuuta 小台 @Trumeet (Yuuta ⠀AppLine.store 正版软件)
CI 调试噩梦,本地无法测试,每次改一点,影响 Commit 历史;云端环境和本机不同;云端没设置缓存,每次重新编译;重新编译耗时长,可能在最后几步出错;CI 文档懒得读,无法一次写好等等。

谁有点优雅些的调试技巧么?

一些很鉴的 CI 功能:
* Travis CI Debug 功能
* Azure Pipelines Rerun Failed Jobs 功能
duangsuse::Echo
有一个方法是写测试(持续开发持续测试持续集成…… 你可以利用面向对象多态 moke 模拟对象,或者是像 Aqullian 测试组那本 CEDJ Java 书一样,真实在本地自动部署测试环境执行测试),还有一个更好的方法是不写 bug,当然这个是那些欠打的 Haskell 程序员说的,他们连 syntax error 都不屑给你指出。 当然对我来说,我觉得任何应用程序都是不应该写测试的(因为他们不应该能够让你写错代码,无论是从工具的角度还是文档的角度);绝大部分算法和框架的测试都应该可以用 Haskell…
Haskell 里一个可用的 atoi :: [Char] -> Int 是:

atoi = snd . atoi'
atoi' (base, n) (c : cs) = let newbase = base * base in
(newbase, (base * digitToInt c) + n)
atoi' (_, _) [] = (1, 0)

后来我发现我的思想很危险,它不正确,所以我没有进步

import Data.Char (digitToInt)

atoi :: Int -> String -> Int
atoi base = {- snd . -} (flip atoi') 0 {- . reverse -} where
-- atoi' :: (Int, Int) -> String -> Int
-- atoi' (base, n) (c : cs) = let newbase = base * base in
-- atoi' (newbase, (base * digitToInt c) + n) cs
-- atoi' (_, n) [] = n
-- atoi' (_, _) [] = (1, 0) -- 写错了,我忘记了是 L-R 扫描……
atoi' :: String -> Int -> Int
atoi' (c : cs) i = atoi' cs (i*base + digitToInt c)
atoi' [] i = i -- base * 0

你们无法想像我看到输出

*Main> atoi 10 "1234"
100
*Main> atoi 10 "1234"
100020340
*Main> atoi 10 "12345"
10000000200030450
*Main> atoi 10 "12"
120
*Main> atoi 10 "121"
10210
的时候有多么恐慌

由此我们可以得知: 其实测试是必须的…… 如果没有 REPL 我不知道该怎么办

JavaScript 里想写可能更简单
可我这写的是算法,算法的工作成功与否要靠你自己分析…… 比如以上就是我开始想法 naive,没考虑到位置计数法的左右顺序一说。即便理论是对的,实现的时候也要考虑到很多麻烦的因素

而应用程序写的不正确,则往往是你基本功没做好的缘故,也或许是框架文档不好 / 写的太复杂的缘故

我举个例子,Gradle 使用的 PluginResolution Strategy 设计模式,Groovy 里看起来是这样的(我没隐式把 it 当成 this

// 用于支持下面 DSL 的代码在 Java 里都是 Boilerplate,还写了一大堆 Action<? super T>, Kotlin 里就是简单一个 T.() -> Unit 或者说 typealias Action<T> = T.() -> * 解决。
project.pluginManagement {
it.resolutionStragegy {
// 这里,我们要处理一个列表,使得所有插件 artifact 被解析到一个 location,你的程序会加入 Gradle 的这个内部过程,它会给你一个内部的待处理流,让你利用 side effect 来设置你解析到结果
// open class Resolve<out T: Any>(protected var resolved: T?)
// 我们使用模型 data class PluginResolve(val requestedId: Artifact.Name, val requestedVersion: Artifact.Version): Resolve<Artifact.Location>()
it.forEachPlugin {
if (it.requestedId == 'simple-plugin') it.useModule("org.duangsuse:gradle-simple-plugin:${it.requestedVersion}")
// keep it.resolved null
}
}
}

这里,首先模型一个和 Gradle 它有差别的就是:Gradle 里你只能用 useModule, useVersion,换句话说是一个变态的 Builder,我们可以加在 PluginResolve 里
fun Resolve<Artifact.Location>.initialize() { this.resolved = Artifact.Location() }
val Resolve<*>.initialized get() = this.resolved?.run { initialize() } ?: this.resolved!!
fun Resolve<Artifact.Location>.useModule(m: Artifact.Module) { initialized.module = m } //...
data class Requested(val id: Artifact.Name, val version: Artifact.Version): Resolve<Artifact.Location>()

那加一个模板类,就可以弄出 it.requestedId, it.useModule 的效果了,本身都是 Resolve<*>,然后数据装在子类里面
可是 it.requested.id 该怎么实现呢?就是说现在 Resove<*> 得多一个 <C> 类型参数了,
open class Resolve<out T: Any, out C>(protected val requested:C, protected var resolved: T?)
这样就可以了(实际上也就是把之前该父类型实例的 this 移动到了 requested: C 里面)

知识点:那这个 out C 是什么意思呢? #Java #Kotlin
out C 是 Kotlin 的声明处型变,意思就是比如这里有 fun <R> lets(op: Function<C, R>): R = op(this.requested as C)
实际的对应 Java 代码里,类型参数 C 是一个 Type wildcard
void gets(Producer<? extends C> producer) { this.requested = producer.get(); }

这里为什么可以是 Producer<?: C>?其实谈理论谈到什么 upper-bound, lower-bound, subtyping, variance 什么的都扯远了,你只需要知道 Any? 是所有对象的类型、Nothing 不可能是任何对象的类型、Any? 作为 upper-bound 的类型参数,它的取值范围可以是任何类型就对了(这个是好像是全序(total order)?反对称不知道有没有)。

因为你看,我们这个 @FunctionalInterface interface Producer<R> { R gets(); }

它这个 R 是所谓的『生产者位置』,就是说作为返回值绝对可以泛型参数类型安全。
任何使用 Producer<Any> 的位置,对 Producer<Number>, Producer<String> 一样有效,因为他们只能使用 R gets(),而这个 R 它其实可以是任何 Any 的子类型,String, Int, Long, Boolean, Date 都没问题
这种情况就是泛型型变的『协变』(covariant),在面向对象参数化多态(parametric polymorphism) 的领域里,就是说 K<T>, K<T1>T1: T 的时候,存在 K<T1>: K<T> 的关系
比如,<K: Producer<Any>>, Producer<String>: Producer<Any>
这就是 Kotlin 里的 out, Scala 里的 K[+T]

对应的情况就是泛型型变的『逆变』(contravariant),就是说 K<T>, K<T1>T1: T 的时候,存在 K<T>: K<T1> 的关系
比如,<K: Consumer<String>>, Consumer<Any>: Consumer<String>
这就是 Kotlin 里的 in, Scala 里的 K[-T], 因为一个操作连 Any 都能够接受,那 String 也自然没问题喽(不过更高层次的抽象是生产-消费模型而已)。

此外,Kotlin 的类型投影(就是 * 那个看起来像是的类型)也做好了类型安全处理
Function<*, *> // Function<in Any?, out Nothing>, 这个函数啥都可以接受,啥都可以输出,包括 100% (抛出异常什么的)的类型 Nothing
换句话说,* 在生产者位置是等价 Nothing(底类型, bottom type)、在消费者位置是 Any?(顶类型),反观 Java 静态类型系统是没有这么方便的东西的,何况 Java 连类型推导都没多少

>>> System::exit is Function1<*, *>
true

不过有时候也不能全用生产-消费者(PECS, producer-extends, consumer-super)模型的

<R> R lets(Function<? extends C, R> op) { return op((C) this.requested); }

这样就不行了,至于更高级的(比如,System.arraycopy 的那种不能限制为只返回/消耗 T 的)型变性下次有机会再讲。
duangsuse::Echo
Haskell 里一个可用的 atoi :: [Char] -> Int 是: atoi = snd . atoi' atoi' (base, n) (c : cs) = let newbase = base * base in (newbase, (base * digitToInt c) + n) atoi' (_, _) [] = (1, 0) 后来我发现我的思想很危险,它不正确,所以我没有进步 import Data.Char (digitToInt) atoi :: Int…
module NumRepr (atoi, itoa) where

import Data.Char (digitToInt, intToDigit)

atoi :: Int -> String -> Int
atoi base = foldl (\ac x -> ac*base + digitToInt x) 0

itoa :: Int -> Int -> String
itoa base n = (sign . map intToDigit . reverse . itoa') n where
itoa' i
|i == 0 = []
|otherwise = let (ds, d) = i `divMod` base
in (d : itoa' ds)
sign = if (signum n) == (-1) then ('-' :) else id

迫真 point-free 简板,没有任何新东西,新瓶老酒。
第二个 itoa (from) 的支持,我开始居然还打算使用 Maybe :: (* -> *)……

考虑一下我要更新 Share/zhuazhou 和结尾 Dokuss,然后可能去完成 Parser.kt 和 Reflex 解释器
Refelx 这个比较燃,因为我开始设计的比较奇葩,这就是个『表达式语言』,不过所有东西都有可能是变量…… 这门语言的一个特点是,所有 lhs 本身都是(lhs 值),也就是可以 setValue 的东西
这也就导致了,Reflex 很过分地没有任何控制结构,因为你可以用 Infix 实现(可以随便添加 infix 操作符)

fun (xs) [mut accumlator]
for (var i in xs) { accumlator += i }

其中,(var i) 就是一个 Lhs 值…… 我去
其实对于动态作用域,非常自然的一种做法是判断名字的分配是否冲突,然后为冲突的情况提供新的层 Map,我称为 VersionizedMap

对于这门语言的值和操作,首先我得为语法上的便利提供条件:2D Syntax 和一些特殊的 case,比如 fun mainloop() loop... 这种
值,当然基于 JVM 的对象,就是 Boolean, Number, Char, String, Array, List, Set, Map, Object, Function, Error 要提供内建支持(不过这样的话,函数参数的个数也就有限制了…… 我希望是至多 7 个参数)
此外还要提供 IntegralRange 和 RationalRange

就闭包怎么支持 UpValue 来说,其实就是直接包一个引用就行,毕竟『值』的部分是底层的 JVM 辅助的

Body = (EQ Expr) | (loop)? NL Layout:l Stmt (l Stmt)* —这就不是 CFG 了,不过利用 Parserc.kt,contextual { } 解析也很简单
+ fun Name ArgList LocalList Body 是一种语句,它没有值(为了统一就叫 Unit
+ loop 也是语句,for 只是一个函数
+ when 是表达式,if 不在语言层面特化处理,只是一种内部提供的辅助函数
+ do BraceArgList 是表达式
以上语法都提供了使用 {} 的版本和 2D Syntax 带语义缩进的版本

+ 各种 Name ArgList Block? 是表达式
+ 各种 Expr (infix) Expr 是表达式


fun (n) loop
println(when input()
> n => "Too big"
< n => "Too small"
== n => "OK".also { break })

我打算只给一些基本的循环分支提供默认函数,我们知道,Kotlin 对一些本身不是『硬』关键字的东西,都可能有特殊处理,不过我没有这个觉悟,我觉得它能够好看就可以了。
而且不能到处 break,只能有一层(要不然我的求值器就得使用 Exception 来传递控制流执行权……)
欸不对啊,好像只要我提供 closure,就必须使用 exceptions……

像是 if 这种的
if (p) {
} else if (p1) {
} else {
}

就是 infix IfBuilder…… 本身就是接受很多 code block 的东西,emmm
既然假设我有精力,就把『困难』一点的多态选择加进来吧,熟悉一下缓存优化什么的…… 也不对,因为 JVM 实现了这个算法我可以拿来用

typedef Operation = Runnable
infixl else
infix fun IfSyntax.else(if: IfSyntax)
infix fun IfSyntax.else(op: Operation)

不过这个语法该怎么执行,就成了问题,如果没有延迟求值的话…… 🤔(虽然延迟求值本身不是特别困难,我可以要求求值器认为某种 closure 对象是自动执行的)
(在实现 if 语法的时候,显然即便 if(...){...} 是延迟执行的,第一个 if 的语义也可以被正确实现,对 Builder 的方式也能够支持)
(在提供 block 参数的时候,我的想法是不如就像 Kotlin 一样默认最后一个 SAM 接口吧)
duangsuse::Echo
module NumRepr (atoi, itoa) where import Data.Char (digitToInt, intToDigit) atoi :: Int -> String -> Int atoi base = foldl (\ac x -> ac*base + digitToInt x) 0 itoa :: Int -> Int -> String itoa base n = (sign . map intToDigit . reverse . itoa') n where …
关于 Reflex 这门语言,我这里已经有不少(本来不是为它设计) 的稿子了,但因为都是 JVM 语言所以都可以用,就来汇总一下
看了刚才的示例我感觉非常 excited,分分钟想弄出个大新闻 🐸

这是一门多范式 JVM 编程语言,支持特性包括:
+ 基本的值、计算、顺序、分支、循环、递归
+ 支持高阶函数、Block (closure)
+ Java 没有太多值类型,所以都是按引用传递
+ 支持语言层面的惰性求值(call-by-need),惰性求值是针对函数的返回值、参数,不支持列表等 Iterable 上的惰性求值(要不然不就是支持偏 coroutine 了……)。
如果是保存本地变量,不会对惰性块进行求值,其他情况(包括求值后值不需要的)一律进行求值。
+ 支持语言层面的传递表达式(call-by-name),使用关键字 expr 定义一个表达式(语句,按名字引用),表达式作为参数传递不论目标函数的语言都是按值传(要不然,为什么不用 SAM?)
返回表达式是可行的,返回表达式没有特殊语法,表达式返回时不会被求值,这也就导致了 FFI 上,要 instanceof 动态检查类型
如果使用多态,则参数要加上类型声明;如果不使用多态返回值可以不写,不过一般还是写上(也就是类似 Python 那些四不像,本身就是弱类型的)

+ 允许使用 Java 反射访问任何 Java API,语言层面利用 o.method(_) 来进行 Partial application (WIP,在完成前这种语法是 reserved,并且不可以用 o.method 引用函数)
o.property 来获得一个 Var, 它可以使用内部的 (=) 操作符赋值或者直接取值 (o.XXX | o.getXXX, o.setXXX)
返回左值的表示对象会针对对象的 getter/setter 访问性选择 Val/Var 接口实现,如果是公开字段,则是 Var,如果只有 setter,则生成的 Var 在 get 时抛出异常

为了方便起见,应该提供 (.$ #) 操作符(# 表示名字参数)来访问对象的非公开属性
($ 不是有效的标识符组成成分,但是 Reflex 支持 `name` 来表示标识符),特殊处理 (o.isXXX, o.hasXXX)

不要被 (=) 这种表示方法骗了,它不支持类似 Haskell 的 sectioning,不过 Partial application 可以考虑
o::method 来引用对象上的一个方法,还应该提供 o ::$ method 以引用非公开方法

创建的所有函数,都使用 Runnable, Function0<R>, Function1<T0, R>, Function2<T0, T1, R> 实例来表示,且支持 SAM 自动实现
对象上面的方法
,内部使用 Method0<A>: Consumer<A>, Method1<A1>, ... 表示
均只支持到 7 个参数(方法包含 this)

+ 支持内部对外部接口的重写,比如 val Collection.isEmpty get() = size == 0, fun Any?.copy() = this?.clone()

+ 包含各种可以重载的操作符,+-*/ 一应俱全,只要能够创建内部支持类型值的东西,都应该有特殊的对应语法
+ 包含 Nullability 运算符 (?.) (?:) (!!),它们是内建的,多态也支持可空性判断
+ 可以利用 JVM 的接口进行 (is) (as) (as?) 操作,它们是内建接口
+ 标准库扩展了 infix fun Any?.in(container: Any?) 来表示 contains 操作
https://github.com/lice-lang/lice
冰封哥都退团了,emmm,之前听说实现 expr 是有啥问题来着
duangsuse::Echo
https://github.com/lice-lang/lice 冰封哥都退团了,emmm,之前听说实现 expr 是有啥问题来着
之前不知道是怎么回事搞混了 call-by-name 和 call-by-need,我以为 by-name 是惰性求值,by-need 是重复求值
肯定不是我自己记岔了!第一次看到 call-by-name 它好像被人拿来做是惰性求值的意思了……
duangsuse::Echo
关于 Reflex 这门语言,我这里已经有不少(本来不是为它设计) 的稿子了,但因为都是 JVM 语言所以都可以用,就来汇总一下 看了刚才的示例我感觉非常 excited,分分钟想弄出个大新闻 🐸 这是一门多范式 JVM 编程语言,支持特性包括: + 基本的值、计算、顺序、分支、循环、递归 + 支持高阶函数、Block (closure) + Java 没有太多值类型,所以都是按引用传递 + 支持语言层面的惰性求值(call-by-need),惰性求值是针对函数的返回值、参数,不支持列表等 Iterable…
这一段定义了和 Java 的 interop,

+ Reflex 是一门多范式、动态类型的 JVM 语言,它支持多态,在语法上支持类型标记。
+ 控制流,Reflex 支持顺序、分支、循环、递归。
+ Reflex 支持高阶函数和 Block (closure),函数参数都是按 java.lang.Object 引用传递

+ 语言层面支持(针对函数参数和返回值的)惰性求值和(针对返回值的)表达式作为值
+ 惰性求值使用标准库 lazy {} 函数创建,如果将惰性块保存到本地变量或者作为 Reflex 函数的参数、从函数返回,不求值。lazy 是软关键字
在 Java 的层面看来,收到的对象是 org.duangsuse.reflex.rhs.Lazily
+ 表达式使用 expr Name = Expr : Stmt 语法创建,表达式在每一个要求值的地方求值,表达式可以作为返回值,这种情况返回表达式而不计算,expr 是关键字。
在 Java 的层面看来,收到的对象是 org.duangsuse.reflex.rhs.Expression

+ 允许使用反射访问任何 Java API,内部语法 (.) (::)、标准库 (.$ #) (::$ #) (?.$ #) (?::$ #)
+ o.method 不可以使用,这个语法只能用来表示索引属性 Val / Var
public final o.XXX, public o.XXX 字段、public o.getXXX(), public o.setXXX() 方法
如果属性以 is, has 开头,则查找方法时不添加 get/set 前缀
+ o.method(_) 进行 Partial application (WIP,目前先 reserve 这个 _
+ o::method 来引用某个 receiver 上的方法
+ 所有带 $ 的语法,表示访问非公开属性/字段/方法,都由标准库提供(import std.reflect

创建的所有函数,都由 java.lang.Runnable, org.duangsuse.reflex.fn.Function[0-7] 提供
Function0<R>, Function1<T0, R>, ...
对象上面的方法,内部使用 Method0<A>: Consumer<A>, Method1<A1>, ... Method7 实现

函数或方法(不包含 this)的参数个数不能大于 7,vararg 关键字可以支持变长参数,内部支持 SAM(Single Abstract Method) 自动代理实现

ExprOrBlock = (EQ Expr | Block)
+ 可以使用 val Type.Name[: Type] get(L R) ExprOrBlock [ set(L Name R) ExprOrBlock ] 扩展 getter
+ 可以使用 fun Type.Name ArgList[: Type] ExprOrBlock 扩展方法

+ 内部支持 (is) (as?) (as) 操作符
+ 对于 Nullable type 支持 (?:) (!!) 操作符
需要注意的一点是,T?T 的差别在于 T? 可能是 null
+ 标准库还扩展了
infix fun Any?.in(container: Any?) = container.contains(this)
infix fun Any?.`!in`(container: Any?) = container.contains(this)
方法
+ infix operator 定义类似这样:
(infixl | infixr) LitInt (L ... R)
infix fun 默认 precedence 和 (.) 相当
如果 infix operator 的名字两侧包含空白,代表它需要左/右侧有一定数量的空白字符
如果 infix operator 的最右侧是 #,代表它接受一个标识符作为参数,对应 fun 将被传入 org.duangsuse.reflex.data.Name 实例
duangsuse::Echo
这一段定义了和 Java 的 interop, + Reflex 是一门多范式、动态类型的 JVM 语言,它支持多态,在语法上支持类型标记。 + 控制流,Reflex 支持顺序、分支、循环、递归。 + Reflex 支持高阶函数和 Block (closure),函数参数都是按 java.lang.Object 引用传递 + 语言层面支持(针对函数参数和返回值的)惰性求值和(针对返回值的)表达式作为值 + 惰性求值使用标准库 lazy {} 函数创建,如果将惰性块保存到本地变量或者作为 Reflex…
然后是在操作和内部值的层面:

typedef Name EQ Type
const Name EQ Expr

fun Name (L ArgList R)[: Type] [LSQ LocalList RSQ] BlockOrExpr2D : Stmt
do (I ArgList I) | (ArgList.) BlockOrExprNoEQ2D : Expr

loop(L Name R) [(while | until) Expr] Block : Stmt
break(L Name R) : Expr
continue(L Name R) : Expr

WhenBr = when LB { Expr => Expr } [else => Expr] RB
| when NL l:Layout Expr (l Expr)*
WhenInfix = when Expr LB {infix Expr => Expr} [else => Expr] RB | {- 2D part -}
WhenEq = when Expr LB { Expr => Expr } [else => Expr] | {- 2D part -}
When: Expr = WhenBr, WhenInfix, WhenEQ

Var = var Name : Expr
Call = Name (L Args R) [ Block ] : Expr
Infix = Expr infix Expr : Expr {- 大意,因为分 infixl/infixr - }

其中,EQ 的判断方法是 eval(branch).equals(subject), 方便支持模式匹配

2D 表示不需要 where 语法,就可以判定使用基于缩进的块

(=) (+) (-) (*) (/) (mod)
(>) (>=) (<) (<=) (==) (!=)

我们不把负数当数,所以没有类似 Kotlin 的 unaryMinus 语法,但有一个 neg 一元操作符(还是 std.prelude 里面的)
inv 操作没有对应语法

Literal:
Bool true false
Int 0xAF 1_23 0b10 0xFFl
其中,下划线不能连续出现
Real 0.1 0.1f 1.0e10
Str "abc" "User($name)" "${1+1}" "\t\v\a\b\n\r\f" "\x00" "\u0000" "\U0000_0000" (<<heredoc ...)
注意这里 \U 中间的划线是必须写上的

Name (任何在 Java 中有效的标识符,除了包含 $ 的部分) `name` (建模的时候我把标识符也考虑进去了,虽然它表示对存储的引用,我觉得必要时它也是值……)
上面那两条消息就是 #Reflex 的基本设计了,那 numpreety.js 也不是多好写,我先写点看看 🤔
JavaScript 真是辣鸡,我去找点 ES6 to 5 的翻译器,结果我只找到 Babel 和 traceur 两个,而且 traceur 的那个还一年没更了
Babel 的那个根本不能即装即用,亏得它还弄的花里胡哨的彩色啊…… 还啥啥啥定律,我看以后不如用 Kotlin.js 写好了

ES6 都麻烦的要死,为毛现在还是 Next,明明是必品啊,TMD 我写单文件就麻烦,node 本来已经只是包装了还包装不好,看看人家 Ruby
还是巨硬的 TS 好,没被 JavaScript 的辣鸡社区带坏
虽然不如 Kotlin,还是 TS 更可心。
虽然类型推导很差,和 Kotlin 也还算是 VSCode 和 IntelliJ IDEA 比啦,设计上类型系统的确是强大,不过使用起来效果不好,我为啥要给类型签名写参数的名字???
已经足够把 babel 那一些不方便的 ES6 换掉了,虽然不如 Kotlin 好用
Google 不知道是怎么搞的,难道 Google 的前端都是吃干饭的?怎么才弄出那一点东西还不维护了,NPM 上都是几年前更新
numpreety.ts
3.6 KB
numpreety.ts
3.6 KB
等等我发 Gist…… 💭
numpreety.es6
4.5 KB
tsc numpreety.ts --out numpreety.es6 欸不错啊,体积这么小