> Configure project :
Kotlin Multiplatform Projects are an experimental feature.
> Task :compileKotlinJvm
> Task :jvmProcessResources NO-SOURCE
> Task :jvmMainClasses
> Task :compileTestKotlinJvm
> Task :jvmTestProcessResources NO-SOURCE
> Task :jvmTestClasses
> Task :jvmTest
BUILD SUCCESSFUL in 11s
3 actionable tasks: 3 executed
17:20:59: Task execution finished 'jvmTest'.
一看到 All Passed 兴奋的
我该去吃饭了,呼。
啊不对,已经九点了,我好像没吃晚饭
艹,说不定超市已经关门了
这么晚不吃饭吃零食会不会长胖
(反正我也很瘦…… 怎么个受瘦法呢,上面的那一张里齐景轩很瘦,就体型来说我和他一样瘦…… 艹)
啊不对,已经九点了,我好像没吃晚饭
艹,说不定超市已经关门了
这么晚不吃饭吃零食会不会长胖
(反正我也很瘦…… 怎么个
马上开源来的
明天可以写基于它们的解释器(虽然 RingBuffer 是凑数的(其实没有凑数的,都 TMD 是我的心血啊!!!
可是我不想写,虽然很简单
可是我觉得也有必要写,可是没时间了,艹 *3
其实我觉得 Parser.kt 的一部分代码,写出来都会很有意思,体积不知道怎么样,应该没问题
明天可以写基于它们的解释器(虽然 RingBuffer 是凑数的(其实没有凑数的,都 TMD 是我的心血啊!!!
可是我不想写,虽然很简单
可是我觉得也有必要写,可是没时间了,艹 *3
其实我觉得 Parser.kt 的一部分代码,写出来都会很有意思,体积不知道怎么样,应该没问题
我不知道是因为时间太少、还是任务太多、还是上机的时间太珍贵、还是我自控力差,为什么我第一次有这种看瞎眼的感觉?我开了红移(redshift)…… 🤔
This media is not supported in your browser
VIEW IN TELEGRAM
这可是我的血汗码,得加上 GPLv3,不能让妮妮萌萌之类的光明正大拿去抄了,艹
有一个方法是写测试(持续开发持续测试持续集成…… 你可以利用面向对象多态 moke 模拟对象,或者是像 Aqullian 测试组那本 CEDJ Java 书一样,真实在本地自动部署测试环境执行测试),还有一个更好的方法是不写 bug,当然这个是那些欠打的 Haskell 程序员说的,他们连 syntax error 都不屑给你指出。
当然对我来说,我觉得任何应用程序都是不应该写测试的(因为他们不应该能够让你写错代码,无论是从工具的角度还是文档的角度);绝大部分算法和框架的测试都应该可以用 Haskell
文档懒得读可以尝试和作者从一个角度想问题,而不是被动接受别人的解释,比如:
当你看到 gradle 的
当你看到
当你写复用库的时候,应该从用户的角度看问题,比如:你要定义一个
此时,你发现这个 (getter) 方法是纯函数,它没有副作用,所以你定义
很多其他情况你考虑一些诸如『是不是提供 vararg?』『是不是用
当然对我来说,我觉得任何应用程序都是不应该写测试的(因为他们不应该能够让你写错代码,无论是从工具的角度还是文档的角度);绝大部分算法和框架的测试都应该可以用 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 是什么意思?』的时候一样有效
duangsuse::Echo
有一个方法是写测试(持续开发持续测试持续集成…… 你可以利用面向对象多态 moke 模拟对象,或者是像 Aqullian 测试组那本 CEDJ Java 书一样,真实在本地自动部署测试环境执行测试),还有一个更好的方法是不写 bug,当然这个是那些欠打的 Haskell 程序员说的,他们连 syntax error 都不屑给你指出。 当然对我来说,我觉得任何应用程序都是不应该写测试的(因为他们不应该能够让你写错代码,无论是从工具的角度还是文档的角度);绝大部分算法和框架的测试都应该可以用 Haskell…
Haskell 里一个可用的
*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 里想写可能更简单
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 里看起来是这样的(我没隐式把
// 用于支持下面 DSL 的代码在 Java 里都是 Boilerplate,还写了一大堆
// 这里,我们要处理一个列表,使得所有插件 artifact 被解析到一个 location,你的程序会加入 Gradle 的这个内部过程,它会给你一个内部的待处理流,让你利用 side effect 来设置你解析到结果
//
}
}
}
这里,首先模型一个和 Gradle 它有差别的就是:Gradle 里你只能用
那加一个模板类,就可以弄出
可是
这样就可以了(实际上也就是把之前该父类型实例的 this 移动到了
知识点:那这个
实际的对应 Java 代码里,类型参数 C 是一个 Type wildcard
因为你看,我们这个
任何使用
这种情况就是泛型型变的『协变』(covariant),在面向对象参数化多态(parametric polymorphism) 的领域里,就是说
比如,
比如,
此外,Kotlin 的类型投影(就是
换句话说,
>>> System::exit is Function1<*, *>
true
不过有时候也不能全用生产-消费者(PECS, producer-extends, consumer-super)模型的
这样就不行了,至于更高级的(比如,
而应用程序写的不正确,则往往是你基本功没做好的缘故,也或许是框架文档不好 / 写的太复杂的缘故
我举个例子,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 #Kotlinout 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 的)型变性下次有机会再讲。