<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>shsuco</title><description>blog</description><link>https://shsuco.com/</link><language>zh_CN</language><item><title>常见编程语言文档及新特性链接</title><link>https://shsuco.com/posts/%E5%B8%B8%E8%A7%81%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80%E6%96%87%E6%A1%A3%E5%8F%8A%E6%96%B0%E7%89%B9%E6%80%A7%E9%93%BE%E6%8E%A5/</link><guid isPermaLink="true">https://shsuco.com/posts/%E5%B8%B8%E8%A7%81%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80%E6%96%87%E6%A1%A3%E5%8F%8A%E6%96%B0%E7%89%B9%E6%80%A7%E9%93%BE%E6%8E%A5/</guid><pubDate>Wed, 07 Feb 2024 14:32:38 GMT</pubDate><content:encoded>&lt;h1&gt;Java&lt;/h1&gt;
&lt;p&gt;https://www.java.com/&lt;/p&gt;
&lt;h2&gt;Documentation&lt;/h2&gt;
&lt;p&gt;https://docs.oracle.com/en/java/javase/index.html&lt;/p&gt;
&lt;h2&gt;Changes&lt;/h2&gt;
&lt;p&gt;9-21:
https://docs.oracle.com/en/java/javase/21/language/java-language-changes.html&lt;/p&gt;
&lt;p&gt;1.8:
https://www.oracle.com/java/technologies/javase/8-whats-new.html&lt;/p&gt;
&lt;p&gt;1.7:
https://www.oracle.com/java/technologies/javase/7u-relnotes.html&lt;/p&gt;
&lt;h1&gt;Kotlin&lt;/h1&gt;
&lt;p&gt;https://kotlinlang.org&lt;/p&gt;
&lt;h2&gt;Documentation&lt;/h2&gt;
&lt;p&gt;https://kotlinlang.org/docs/home.html&lt;/p&gt;
&lt;h2&gt;Changes&lt;/h2&gt;
&lt;p&gt;https://kotlinlang.org/docs/whatsnew1920.html#0&lt;/p&gt;
&lt;h1&gt;Swift&lt;/h1&gt;
&lt;p&gt;https://www.swift.org&lt;/p&gt;
&lt;h2&gt;Documentation&lt;/h2&gt;
&lt;p&gt;https://www.swift.org/documentation/&lt;/p&gt;
&lt;h2&gt;Changes&lt;/h2&gt;
&lt;p&gt;https://www.swift.org/swift-evolution/
https://github.com/apple/swift/blob/main/CHANGELOG.md&lt;/p&gt;
&lt;h1&gt;Objective-C&lt;/h1&gt;
&lt;h2&gt;Documentation&lt;/h2&gt;
&lt;p&gt;https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/Introduction/Introduction.html#//apple_ref/doc/uid/TP40011210&lt;/p&gt;
&lt;p&gt;https://developer.apple.com/documentation/technologies&lt;/p&gt;
&lt;h2&gt;Changes&lt;/h2&gt;
&lt;p&gt;https://developer.apple.com/library/archive/releasenotes/ObjectiveC/ModernizationObjC/AdoptingModernObjective-C/AdoptingModernObjective-C.html&lt;/p&gt;
&lt;h1&gt;C&lt;/h1&gt;
&lt;p&gt;https://en.cppreference.com/w/c&lt;/p&gt;
&lt;h1&gt;C++&lt;/h1&gt;
&lt;p&gt;https://en.cppreference.com/w/cpp&lt;/p&gt;
&lt;h1&gt;Rust&lt;/h1&gt;
&lt;p&gt;https://www.rust-lang.org&lt;/p&gt;
&lt;h2&gt;Documentation&lt;/h2&gt;
&lt;p&gt;https://www.rust-lang.org/learn&lt;/p&gt;
&lt;h2&gt;Changes&lt;/h2&gt;
&lt;p&gt;https://github.com/rust-lang/rust/blob/master/RELEASES.md&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- more --&amp;gt;&lt;/p&gt;
&lt;h1&gt;Go&lt;/h1&gt;
&lt;p&gt;https://go.dev&lt;/p&gt;
&lt;h2&gt;Documentation&lt;/h2&gt;
&lt;p&gt;https://go.dev/doc/&lt;/p&gt;
&lt;h2&gt;Changes&lt;/h2&gt;
&lt;p&gt;https://go.dev/doc/devel/release&lt;/p&gt;
&lt;h1&gt;Scala&lt;/h1&gt;
&lt;p&gt;https://www.scala-lang.org&lt;/p&gt;
&lt;h2&gt;Documentation&lt;/h2&gt;
&lt;p&gt;https://docs.scala-lang.org&lt;/p&gt;
&lt;h2&gt;Changes&lt;/h2&gt;
&lt;p&gt;https://docs.scala-lang.org/scala3/new-in-scala3.html&lt;/p&gt;
&lt;h1&gt;Groovy&lt;/h1&gt;
&lt;p&gt;https://groovy-lang.org&lt;/p&gt;
&lt;h2&gt;Documentation&lt;/h2&gt;
&lt;p&gt;https://groovy-lang.org/documentation.html&lt;/p&gt;
&lt;h2&gt;Changes&lt;/h2&gt;
&lt;p&gt;http://groovy-lang.org/releases.html
http://groovy-lang.org/changelogs.html&lt;/p&gt;
&lt;h1&gt;Clojure&lt;/h1&gt;
&lt;p&gt;https://clojure.org&lt;/p&gt;
&lt;h1&gt;Haskell&lt;/h1&gt;
&lt;p&gt;https://www.haskell.org&lt;/p&gt;
&lt;h2&gt;Documentation&lt;/h2&gt;
&lt;p&gt;https://www.haskell.org/documentation/&lt;/p&gt;
&lt;h1&gt;Lisp&lt;/h1&gt;
&lt;h2&gt;Common Lisp&lt;/h2&gt;
&lt;p&gt;https://common-lisp.net
https://lisp-lang.org&lt;/p&gt;
&lt;h3&gt;Documentation&lt;/h3&gt;
&lt;p&gt;https://lisp-lang.org/wiki/
https://common-lisp.net/documentation&lt;/p&gt;
&lt;h2&gt;Racket&lt;/h2&gt;
&lt;p&gt;https://racket-lang.org&lt;/p&gt;
&lt;h3&gt;Documentation&lt;/h3&gt;
&lt;p&gt;https://docs.racket-lang.org&lt;/p&gt;
&lt;h1&gt;OCaml&lt;/h1&gt;
&lt;p&gt;https://ocaml.org&lt;/p&gt;
&lt;h2&gt;Documentation&lt;/h2&gt;
&lt;p&gt;https://ocaml.org/docs&lt;/p&gt;
&lt;h2&gt;Changes&lt;/h2&gt;
&lt;p&gt;https://ocaml.org/releases
https://ocaml.org/changelog&lt;/p&gt;
&lt;h1&gt;Idris&lt;/h1&gt;
&lt;p&gt;https://www.idris-lang.org&lt;/p&gt;
&lt;h2&gt;Documentation&lt;/h2&gt;
&lt;p&gt;https://www.idris-lang.org/pages/documentation.html&lt;/p&gt;
&lt;h2&gt;Changes&lt;/h2&gt;
&lt;p&gt;https://www.idris-lang.org/category/news.html&lt;/p&gt;
&lt;h1&gt;Python&lt;/h1&gt;
&lt;p&gt;https://www.python.org&lt;/p&gt;
&lt;h2&gt;Documentation&lt;/h2&gt;
&lt;p&gt;https://docs.python.org/3/&lt;/p&gt;
&lt;h2&gt;Changes&lt;/h2&gt;
&lt;p&gt;https://docs.python.org/3/whatsnew/index.html&lt;/p&gt;
&lt;h1&gt;Ruby&lt;/h1&gt;
&lt;p&gt;https://www.ruby-lang.org/en/&lt;/p&gt;
&lt;h2&gt;Documentation&lt;/h2&gt;
&lt;p&gt;https://www.ruby-lang.org/en/documentation/&lt;/p&gt;
&lt;h2&gt;Changes&lt;/h2&gt;
&lt;p&gt;https://www.ruby-lang.org/en/news/&lt;/p&gt;
&lt;h1&gt;Lua&lt;/h1&gt;
&lt;p&gt;https://www.lua.org&lt;/p&gt;
&lt;h2&gt;Documentation&lt;/h2&gt;
&lt;p&gt;https://www.lua.org/docs.html&lt;/p&gt;
&lt;h2&gt;Changes&lt;/h2&gt;
&lt;p&gt;https://www.lua.org/versions.html&lt;/p&gt;
&lt;h1&gt;Perl&lt;/h1&gt;
&lt;p&gt;https://www.perl.org&lt;/p&gt;
&lt;h2&gt;Documentation&lt;/h2&gt;
&lt;p&gt;https://www.perl.org/docs.html&lt;/p&gt;
&lt;h2&gt;Changes&lt;/h2&gt;
&lt;p&gt;https://dev.perl.org/perl5/news/&lt;/p&gt;
&lt;h1&gt;C#&lt;/h1&gt;
&lt;p&gt;https://dotnet.microsoft.com/en-us/languages/csharp&lt;/p&gt;
&lt;h2&gt;Documentation&lt;/h2&gt;
&lt;p&gt;https://learn.microsoft.com/zh-cn/dotnet/csharp/&lt;/p&gt;
&lt;h2&gt;Changes&lt;/h2&gt;
&lt;p&gt;https://learn.microsoft.com/zh-cn/dotnet/csharp/whats-new/&lt;/p&gt;
&lt;h1&gt;F#&lt;/h1&gt;
&lt;p&gt;https://fsharp.org&lt;/p&gt;
&lt;h2&gt;Documentation&lt;/h2&gt;
&lt;p&gt;https://fsharp.org/docs/&lt;/p&gt;
&lt;h1&gt;Zig&lt;/h1&gt;
&lt;p&gt;https://ziglang.org&lt;/p&gt;
&lt;h2&gt;Documentation&lt;/h2&gt;
&lt;p&gt;https://ziglang.org/documentation/&lt;/p&gt;
&lt;h2&gt;Changes&lt;/h2&gt;
&lt;p&gt;https://ziglang.org/download/release-notes.html&lt;/p&gt;
&lt;h1&gt;JavaScript&lt;/h1&gt;
&lt;p&gt;https://developer.mozilla.org/en-US/docs/Web/JavaScript
https://developer.mozilla.org/en-US/docs/Glossary/ECMAScript&lt;/p&gt;
&lt;h1&gt;TypeScript&lt;/h1&gt;
&lt;p&gt;https://www.typescriptlang.org&lt;/p&gt;
&lt;h2&gt;Documentation&lt;/h2&gt;
&lt;p&gt;https://www.typescriptlang.org/docs/&lt;/p&gt;
&lt;h2&gt;Changes&lt;/h2&gt;
&lt;p&gt;https://www.typescriptlang.org/docs/handbook/release-notes/&lt;/p&gt;
&lt;h1&gt;Dart&lt;/h1&gt;
&lt;p&gt;https://dart.dev&lt;/p&gt;
&lt;h2&gt;Documentation&lt;/h2&gt;
&lt;p&gt;https://dart.dev/guides&lt;/p&gt;
&lt;h2&gt;Changes&lt;/h2&gt;
&lt;p&gt;https://dart.dev/guides/whats-new&lt;/p&gt;
&lt;h1&gt;Reason&lt;/h1&gt;
&lt;p&gt;https://reasonml.github.io&lt;/p&gt;
&lt;h2&gt;Documentation&lt;/h2&gt;
&lt;p&gt;https://reasonml.github.io/docs/en/what-and-why&lt;/p&gt;
&lt;h1&gt;Flow&lt;/h1&gt;
&lt;p&gt;https://flow.org&lt;/p&gt;
&lt;h2&gt;Documentation&lt;/h2&gt;
&lt;p&gt;https://flow.org/en/docs/&lt;/p&gt;
&lt;h1&gt;CoffeeScript&lt;/h1&gt;
&lt;p&gt;https://coffeescript.org&lt;/p&gt;
&lt;h1&gt;ClojureScript&lt;/h1&gt;
&lt;p&gt;https://clojurescript.org&lt;/p&gt;
&lt;h1&gt;Erlang&lt;/h1&gt;
&lt;p&gt;https://www.erlang.org&lt;/p&gt;
&lt;h2&gt;Documentation&lt;/h2&gt;
&lt;p&gt;https://www.erlang.org/docs&lt;/p&gt;
&lt;h2&gt;Changes&lt;/h2&gt;
&lt;p&gt;https://www.erlang.org/news&lt;/p&gt;
&lt;h1&gt;Vala&lt;/h1&gt;
&lt;p&gt;https://vala.dev&lt;/p&gt;
&lt;h2&gt;Documentation&lt;/h2&gt;
&lt;p&gt;https://wiki.gnome.org/Projects/Vala/Documentation&lt;/p&gt;
</content:encoded></item><item><title>命令行时间戳与时间转换</title><link>https://shsuco.com/posts/%E5%91%BD%E4%BB%A4%E8%A1%8C%E6%97%B6%E9%97%B4%E6%88%B3%E4%B8%8E%E6%97%B6%E9%97%B4%E8%BD%AC%E6%8D%A2/</link><guid isPermaLink="true">https://shsuco.com/posts/%E5%91%BD%E4%BB%A4%E8%A1%8C%E6%97%B6%E9%97%B4%E6%88%B3%E4%B8%8E%E6%97%B6%E9%97%B4%E8%BD%AC%E6%8D%A2/</guid><pubDate>Wed, 18 Oct 2023 10:38:03 GMT</pubDate><content:encoded>&lt;h2&gt;Linux&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;date -d @&amp;lt;timestamp&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;macOS / FreeBSD&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;date -r &amp;lt;timestamp&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;PowerShell&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;[datetime]::UnixEpoch.AddSeconds(&amp;lt;timestamp&amp;gt;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Zsh&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;strftime &apos;%Y-%m-%d %H:%M:%S&apos; &amp;lt;timestamp&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Python&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;python3 -c &quot;import sys, time; print(time.strftime(&apos;%Y-%m-%d %H:%M:%S&apos;, time.localtime(int(sys.argv[1]))))&quot; &amp;lt;timestamp&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Perl&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;perl -MPOSIX -le &apos;print strftime &quot;%Y-%m-%d %H:%M:%S&quot;, localtime($ARGV[0])&apos; &amp;lt;timestamp&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Ruby&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;ruby -e &apos;puts Time.at(ARGV[0].to_i).strftime(&quot;%Y-%m-%d %H:%M:%S&quot;)&apos; &amp;lt;timestamp&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Groovy&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;groovy -e &quot;println new Date(args[0] as long).format(&apos;yyyy-MM-dd HH:mm:ss&apos;)&quot; &amp;lt;timestamp&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Node&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;node -e &quot;console.log(new Date(parseInt(process.argv[1])).toLocaleString())&quot; &amp;lt;timestamp&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>存在类型 (existential type)</title><link>https://shsuco.com/posts/%E5%AD%98%E5%9C%A8%E7%B1%BB%E5%9E%8B-existential-type/</link><guid isPermaLink="true">https://shsuco.com/posts/%E5%AD%98%E5%9C%A8%E7%B1%BB%E5%9E%8B-existential-type/</guid><pubDate>Thu, 06 Jul 2023 11:29:27 GMT</pubDate><content:encoded>&lt;p&gt;存在类型(existential type), 表示某个类型存在, 设Trait A, 则&lt;code&gt;some A&lt;/code&gt;表示存在某个类型符合A特征;
与存在类型相对应的为任意类型, &lt;code&gt;any A&lt;/code&gt;表示负责A特征的任意类型.&lt;/p&gt;
&lt;p&gt;设B &amp;lt;: A, C &amp;lt;: A, 则List[some A]可以是List[B]或List[C], List中元素皆为同一类型; 与之对应, List[any A]则表示该List可以存储B或C类型, List中元素不需要为同一类型.&lt;/p&gt;
&lt;p&gt;通常, 存在类型可以编译期确定, 而任意类型需要运行期确定, 所以存在类型可以静态派发, 任意类型一般需要动态派发, 因此使用存在类型代替任意类型可以提高效率.&lt;/p&gt;
&lt;h2&gt;Rust存在类型&lt;/h2&gt;
&lt;p&gt;设有Trait A:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;trait A {...}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Rust以&lt;code&gt;impl A&lt;/code&gt;表示存在类型:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fn foo(a: impl A) {...}

fn bar(a: Vec&amp;lt;impl A&amp;gt;) {...}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这等价于&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fn foo&amp;lt;T: A&amp;gt;(a: T) {...}

fn bar&amp;lt;T: A&amp;gt;(a: Vec&amp;lt;T&amp;gt;) {...}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Rust以&lt;code&gt;dyn A&lt;/code&gt;表示任意类型:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fn foo(a: &amp;amp;dyn A) {...}

fn bar(a: Vec&amp;lt;&amp;amp;dyn A&amp;gt;) {...}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;或&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fn foo(a: Box&amp;lt;dyn A&amp;gt;) {...}

fn bar(a: Vec&amp;lt;Box&amp;lt;dyn A&amp;gt;&amp;gt;) {...}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;由于&lt;code&gt;dyn A&lt;/code&gt;的大小编译期未知, 所以需要使用引用, 或者使用指针例如&lt;code&gt;Box&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Swift存在类型(不透明类型)&lt;/h2&gt;
&lt;p&gt;设有Protocol A:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;protocol A {...}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Swift以不透明类型表示存在类型, 与Rust非常相似:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func foo(a: some A) {...}

func bar(a: Array&amp;lt;some A&amp;gt;) {...}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;类似的, 这也等价于&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func foo&amp;lt;T: A&amp;gt;(a: T) {...}

func bar&amp;lt;T: A&amp;gt;(a: Array&amp;lt;T&amp;gt;) {...}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Swift以&lt;code&gt;any A&lt;/code&gt;表示任意类型:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func foo(a: any A) {...}

func bar(a: Array&amp;lt;any A&amp;gt;) {...}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;C++存在类型&lt;/h2&gt;
&lt;p&gt;C++中可以使用模板表示存在类型:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;template&amp;lt;typename T&amp;gt;
auto foo(T) -&amp;gt; void {...}

template&amp;lt;typename T&amp;gt;
auto bar(std::span&amp;lt;T&amp;gt;) -&amp;gt; void {...}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也可以加入concept约束&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;template&amp;lt;typename T&amp;gt;
concept A = requires(T a) {...};

template&amp;lt;A T&amp;gt;
auto foo(T) -&amp;gt; void {...}

template&amp;lt;A T&amp;gt;
auto bar(std::span&amp;lt;T&amp;gt;) -&amp;gt; void {...}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;C++中任意类型和存在类型无法统一, 因为模板和concept只能用于静态约束, 任意类型需要动态约束, 可以用继承:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class A {...};

auto foo(A&amp;amp;) -&amp;gt; void {...}  // 可以传入任意A的子类型

auto bar(std::span&amp;lt;A&amp;amp;&amp;gt;) -&amp;gt; void {...} // span中可以存储任意A的子类型引用
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中引用也可以替换为指针或者智能指针.&lt;/p&gt;
&lt;h2&gt;参考链接&lt;/h2&gt;
&lt;p&gt;https://en.wikipedia.org/wiki/Type_system#Existential_types&lt;/p&gt;
</content:encoded></item><item><title>多态 (polymorphism)</title><link>https://shsuco.com/posts/%E5%A4%9A%E6%80%81/</link><guid isPermaLink="true">https://shsuco.com/posts/%E5%A4%9A%E6%80%81/</guid><pubDate>Thu, 06 Jul 2023 09:42:37 GMT</pubDate><content:encoded>&lt;p&gt;多态 (polymorphism) 指为不同数据类型的实体提供统一的接口, 或使用一个单一的符号来表示多个不同的类型.&lt;/p&gt;
&lt;h2&gt;子类型多态 (subtyping)&lt;/h2&gt;
&lt;p&gt;面向对象中经常提到的多态为子类型多态, 如C++的虚函数&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class A {
public:
    virtual void f() {}
};

class B : public A { 
public :
    virtual void f() {}
};

int main() {
    B b{};
    A &amp;amp;a = b;
    a.f();
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一般采用虚函数表来实现, 每个含有虚函数(包括继承得到)的类都会有一个对应的虚函数表, 存储虚函数对应指针,
编译器会为这些类型添加一个指针字段指向对应的虚函数表. 虚函数调用时, 通过该指针找到虚函数表, 再调用目标函数.&lt;/p&gt;
&lt;p&gt;需要注意子类型并非与继承强绑定. 设类型A, B, 如果需要用到A的位置皆可由B替代, 则B是A的子类型, 即&lt;code&gt;B &amp;lt;: A&lt;/code&gt;.
例如存在隐式转换&lt;code&gt;int -&amp;gt; long&lt;/code&gt;, 则 &lt;code&gt;int &amp;lt;: long&lt;/code&gt;; 协变情况下 &lt;code&gt;List[B] &amp;lt;: List[A]&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;特设多态 (Ad hoc)&lt;/h2&gt;
&lt;p&gt;特设多态即针对同一调用不同类型有不同实现, 例如函数重载:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def add(a: Int, b: Int) = a + b

def add(a: String, b: String) = a ++ b
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Rust中的Trait:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;trait Add {
    fn add(self, rhs: Self) -&amp;gt; Self;
}

struct A;
struct B;

impl Add for A {...}
impl Add for B {...}

fn main {
    let a = A;
    let b = B;
    a.add(a);
    b.add(b);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此外, C++模板特化, C++ concept, Go interface, Swift protocol等也可表现特设多态.&lt;/p&gt;
&lt;h2&gt;参数多态 (Parametric polymorphism)&lt;/h2&gt;
&lt;p&gt;参数多态即将类型参数化, 进而做统一处理.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def append[T](a: List[T], b: List[T]) = a ++ b
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;def add[T: Add](a: T, b: T) = a.add(b)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;静态多态与动态多态&lt;/h2&gt;
&lt;p&gt;静态多态生效于编译期, 例如C++模板, Rust静态Trait, Swift不透明类型, 静态派发的函数重载&lt;/p&gt;
&lt;p&gt;动态多态生效于运行时, 例如虚函数, Rust动态Trait, Go interface, 动态派发的函数重载&lt;/p&gt;
&lt;h2&gt;参考资料&lt;/h2&gt;
&lt;p&gt;https://zh.wikipedia.org/zh-cn/多态_(计算机科学)&lt;/p&gt;
</content:encoded></item><item><title>SSH 端口转发</title><link>https://shsuco.com/posts/ssh%E7%AB%AF%E5%8F%A3%E8%BD%AC%E5%8F%91/</link><guid isPermaLink="true">https://shsuco.com/posts/ssh%E7%AB%AF%E5%8F%A3%E8%BD%AC%E5%8F%91/</guid><pubDate>Fri, 26 May 2023 14:38:58 GMT</pubDate><content:encoded>&lt;h2&gt;本地转发&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;ssh -L [bind_address:]port:host:hostport destination
ssh -L [bind_address:]port:remote_socket destination
ssh -L local_socket:host:hostport destination
ssh -L local_socket:remote_socket destination
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;将本地(客户端)主机上的给定 TCP 端口或 Unix Socket 转发到远程端的给定主机和端口或 Unix Socket.&lt;/p&gt;
&lt;p&gt;例如, 服务器8080端口运行私有服务, 可以将本地8081端口转发给服务器8080端口:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ssh -N -L 8081:localhost:8080 user@host
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;即可通过访问localhost:8081来访问host:8080, 其中-N参数表示不进入交互式Shell.&lt;/p&gt;
&lt;p&gt;假设有一内网服务器仅支持内外访问, 且没有运行ssh服务, 这时可以通过转发到另一台开启ssh服务的内网主机来访问:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ssh -N -L 8081:host1:8080 user@host2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;即可通过访问localhost:8081来访问host1:8080, 其中host2为开启ssh服务的另一台主机.&lt;/p&gt;
&lt;p&gt;以上命令皆省略了bind address, 默认绑定0.0.0.0, 也就是所有网络接口; 安全起见可以指明bind address明确访问范围:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ssh -N -L localhost:8080:host1:8080 user@host2    // 仅可通过localhost访问
ssh -N -L 192.168.1.1:8080:host1:8080 user@host2  // 监听当前内网ip, 仅可通过内网访问
...
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;远程转发&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;ssh -N -R [bind_address:]port:host:hostport destination
ssh -N -R [bind_address:]port:local_socket destination
ssh -N -R remote_socket:host:hostport destination
ssh -N -R remote_socket:local_socket destination
ssh -N -R [bind_address:]port destination
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;将远程(服务器)主机上的给定 TCP 端口或 Unix Socket 转发到本地.&lt;/p&gt;
&lt;p&gt;假设本地8080端口运行私有服务, 将host上的8081端口转发给本地8080:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ssh -R 8081:localhost:8080 user@host
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;即可通过访问host:8081来访问localhost:8080, 这可以用来实现内网穿透.&lt;/p&gt;
&lt;p&gt;类似地, 这里省略了bind address, 所以可以通过所以网络接口访问host该转发, 安全起见应指明bind address.&lt;/p&gt;
&lt;h2&gt;动态转发&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;ssh -N -D [bind_address:]port destination
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这会启动一个SOCKS代理服务器, 例如&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ssh -N -D 1080 user@host
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;启动SOCKS服务, 监听1080端口, 那么将SOCKS代理指定为localhost:1080, 会将流量转发到host, 由host发出, 例如&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -x socks5://localhost:1080 https://www.google.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;该请求会被host代理.&lt;/p&gt;
</content:encoded></item><item><title>Monad 浅析</title><link>https://shsuco.com/posts/monad%E6%B5%85%E6%9E%90/</link><guid isPermaLink="true">https://shsuco.com/posts/monad%E6%B5%85%E6%9E%90/</guid><description>本文结合范畴论和Haskell语言, 浅要分析Monad(单子)</description><pubDate>Sat, 20 May 2023 21:19:41 GMT</pubDate><content:encoded>&lt;p&gt;在Haskell中, 所有Monad都为Applicative函子, 而所有Applicative函子都为Functor, 即&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Applicative m =&amp;gt; Monad m

class Functor f =&amp;gt; Applicative f
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因此, 要想了解Monad, 首先要了解Functor和Applicative.&lt;/p&gt;
&lt;h2&gt;Functor, 函子&lt;/h2&gt;
&lt;h3&gt;引例&lt;/h3&gt;
&lt;p&gt;大多数语言拥有&lt;code&gt;空&lt;/code&gt;概念, 表示不存在的值, 例如C中的&lt;code&gt;NULL&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#define NULL ((void*)0)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;表示空指针, 可以转换为任意指针类型;&lt;/p&gt;
&lt;p&gt;在Scala中, &lt;code&gt;null&lt;/code&gt;表示空引用, 其类型为&lt;code&gt;Null&lt;/code&gt;, 是任意引用类型的子类型, 所以&lt;code&gt;null&lt;/code&gt;可以转换为任意引用类型:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;val foo: Null = null 
val bar: SomeRefType = null
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在Kotlin和Swift等语言中, 使用&lt;code&gt;SomeType?&lt;/code&gt;标识可空类型, 例如&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;val a: Int? = 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;本质上, 可以为空的类型为&lt;code&gt;空&lt;/code&gt;与任意类型的和类型, 在Haskell中实现为&lt;code&gt;Maybe&lt;/code&gt;类型:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;data  Maybe a  =  Nothing | Just a
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;假设有如下Kotlin定义&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;data class User(val name: String)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于其实例&lt;code&gt;user&lt;/code&gt;, 可以通过&lt;code&gt;user.name&lt;/code&gt;访问name, 即&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;User -&amp;gt; String  // user.name 中的 &quot;.&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于可空类型&lt;code&gt;User?&lt;/code&gt;, 考虑safe call运算符&lt;code&gt;?.&lt;/code&gt;,&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;val name = user?.name  // 从user中取出name, 否则返回null
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;则实际完成了以下操作:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Maybe User -&amp;gt; Maybe String  // user?.name 中的 &quot;?.&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也就是通过使用&lt;code&gt;?.&lt;/code&gt;运算符将&lt;code&gt;(User -&amp;gt; String)&lt;/code&gt;映射为&lt;code&gt;(Maybe User -&amp;gt; Maybe String)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;一般地, 对于任意函数&lt;code&gt;(a -&amp;gt; b)&lt;/code&gt;都可以映射得到&lt;code&gt;(Maybe a -&amp;gt; Maybe b)&lt;/code&gt;, 以fmap表示, 则可以有如下实现:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fmap :: (a -&amp;gt; b) -&amp;gt; Maybe a -&amp;gt; Maybe b
fmap _ Nothing       = Nothing
fmap f (Just a)      = Just (f a)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Functor包含了对这种映射的抽象. 事实上, Maybe也是最简单的Functor.&lt;/p&gt;
&lt;h3&gt;定义&lt;/h3&gt;
&lt;p&gt;函子在范畴论中定义如下:&lt;/p&gt;
&lt;p&gt;设$$C$$和$$D$$为范畴, 从$$C$$至$$D$$的函子为一映射$$F$$:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;将每个对象$$X \in C$$映射至一对象$$F(X) \in D$$上&lt;/li&gt;
&lt;li&gt;将每个态射$$f:X\rightarrow Y \in C$$映射至一态射$$F(f):F(X) \rightarrow F(Y) \in D$$上, 使之满足下列条件:
2.1. 对任何对象$$X \in C$$, 恒有$${\displaystyle F(\mathbf {id} _{X})=\mathbf {id} _{F(X)}}$$
2.2. 对任何态射$$f: X \to Y, ; g: Y \to Z$$, 恒有$$F(g \circ f) = F(g) \circ F(f)$$.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;换言之, 函子会保持单位态射与态射的复合.
一个由一范畴映射至其自身的函子称之为“自函子”.&lt;/p&gt;
&lt;p&gt;以Maybe为例:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1 即为构造 &lt;code&gt;Just&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;2 为函数 &lt;code&gt;fmap&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;2.1 表示不变 &lt;code&gt;fmap id === id&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;2.2 表示组合 &lt;code&gt;fmap (f . g) === (fmap f) . (fmap g)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在Haskell中, Functor表示为类型类:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Functor f where
    fmap :: (a -&amp;gt; b) -&amp;gt; f a -&amp;gt; f b
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;成为Functor需要满足条件2.1与2.2; 这里可以验证Maybe的上述fmap实现满足条件.&lt;/p&gt;
&lt;h2&gt;Applicative函子&lt;/h2&gt;
&lt;p&gt;假设有如下Kotlin定义&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;val f: ((A) -&amp;gt; B)? = null
val a: A? = null
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;那么将f施用给a会有点麻烦, 可以使用以下表达式:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;val g: (A?) -&amp;gt; B? = { a -&amp;gt; a?.let { f?.invoke(it) }  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;完成&lt;code&gt;((A) -&amp;gt; B)?&lt;/code&gt;到&lt;code&gt;(A?) -&amp;gt; B?&lt;/code&gt;的映射, Applicative函子包含了对这种映射的抽象.&lt;/p&gt;
&lt;h3&gt;定义&lt;/h3&gt;
&lt;p&gt;Applicative是Functor和Monad的中间结构, 在Haskell标识为类型类:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Functor f =&amp;gt; Applicative f where
    pure :: a -&amp;gt; f a
    (&amp;lt;*&amp;gt;) :: f (a -&amp;gt; b) -&amp;gt; f a -&amp;gt; f b
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;pure&lt;/code&gt;将值装入Applicative, 对应Maybe即为&lt;code&gt;Just&lt;/code&gt;;
&lt;code&gt;(&amp;lt;*&amp;gt;)&lt;/code&gt;将装入Applicative的函数映射为装入Applicative中的值间的函数, 对应Maybe为&lt;code&gt;Maybe (a -&amp;gt; b) -&amp;gt; Maybe a -&amp;gt; Maybe b&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;成为Applicative函子需满足以下条件:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pure id &amp;lt;*&amp;gt; v = v                            // 不变
pure (.) &amp;lt;*&amp;gt; u &amp;lt;*&amp;gt; v &amp;lt;*&amp;gt; w = u &amp;lt;*&amp;gt; (v &amp;lt;*&amp;gt; w) // 组合
pure f &amp;lt;*&amp;gt; pure x = pure (f x)               // 同态
u &amp;lt;*&amp;gt; pure y = pure ($ y) &amp;lt;*&amp;gt; u              // 交换
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Maybe的Applicative实现:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;instance Applicative Maybe where
    pure = Just

    Just f  &amp;lt;*&amp;gt; m       = fmap f m
    Nothing &amp;lt;*&amp;gt; _m      = Nothing
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Applicative使函子拥有了将装入函子的函数应用给装入函子的值的能力.&lt;/p&gt;
&lt;h2&gt;Monad, 单子&lt;/h2&gt;
&lt;h3&gt;引例&lt;/h3&gt;
&lt;p&gt;假设有如下Kotlin定义&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;data class User(val name: String?)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;那么对于可空类型User?实例user, 调用safe call运算符“user?.name”时, 理论上会得到String??类型, 但是“可以为空的可空类型”理应与可空类型等价, 所以String??自动折叠为String?类型.&lt;/p&gt;
&lt;p&gt;在Haskell中用Maybe来表示, 即&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(Just a | Nothing) | Nothing === Just a | Nothing
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也就是&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Maybe (Maybe String) === Maybe String
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但事实并非如此, &lt;code&gt;Maybe (Maybe String)&lt;/code&gt; 与 &lt;code&gt;Maybe String&lt;/code&gt; 是两种不同的类型; Monad则可以进行这种折叠操作, 或者避免出现这种嵌套类型.&lt;/p&gt;
&lt;h3&gt;定义&lt;/h3&gt;
&lt;p&gt;Monad在范畴论中定义如下:&lt;/p&gt;
&lt;p&gt;$${\displaystyle {\mathcal {C}}}$$表示某范畴,
$${\displaystyle {\mathcal {C}}}$$上的单子是函子
$${\displaystyle T:{\mathcal {C}}\to {\mathcal {C}}}$$, 连同两个自然变换,
分别是单位$${\displaystyle \eta :1_{\mathcal {C}}\to T}$$(其中$${\displaystyle 1_{\mathcal {C}}}$$是$${\displaystyle {\mathcal {C}}}$$上的恒等函子)
与乘法$${\displaystyle \mu :T^{2}\to T}$$(其中$${\displaystyle T^{2}}$$是复$${\displaystyle T\circ T}$$, 亦是$${\displaystyle {\mathcal {C}}}$$到$${\displaystyle {\mathcal {C}}}$$的函子), 且要满足下列连贯条件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$${\displaystyle \mu \circ T\mu =\mu \circ \mu T}$$ (左右皆为
$${\displaystyle T^{3}\to T}$$的自然变换). 此处
$${\displaystyle T\mu }$$与
$${\displaystyle \mu T}$$经水平复合而得.&lt;/li&gt;
&lt;li&gt;$${\displaystyle \mu \circ T\eta =\mu \circ \eta T=1_{T}}$$ (两者皆为
$${\displaystyle T\to T}$$的自然变换). 此处
$${\displaystyle 1_{T}}$$表示由函子
$$T$$到自身的恒等自然变换.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在Haskell中, Monad表示为类型类:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Applicative m =&amp;gt; Monad m where
    (&amp;gt;&amp;gt;=)       :: forall a b. m a -&amp;gt; (a -&amp;gt; m b) -&amp;gt; m b
    return      :: a -&amp;gt; m a
    return      = pure
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;与范畴论定义对应如下:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;return构造即为单位$${\displaystyle \eta}$$&lt;/li&gt;
&lt;li&gt;(&amp;gt;&amp;gt;=)函数为乘法$${\displaystyle \mu}$$&lt;/li&gt;
&lt;li&gt;$${\displaystyle \mu \circ T\mu =\mu \circ \mu T}$$ 表示结合, 即 &lt;code&gt;m &amp;gt;&amp;gt;= (\x -&amp;gt; k x &amp;gt;&amp;gt;= h) === (m &amp;gt;&amp;gt;= k) &amp;gt;&amp;gt;= h&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;$${\displaystyle \mu \circ T\eta =\mu \circ \eta T=1_{T}}$$ 表示左右单位, 即 &lt;code&gt;m &amp;gt;&amp;gt;= return === m&lt;/code&gt; 和 &lt;code&gt;return a &amp;gt;&amp;gt;= k === k a&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Maybe的Monad可以如下实现:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;instance  Monad Maybe  where
    (Just x) &amp;gt;&amp;gt;= k      = k x
    Nothing  &amp;gt;&amp;gt;= _      = Nothing
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;回到引例, 我们可以这样对&lt;code&gt;Maybe (Maybe String)&lt;/code&gt;进行折叠:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;a :: Maybe (Maybe String)
a = Just (Just &quot;&quot;)

a&apos; :: Maybe String
a&apos; = a &amp;gt;&amp;gt;= id  // 常简写为join a
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;或者避免产生&lt;code&gt;Maybe (Maybe String)&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;user :: Maybe User
user = Just User {name = Just &quot;&quot;}

name&apos; :: Maybe String
name&apos; = user &amp;gt;&amp;gt;= name
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Monad是自函子范畴上的幺半群&lt;/h3&gt;
&lt;h4&gt;半群&lt;/h4&gt;
&lt;p&gt;定义:
集合S和其上的二元运算·:S×S→S。若·满足结合律，即：∀x,y,z∈S，有(x·y)·z=x·(y·z), 则称有序对(S,·)为半群, 运算·称为该半群的乘法.
之所以称为半群, 是因为半群只满足群的部分性质.&lt;/p&gt;
&lt;p&gt;在Haskell中, 半群用类型类Semigroup表示:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Semigroup a where
    (&amp;lt;&amp;gt;) :: a -&amp;gt; a -&amp;gt; a
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;(&amp;lt;&amp;gt;)&lt;/code&gt;为半群的二元运算.&lt;/p&gt;
&lt;h4&gt;幺半群 (单位半群)&lt;/h4&gt;
&lt;p&gt;幺半群即含有幺元(单位元)的半群, 即: ∃e∈S，使得∀s∈S, e·s=s·e=s, 则S称为幺半群(独异点).&lt;/p&gt;
&lt;p&gt;在Haskell中, 幺半群用Monoid表示:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Semigroup a =&amp;gt; Monoid a where
    mempty :: a
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;mempty&lt;/code&gt;为幺半群的单位元.&lt;/p&gt;
&lt;h4&gt;幺半群的例子&lt;/h4&gt;
&lt;h5&gt;(Int, *) e = 1&lt;/h5&gt;
&lt;p&gt;&lt;code&gt;1 * x == x * 1 == ×&lt;/code&gt;
&lt;code&gt;(a * b) * c == a * (b * c)&lt;/code&gt;&lt;/p&gt;
&lt;h5&gt;(Int, +) e = 0&lt;/h5&gt;
&lt;p&gt;&lt;code&gt;0 + x == x + 0 == x&lt;/code&gt;
&lt;code&gt;(a + b) + c == a + (b + c)&lt;/code&gt;&lt;/p&gt;
&lt;h5&gt;(String, ++) e = &quot;&quot;&lt;/h5&gt;
&lt;p&gt;&lt;code&gt;&quot;&quot; ++ x == x ++ &quot;&quot; == x&lt;/code&gt;
&lt;code&gt;(a ++ b) ++ c == a ++ (b ++ c)&lt;/code&gt;&lt;/p&gt;
&lt;h4&gt;Monad幺半群&lt;/h4&gt;
&lt;p&gt;Monad的范畴论定义中, 若将$$\mu $$当成幺半群的乘法, 则第一条公理类似幺半群的乘法结合律, 而第二条公理类似单位元的存在性(由$$\eta$$ 给出). 准确而言, $${\displaystyle {\mathcal {C}}}$$上的单子, 可以等价地定义为$${\displaystyle {\mathcal {C}}}$$的内函子范畴$${\displaystyle \mathbf {End} _{\mathcal {C}}}$$中的幺半群. 如此, 单子不仅在形式上具有与幺半群相似的公理, 甚而单子就是幺半群的特例.&lt;/p&gt;
&lt;p&gt;对于Haskell中的Monad, 其单位元为return, 乘法为(&amp;gt;&amp;gt;=):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;左单位元: &lt;code&gt;m &amp;gt;&amp;gt;= return === m&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;右单位元: &lt;code&gt;return a &amp;gt;&amp;gt;= k === k a&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;结合: &lt;code&gt;m &amp;gt;&amp;gt;= (\x -&amp;gt; k x &amp;gt;&amp;gt;= h) === (m &amp;gt;&amp;gt;= k) &amp;gt;&amp;gt;= h&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Monad例子&lt;/h2&gt;
&lt;h3&gt;List, 集合&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;data List a = Nil | Cons a (List a)

instance Functor List where
    fmap _ Nil = Nil
    fmap f (Cons x xs) = Cons (f x) (fmap f xs)

instance Semigroup (List a) where
    Nil &amp;lt;&amp;gt; xs = Nil  
    Cons x xs &amp;lt;&amp;gt; ys = Cons x (xs &amp;lt;&amp;gt; ys)

instance Monoid (List a) where
    mempty = Nil

instance Applicative List where
    pure x = Cons x Nil
    Nil &amp;lt;*&amp;gt; _ = Nil
    _ &amp;lt;*&amp;gt; Nil = Nil
    Cons f fs &amp;lt;*&amp;gt; Cons x xs = Cons (f x) (fs &amp;lt;*&amp;gt; xs)

instance Monad List where
    Nil &amp;gt;&amp;gt;= f = Nil
    Cons x xs &amp;gt;&amp;gt;= f = f x &amp;lt;&amp;gt; (xs &amp;gt;&amp;gt;= f)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Result, 错误处理&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;data Result e a = Err e | Ok a

instance Functor (Result e) where
    fmap _ (Err e) = Err e
    fmap f (Ok a) = Ok (f a)

instance Applicative (Result e) where
    pure = Ok
    Err e &amp;lt;*&amp;gt; _ = Err e
    _ &amp;lt;*&amp;gt; Err e = Err e
    Ok f &amp;lt;*&amp;gt; Ok a = Ok (f a)

instance Monad (Result e) where
    Err e &amp;gt;&amp;gt;= k = Err e
    Ok a &amp;gt;&amp;gt;= k = k a
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Promise, 异步&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;data Promise e a = Reject e | Resolve a
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;参考资料&lt;/h2&gt;
&lt;p&gt;https://zh.wikipedia.org/wiki/函子
https://zh.wikipedia.org/wiki/單子_(範疇論)
https://zh.wikipedia.org/wiki/半群
https://hackage.haskell.org/package/base-4.18.0.0/docs/src/GHC.Base.html&lt;/p&gt;
</content:encoded></item><item><title>Google IO 2023 记录 - Android 版本 Kotlin 的新动态</title><link>https://shsuco.com/posts/google-io-2023%E8%AE%B0%E5%BD%95-android%E7%89%88%E6%9C%ACkotlin%E7%9A%84%E6%96%B0%E5%8A%A8%E6%80%81/</link><guid isPermaLink="true">https://shsuco.com/posts/google-io-2023%E8%AE%B0%E5%BD%95-android%E7%89%88%E6%9C%ACkotlin%E7%9A%84%E6%96%B0%E5%8A%A8%E6%80%81/</guid><pubDate>Thu, 18 May 2023 14:47:52 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;说是新动态, 其实内容大家基本上都见过了&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;2017 Kotlin成为Android官方支持语言&lt;/p&gt;
&lt;p&gt;2019 Android开发Kotlin优先, 文档和库都是主要为Kotlin设计的&lt;/p&gt;
&lt;p&gt;如今, Play商店排行前1000中, 有95%使用Kotlin (包括依赖)&lt;/p&gt;
&lt;p&gt;70%应用本身使用Kotlin&lt;/p&gt;
&lt;p&gt;55%使用Kotlin协程&lt;/p&gt;
&lt;p&gt;Jetpack Compose 是完全为Kotlin设计的, 充分利用了Kotlin语言特性&lt;/p&gt;
&lt;p&gt;Google自2019将Kotlin普遍用于Android开发, 自2022年起将Kotlin用于服务端开发&lt;/p&gt;
&lt;p&gt;Google中数以千计的开发者编写Kotlin代码, 版本控制系统有超过1500万行Kotlin代码&lt;/p&gt;
&lt;h2&gt;Kotlin 2.0编译器 (K2编译器)&lt;/h2&gt;
&lt;h3&gt;性能提升两倍&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;./images/k2-performance.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;路线图&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;./images/k2-roadmap.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;See also&lt;/p&gt;
&lt;p&gt;https://blog.jetbrains.com/kotlin/2023/02/k2-kotlin-2-0/&lt;/p&gt;
&lt;p&gt;https://blog.jetbrains.com/kotlin/2021/10/the-road-to-the-k2-compiler/&lt;/p&gt;
&lt;h3&gt;测试K2编译器(Kotlin1.8.20)&lt;/h3&gt;
&lt;p&gt;使用命令行参数:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;-language-version 2.0&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;或者使用Gradle配置:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;kotlin {
   sourceSets.all {
       languageSettings {
           languageVersion = &quot;2.0&quot;
       }
   }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;See also&lt;/p&gt;
&lt;p&gt;https://kotlinlang.org/docs/whatsnew1820.html&lt;/p&gt;
&lt;h2&gt;Gradle Kotlin DSL&lt;/h2&gt;
&lt;p&gt;从Android Studio Giraffe开始, 项目模板将默认使用Gradle Kotlin DSL&lt;/p&gt;
&lt;p&gt;和GroovyDSL相比, 得益于使用更多的静态类型, Gradle Kotlin DSL可以有更多的代码提示&lt;/p&gt;
&lt;p&gt;并且可以通过转到定义查看更多文档&lt;/p&gt;
&lt;p&gt;See also&lt;/p&gt;
&lt;p&gt;https://docs.gradle.org/current/userguide/kotlin_dsl.html&lt;/p&gt;
&lt;h3&gt;Gradle版本目录(实验性功能)&lt;/h3&gt;
&lt;p&gt;使用TOML集中化管理依赖, 更加简明易维护&lt;/p&gt;
&lt;p&gt;示例ibs.versions.toml&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[versions]
com-android-application = &quot;8.1.0-alpha11&quot;
org-jetbrains-kotlin-android = &quot;1.7.20&quot;
core-ktx = &quot;1.9.0&quot;
junit = &quot;4.13.2&quot;
androidx-test-ext-junit = &quot;1.1.3&quot;
espresso-core = &quot;3.4.0&quot;
lifecycle-runtime-ktx = &quot;2.3.1&quot;
activity-compose = &quot;1.5.1&quot;
compose-bom = &quot;2022.10.00&quot;

[libraries]
core-ktx = { group = &quot;androidx.core&quot;, name = &quot;core-ktx&quot;, version.ref = &quot;core-ktx&quot; }
junit = { group = &quot;junit&quot;, name = &quot;junit&quot;, version.ref = &quot;junit&quot; }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;See also&lt;/p&gt;
&lt;p&gt;https://docs.gradle.org/current/userguide/platforms.html#sec:sharing-catalogs&lt;/p&gt;
&lt;p&gt;https://developer.android.com/build/migrate-to-catalogs&lt;/p&gt;
&lt;h2&gt;Kotlin Symbol Processing (KSP)&lt;/h2&gt;
&lt;h3&gt;kapt (Kotlin Annotation Processing Tool)&lt;/h3&gt;
&lt;p&gt;由kotlin代码生成Java stubs, 再由Java Annotation Processors处理&lt;/p&gt;
&lt;p&gt;存在问题: 速度慢&lt;/p&gt;
&lt;h3&gt;ksp: kapt替代方案&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;直接分析Kotlin代码&lt;/li&gt;
&lt;li&gt;速度提升两倍&lt;/li&gt;
&lt;li&gt;更好支持Kotlin特性(如可空类型)&lt;/li&gt;
&lt;li&gt;支持多平台项目&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;迁移到ksp&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;添加ksp插件&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;// 顶层声明插件版本
plugins {
    id &apos;com.google.devtools.ksp&apos; version &apos;1.8.10-1.0.9&apos; apply false
}

// 应用插件
plugins {
    id &apos;com.google.devtools.ksp&apos;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;以ksp替换kapt&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;dependencies {
    kapt &apos;androidx.room:room-compiler:2.5.0&apos;
    ksp &apos;androidx.room:room-compiler:2.5.0&apos;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意, 并非所有支持kapt的库都支持ksp, 需要库作者适配&lt;/p&gt;
&lt;p&gt;目前支持ksp的项目: Room, Glide, Moshi, etc.&lt;/p&gt;
&lt;p&gt;See also&lt;/p&gt;
&lt;p&gt;https://developer.android.com/build/migrate-to-ksp&lt;/p&gt;
&lt;h2&gt;Kotlin Multiplatform (Beta)&lt;/h2&gt;
&lt;h3&gt;使用Kotlin Multiplatform for Mobile(KMM)在Android和iOS中共享代码&lt;/h3&gt;
&lt;p&gt;See also&lt;/p&gt;
&lt;p&gt;https://kotlinlang.org/docs/multiplatform-mobile-getting-started.html&lt;/p&gt;
&lt;h3&gt;一些Jetpack库开始支持Kotlin Multiplatform:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Annotations 1.7.0-alpha2&lt;/li&gt;
&lt;li&gt;Collections 1.3.0-alpha4&lt;/li&gt;
&lt;li&gt;DataStore 1.1.0-alpha3&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;See also&lt;/p&gt;
&lt;p&gt;https://developer.android.com/kotlin/multiplatform?hl=zh-cn&lt;/p&gt;
&lt;p&gt;https://android-developers.googleblog.com/2023/04/whats-new-in-jetpack-multiplatform.html&lt;/p&gt;
</content:encoded></item><item><title>Zig 初见</title><link>https://shsuco.com/posts/zig%E5%88%9D%E8%A7%81/</link><guid isPermaLink="true">https://shsuco.com/posts/zig%E5%88%9D%E8%A7%81/</guid><pubDate>Sun, 07 May 2023 19:11:43 GMT</pubDate><content:encoded>&lt;h2&gt;什么是Zig?&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Zig is a general-purpose programming language and toolchain for maintaining robust, optimal and reusable software.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;按照&lt;a href=&quot;https://ziglang.org&quot;&gt;官网&lt;/a&gt;所说, Zig是一种通用编程语言和工具链, 用于维护健壮、最佳和可重用的软件.&lt;/p&gt;
&lt;h4&gt;特点&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;无GC, 手动管理内存(支持arena等多种Allocator)&lt;/li&gt;
&lt;li&gt;强大的编译时计算能力&lt;/li&gt;
&lt;li&gt;类型可以在编译时像变量一样传递&lt;/li&gt;
&lt;li&gt;超多Targets交叉编译&lt;/li&gt;
&lt;li&gt;直接调用C语言代码和库&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Hello, World&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;const std = @import(&quot;std&quot;);

pub fn main() !void {
    const stdout = std.io.getStdOut().writer();
    try stdout.print(&quot;Hello, {s}!\n&quot;, .{&quot;world&quot;});
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;通过 &lt;code&gt;@import()&lt;/code&gt; 导入文件, 以 &lt;code&gt;@&lt;/code&gt; 开头的函数为编译器内置函数&lt;/li&gt;
&lt;li&gt;&lt;code&gt;!void&lt;/code&gt; 为 &lt;code&gt;void&lt;/code&gt; 与 &lt;code&gt;anyerror&lt;/code&gt; 的和类型&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;编译时计算&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;fn LinkedList(comptime T: type) type {
    return struct {
        pub const Node = struct {
            prev: ?*Node,
            next: ?*Node,
            data: T,
        };

        first: ?*Node,
        last:  ?*Node,
        len:   usize,
    };
}

test &quot;linked list&quot; {
    // Functions called at compile-time are memoized. This means you can
    // do this:
    try expect(LinkedList(i32) == LinkedList(i32));

    var list = LinkedList(i32) {
        .first = null,
        .last = null,
        .len = 0,
    };
    try expect(list.len == 0);

    // Since types are first class values you can instantiate the type
    // by assigning it to a variable:
    const ListOfInts = LinkedList(i32);
    try expect(ListOfInts == LinkedList(i32));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;调用C语言&lt;/h2&gt;
&lt;p&gt;可以通过 &lt;code&gt;zig translate-c&lt;/code&gt; 翻译C源文件为Zig源码, 甚至可以在Zig代码中通过 &lt;code&gt;@cImport&lt;/code&gt; 直接导入C源文件和代码, 两者原理相同.&lt;/p&gt;
&lt;p&gt;以调用OpenSSL计算MD5为例(Zig标准库支持MD5计算: std.crypto.hash.Md5, 此处仅为演示):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const std = @import(&quot;std&quot;);
const debug = std.debug;

const ssl = @cImport({
    @cInclude(&quot;openssl/ssl.h&quot;);
    @cInclude(&quot;openssl/crypto.h&quot;);
    @cInclude(&quot;openssl/md5.h&quot;);
}); // 导入OpenSSL头文件

fn md5sum(bytes: anytype) [ssl.MD5_DIGEST_LENGTH]u8 {
    comptime debug.assert(@TypeOf(bytes[0]) == u8);
    var md: [ssl.MD5_DIGEST_LENGTH]u8 = undefined;
    _ = ssl.MD5(bytes, bytes.len, &amp;amp;md); // 调用C函数
    return md;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 &lt;code&gt;build.zig&lt;/code&gt; 中添加链接库:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;exe.linkSystemLibrary(&quot;libcrypto&quot;);  // 确保pkgconfig可以找到OpenSSL库, 此处就可以自动处理Include路径及链接库路径
exe.linkSystemLibrary(&quot;libssl&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;内建Test&lt;/h2&gt;
&lt;p&gt;如同Go和Rust, Zig也内建test支持&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;test &quot;simple test&quot; {
    try std.testing.expectEqual(2, 1 + 1);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;run:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;zig test name.zig&lt;/code&gt; or &lt;code&gt;zig build test&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;内存管理&lt;/h2&gt;
&lt;p&gt;arena示例&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const allocator = arena.allocator();
var map = std.StringHashMap([]const u8).init(allocator);
defer map.deinit();
try map.put(&quot;key&quot;, &quot;value&quot;);
try testing.expect(std.mem.eql(u8, &quot;value&quot;, map.get(&quot;key&quot;) orelse &quot;&quot;));
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;可空类型&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;var foo: ?i32 = null;

// Coerce from child type of an optional
foo = 1234;

// Use compile-time reflection to access the child type of the optional:
comptime try expect(@typeInfo(@TypeOf(foo)).Optional.child == i32);

// Pointers cannot be null. If you want a null pointer, use the optional
// prefix `?` to make the pointer type optional.
var ptr: ?*i32 = null;

var x: i32 = 1;
ptr = &amp;amp;x;

try expect(ptr.?.* == 1);

// Optional pointers are the same size as normal pointers, because pointer
// value 0 is used as the null value.
try expect(@sizeOf(?*i32) == @sizeOf(*i32));
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;错误处理&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;fn failingFunction() error{Oops}!void {
    return error.Oops;
}

test &quot;returning an error&quot; {
    failingFunction() catch |err| {
        try expect(err == error.Oops);
        return;
    };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;交叉编译&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;$ zig targets
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;构建系统&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;$ zig help
Usage: zig [command] [options]

Commands:

  build            Build project from build.zig
  init-exe         Initialize a `zig build` application in the cwd
  init-lib         Initialize a `zig build` library in the cwd

  ast-check        Look for simple compile errors in any set of files
  build-exe        Create executable from source or object files
  build-lib        Create library from source or object files
  build-obj        Create object from source or object files
  fmt              Reformat Zig source into canonical form
  run              Create executable and run immediately
  test             Create and run a test build
  translate-c      Convert C code to Zig code

  ar               Use Zig as a drop-in archiver
  cc               Use Zig as a drop-in C compiler
  c++              Use Zig as a drop-in C++ compiler
  dlltool          Use Zig as a drop-in dlltool.exe
  lib              Use Zig as a drop-in lib.exe
  ranlib           Use Zig as a drop-in ranlib
  objcopy          Use Zig as a drop-in objcopy

  env              Print lib path, std path, cache directory, and version
  help             Print this help and exit
  libc             Display native libc paths file or validate one
  targets          List available compilation targets
  version          Print version number and exit
  zen              Print Zen of Zig and exit

General Options:

  -h, --help       Print command-specific usage
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;包管理&lt;/h2&gt;
&lt;p&gt;暂时还没有&lt;/p&gt;
</content:encoded></item><item><title>Typst 体验</title><link>https://shsuco.com/posts/typst%E4%BD%93%E9%AA%8C/</link><guid isPermaLink="true">https://shsuco.com/posts/typst%E4%BD%93%E9%AA%8C/</guid><pubDate>Thu, 20 Apr 2023 16:59:38 GMT</pubDate><content:encoded>&lt;p&gt;Typst是一个Rust编写的基于标记的排版系统, 正在积极开发中, 截至2023.5最新版本为&lt;code&gt;0.3.0&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;暂时取代LaTex不太现实, 因为各报刊杂志只会提供LaTex模板; 但是排版能力比MarkDown强太多, 可读性也比LaTex好, 而且很轻量, 只需要20MB(目前)的单二进制文件便可使用, 所以很适合处理一些轻量的排版任务.&lt;/p&gt;
&lt;p&gt;官网: https://typst.app&lt;/p&gt;
&lt;p&gt;GitHub: https://github.com/typst/typst&lt;/p&gt;
&lt;p&gt;文档: https://typst.app/docs/&lt;/p&gt;
&lt;h2&gt;安装&lt;/h2&gt;
&lt;p&gt;macOS/Linux&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;brew install typst
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Arch Linux&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pacman -S typst
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其他平台可参考https://github.com/typst/typst#installation&lt;/p&gt;
&lt;h2&gt;命令行使用&lt;/h2&gt;
&lt;p&gt;编译&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;typst compile file.typ
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;监听改动自动编译&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;typst watch file.typ
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查看已安装字体&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;typst fonts
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;与vscode配合使用&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=nvarner.typst-lsp&quot;&gt;Typst LSP&lt;/a&gt;插件提供了typst的lsp, 支持语法高亮和补全.&lt;/p&gt;
&lt;h2&gt;示例: 制作简历&lt;/h2&gt;
&lt;p&gt;语法很简单, 看一遍文档即可上手.&lt;/p&gt;
&lt;p&gt;效果&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/resume-demo.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#set text(
    font: (
        // &quot;Source Han Serif SC&quot;,  // 衬线字体, 用于打印
        // &quot;Times New Roman&quot;,
        // &quot;Songti SC&quot;,
        // &quot;STSong&quot;,
        &quot;Source Han Sans SC&quot;,  // 无衬线字体, 用于显示
        &quot;Hiragino Sans GB&quot;,
        &quot;Helvetica&quot;,
    ),
    size: 12pt,
    lang: &quot;zh&quot;,
)
#set page(
    paper: &quot;a4&quot;,
    margin: (x: 1.5cm, y: 1.5cm),
    footer: [
        #h(1fr) #text(fill: rgb(&quot;#d9d9d9&quot;))[powered by typst]
    ]
)

#set par(justify: true)

// #show heading.where(level: 2): it =&amp;gt; text(
//     weight: &quot;medium&quot;,
//     it.body,
// )

#show link: underline

#let section_line() = {
    v(2pt); 
    line(length: 100%, stroke: 1pt + green); 
    v(-5pt);
}

= Alan

simpleslight\@icloud.com |
195\*\*\*\*2202 |
#link(&quot;https://github.com/suransea&quot;)[github.com/suransea] | 
#link(&quot;https://shoco.top&quot;)[shoco.top]

== Summary
#section_line()

#lorem(18)

== Key Skills
#section_line()

- #lorem(7)
- #lorem(6)
- #lorem(8)
- #lorem(7)
- #lorem(6)
- #lorem(9)

== Experience
#section_line()

==== #lorem(4) #h(1fr) 2021 --

- #lorem(10)
- #lorem(18)
- #lorem(15)
- #lorem(12)
- #lorem(19)
- #lorem(13)

==== #lorem(5) #h(1fr) 2020 -- 2021

- #lorem(19)
- #lorem(15)
- #lorem(10)
- #lorem(18)
- #lorem(13)

== Education
#section_line()

#lorem(3)

#lorem(4)

2020
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>ADB 常用命令</title><link>https://shsuco.com/posts/adb%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4/</link><guid isPermaLink="true">https://shsuco.com/posts/adb%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4/</guid><pubDate>Tue, 18 Apr 2023 18:57:32 GMT</pubDate><content:encoded>&lt;h3&gt;改变屏幕分辨率&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;adb shell wm size 1080x1920
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;测试不同屏幕比例适配时很有用,
可以通过&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;adb shell wm size reset
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;重置为默认分辨率&lt;/p&gt;
&lt;h3&gt;获取设备文字&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;adb exec-out uiautomator dump /dev/stdout
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;有时需要将设备上显示的一些文字复制到电脑上, 用这个命令配合grep可以快速完成&lt;/p&gt;
&lt;h3&gt;向设备输入文字&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;adb shell input text &quot;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;向设备文本框输入文字, 可以用来传输文字到设备&lt;/p&gt;
&lt;h3&gt;查看设备代号&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;adb shell getprop | grep product
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;刷机时确认代号常用&lt;/p&gt;
&lt;h3&gt;查看当前活动&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;adb shell dumpsys activity activities | grep ResumedActivity
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;定位当前Activity&lt;/p&gt;
&lt;h3&gt;清除数据&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;adb shell pm clear com.example.app
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;bugreport&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;adb bugreport
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>IntelliJ IDEA 常用快捷键 (macOS)</title><link>https://shsuco.com/posts/intellij-idea-%E5%B8%B8%E7%94%A8%E5%BF%AB%E6%8D%B7%E9%94%AE/</link><guid isPermaLink="true">https://shsuco.com/posts/intellij-idea-%E5%B8%B8%E7%94%A8%E5%BF%AB%E6%8D%B7%E9%94%AE/</guid><pubDate>Mon, 17 Apr 2023 17:42:14 GMT</pubDate><content:encoded>&lt;p&gt;格式化代码 &lt;code&gt;command + option + L&lt;/code&gt;
优化导入 &lt;code&gt;^ + option + O&lt;/code&gt;
格式化代码选项 &lt;code&gt;command + option + shift + L&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;返回 &lt;code&gt;command + [&lt;/code&gt;
前进 &lt;code&gt;command + ]&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;最近文件 &lt;code&gt;command + E&lt;/code&gt;
最近位置 &lt;code&gt;command + shift + E&lt;/code&gt;
转到行列 &lt;code&gt;command + L&lt;/code&gt;
转到上/下个函数 &lt;code&gt;^ + shift + up/down&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;复制一行 &lt;code&gt;command + D&lt;/code&gt;, 相当于 &lt;code&gt;command + C&lt;/code&gt; &amp;amp;&amp;amp; &lt;code&gt;command + V&lt;/code&gt;
删除一行 &lt;code&gt;command + backspace&lt;/code&gt;
新起一行 &lt;code&gt;shift + enter&lt;/code&gt;
上/下移一行 &lt;code&gt;option + shift + up/down&lt;/code&gt;
上/下移部分 &lt;code&gt;commang + shift + up/down&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;注释/取消 &lt;code&gt;command + /&lt;/code&gt;
块注释/取消 &lt;code&gt;option + command + /&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;查找 &lt;code&gt;command + F&lt;/code&gt;
替换 &lt;code&gt;command + R&lt;/code&gt;
所有文件中查找 &lt;code&gt;command + shift + F&lt;/code&gt;
所有文件中替换 &lt;code&gt;command + shift + R&lt;/code&gt;
查找任意 &lt;code&gt;shift + shift&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;在匹配括号间移动 &lt;code&gt;^ + M&lt;/code&gt;
生成 &lt;code&gt;command + N&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;重构 &lt;code&gt;^ + T&lt;/code&gt;
重命名 &lt;code&gt;shift + F6&lt;/code&gt;&lt;/p&gt;
</content:encoded></item><item><title>Gnome 开启分数缩放</title><link>https://shsuco.com/posts/gnome%E5%BC%80%E5%90%AF%E5%88%86%E6%95%B0%E7%BC%A9%E6%94%BE/</link><guid isPermaLink="true">https://shsuco.com/posts/gnome%E5%BC%80%E5%90%AF%E5%88%86%E6%95%B0%E7%BC%A9%E6%94%BE/</guid><pubDate>Wed, 28 Sep 2022 14:30:29 GMT</pubDate><content:encoded>&lt;p&gt;参考链接: https://wiki.archlinux.org/title/HiDPI&lt;/p&gt;
&lt;h2&gt;Wayland&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;gsettings set org.gnome.mutter experimental-features &quot;[&apos;scale-monitor-framebuffer&apos;]&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Xorg&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;gsettings set org.gnome.mutter experimental-features &quot;[&apos;x11-randr-fractional-scaling&apos;]&quot;
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>安装配置 LaTeX 环境, TeX Live + VSCode</title><link>https://shsuco.com/posts/%E5%AE%89%E8%A3%85%E9%85%8D%E7%BD%AElatex%E7%8E%AF%E5%A2%83-texlive-vscode/</link><guid isPermaLink="true">https://shsuco.com/posts/%E5%AE%89%E8%A3%85%E9%85%8D%E7%BD%AElatex%E7%8E%AF%E5%A2%83-texlive-vscode/</guid><pubDate>Fri, 12 Aug 2022 10:25:11 GMT</pubDate><content:encoded>&lt;h2&gt;安装TeX Live&lt;/h2&gt;
&lt;h3&gt;macOS/Linux&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;$ brew install texlive
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Linux下更推荐用自带包管理安装.&lt;/p&gt;
&lt;h3&gt;Windows&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;PS &amp;gt; choco install texlive
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;配置TeX Live&lt;/h2&gt;
&lt;p&gt;查看TeX Live包管理版本&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ tlmgr --version
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;测试安装包&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ tlmgr install latexindent
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果报错&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;tlmgr: action not allowed in system mode: install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;则需要在命令上加上&lt;code&gt;--usermode&lt;/code&gt;参数:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ tlmgr install latexindent --usermode
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果报错&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;TLPDB: not a directory, not loading: /Users/xxx/texmf
cannot setup TLPDB in /Users/xxx/texmf at /usr/local/bin/tlmgr line 6887.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;则先执行&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ tlmgr init-usertree
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;再执行安装命令.&lt;/p&gt;
&lt;p&gt;随后可以安装一些常用包, 例如beamer:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ tlmgr install beamer
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;配置VSCode&lt;/h2&gt;
&lt;p&gt;安装LaTeX Workshop扩展:
https://marketplace.visualstudio.com/items?itemName=James-Yu.latex-workshop&lt;/p&gt;
&lt;p&gt;打开扩展设置, 搜索 &lt;code&gt;@ext:James-Yu.latex-workshop recipes&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;定位到 &lt;code&gt;Latex-workshop › Latex: Recipes&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;点击 &lt;code&gt;Edit in settings.json&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;将xelatex移动至第一位&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;latex-workshop.latex.recipes&quot;: [
    {
        &quot;name&quot;: &quot;latexmk (xelatex)&quot;,
        &quot;tools&quot;: [
            &quot;xelatexmk&quot;
        ]
    },
    ...
]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;每次保存tex文件时会自动编译, 此行为可以通过配置&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;latex-workshop.latex.autoBuild.run&quot;: &quot;onFileChange&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;来更改, 可选值为&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;onFileChange&lt;/code&gt; 监测到文件改动时&lt;/li&gt;
&lt;li&gt;&lt;code&gt;onSave&lt;/code&gt; 文件保存时&lt;/li&gt;
&lt;li&gt;&lt;code&gt;never&lt;/code&gt; 永不&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;遇到编译错误时, 如果原因为缺少包, 可以通过包管理&lt;code&gt;tlmgr&lt;/code&gt;安装.&lt;/p&gt;
</content:encoded></item><item><title>AndroidX 注解</title><link>https://shsuco.com/posts/androidx%E6%B3%A8%E8%A7%A3/</link><guid isPermaLink="true">https://shsuco.com/posts/androidx%E6%B3%A8%E8%A7%A3/</guid><pubDate>Fri, 29 Jul 2022 11:23:47 GMT</pubDate><content:encoded>&lt;h2&gt;@Nullable, @NonNull&lt;/h2&gt;
&lt;p&gt;用于修饰变量/参数/返回值. @Nullbale表示可空, @NonNull表示非空.&lt;/p&gt;
&lt;p&gt;以下行为会生成警告:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;将null/可空值赋值给非空变量/参数/字段&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;未进行null检查而直接调用可空值的方法/字段&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;此外, null注解也作用于Kotlin于Java交互:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@NonNull
String foo(@Nullable String a, @NonNull String b) {}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对应Kotlin函数签名:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fun foo(a: String?, b: String): String
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;!-- more --&amp;gt;&lt;/p&gt;
&lt;h2&gt;Resource ID&lt;/h2&gt;
&lt;p&gt;表示目标资源ID的类型.&lt;/p&gt;
&lt;p&gt;任意类型资源ID: &lt;code&gt;@AnyRes&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;示例:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fun @receiver:AnyRes Int.uri() = &quot;${SCHEME_ANDROID_RESOURCE}://${BuildConfig.APPLICATION_ID}/$this&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;指定类型资源ID:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@StringRes&lt;/code&gt;, &lt;code&gt;@LayoutRes&lt;/code&gt;, &lt;code&gt;@DrawableRes&lt;/code&gt;, &lt;code&gt;@DimenRes&lt;/code&gt;, &lt;code&gt;@ColorRes&lt;/code&gt;, &lt;code&gt;@InterpolatorRes&lt;/code&gt;, &lt;code&gt;@IntegerRes&lt;/code&gt;,
&lt;code&gt;@IdRes&lt;/code&gt;, &lt;code&gt;@StyleRes&lt;/code&gt;, &lt;code&gt;@StyleableRes&lt;/code&gt;, &lt;code&gt;@TransitionRes&lt;/code&gt;, &lt;code&gt;@XmlRes&lt;/code&gt;, &lt;code&gt;@RawRes&lt;/code&gt;, &lt;code&gt;@PluralsRes&lt;/code&gt;, &lt;code&gt;@MenuRes&lt;/code&gt;,
&lt;code&gt;@NavigationRes&lt;/code&gt;, &lt;code&gt;@FontRes&lt;/code&gt;, &lt;code&gt;@FractionRes&lt;/code&gt;, &lt;code&gt;@BoolRes&lt;/code&gt;, &lt;code&gt;@ArrayRes&lt;/code&gt;, &lt;code&gt;@AttrRes&lt;/code&gt;, &lt;code&gt;@AnimatorRes&lt;/code&gt;, &lt;code&gt;@AnimRes&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;@ColorInt, @ColorLong&lt;/h2&gt;
&lt;p&gt;表示目标类型为ARGB Color.&lt;/p&gt;
&lt;p&gt;示例:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public abstract void setTextColor(@ColorInt int color);

public void setFillColor(@ColorLong long color);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;@Dimension&lt;/h2&gt;
&lt;p&gt;表示目标类型为尺寸类型.&lt;/p&gt;
&lt;p&gt;示例:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@Dimension(unit = DP)
public int getDesiredHeight() {
    return mDesiredHeight;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;@Px&lt;/h2&gt;
&lt;p&gt;等价于&lt;code&gt;@Dimension(unit = PX)&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;@IntRange, @FloatRange&lt;/h2&gt;
&lt;p&gt;表示值的范围, 不符合会生成警告.&lt;/p&gt;
&lt;p&gt;示例:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fun setAlpha(@FloatRange(from = 0.0, to = 1.0) alpha: Float) {...}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;from, to参数可以仅指定一个.&lt;/p&gt;
&lt;h2&gt;@Size&lt;/h2&gt;
&lt;p&gt;表示集合或数组的大小.&lt;/p&gt;
&lt;p&gt;可以指定以下参数:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;@Size(min=1) 最小值&lt;/li&gt;
&lt;li&gt;@Size(max=2) 最大值&lt;/li&gt;
&lt;li&gt;@Size(multiple=2) 倍数&lt;/li&gt;
&lt;li&gt;@Size(2) 准确值&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;@HalfFloat&lt;/h2&gt;
&lt;p&gt;表示short类型/short数组存储半精度16位浮点数, 详见&lt;code&gt;android.util.Half&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;@CheckResult&lt;/h2&gt;
&lt;p&gt;表示调用方应检查返回值.&lt;/p&gt;
&lt;p&gt;常用于难以区分纯函数/修改自身的函数的情况, 例如:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public @CheckResult String trim(String s) { return s.trim(); }

s.trim(); // this is probably an error
s = s.trim(); // ok
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;@CallSuper&lt;/h2&gt;
&lt;p&gt;表示重写方法应调用super.&lt;/p&gt;
&lt;p&gt;示例:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@CallSuper
override fun onCreate(savedInstanceState: Bundle?) {
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;线程&lt;/h2&gt;
&lt;p&gt;表示目标方法/目标类中全部方法需要从指定线程调用.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@MainThread&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;主线程&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@UiThread&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;UI线程. 一般情况下与主线程相同, 如果有位于非主线程的View, 则可能表示其他线程.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@WorkerThread&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;工作线程&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@BinderThread&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Binder线程&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@AnyThread&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;任意线程, 调用时无需考虑所在线程&lt;/p&gt;
&lt;h2&gt;@GuardedBy&lt;/h2&gt;
&lt;p&gt;表示目标方法或属性在访问时需持有指定锁.&lt;/p&gt;
&lt;p&gt;示例:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;final Object objectLock = new Object();

@GuardedBy(&quot;objectLock&quot;)
volatile Object object;

Object getObject() {
    synchronized (objectLock) {
        if (object == null) {
            object = new Object();
        }
    }
    return object;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;@Discouraged&lt;/h2&gt;
&lt;p&gt;表示目标不鼓励使用, 比@Deprecated程度要轻.&lt;/p&gt;
&lt;p&gt;示例:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@Discouraged(message = &quot;It is much more efficient to retrieve &quot;
                        + &quot;resources by identifier than by name.&quot;)
public void getValue(String name) {
    ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;@DoNotInline&lt;/h2&gt;
&lt;p&gt;表示方法不要内联. 生效于混淆规则:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-keepclassmembers,allowobfuscation class * {
  @androidx.annotation.DoNotInline &amp;lt;methods&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;@Keep&lt;/h2&gt;
&lt;p&gt;表示目标应在编译时保留. 生效于混淆规则:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-keep,allowobfuscation @interface androidx.annotation.Keep
-keep @androidx.annotation.Keep class * {*;}

-keepclasseswithmembers class * {
    @androidx.annotation.Keep &amp;lt;methods&amp;gt;;
}

-keepclasseswithmembers class * {
    @androidx.annotation.Keep &amp;lt;fields&amp;gt;;
}

-keepclasseswithmembers class * {
    @androidx.annotation.Keep &amp;lt;init&amp;gt;(...);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可用于防止POJO类型混淆.&lt;/p&gt;
&lt;h2&gt;@RestrictTo&lt;/h2&gt;
&lt;p&gt;限制访问范围.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;@RestrictTo(RestrictTo.Scope.SUBCLASSES) 子类&lt;/li&gt;
&lt;li&gt;@RestrictTo(RestrictTo.Scope.LIBRARY) 库&lt;/li&gt;
&lt;li&gt;@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 相同GroupID&lt;/li&gt;
&lt;li&gt;@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) 相同Group前缀&lt;/li&gt;
&lt;li&gt;@RestrictTo(RestrictTo.Scope.TESTS) 测试, 等价于 @VisibleForTesting(otherwise = VisibleForTesting.NONE)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;@VisibleForTesting&lt;/h2&gt;
&lt;p&gt;表示目标可见性大于本应具有的可见性, 扩大可见性的原因是用于测试. 参数为本应具有的可见性.&lt;/p&gt;
&lt;p&gt;示例:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
fun myMethod() {
    ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;@DisplayContext, @UiContext, @NonUiContext&lt;/h2&gt;
&lt;p&gt;@DisplayContext表示目标context是DisplayContext, 即可以用于&lt;code&gt;getDisplay&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;@UiContext表示目标context是UiContext, 即目标是DisplayContext, 并且可以用于获取UI相关服务(WindowManager, LayoutInflater, WallpaperManager).&lt;/p&gt;
&lt;p&gt;@NonUiContext表示目标不是UiContext.&lt;/p&gt;
&lt;h2&gt;@IntDef, @StringDef&lt;/h2&gt;
&lt;p&gt;用于创建一个约束值注解, 常用于替代枚举.&lt;/p&gt;
&lt;p&gt;示例:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@Retention(SOURCE)
@IntDef({NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS})
public @interface NavigationMode {}

public static final int NAVIGATION_MODE_STANDARD = 0;
public static final int NAVIGATION_MODE_LIST = 1;
public static final int NAVIGATION_MODE_TABS = 2;

public abstract void setNavigationMode(@NavigationMode int mode);

@NavigationMode
public abstract int getNavigationMode();
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;@GravityInt&lt;/h2&gt;
&lt;p&gt;表示目标int类型为Gravity, 详见&lt;code&gt;android.view.Gravity&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;应该等价于&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {
    Gravity.FILL,
    Gravity.FILL_HORIZONTAL,
    Gravity.FILL_VERTICAL,
    Gravity.START,
    Gravity.END,
    Gravity.LEFT,
    Gravity.RIGHT,
    Gravity.TOP,
    Gravity.BOTTOM,
    Gravity.CENTER,
    Gravity.CENTER_HORIZONTAL,
    Gravity.CENTER_VERTICAL,
    Gravity.DISPLAY_CLIP_HORIZONTAL,
    Gravity.DISPLAY_CLIP_VERTICAL,
    Gravity.CLIP_HORIZONTAL,
    Gravity.CLIP_VERTICAL,
    Gravity.NO_GRAVITY
})
public @interface GravityFlags {}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;@RequiresApi&lt;/h2&gt;
&lt;p&gt;表示目标需要具有指定或更高的API等级才能调用, 类似于&lt;code&gt;android.annotation.TargetApi&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;示例:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
public void cubicToPoint(...);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;@ChecksSdkIntAtLeast&lt;/h2&gt;
&lt;p&gt;表示目标方法/属性将检查ApiLevel.&lt;/p&gt;
&lt;p&gt;示例:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.O)
public static boolean isAtLeastO() {
    return Build.VERSION.SDK_INT &amp;gt;= 26;
}

void foo() {
    if (isAtLeastO()) {
        // call @RequiresApi(&amp;gt;=26) and no warning.
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;@RequiresPermission&lt;/h2&gt;
&lt;p&gt;表示调用方需具有指定权限.&lt;/p&gt;
&lt;p&gt;示例:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@RequiresPermission(Manifest.permission.SET_WALLPAPER)
public abstract void setWallpaper(Bitmap bitmap);

@RequiresPermission(allOf = {
    Manifest.permission.READ_EXTERNAL_STORAGE,
    Manifest.permission.ACCESS_MEDIA_LOCATION})
public static final void copyImageFile(String dest, String source) {
    //...
}

@RequiresPermission(android.Manifest.permission.BLUETOOTH)
public static final String ACTION_REQUEST_DISCOVERABLE =
            &quot;android.bluetooth.adapter.action.REQUEST_DISCOVERABLE&quot;;

@RequiresPermission.Read(@RequiresPermission(READ_HISTORY_BOOKMARKS))
@RequiresPermission.Write(@RequiresPermission(WRITE_HISTORY_BOOKMARKS))
public static final Uri BOOKMARKS_URI = Uri.parse(&quot;content://browser/bookmarks&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;@ContentView&lt;/h2&gt;
&lt;p&gt;表示目标构造函数接受一个LayoutRes参数, 并将设置为自身布局.&lt;/p&gt;
&lt;p&gt;示例:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@ContentView
public Fragment(@LayoutRes int contentLayoutId) {
    this();
    mContentLayoutId = contentLayoutId;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;@RequiresFeature&lt;/h2&gt;
&lt;p&gt;表示调用方应具有目标特性.&lt;/p&gt;
&lt;p&gt;示例:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@RequiresFeature(PackageManager.FEATURE_BLUETOOTH)
public final class BluetoothManager {
    ...
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Modem, Router, Switch, Hub...</title><link>https://shsuco.com/posts/modem-router-switch-hub/</link><guid isPermaLink="true">https://shsuco.com/posts/modem-router-switch-hub/</guid><pubDate>Thu, 28 Jul 2022 17:56:40 GMT</pubDate><content:encoded>&lt;h2&gt;Modem&lt;/h2&gt;
&lt;p&gt;调试解调器, 用于信号转换&lt;/p&gt;
&lt;h2&gt;Router&lt;/h2&gt;
&lt;p&gt;路由器, 根据IP地址在网络间传输/转发数据报&lt;/p&gt;
&lt;h2&gt;Switch&lt;/h2&gt;
&lt;p&gt;交换机, 根据MAC地址将数据报转发给目标接口&lt;/p&gt;
&lt;h2&gt;Hub&lt;/h2&gt;
&lt;p&gt;集线器, 将每个数据报广播到所有接口&lt;/p&gt;
&lt;h2&gt;Bridge&lt;/h2&gt;
&lt;p&gt;网桥, 可以为Hub局域网划分网段&lt;/p&gt;
&lt;h2&gt;AP&lt;/h2&gt;
&lt;p&gt;接入点, 无线交换机&lt;/p&gt;
&lt;h2&gt;Gateway&lt;/h2&gt;
&lt;p&gt;网关, 用于网络层以上网络互联&lt;/p&gt;
</content:encoded></item><item><title>Swift 并发, 对比 Kotlin协程</title><link>https://shsuco.com/posts/swift%E5%B9%B6%E5%8F%91-%E5%AF%B9%E6%AF%94kotlin%E5%8D%8F%E7%A8%8B/</link><guid isPermaLink="true">https://shsuco.com/posts/swift%E5%B9%B6%E5%8F%91-%E5%AF%B9%E6%AF%94kotlin%E5%8D%8F%E7%A8%8B/</guid><pubDate>Wed, 27 Jul 2022 16:12:56 GMT</pubDate><content:encoded>&lt;h2&gt;Hello, 异步函数&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;func hello() async -&amp;gt; String {
    try? await Task.sleep(nanoseconds: 1000_000_000)
    return &quot;hello&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Kotlin:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;suspend fun hello(): String {
    delay(1.seconds)
    return &quot;hello&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;!-- more --&amp;gt;&lt;/p&gt;
&lt;h2&gt;结构化并发&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;let task = Task {
    await hello()
}
task.cancel()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Kotlin:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;val scope = CoroutineScope(EmptyCoroutineContext)
scope.launch(Dispatchers.Main) {
    hello()
}
scope.cancel()
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Task&lt;/h3&gt;
&lt;p&gt;async标记的函数只能在async标记的函数或闭包中调用, Task的初始化器提供了async闭包环境:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public init(priority: TaskPriority? = nil, operation: @escaping @Sendable () async -&amp;gt; Success)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可选使用TaskPriority参数:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Task(priority: .high) {
    ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;取值:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;high, medium, low,
userInitiated, utility, background,
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;后面三种等级类似于&lt;code&gt;DispatchQos&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;withUnsafeCurrentTask&lt;/h3&gt;
&lt;p&gt;如果要获取当前task, 可以使用&lt;code&gt;withUnsafeCurrentTask&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;withUnsafeCurrentTask { task in
    task?.isCancelled
    task?.priority
    task?.cancel()
    ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此函数非async, 如果当前环境不存在task, task值为nil.&lt;/p&gt;
&lt;p&gt;如果仅想检查任务优先级和任务是否取消, 可以直接使用&lt;code&gt;Task.currentPriority&lt;/code&gt;和&lt;code&gt;Task.isCancelled&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Task嵌套&lt;/h3&gt;
&lt;p&gt;对于Task嵌套的情况:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Task {
    Task {
        ...
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;子Task会继承当前Task的环境(但仍然是独立的Task, 非结构化并发), 如果不想继承可以使用&lt;code&gt;Task.detached(priority:operation:)&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Task {
    Task.detached {
        ...
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;CancelBag&lt;/h3&gt;
&lt;p&gt;task离开作用域时不会被取消, 如果需要可以仿照DisposeBag实现CancelBag(非线程安全):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public final class CancelBag {

    private var cancels = [() -&amp;gt; Void]()

    public func on(cancel: @escaping () -&amp;gt; Void) {
        self.cancels.append(cancel)
    }

    private func cancel() {
        cancels.forEach { cancel in cancel() }
    }
    
    deinit {
        self.cancel()
    }
}

extension Task {
    func cancel(by bag: CancelBag) {
        bag.on(cancel: { cancel() })
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let bag = CancelBag()

Task {
    ...
}.cancel(by: bag)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;要注意循环引用. 如果不想在task闭包中处理循环引用, 可以使用手动的CancelBag:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public final class CancelBag {

    private var cancels = [() -&amp;gt; Void]()

    public func on(cancel: @escaping () -&amp;gt; Void) {
        self.cancels.append(cancel)
    }

    public func cancel() {
        cancels.forEach { cancel in cancel() }
        cancels.removeAll()
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;并在适当时机调用&lt;code&gt;bag.cancel()&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;async let&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;async let hello = hello()
async let world = world()
print(&quot;\(await hello) \(await world)&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Kotlin:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;val hello = async { hello() }
val world = async { world() }
println(&quot;${hello.await()} ${world.await()}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;同时执行多个async函数.
比如发送网络请求时, 如果第三个请求的参数依赖前两个请求的结果:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;async let x = reqX()
async let y = reqY()
let z = await reqZ(await x, await y)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以让x, y请求同时发出, 都得到响应后, 再发出z请求&lt;/p&gt;
&lt;h2&gt;for await&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;func number() async -&amp;gt; Int {
    .random(in: 1...100)
}

await withTaskGroup(of: Int.self) { group in
    for _ in 1...100 {
        group.addTask { await number() }
    }
    
    for await i in group {
        print(i)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Kotlin:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;suspend fun number() = Random.nextInt(0..100)

val numbers = produce {
    coroutineScope {
        repeat(100) {
            launch { send(number()) }
        }
    }
}

for (i in numbers) {
    println(i)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;for await&lt;/code&gt;用于迭代实现了&lt;code&gt;AsyncSequence&lt;/code&gt;协议的类型, 如&lt;code&gt;TaskGroup&lt;/code&gt;和下文将要提到的&lt;code&gt;AsyncStream&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;withTaskGroup(of:)&lt;/code&gt;效果类似于&lt;code&gt;corountineScope&lt;/code&gt;, 可以添加一系列子任务并等待这些任务完成.&lt;/p&gt;
&lt;p&gt;如果子Task会调用throws, 可以使用&lt;code&gt;withThrowingTaskGroup&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;try await withThrowingTaskGroup(of: Int.self) { group in
    group.addTask { throw SomeError() }

    for try await i in group {
	...
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;await all&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;await withTaskGroup(of: Int.self) { group in
    for _ in 1...100 {
        group.addTask { await number() }
    }
    
    await group.reduce(into: []) { result, transition in result.append(transition) }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;(1..100)
    .map { async { number() } }
    .awaitAll()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;收集子任务结果.&lt;/p&gt;
&lt;h2&gt;AsyncStream&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;let stream = AsyncStream&amp;lt;Int&amp;gt; { continuation in
    for i in 1...100 {
        continuation.yield(i)
    }
    continuation.finish()
}

for await i in stream {
    print(i)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;let stream = AsyncStream&amp;lt;Int&amp;gt; {
    try? await Task.sleep(nanoseconds: 1000_000_000)
    return .random(in: 1...100)
}

for await i in stream {
    print(i)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Kotlin:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flow {
    (1..10).forEach { emit(it) }
}.collect {
    println(it)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;flow {
    while (true) {
        delay(1.seconds)
        emit(Random.nextInt(1..10))
    }
}.collect {
    println(it)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里AsyncStream和flow对比比较直观, 但其行为更像ReceiveChannel, 因为flow是冷的.&lt;/p&gt;
&lt;h2&gt;Actor&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;actor Counter {
    var count: Int = 0
    
    func inc() {
        count += 1
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;let couter = Counter()
await couter.inc()
let count = await couter.count
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Kotlin:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sealed interface CounterMsg

object IncCount : CounterMsg
class GetCount(val response: CompletableDeferred&amp;lt;Int&amp;gt; = CompletableDeferred()) : CounterMsg

fun CoroutineScope.counterActor() = actor&amp;lt;CounterMsg&amp;gt; {
    var count = 0
    for (msg in channel) {
        when (msg) {
            is GetCount -&amp;gt; msg.response.complete(count)
            is IncCount -&amp;gt; ++count
        }
    }
}

suspend fun SendChannel&amp;lt;CounterMsg&amp;gt;.count() = GetCount().also { send(it) }.response.await()
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;val counter = counterActor()

counter.send(IncCount)
val count = counter.count()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Swift在语言层面支持了actor, 因此使用actor要比Kotlin方便许多.&lt;/p&gt;
&lt;p&gt;从外部调用actor中的函数、可变属性和计算属性需要使用await. 如果确定不存在数据竞争, 可以用&lt;code&gt;nonisolated&lt;/code&gt;修饰:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nonisolated func foo() -&amp;gt; String {
    ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样就可以像调用普通函数一样从外部调用.&lt;/p&gt;
&lt;h3&gt;GlobalActor&lt;/h3&gt;
&lt;p&gt;用@globalActor创建一个修饰器, 用此修饰器修饰, 可以让函数/属性/class共同使用一个actor环境:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@globalActor
actor BusActor: GlobalActor {
    public static let shared = BusActor()
}

@BusActor func foo() {
    ...
}

@BusActor var bar = 0

@BusActor class Bus {}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;MainActor&lt;/h3&gt;
&lt;p&gt;MainActor是运行于主线程的GlobalActor:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@globalActor final public actor MainActor : GlobalActor
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以把更新UI的函数用@MainActor修饰:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@MainActor
func update() {
    ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以保证此函数一定在主线程执行.&lt;/p&gt;
&lt;h3&gt;Sendable&lt;/h3&gt;
&lt;p&gt;Sendable是一个mark协议, 用于标记类型为可安全在actor间移动的, 类似于Rust的&lt;code&gt;Send&lt;/code&gt;trait.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public protocol Sendable {
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以下类型是Sendable的:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;基本类型&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;actor类型&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;由Sendable类型构成的struct&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;满足以下条件的@Sendable修饰的函数或闭包:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;捕获变量是Sendable类型&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;捕获变量不可变&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;满足以下条件的显式实现Sendable的class:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;final修饰, 即不可继承&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;存储属性是Sendable类型&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;存储属性不可变&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;无父类或父类是NSObject&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果确定不符合以上条件的class可以安全Send, 可以实现@unchecked Sendable以跳过编译检查:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Foo: @unchecked Sendable {
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;从Callback迁移&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;func request(_ completion: (String) -&amp;gt; Void) {
    ...
}

func request() async -&amp;gt; String {
    await withCheckedContinuation { continuation in
        request { response in
            continuation.resume(returning: response)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Kotlin:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fun request(callback: (String) -&amp;gt; Unit) {
    ...
}

suspend fun request(): String = suspendCoroutine { continuation -&amp;gt;
    request {
        continuation.resume(it)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果闭包内调用了throws, 可以使用&lt;code&gt;withCheckedThrowingContinuation(function:_:)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;如果可以确保调用且仅调用一次&lt;code&gt;resume&lt;/code&gt;, 可以使用unsafe版本:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;withUnsafeContinuation(_:)&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;withUnsafeThrowingContinuation(_:)&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Arch Linux 安装</title><link>https://shsuco.com/posts/arch-linux%E5%AE%89%E8%A3%85/</link><guid isPermaLink="true">https://shsuco.com/posts/arch-linux%E5%AE%89%E8%A3%85/</guid><pubDate>Tue, 26 Jul 2022 16:22:45 GMT</pubDate><content:encoded>&lt;h1&gt;安装&lt;/h1&gt;
&lt;h2&gt;下载镜像&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;$ wget https://mirrors.tuna.tsinghua.edu.cn/archlinux/iso/latest/archlinux-x86_64.iso&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;wget可以替换成其他工具, 比如aria2c&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;mirrors list: https://archlinux.org/download/&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;制作启动U盘&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;# dd bs=4M if=archlinux-x86_64.iso of=/dev/sdx status=progress &amp;amp;&amp;amp; sync&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;也可以使用GUI工具, 比如etcher, rufus(Windows)&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- more --&amp;gt;&lt;/p&gt;
&lt;h2&gt;以U盘启动&lt;/h2&gt;
&lt;p&gt;将U盘插入目标设备, 进入BIOS, 如果安全启动开启需要先关闭安全启动, 调整启动顺序后保存退出&lt;/p&gt;
&lt;p&gt;如果设备支持快速启动, 也可以从快速启动中选中U盘启动&lt;/p&gt;
&lt;p&gt;重启后, 如果一切顺利, 将会看到archinstall的shell&lt;/p&gt;
&lt;h2&gt;连接网络&lt;/h2&gt;
&lt;p&gt;参考{% post_link Linux命令行连接网络 %}&lt;/p&gt;
&lt;h2&gt;更新镜像&lt;/h2&gt;
&lt;p&gt;安装Reflector&lt;/p&gt;
&lt;p&gt;&lt;code&gt;# pacman -Syy reflector&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;# reflector --verbose --country China --latest 5 --protocol https --sort rate --save /etc/pacman.d/mirrorlist&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;启动安装脚本&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;# archinstall&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;按照步骤配置即可&lt;/p&gt;
&lt;h1&gt;后续步骤(供参考)&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;常用软件包索引: https://wiki.archlinux.org/title/List_of_applications_(简体中文)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;安装git&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;$ sudo pacman -Syyu git&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;安装paru(AUR工具)&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;$ git clone https://aur.archlinux.org/paru-bin.git&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ cd paru-bin&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ makepkg -si&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;安装papirus图标包&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;$ sudo pacman -S papirus-icon-theme&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;安装字体&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;$ sudo pacman -S adobe-source-sans-fonts \
        adobe-source-han-sans-cn-fonts \
        adobe-source-han-sans-tw-fonts \
        adobe-source-han-sans-jp-fonts \
        adobe-source-han-sans-kr-fonts \
        noto-fonts noto-fonts-cjk
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Swift 调用 GLib</title><link>https://shsuco.com/posts/swift%E8%B0%83%E7%94%A8glib/</link><guid isPermaLink="true">https://shsuco.com/posts/swift%E8%B0%83%E7%94%A8glib/</guid><pubDate>Tue, 26 Jul 2022 14:22:39 GMT</pubDate><content:encoded>&lt;h2&gt;g_signal_connect&lt;/h2&gt;
&lt;p&gt;Swift无法调用C宏, 需要使用&lt;code&gt;g_signal_connect_data&lt;/code&gt;函数&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let slot: @convention(c) (UnsafeMutableRawPointer?) -&amp;gt; Void = { data in
    ...
}
g_signal_connect_data(ptr, &quot;signal&quot;, unsafeBitCast(slot, to: GCallback?.self), data, nil, G_CONNECT_AFTER)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;slot必须标记为&lt;code&gt;@convention(c)&lt;/code&gt;, 否则&lt;code&gt;unsafeBitCast&lt;/code&gt;将产生fatalError&lt;/p&gt;
&lt;h2&gt;g_object_get_property&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;func gproperty&amp;lt;T, R&amp;gt;(of ptr: UnsafeMutablePointer&amp;lt;T&amp;gt;, name: String, transform: (UnsafeMutablePointer&amp;lt;GValue&amp;gt;) -&amp;gt; R) -&amp;gt; R {
    ptr.withMemoryRebound(to: GObject.self, capacity: 1) {
        var value = GValue()
        g_object_get_property($0, name, &amp;amp;value)
        return transform(&amp;amp;value)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;再使用g_value_get族函数从GValue中获取值&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let property = gproperty(of: ptr, name: &quot;property&quot;) { g_value_get_variant($0) }
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;待续..&lt;/h2&gt;
</content:encoded></item><item><title>Android 安装系统CA证书</title><link>https://shsuco.com/posts/android%E5%AE%89%E8%A3%85%E7%B3%BB%E7%BB%9Fca%E8%AF%81%E4%B9%A6/</link><guid isPermaLink="true">https://shsuco.com/posts/android%E5%AE%89%E8%A3%85%E7%B3%BB%E7%BB%9Fca%E8%AF%81%E4%B9%A6/</guid><pubDate>Tue, 26 Jul 2022 10:43:50 GMT</pubDate><content:encoded>&lt;p&gt;HTTPS抓包时, 如果应用不信任用户证书, 需要把证书安装为系统证书.&lt;/p&gt;
&lt;p&gt;如果证书为二进制格式(cer, der), 需要先转换为文本格式&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ openssl x509 -inform der -in cert.cer -outform pem -out cert.pem&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;获取hash, 存为$CERT_HASH&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ CERT_HASH=`openssl x509 -inform pem -in cert.pem -subject_hash_old | head -1` &lt;/code&gt;&lt;/p&gt;
&lt;p&gt;制作证书文件&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ cp cert.pem ${CERT_HASH}.0&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ openssl x509 -inform pem -in cert.pem -text -noout &amp;gt;&amp;gt; ${CERT_HASH}.0&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- more --&amp;gt;&lt;/p&gt;
&lt;p&gt;push到设备中&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ adb root&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ adb remount&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ adb push ${CERT_HASH}.0 /system/etc/security/cacerts/&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;更改权限&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ adb shell chmod 644 /system/etc/security/cacerts/${CERT_HASH}.0&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;如果Android13以上无法remount, 可以尝试&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ adb shell setprop sys.debug.allow_remount true
$ adb shell stop &amp;amp;&amp;amp; adb shell start
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;或者使用overlayfs&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ adb shell
$ mkdir -p /data/overlay/upper
$ mkdir -p /data/overlay/work
$ mount -t overlay overlay -o lowerdir=/system/etc/security/cacerts,upperdir=/data/overlay/upper,workdir=/data/overlay/work /system/etc/security/cacerts
$ exit
$ adb push ${CERT_HASH}.0 /data/overlay/upper/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;或者使用&lt;code&gt;mount --bind&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ adb shell
$ mkdir -p /data/mnt
$ adb push ${CERT_HASH}.0 /data/mnt/
$ mount --bind /data/mnt/${CERT_HASH}.0 /system/etc/security/cacerts/${CERT_HASH}.0
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Android UID</title><link>https://shsuco.com/posts/android-uid/</link><guid isPermaLink="true">https://shsuco.com/posts/android-uid/</guid><pubDate>Tue, 19 Jul 2022 11:21:47 GMT</pubDate><content:encoded>&lt;h2&gt;UID&lt;/h2&gt;
&lt;p&gt;Linux会为每个用户分配一个uid, 可以用&lt;code&gt;id&lt;/code&gt;命令查看:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ id
uid=501(sea) gid=20(staff) groups=20(staff)...
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;root用户uid=0&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;而在Android系统中, 默认情况下，Android会为新安装应用分配uid, 范围[10000, 19999], 称之为appid&lt;/p&gt;
&lt;p&gt;通过在manifest文件中指定&lt;code&gt;android:sharedUserId=&quot;&quot;&lt;/code&gt;可以更改此行为, 使用相同sharedUserId的应用需要使用相同的签名, 使用相同sharedUserId的应用之间可以直接访问数据, 甚至在同一进程中运行.&lt;/p&gt;
&lt;p&gt;支持多用户后, Android的uid由两部分组成:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;uid = userid * 100_000 + appid&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- more --&amp;gt;&lt;/p&gt;
&lt;p&gt;使用ps查看进程, 可以看到用户名形如&lt;code&gt;u0_a66&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ ps -ef
u0_a66       11391   484 0 11:37:58 ?     00:00:00 com.android.camera
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;则该进程userid为&lt;code&gt;0&lt;/code&gt;, appid为&lt;code&gt;10066&lt;/code&gt;, uid为&lt;code&gt;0 * 100_000 + 10066 = 10066&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;编程中使用&lt;code&gt;android.os.Process.myUid()&lt;/code&gt;可以获取当前uid&lt;/p&gt;
&lt;h2&gt;UserHandle&lt;/h2&gt;
&lt;p&gt;UserHandle为对userid的封装。&lt;/p&gt;
&lt;p&gt;使用&lt;code&gt;android.os.Process.myUserHandle()&lt;/code&gt;可以获取当前UserHandle&lt;/p&gt;
&lt;p&gt;UserHandle中包含一些对uid进行转换的方法:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public static @UserIdInt int getUserId(int uid) // uid2userid

public static @AppIdInt int getAppId(int uid) // uid2appid

public static int getUid(@UserIdInt int userId, @AppIdInt int appId) // (userid+appid)2uid
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;UserHandle中包含一些预定义的userid:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public static final @UserIdInt int USER_ALL = -1;  // 全部用户

public static final @UserIdInt int USER_CURRENT = -2;  // 当前用户

public static final @UserIdInt int USER_CURRENT_OR_SELF = -3;  // 当前用户或当前应用用户

public static final @UserIdInt int USER_NULL = -10000;  // 未定义

public static final @UserIdInt int USER_OWNER = 0;  // 系统默认用户, 已弃用

public static final @UserIdInt int USER_SYSTEM = 0;  // 系统默认用户
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;UID System (uid=1000)&lt;/h2&gt;
&lt;p&gt;使用ps查看进程时, 可以看到名为system的用户:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ ps -ef
system       19724   484 0 17:53:31 ?     00:06:57 com.android.settings:remote
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;该用户uid为1000, 开发系统应用时, 指定&lt;code&gt;android:sharedUserId=&quot;android.uid.system&quot;&lt;/code&gt;, 并使用系统签名, 可以直接访问系统应用数据.&lt;/p&gt;
</content:encoded></item><item><title>IPv6 常见问题</title><link>https://shsuco.com/posts/ipv6%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98/</link><guid isPermaLink="true">https://shsuco.com/posts/ipv6%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98/</guid><pubDate>Mon, 20 Jun 2022 18:03:54 GMT</pubDate><content:encoded>&lt;h3&gt;未指定地址&lt;/h3&gt;
&lt;p&gt;v4: &lt;code&gt;0.0.0.0&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;v6: &lt;code&gt;::&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;回环地址&lt;/h3&gt;
&lt;p&gt;v4: &lt;code&gt;127.0.0.1/8&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;v6: &lt;code&gt;::1&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;本地地址&lt;/h3&gt;
&lt;p&gt;v4: &lt;code&gt;169.254.0.0/16&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;v6: &lt;code&gt;fe80::/10&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;临时地址&lt;/h3&gt;
&lt;p&gt;设备可能会获取到多个IPv6地址，包含临时地址和永久地址。由于没有NAT，IPv6地址会让你的设备暴露于整个网络中。因此，设备发起连接时默认使用临时地址，以减少受攻击的风险。&lt;/p&gt;
&lt;h3&gt;用于链接&lt;/h3&gt;
&lt;p&gt;v4: &lt;code&gt;http://ipv4-address:port/&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;v6: &lt;code&gt;http://[ipv6-address]:port/&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;即IPv6地址需要加方括号&lt;/p&gt;
</content:encoded></item><item><title>本站已支持 IPv6</title><link>https://shsuco.com/posts/%E6%9C%AC%E7%AB%99%E5%B7%B2%E6%94%AF%E6%8C%81ipv6/</link><guid isPermaLink="true">https://shsuco.com/posts/%E6%9C%AC%E7%AB%99%E5%B7%B2%E6%94%AF%E6%8C%81ipv6/</guid><pubDate>Mon, 20 Jun 2022 14:43:00 GMT</pubDate><content:encoded>&lt;p&gt;curl&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ curl -6v https://shoco.top        
*   Trying 2401:c080:1c02:10f9:5400:3ff:fece:b230:443...
* Connected to shoco.top (2401:c080:1c02:10f9:5400:3ff:fece:b230) port 443 (#0)
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;!-- more --&amp;gt;&lt;/p&gt;
&lt;p&gt;ping&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ ping6 -c 4 shoco.top
PING6(56=40+8+8 bytes) 2408:8607:1b00:1e:5067:f9e9:4fdc:fe64 --&amp;gt; 2401:c080:1c02:10f9:5400:3ff:fece:b230
16 bytes from 2401:c080:1c02:10f9:5400:3ff:fece:b230, icmp_seq=0 hlim=46 time=109.457 ms
16 bytes from 2401:c080:1c02:10f9:5400:3ff:fece:b230, icmp_seq=1 hlim=46 time=108.656 ms
16 bytes from 2401:c080:1c02:10f9:5400:3ff:fece:b230, icmp_seq=2 hlim=46 time=108.431 ms
16 bytes from 2401:c080:1c02:10f9:5400:3ff:fece:b230, icmp_seq=3 hlim=46 time=117.308 ms

--- shoco.top ping6 statistics ---
4 packets transmitted, 4 packets received, 0.0% packet loss
round-trip min/avg/max/std-dev = 108.431/110.963/117.308/3.683 ms
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;nslookup&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ nslookup -query=AAAA shoco.top
...
shoco.top	has AAAA address 2401:c080:1c02:10f9:5400:3ff:fece:b230
...

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;dig&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ dig AAAA shoco.top

; &amp;lt;&amp;lt;&amp;gt;&amp;gt; DiG 9.10.6 &amp;lt;&amp;lt;&amp;gt;&amp;gt; AAAA shoco.top
;; global options: +cmd
;; Got answer:
;; -&amp;gt;&amp;gt;HEADER&amp;lt;&amp;lt;- opcode: QUERY, status: NOERROR, id: 3514
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 8, ADDITIONAL: 9

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;shoco.top.			IN	AAAA

;; ANSWER SECTION:
shoco.top.		678	IN	AAAA	2401:c080:1c02:10f9:5400:3ff:fece:b230
...
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>APP View 整体变灰</title><link>https://shsuco.com/posts/app-view%E6%95%B4%E4%BD%93%E5%8F%98%E7%81%B0/</link><guid isPermaLink="true">https://shsuco.com/posts/app-view%E6%95%B4%E4%BD%93%E5%8F%98%E7%81%B0/</guid><pubDate>Fri, 10 Jun 2022 17:34:46 GMT</pubDate><content:encoded>&lt;h2&gt;背景&lt;/h2&gt;
&lt;p&gt;国内有些日期会要求APP整体置灰&lt;/p&gt;
&lt;h2&gt;Android&lt;/h2&gt;
&lt;p&gt;应用ColorMatrix设置饱和度为零&lt;/p&gt;
&lt;h3&gt;Kotlin&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;view.setLayerType(View.LAYER_TYPE_HARDWARE, Paint().apply {
    colorFilter = ColorMatrixColorFilter(ColorMatrix().apply { setSaturation(0F) })
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;!-- more --&amp;gt;&lt;/p&gt;
&lt;h3&gt;Java&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;Paint paint = new Paint();
ColorMatrix colorMatrix = new ColorMatrix();
colorMatrix.setSaturation(0);
paint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));
view.setLayerType(View.LAYER_TYPE_HARDWARE, paint);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;iOS&lt;/h2&gt;
&lt;p&gt;添加layer并设置compositingFilter为saturationBlendMode，设置背景颜色为0饱和度颜色，如黑白灰，或者
&lt;code&gt;UIColor(hue: 0, saturation: 0, brightness: 1, alpha: 1)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;色相和亮度可以为[0, 1]任意值&lt;/p&gt;
&lt;h3&gt;Swift&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;let layer = CALayer()
layer.frame = view.frame
layer.compositingFilter = &quot;saturationBlendMode&quot;
layer.backgroundColor = UIColor.white.cgColor
view.layer.addSublayer(layer)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Objective-C&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;CALayer *layer = CALayer.new;
layer.frame = view.frame;
layer.compositingFilter = @&quot;saturationBlendMode&quot;;
layer.backgroundColor = UIColor.whiteColor.CGColor;
[view.layer addSublayer:layer];
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;关于SaturationBlendMode:
&lt;img src=&quot;./images/SaturationBlendMode.JPG&quot; alt=&quot;&quot; /&gt;
应用为compositingFilter后会取当前layer的饱和度与其他layer的色相亮度混合，当前layer饱和度为0时也就间接将其他layer的饱和度设置为0&lt;/p&gt;
&lt;p&gt;此外，可以用这种方式调整为任意灰度，即
grayscale = 1 - saturation&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>Linux 添加 SSH Keys</title><link>https://shsuco.com/posts/linux%E6%B7%BB%E5%8A%A0ssh-keys/</link><guid isPermaLink="true">https://shsuco.com/posts/linux%E6%B7%BB%E5%8A%A0ssh-keys/</guid><pubDate>Wed, 08 Jun 2022 18:41:57 GMT</pubDate><content:encoded>&lt;h2&gt;快速添加本地SSH公钥到服务器&lt;/h2&gt;
&lt;p&gt;如果有&lt;code&gt;ssh-copy-id&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ ssh-copy-id USER@HOSTNAME&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;否则&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ cat ~/.ssh/id_rsa.pub | ssh USER@HOSTNAME &quot;mkdir -p ~/.ssh &amp;amp;&amp;amp; cat &amp;gt;&amp;gt; ~/.ssh/authorized_keys&quot;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- more --&amp;gt;&lt;/p&gt;
&lt;h2&gt;手动添加&lt;/h2&gt;
&lt;p&gt;Local&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ cat ~/.ssh/id_rsa.pub&lt;/code&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;如果文件不存在请先执行&lt;code&gt;$ ssh-keygen&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Server&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ vim ~/.ssh/authorized_keys&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;粘贴key到文件末尾，包含换行&lt;/p&gt;
</content:encoded></item><item><title>Linux 命令行连接网络</title><link>https://shsuco.com/posts/linux%E5%91%BD%E4%BB%A4%E8%A1%8C%E8%BF%9E%E6%8E%A5%E7%BD%91%E7%BB%9C/</link><guid isPermaLink="true">https://shsuco.com/posts/linux%E5%91%BD%E4%BB%A4%E8%A1%8C%E8%BF%9E%E6%8E%A5%E7%BD%91%E7%BB%9C/</guid><description>Linux 下用Shell连接网络, 包括有线和无线连接</description><pubDate>Wed, 08 Jun 2022 11:00:07 GMT</pubDate><content:encoded>&lt;h2&gt;有线连接&lt;/h2&gt;
&lt;h3&gt;查看网卡&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;# ip link&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;获取IP地址&lt;/h3&gt;
&lt;p&gt;假设网卡为eth0&lt;/p&gt;
&lt;p&gt;&lt;code&gt;# dhcpcd eth0&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;无线连接 (Wi-Fi)&lt;/h2&gt;
&lt;h3&gt;已安装iwd (iwctl)&lt;/h3&gt;
&lt;p&gt;进入交互模式&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ iwctl&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;列出所有可用的命令&lt;/p&gt;
&lt;p&gt;&lt;code&gt;[iwd]# help&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;列出所有 WiFi 设备&lt;/p&gt;
&lt;p&gt;&lt;code&gt;[iwd]# device list&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;扫描网络&lt;/p&gt;
&lt;p&gt;&lt;code&gt;[iwd]# station DEVICE scan&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;列出所有可用的网络&lt;/p&gt;
&lt;p&gt;&lt;code&gt;[iwd]# station DEVICE get-networks&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;连接网络&lt;/p&gt;
&lt;p&gt;&lt;code&gt;[iwd]# station DEVICE connect SSID&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;如果要求输入网络密码，将会提示用户输入。此外，连接操作可以应用成命令行参数的形式&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ iwctl --passphrase passphrase station DEVICE connect SSID&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;未安装iwd&lt;/h3&gt;
&lt;h4&gt;查看网卡&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;# ip link&lt;/code&gt;&lt;/p&gt;
&lt;h4&gt;查看连接状态&lt;/h4&gt;
&lt;p&gt;假设网卡为wlan0&lt;/p&gt;
&lt;p&gt;&lt;code&gt;# iw dev wlan0 link&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;# iw dev wlan0 scan&lt;/code&gt;&lt;/p&gt;
&lt;h4&gt;连接网络&lt;/h4&gt;
&lt;h5&gt;快速连接&lt;/h5&gt;
&lt;p&gt;&lt;code&gt;# wpa_supplicant -B -i wlan0 -c &amp;lt;(wpa_passphrase SSID passphrase)&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-B - Fork into background.
-i interface - Interface to listen on.
-c filename - Path to configuration file.
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;由于存在进程替换，这个命令不能以 sudo 方式执行，必须切换到 root 身份&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h5&gt;使用wpa_cli&lt;/h5&gt;
&lt;p&gt;&lt;code&gt;# wpa_cli&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;gt; scan&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;OK
&amp;lt;3&amp;gt;CTRL-EVENT-SCAN-RESULTS
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;&amp;gt; scan_results&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bssid / frequency / signal level / flags / ssid
00:00:00:00:00:00 2462 -49 [WPA2-PSK-CCMP][ESS] MYSSID
11:11:11:11:11:11 2437 -64 [WPA2-PSK-CCMP][ESS] ANOTHERSSID
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;&amp;gt; add_network&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;&amp;gt; set_network 0 ssid &quot;MYSSID&quot;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;gt; set_network 0 psk &quot;passphrase&quot;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;gt; enable_network 0&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;2&amp;gt;CTRL-EVENT-CONNECTED - Connection to 00:00:00:00:00:00 completed (reauth) [id=0 id_str=]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;&amp;gt; save_config&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;OK
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>ADB 使用</title><link>https://shsuco.com/posts/adb%E4%BD%BF%E7%94%A8/</link><guid isPermaLink="true">https://shsuco.com/posts/adb%E4%BD%BF%E7%94%A8/</guid><pubDate>Tue, 07 Jun 2022 20:06:11 GMT</pubDate><content:encoded>&lt;h1&gt;ADB&lt;/h1&gt;
&lt;h2&gt;安装&lt;/h2&gt;
&lt;p&gt;已安装Android SDK:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;export PATH=$PATH:/path/to/Android/sdk/platform-tools&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;不安装Android SDK:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ brew install android-platform-tools&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- more --&amp;gt;&lt;/p&gt;
&lt;h2&gt;通过WLAN连接&lt;/h2&gt;
&lt;p&gt;Android 11以上:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ adb pair ipaddr:port&lt;/code&gt;
&lt;code&gt;$ adb connect ipaddr:port&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Android11以下：&lt;/p&gt;
&lt;p&gt;先连接USB&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ adb tcpip &amp;lt;port&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;随后可以断开USB连接&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ adb connect ipaddr:port&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;查看已连接设备&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;$ adb devices -l&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;停止daemon&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;$ adb kill-server&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;指定设备&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;$ adb devices&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;List of devices attached
emulator-5555 device
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;$ adb -s emulator-5555 install /path/to/some.apk&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;不会引起歧义时，也可以-e指定模拟器，-d指定物理设备。&lt;/p&gt;
&lt;h2&gt;端口转发(设备端口给主机访问)&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;$ adb forward tcp:port tcp:port&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;顺序: LOCAL REMOTE&lt;/p&gt;
&lt;h2&gt;反向代理(主机端口给设备访问)&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;$ adb reverse tcp:port tcp:port&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;顺序: REMOTE LOCAL&lt;/p&gt;
&lt;h2&gt;复制文件&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;$ adb pull /remote/path /local/path&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ adb push /local/path /remote/path&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;Shell&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;$ adb shell&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;安装应用&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;$ adb install /local/path/to/some.apk&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ adb shell pm install /device/path/to/some.apk&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;卸载应用&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;$ adb uninstall &amp;lt;pkg_name&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ adb shell pm uninstall &amp;lt;pkg_name&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;截图&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;$ adb shell screencap /sdcard/demo.png&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;录屏&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;$ adb shell screenrecord /sdcard/demo.mp4&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;logcat&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;$ adb logcat -b all -v color &apos;*:D&apos;&lt;/code&gt;&lt;/p&gt;
</content:encoded></item><item><title>Hexo 搭建, 部署到自定义 server</title><link>https://shsuco.com/posts/hexo%E6%90%AD%E5%BB%BA-%E9%83%A8%E7%BD%B2%E5%88%B0%E8%87%AA%E5%AE%9A%E4%B9%89server/</link><guid isPermaLink="true">https://shsuco.com/posts/hexo%E6%90%AD%E5%BB%BA-%E9%83%A8%E7%BD%B2%E5%88%B0%E8%87%AA%E5%AE%9A%E4%B9%89server/</guid><pubDate>Tue, 07 Jun 2022 17:10:25 GMT</pubDate><content:encoded>&lt;h1&gt;Hexo 搭建, 部署到自定义 server&lt;/h1&gt;
&lt;p&gt;使用Hexo搭建blog，部署到服务器上&lt;/p&gt;
&lt;p&gt;&amp;lt;!--more--&amp;gt;&lt;/p&gt;
&lt;h2&gt;Server配置&lt;/h2&gt;
&lt;h3&gt;安装http服务&lt;/h3&gt;
&lt;p&gt;安装Apache2可参考 {% post_link Apache2-配置https %}&lt;/p&gt;
&lt;h3&gt;创建git用户&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;$ adduser git&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;修改git用户shell&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;# vim /etc/passed&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;定位到&lt;/p&gt;
&lt;p&gt;&lt;code&gt;git:x:1000:1000:,,,:/home/git:/bin/bash&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;将shell修改为&lt;code&gt;/bin/git-shell&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;创建blog仓库&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;$ cd /home/git&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;# sudo -u git mkdir blog.git&lt;/code&gt; (以git用户执行命令，下同)&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ cd blog.git&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;# sudo -u git git init --bare&lt;/code&gt;&lt;/p&gt;
&lt;h4&gt;clone到Web目录&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;# rm -rf /var/www/html&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;# sudo -u git git clone /home/git/blog.git /var/www/html&lt;/code&gt;&lt;/p&gt;
&lt;h4&gt;设置hooks&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;$ cd /home/git/blog.git/hooks&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;# sudo -u git cp post-update.sample post-update&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ vim post-update&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;修改内容为&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/sh

unset $(git rev-parse --local-env-vars)
cd /var/www/html
git pull -f
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;创建hexo仓库（非必需，hexo项目可以放到本地或GitHub等其他位置）&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;$ cd /home/git&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;# sudo -u git mkdir hexo-blog.git&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ cd hexo-blog.git&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;# sudo -u git git init --bare&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;至此服务端配置完成。&lt;/p&gt;
&lt;h2&gt;Local配置&lt;/h2&gt;
&lt;h3&gt;安装npm&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;$ brew install npm&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;or  &lt;code&gt;$ choco install nodejs&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;or  &lt;code&gt;$ winget install OpenJS.NodeJS&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;安装hexo&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;$ npm install hexo-cli -g&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;创建hexo-blog&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;$ hexo init hexo-blog&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ cd hexo-blog&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ git init &amp;amp;&amp;amp; git add --all &amp;amp;&amp;amp; git commit -m &quot;Initial Commit&quot;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ git remote add origin ssh://git@yourdomain/home/git/hexo-blog.git&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ git push -u origin master&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;本地预览&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;$ hexo server&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;安装NexT主题 (可选)&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;$ git clone https://github.com/theme-next/hexo-theme-next themes/next&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;修改主题为next&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ vim _config.yml&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;...
theme: next
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;初始化配置&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ cp themes/next/_config.yml ./source/_data/next.yml&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ vim source/_data/next.yml&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;修改override为true&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;...
override: true
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此后使用next.yml来配置NexT主题。&lt;/p&gt;
&lt;h3&gt;安装hexo-deployer-git&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;$ npm install hexo-deployer-git --save&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ vim _config.yml&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;配置仓库&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;...
deploy:
  type: git
  repo: ssh://yourdomain/home/git/blog.git
  branch: master
...
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;测试部署&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;$ hexo clean &amp;amp;&amp;amp; hexo deploy&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;访问&lt;code&gt;https://yourdomain&lt;/code&gt;测试是否部署成功&lt;/p&gt;
</content:encoded></item><item><title>WordPress 搭建</title><link>https://shsuco.com/posts/wordpress%E6%90%AD%E5%BB%BA/</link><guid isPermaLink="true">https://shsuco.com/posts/wordpress%E6%90%AD%E5%BB%BA/</guid><pubDate>Tue, 07 Jun 2022 15:56:09 GMT</pubDate><content:encoded>&lt;h1&gt;WordPress搭建&lt;/h1&gt;
&lt;p&gt;debian下搭建WordPress&lt;/p&gt;
&lt;p&gt;&amp;lt;!--more--&amp;gt;&lt;/p&gt;
&lt;h3&gt;安装Apache2&lt;/h3&gt;
&lt;p&gt;参考{% post_link Apache2-配置https %}&lt;/p&gt;
&lt;h3&gt;安装Mariadb&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;# apt install mariadb-server&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;# mysql_secure_installation&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;安装PHP&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;# apt install php&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;安装WordPress&lt;/h3&gt;
&lt;h4&gt;下载&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;$ wget https://wordpress.org/latest.tar.gz&lt;/code&gt;&lt;/p&gt;
&lt;h4&gt;解压&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;$ tar -zxvf ./lastest.tar.gz&lt;/code&gt;&lt;/p&gt;
&lt;h4&gt;复制到Web目录&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;$ cp -R ./wordpress/* /var/www/html/&lt;/code&gt;&lt;/p&gt;
&lt;h4&gt;安装&lt;/h4&gt;
&lt;p&gt;访问 https://yourdomain/wp-admin/install.php`&lt;/p&gt;
&lt;p&gt;WordPress安装至此已完成。&lt;/p&gt;
&lt;p&gt;后续步骤：&lt;/p&gt;
&lt;h3&gt;安装PHP插件&lt;/h3&gt;
&lt;p&gt;访问站点健康页，可以看到缺少的php插件。&lt;/p&gt;
&lt;p&gt;通过&lt;code&gt;$ apt install php-插件名&lt;/code&gt; 进行安装&lt;/p&gt;
&lt;h3&gt;配置SMTP&lt;/h3&gt;
&lt;p&gt;访问插件页 https://yourdomain/wp-admin/plugins.php&lt;/p&gt;
&lt;p&gt;安装并启用WP Mail SMTP插件&lt;/p&gt;
&lt;p&gt;按照向导配置SMTP，邮箱密码可能需要生成应用专用密码。&lt;/p&gt;
&lt;h3&gt;允许上传头像&lt;/h3&gt;
&lt;p&gt;安装Simple Local Avatars插件。&lt;/p&gt;
&lt;h3&gt;禁用图片压缩&lt;/h3&gt;
&lt;p&gt;安装Disable &quot;BIG image&quot; Threshold插件。&lt;/p&gt;
</content:encoded></item><item><title>Linux 修改 hostname</title><link>https://shsuco.com/posts/linux%E4%BF%AE%E6%94%B9hostname/</link><guid isPermaLink="true">https://shsuco.com/posts/linux%E4%BF%AE%E6%94%B9hostname/</guid><pubDate>Tue, 07 Jun 2022 15:51:09 GMT</pubDate><content:encoded>&lt;h2&gt;查看当前hostname&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;$ hostnamectl&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;修改hostname&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;$ hostnamectl set-hostname mydomain.com&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;!--more--&amp;gt;&lt;/p&gt;
&lt;h2&gt;编辑/etc/hosts&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;# vim /etc/hosts&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;...
127.0.1.1 mydomian.com mydomain
127.0.0.1 localhost
...
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Apache 服务器安装, 配置 HTTPS</title><link>https://shsuco.com/posts/apache2-%E9%85%8D%E7%BD%AEhttps/</link><guid isPermaLink="true">https://shsuco.com/posts/apache2-%E9%85%8D%E7%BD%AEhttps/</guid><pubDate>Tue, 07 Jun 2022 11:48:01 GMT</pubDate><content:encoded>&lt;h1&gt;Apache服务器安装，配置HTTPS&lt;/h1&gt;
&lt;p&gt;Debian环境，安装Apache2，使用acme.sh配置https&lt;/p&gt;
&lt;p&gt;&amp;lt;!--more--&amp;gt;&lt;/p&gt;
&lt;h3&gt;安装Apache2&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;# apt install apache2&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;安装acme.sh&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;$ curl https://get.acme.sh | sh -s email=my@example.com&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ source ~/.bashrc&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;签发证书&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;$ acme.sh --issue -d mydomain.com -d www.mydomain.com --apache&lt;/code&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;签发前请确保相关域名已正确解析到该server上。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;安装证书&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;$ acme.sh --install-cert -d mydomain.com \
	  --cert-file      /path/to/cert.pem  \
	  --key-file       /path/to/key.pem  \
	  --fullchain-file /path/to/fullchain.pem \
	  --reloadcmd     &quot;systemctl restart apache2&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;这个命令同时会用于刷新证书，请确保reloadcmd正常工作&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;修改apache2配置&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;# vim /etc/apache2/sites-available/default-ssl.conf&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;...
SSLCertificateFile	  /path/to/cert.pem
SSLCertificateKeyFile     /path/to/key.pem
SSLCertificateChainFile   /path/to/fullchain.pem
...
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;启用SSL&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;# a2enmod ssl&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;# a2ensite default-ssl.conf&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;http重定向到https&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;# vim /etc/apache2/sites-available/000-default.conf&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;...
ServerName mydomain.com
RewriteEngine on
RewriteCond   %{HTTPS} !=on
RewriteRule   ^(.*)  https://%{SERVER_NAME}$1 [L,R=301]
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;# a2enmod rewrite&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;重启apache2&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;# systemctl restart apache2&lt;/code&gt;&lt;/p&gt;
</content:encoded></item><item><title>PLL &amp; COLL &amp; OLL</title><link>https://shsuco.com/posts/pll-coll-oll/</link><guid isPermaLink="true">https://shsuco.com/posts/pll-coll-oll/</guid><description>三阶魔方PLL &amp; COLL &amp; OLL公式</description><pubDate>Sat, 04 Jun 2022 18:37:51 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;./images/PLL.JPEG&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/COLL.JPEG&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/OLL.JPEG&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
</content:encoded></item></channel></rss>