<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Hsing Liu on Medium]]></title>
        <description><![CDATA[Stories by Hsing Liu on Medium]]></description>
        <link>https://medium.com/@automorphism?source=rss-a2509555d989------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/0*sRrxCEswM2yEqMKO.</url>
            <title>Stories by Hsing Liu on Medium</title>
            <link>https://medium.com/@automorphism?source=rss-a2509555d989------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 30 May 2026 20:31:07 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@automorphism/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Strict Concurrency Check for Older Projects]]></title>
            <link>https://automorphism.medium.com/strict-concurrency-check-for-older-projects-6ac130a4b7e0?source=rss-a2509555d989------2</link>
            <guid isPermaLink="false">https://medium.com/p/6ac130a4b7e0</guid>
            <category><![CDATA[concurrency]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[swift]]></category>
            <dc:creator><![CDATA[Hsing Liu]]></dc:creator>
            <pubDate>Sun, 31 Mar 2024 05:07:31 GMT</pubDate>
            <atom:updated>2024-03-31T08:35:02.033Z</atom:updated>
            <content:encoded><![CDATA[<p>前陣子 Swift 5.10 釋出了，同時代表 Swift 正準備進入下一個 major release — Swift 6。 <a href="https://forums.swift.org/t/on-the-road-to-swift-6/">Swift 6 這條路</a> 算來已經走了超過四年，可喜可賀。</p><p>每次這樣的大改版，總是帶給開發者們各種期待及焦慮。雖然升上 4 &amp; 5 時都還算平順，但 Swift 3 可是當年 Swift 的 early adopters 難以忘懷的大改，著實讓人傷了些腦筋。</p><p>這次從 Swift 5 升到 6 的一個改進重點是在 concurrency 領域。這些改變對一些老 AppKit/UIKit 專案可能有很大的影響，改動的幅度讓人聯想起當年的 Swift 3。所以在 Swift 5 就有一個 “Strict Concurrency Check” 的 compiler 選項，讓大家可以提前開始準備。</p><p>我在手邊的幾個 AppKit 和 UIKit 專案裡試開啟了這個 strict concurrency check = complete，看那一次數百個的 errors 和 warnings 實在是衝擊感十足。</p><p>如果手邊有老專案，想先提前先瞭解情況，可以從哪裡下手呢？官方 blog post <a href="https://www.swift.org/blog/swift-5.10-released/">Swift 5.10 Released</a> 提供了一個準確精簡的概要，而且也預告了將會有官方的 migration guide。其實國內外的開發者社群裡，許多人都已經意識到將臨的挑戰，並開始編輯各種教學資源，最近明顯多了起來。另外在 compiler &amp; Xcode 那邊也可望會有更多改進，所以我們也還不必太焦慮。</p><p>我研究這個主題，似乎算是開始得較早。於是一時興起，決定把我目前所知彙整起來，成了這篇筆記性質的雜文。雖然許多細節還沒想得很清楚，但望拋磚引玉，歡迎討論指教。</p><p>以下會談到：</p><ul><li>什麼是 &amp; 為什麼要做 strict concurrency check</li><li>如何理解 concurrency errors/warnings 和解法，基本概念和名詞</li><li>如何排除各種錯誤和警告，workarounds/solutions/mindsets</li></ul><h3>The “What”: Strict Concurrency Check Compiler Flag</h3><p>在 Xcode 15 / Swift 5.10 的 build settings 中，把 “Strict Concurrency Checking” 的選項改成 “Complete”，這樣 compiler 就會和未來的 Swift 6 一樣，針對 <strong>sendable constraints</strong> 以及 <strong>actor isolation</strong> 做完整的檢查。</p><p>官網說明： <a href="https://www.swift.org/documentation/concurrency/">Enabling Complete Concurrency Checking</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/968/0*NsV3kGO8ZGzsy6NA.png" /></figure><h3>The Why: 歷史背景</h3><p>Swift 的創造者 Chris Lattner 在 2017 年時寫了 <a href="https://gist.github.com/lattner/31ed37682ef1576b16bca1432ea9f782">Swift Concurrency Manifesto</a> ，立下了 Swift 語言在 concurrency 上的方向及願景。其中一個目標，是 “safe by default”。</p><p>Swift 想要成為一個更安全的語言，一個明顯的先例就是 Optional。如果正確使用 Optional，那只要 compile 能過，我們就已經避開了許多從前 null pointer 會造成的 bugs。代價之一則是我們到處都要思考如何處理 Optional。</p><p>而這次的 safety 目標，是想預防 concurrent programming 裡常見所謂的 data race 問題（在此恕不解釋 data race，若不熟悉請參見其他說明）。我們希望 compiler 有能力禁止或警告我們寫的 code 裡可能有 data race bugs，不要等到 run time 出了問題才發現。</p><p>為了達到這樣的目標，到 Swift 5.10 時，基本機制終於齊備了，並準備在 Swift 6 進入「正確寫就不會發生 data race」的時代。</p><p>不過，Swift 要怎麼達成這件事呢？</p><h3>心智模型：The Actor Model</h3><p>Swift 的 concurrency 設計，除了 async/await 語法外，一個主要概念取自 <a href="https://en.wikipedia.org/wiki/Actor_model">the actor model</a> 。在這個 model 裡，所謂的 actor 是一個計算單位，而在各 actor 之間如果要傳遞資訊，它們不能直接存取共享的同一份資訊，而是要透過傳送 messages 來達成。</p><p>在 WWDC 2022 <a href="https://developer.apple.com/videos/play/wwdc2022/110351/">Eliminate data races using Swift Concurrency</a> session 中，用了一個 “sea of concurrency” 的類比。Task 是海上的船，各種 data 是船上的貨物，而 actors 是海上的島。各個島獨立運作，而船載著貨物在它們之間互相連繫。</p><p>這個類比不是很完美，常常拿它來做多一點的聯想的時候就「壞掉」了，但用來想像 actor model 還是很實用。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*isk33PlQoRFfQ1Fj.jpg" /><figcaption>WWDC 2022–110351 sea of concurrency</figcaption></figure><p>要怎麼利用 actor model 來防止 data race 的危險呢？首先，每座島都一次只做一件事，所以任何「貨物」在不離島的情況下都很安全。再來，如果一個貨物要「上船」，那它必需在設計上就不怕被人在兩個以上的地方同時讀寫。把這些原則化為 compiler 的規則，就成了可以預防 race condition 發生的 strict concurrency check 了。</p><h3>Concurrency Check 要確保什麼？</h3><p>如果用上述海洋的比喻來說，那所謂的 strict concurrency check 大略上就是 compiler 會檢查：</p><ul><li>有些貨物會指定必需在哪一個特定的島上處理。如果 compiler 覺得我們沒有確保會在特定島上處理，就會阻止我們。這叫做 actor isolation。</li><li>有些貨物，我們想要讓它送上船，從一座島送到另一座島。這樣的話，這些貨物必需經過核對、貼上「本貨品可被安全送件」標籤，才準許上船。這稱為 Sendable。</li></ul><p>如果能滿足這些檢查，那麼在這片 concurrency 的大海上，compiler 就能確保不會有 data race 發生了。</p><p>又或換一種比較抽象的說法，可以說 compiler 要確保的是沒有危險的 shared mutable state（這個詞也不在此解釋，若不熟悉請參見其他說明）。確保的方式，是限制什麼樣的東西可以被 share，和限制要在哪裡 access 它們。</p><h3>實際上 Compiler 會如何刁難我們？</h3><p>對 AppKit/UIKit 老專案而言，一開始前者最主要的考量，會是所有跟 UI 扯上關係的地方，我們有沒有用 compiler 看得懂的方式，來確保所有該在 MainActor 上跑的東西都跑在 MainActor 上。</p><p>至於後者，則是會要檢討在有 callback closure 之類的地方（也就是「上下船」的時候），使用到的 data、controller 等等的 types，如何幫它們加上 Sendable protocol conformance（「安全標籤」）。</p><p>那麼，overview 就到這裡。接下來，我想直接列舉一些我看過、想過的方法、思維，來讓一個老專案通過 strict concurrency check。</p><p>首先會從幾個所謂的「逃生門」，那些又快又髒的逃避方法說起。</p><h3>Swift 5 Language Mode</h3><p>終極的逃生門，就是留在 Swift 5 mode。就算未來 Swift 6 正式版了，我們還是可以選擇在舊專案裡使用 Swift 5 模式，就這樣永遠逃避下去。</p><h3>@unchecked Sendable</h3><p>逃生門之一，為一個 type 無條件加上 Sendable conformance。</p><p>Sendable protocol 是個標記「安全貨物」用的 protocol，它不用實作任何東西，但要滿足一些條件。</p><p>經典情境可能是有個 network call 之類的 callback。Network call 是在另一座島上發生的，callback closure 是往來的船，而我們讓 self 坐上船，好接收 data。但 compiler 會抱怨說它不是一種能安全上船的東西。</p><pre>class DataFetcher {<br>    func fetchStuff() {<br>        URLSession.shared.dataTask(with: request) { data, response, error in<br>            self.processData(data)<br>            // Warning: Capture of &#39;self&#39; with non-sendable type &#39;DataFetcher&#39; in a `@Sendable` closure<br>        }<br>        .resume()<br>    }<br>}</pre><p>實際上這也的確不安全，因為 callback 可能會和其他操作在不同 thread 上同時發生、同時讀寫 self 的某些 properties。</p><p>骯髒危險的解法就是叫 compiler 無條件相信你。不要檢查，當它是安全的就好。</p><pre>class DataFetcher: @unchecked Sendable {<br>    // ...<br>}</pre><h3>nonisolated(unsafe)</h3><p>逃生門之二，為一個 variable 無條件跳過檢查。</p><p>經典情境是有 singleton 之類的 global / static variable 這種，很多地方都可能伸手取用的貨物。</p><pre>class DataFetcher {<br>    static var urlString = &quot;http://my.server/...&quot;<br>    // Warning: Static property &#39;urlString&#39; is not concurrency-safe because it is non-isolated global shared mutable state; this is an error in Swift 6<br>}</pre><p>骯髒危險的解法就是跟 compiler 說，這個貨物的確沒有規定要在哪座島上處理（nonisolated），但不要管我。</p><pre>class DataFetcher {<br>    nonisolated(unsafe) static var urlString = &quot;http://my.server/...&quot;<br>}</pre><h3>@preconcurrency import</h3><p>逃生門之三，從某個 imported module 來的 type （包含，或者說特別是某些 Apple 自己的 frameworks）沒有被標記成 Sendable。這時 compiler 可能會建議你在 importXXX 前面加上 @preconcurrency ，也是一樣，就是忽略檢查從這裡 import 的東西。</p><p>逃生門、簡單解法不多，總是逃避也不是辦法，慢慢的我們也只好開始認真思考，到底怎麼把東西做對。</p><h3>@MainActor</h3><p>AppKit/UIKit app 常會有一些跟 UI 高度相關的 types，可能是跟 view 相關，也可能是做輔助 navigation 之類的工作。</p><pre>class Coordinator {<br>    func showLoginController() {<br>        let viewController = LoginViewController() // Warning<br>        viewController.completion = { [self] in<br>            self.completeLogin() // Warning<br>        }<br>    }<br>    <br>    func completeLogin() {<br>        // ...<br>    }<br>}</pre><p>這種物件在和 view 對接的地方很容易就會爆出各種警告。原因是， UIViewController， UIView 和許多像是 UITableViewDataSource 一類的東西都已經被標記為 MainActor 「主島專屬貨物」，所以你如果不是人在主島就不能亂動它們。</p><p>一種可能的解法，就是主張你的那個物件也是 MainActor isolated。這樣就能保證它會跟其它人在 main thread 上和睦相處。而且一但它有了 MainActor isolation，它就會被默許是 Sendable type。</p><pre>@MainActor<br>class Coordinator {<br>    // ...<br>}</pre><p>@MainActor 也可以被加在 function 等等的地方。其實一個 app 裡面，我們本來就預期有很多東西會跑在 main thread 上，所以加 MainActor 也不過是跟 compiler 把話說清楚講明白罷了。</p><p>在 Swift Concurrency 之前的時代，我就記得讀 DispatchQueue 的教學時，看到作者說常常最好的 concurrency 就是不要 concurrent（paraphrased，已不記得出處，有看過這種說法的歡迎回饋）。所以我感覺很多時候並不用太害怕加 @MainActor 。</p><h3>True Sendable Conformance</h3><p>總是用 @unchecked Sendable 逃避也不是辦法。假設我們想要正視這個問題，那就要來看看能不能實作堂堂正正的 Sendable types。這時我們可以來看 <a href="https://developer.apple.com/documentation/swift/sendable">Sendable 文件</a> 說明的條件是哪些。</p><p>原則上，一種 type 如果是「安全貨物」，那代表它不怕有人試著在兩個以上的地方同時讀寫。</p><p>最好的情況是，有的 types 其實根本不用 @unchecked 就可以直接加註為 Sendable 。次好的情況是，有的 properties 可以從 var 改成 let，及有的 properties 自己也需要實作 Sendable，就不用使用逃生門了。這些情況恐怕不很常見，但當遇到需要 Sendable type 的時候，都可以思考一下它跟 Sendable 的距離到底有多遠。</p><h3>Sendable 包裝</h3><p>有時會遇到類似這種情況：</p><pre>// buffer: CMSampleBuffer<br><br>DispatchQueue.main.async {<br>    self.display(buffer: buffer)<br>    // Warning: Capture of &#39;buffer&#39; with non-sendable type &#39;CMSampleBuffer&#39; in a `@Sendable` closure<br>}</pre><p>這裡 compiler 抱怨 AVFoundation 裡的 CMSampleBuffer 不能安全上船。一個方法是把之前說過的 @unchecked Sendable 加在 CMSampleBuffer 上面，但是這樣又太粗暴，所有用 CMSampleBuffer 的地方都會受到影響。</p><p>我遇到這種問題時都忍不住想，有沒有辦法把 buffer 用一個箱子「包裝」保護起來，就能安全的送上船？寫一個這樣的容器其實蠻容易，但如果想要現成的，我們有 <a href="https://github.com/pointfreeco/swift-concurrency-extras">ConcurrencyExtras</a> package 可用。事實上，ConcurrencyExtras 裡面有三種這類型的容器：</p><ul><li><a href="https://github.com/pointfreeco/swift-concurrency-extras/blob/main/Sources/ConcurrencyExtras/UncheckedSendable.swift">UncheckedSendable</a>，不做保護，不安全的盒子</li><li><a href="https://github.com/pointfreeco/swift-concurrency-extras/blob/main/Sources/ConcurrencyExtras/ActorIsolated.swift">ActorIsolated</a> 和 <a href="https://github.com/pointfreeco/swift-concurrency-extras/blob/main/Sources/ConcurrencyExtras/LockIsolated.swift">LockIsolated</a>，內容物受到保護的盒子</li></ul><p>但這時我們會發現，想要靠盒子就做好保護這種想法，其實有點問題。譬如試著修理上個例子：</p><pre>// buffer: CMSampleBuffer<br>let isolatedBuffer = LockIsolated(buffer)<br>        <br>DispatchQueue.main.async {<br>    self.show(buffer: isolatedBuffer.value)<br>}<br><br>// 如果在這裡偷用 buffer...?</pre><p>很明顯，在這個例子裡，如果我想保護的 buffer 在裝進保護箱之前就已經曝露在外，那光是裝箱子送件，完全不會讓它變得更安全。</p><p>其實如果用的是 ConcurrencyExtras 裡的 LockIsolated，這裡會有更多警告。要讓 compiler 高興，就只能用 UncheckedSendable，這個不做保護也不檢查，單純是在單點用來取代 @unchecked Sendable 的箱子。</p><p>咦，那如果把東西「裝箱」完全沒有保護效果，那 ActorIsolated 或 LockIsolated 不是就沒用了嗎？</p><p>關鍵在於，這種有保護的箱子，它所保護的東西必需是打從出生就在箱子裡的。譬如上個例子，如果 buffer 是我們自己的生成的：</p><pre>let isolatedBuffer = LockIsolated(createBuffer())<br>        <br>DispatchQueue.main.async {<br>    isolatedBuffer.withValue { buffer in<br>        self.show(buffer: buffer)<br>    }<br>}</pre><p>LockIsolated.init 使用 autoclosure，所以 buffer 是在保護範圍內生成的，之後也都是在保護下被使用，那就真的有保護效果。</p><p>我覺得這種做 Sendable 包裝的邏輯實在有點微妙，要是能想通，應該會少走一些冤枉路。另一方面，之後也許會有新的方法來讓「事後包裝」變得可能，像是 transferred ownership 或是 region-based isolation 之類的，所以還有待觀察。</p><h3>反思 Sending &amp; Capturing 策略</h3><p>和上面相似的情況，有時卻有完全不同的解法：</p><pre>// userInfo: [String: Any]<br><br>DispatchQueue.main.async {<br>    if let count = userInfo[&quot;count&quot;] as? Int {<br>        // Warning: Capture of &#39;userInfo&#39; with non-sendable type &#39;[String : Any]&#39; in a `@Sendable` closure<br>    }<br>}</pre><p>和之前的差別在於，這次我們其實不需要整個 Dictionary 上船，因為我們本來就只會用到裡面的一個 Int。</p><pre>// userInfo: [String: Any]<br><br>if let count = userInfo[&quot;count&quot;] as? Int {<br>    DispatchQueue.main.async {<br>        // use `count`, ok<br>    }<br>}</pre><p>雖然原本整個物件不是 Sendable，但我們並不需要用到整個物件。把需要的部分抽出來以後，這部分是 Sendable 就沒問題了。</p><p>與其煩惱怎麼讓某個 type 變成 Sendable，可能更值得思考的是，我們設計、使用 API 時到底想要有多少東西穿越 concurrent context？船上要載的貨物能不能更單純？另外，我們能不能在 closure capture list 裡只捕捉一部份真正會用到的東西？等等。</p><p>這樣一來，我們也許就會從只是想要讓 compiler 安靜，升格為重新設計更簡潔、安全的架構。畢竟，程式的安全穩定才是真正的重點。</p><h3>何時定義 Actor？</h3><p>回到這個例子。</p><pre>class DataFetcher: @unchecked Sendable {<br>    // ...<br>}</pre><p>如果這個 class 很難不用 @unchecked 來實作 Sendable ，也不希望它限定在 main thread 上，還有什麼選項？</p><p>把它重定義為 actor 呢？雖說用起來會變得挺麻煩的，但總可以確保 Sendability 了吧？</p><pre>actor DataFetcher {<br>    // ...<br>}</pre><p>其實，自從 Swift 有了 actor type，我就一直懷疑著，到底什麼時候適合把東西定義為 actor？如果純粹是為了 Sendability，就把一個 type 給定義成 actor，不覺得很容易變成殺雞用牛刀嗎？</p><p>下面談談我目前的思維，一部分是從 <a href="https://developer.apple.com/videos/play/wwdc2021/10194/">WWDC 2021: Swift concurrency: Update a sample app</a> 的前幾分鐘來推論。</p><p>在 actor model 的海洋比喻中，actor 是島。生成一個島時，我們就生成了一個所謂的 concurrent context。換一種想法，可以想像每生成一個 actor，它都自帶一個 synchronous DispatchQueue。</p><p>如果有重覆、多次生成的 data type，我們應該不會希望每一個都連帶生出一個 DispatchQueue，只為了預防 data race。我們要的應該是只定義一個「情境」，而讓一連串高度相關的事情，一起被隔離在該情境下完成。</p><p>所以在想要不要定義一個 actor type 時，我會思考它是不是要成為某個情境的進入點。譬如說圖片轉換，networking，sensor signal filtering… 這些就可能就是情境、是「島」，所以可能會用 actor 定義。但進出這些島、或在島上週轉的貨物，則希望不需要用 actor 來定義。</p><p>我有把這樣的原則試應用在一個專案裡，感覺可行，但也許不是最佳解。又是一個期待有更多高手來解惑的問題呢。</p><h3>投入 Async Await 的懷抱</h3><p>到目前為止的敘述，多少有些假設我們的 AppKit/UIKit app 還不想用太多的 async/await 語法。可能是我們自己還不熟，也可能還想再支援 macOS 10.13 一陣子（!?）。</p><p>但我覺得，現在差不多是可以開始熟悉 async/await 的時候了。如果要滿足 Swift 6 的 strict concurrency check，在 async function 底下的選擇還是比較多，也比較乾淨。在 legacy code 不想一次改太多的前提下，可以適度的用 withCheckedContinuation 之類的方法來把一些 callback 接進 async context。有的時候，Sendability / actor isolation 的問題就會自然改善。</p><p>例如，離開 main thread 再回來的情況，原本看起來像是要確保兩處的上下船。</p><pre>class ViewController: UIViewController {<br><br>    // ...<br><br>    func getUsers() {<br>        clearUsersList() // on main queue<br>        userFetcher.fetch { users in // 在別的 queue callback<br>            DispatchQueue.main.async { // 再回去 main queue<br>                self.show(users: users)<br>            }<br>        }<br>    }<br>}</pre><p>可是一但用 async await 改寫，就看起來像是兩次 UI 操作都是在 MainActor 底下，只是中間去做了其他事情。</p><pre>class ViewController: UIViewController {<br><br>    // ...<br><br>    func getUsers() async {<br>        clearUsersList() // on MainActor<br>        let users = await userFetcher.fetch()<br>        show(users: users) // on MainActor, 有如從未離開<br>    }<br>}</pre><p>如果有一些其他 data 是在 fetch 的前後都會用到的，那在 async 版本底下它們會看起來從未跨越 actor context。這個例子有點爛，也許沒什麼幫助，但我想表達的是，你可能會發現，在 async function 底下做 concurrency check 的相關處理，阻力會比較低。</p><h3>Be More Functional</h3><p>在思考怎麼讓老專案通過 concurrency check 時，很難不注意到有些原則和 functional programming 的原則不謀而合。的確，FP 的一大賣點就是它很適合 concurrent programming。不知道 actor model 或 Sendable 的設計上，是不是也融合了來自於 FP 的觀念呢。</p><p>其一，是 immutability 和使用 value types。可以標註為 Sendable 的，正是 value types 和 “reference types with no mutable storage”。我們在試著滿足 strict concurrency check 時，也可以多思考：這裡是否可以用 let 取代 var？或用 struct 取代 class？如此來實作 Sendable type。</p><p>其二，是 pure functions。我們能不能減少使用 global variables 和 singletons，以及 classes 裡的 mutable properties？如果試著去思考怎麼讓 function 更 “pure”，那也許能幫助我們減少 shared mutable state。與其問如何把東西變成 Sendable，不如從根源減少需要 Sendability 的情況。</p><h3>正義，未來</h3><p>以上從 complete strict concurrency check 的背景、思維，漫談到各種可能的解法。</p><p>如開頭所說，這是篇筆記性質的雜文。想必在不久的將來，很多內容就會過時了吧。這是件好事，因為我覺得目前所看到的 concurrency check，侷限實在是有點太多了。</p><p>最近聽電腦科學教育學家 <a href="https://amyjko.medium.com">Amy J. Ko</a> 的一個講座時，突然很有感觸。Swift 這個語言在設計上，不論是強型別、Optional 還是現在的 concurrency，常偏向給 compiler 更大的能力來決定對錯，給我們各種限制，逼我們寫對。但相對於其他如 JavaScript 或 Python 等的語言，我們寫 Swift 時可以說是比較不自由的。對專業工程師來說這也許很有幫助。但對新手來說，我們本希望寫程式是給予他們自由創造的工具和能力，但他們感受到的卻是處處受 compiler 的限制、compiler 才知道什麼是對的，做什麼都要聽 compiler 的指示，剝奪了使用者的 self-efficacy （「自我效能」，自主性？），可視為一種不正義。（ <a href="https://www.google.com/search?q=Juiz+d%27Arc">Juiz</a> … j/k）</p><p>我在這方面涉獵不多，沒辦法解釋得很好，但言歸正傳回到 Swift 的 Sendability &amp; actor isolation check，我期待後續的發展能給 Swift programmers 更多的自由與權力。</p><p>一方面，compiler 會更聰明、在一些地方懂得放鬆規則。像是 <a href="https://github.com/apple/swift-evolution/blob/main/proposals/0414-region-based-isolation.md">SE-0414: Region based Isolation</a> 就會讓之前討論「Sendable 包裝」時的一些情境下，compiler 能夠自行判斷這裡「上船」的貨物不需要有「安全標籤」。這樣我們也會較少需要在明明知道安全的情況下，還要繞圈子來設法迴避 compiler warnings。</p><p>另一方面，我不知道這方面的進展如何，但 programmers 也應該要有更多跳過 compiler check 的方法，就好像我們如果想要直接管理 memory，可以用 UnsafePointer 系列的 API，或是用 compiler flag 來限制某些警告。</p><p>以目前來說，有興趣的人可以和我一樣，把 strict concurrency check 當成一個有趣的謎題來挑戰、學習。不然的話，讓子彈再飛一會兒，也是個不錯的選項。</p><h3>雜資源</h3><ul><li><a href="https://gist.github.com/tclementdev/6af616354912b0347cdf6db159c37057">libdispatch efficiency tips</a>：有些 GCD 的建議其實也適用於 Swift Concurrency。像是 go serial first，以及 “few, long-lived, well-defined queues” 可能也適用於 actor。</li><li><a href="https://hachyderm.io/@holly/111937796779006395">Holly Borla: Let’s talk about data race safety in Swift 6!</a>：不用急。</li></ul><p>WWDC 2021</p><ul><li><a href="https://developer.apple.com/videos/play/wwdc2021/10194">Swift concurrency: Update a sample app</a>：更新老 code，預防 data race，用 actors 重新架構</li><li><a href="https://developer.apple.com/videos/play/wwdc2021/10133/">Protect mutable state with Swift actors</a>：data races 為何發生，shared mutable state，什麼是 Sendable</li><li><a href="https://developer.apple.com/videos/play/wwdc2021/10254/">Swift concurrency: Behind the scenes</a></li></ul><p>WWDC 2022</p><ul><li><a href="https://developer.apple.com/videos/play/wwdc2022/110351/">Eliminate data races using Swift Concurrency</a>：海洋和船的類比，怎麼標示 Sendable</li></ul><p><em>Originally published at </em><a href="http://mathemusician.net/2024/03/concurrency-check/"><em>http://mathemusician.net</em></a><em> on March 31, 2024.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6ac130a4b7e0" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[又要個人化教育？Teaching Machines 讀書心得]]></title>
            <link>https://automorphism.medium.com/%E5%8F%88%E8%A6%81%E5%80%8B%E4%BA%BA%E5%8C%96%E6%95%99%E8%82%B2-teaching-machines-%E8%AE%80%E6%9B%B8%E5%BF%83%E5%BE%97-93ca3e9c9c6?source=rss-a2509555d989------2</link>
            <guid isPermaLink="false">https://medium.com/p/93ca3e9c9c6</guid>
            <category><![CDATA[personalized-learning]]></category>
            <category><![CDATA[讀書心得]]></category>
            <category><![CDATA[machine-teaching]]></category>
            <category><![CDATA[教育科技]]></category>
            <dc:creator><![CDATA[Hsing Liu]]></dc:creator>
            <pubDate>Wed, 05 Oct 2022 10:08:46 GMT</pubDate>
            <atom:updated>2022-10-05T10:11:39.551Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="Skinner’s Teaching Machine" src="https://cdn-images-1.medium.com/max/1024/1*zlrOwoHmCThQSxwRODbcwA.jpeg" /><figcaption>Skinner’s Teaching Machine, source: <a href="https://commons.wikimedia.org/wiki/File:Skinner_teaching_machine_01.jpg">Wikimedia</a></figcaption></figure><p>最近讀了 <em>Teaching Machines: The History of Personalized Learning</em> 一書 (<a href="https://mitpress.mit.edu/9780262546065/teaching-machines/">MIT Press</a>, <a href="https://www.amazon.com/Teaching-Machines-History-Personalized-Learning/dp/0262045699/">Amazon</a>)，作者是 Audrey Watters，一位在教育科技的論述中常有與眾不同論述的學者/作家。我這幾年三不五時會讀到她的著作，它們十分適合在感受到「科技將會翻轉教育！」的一頭熱時，澆下一勺冷水來回復理性。</p><p>這本書出版前我就充滿期待，而 Watters 也沒有讓我失望，讓我讀的過程中時不時寒毛直豎，再閤上書細想自己這幾年在教育科技上的心路歷程。</p><h4>關於本書</h4><p><em>Teaching Machines</em>，如其標題，講的是用「教學機器」來實現個人化學習的歷史。過去十年來，世界上掀起了一陣看似新的熱潮，要將科技和 AI 帶入教學，實現個人化的學習。但在科技界興奮地主張將為教育帶來全新面貌的同時，這些論述和方法，其實大有先例，而且可以向過去追溯大約一百年。</p><p>遠在個人電腦時代來臨之前，就已有各種機械發明，能簡單的做出程序化的「教學」。最著名的，莫過於「行為主義」的代表心理學家，Skinner。Skinner 在 1950 左右的年代曾發明、試著推銷這種機器，讓 teaching machine 這類產品染上了濃厚的行為主義色彩。但，Skinner 不是唯一嘗試過的人，也不是第一人。向前追溯，早在 1920 年代，就有位名為 Sidney Pressey 的心理學教授便做出了這樣的機器。在這兩位名人以外，也還有其他人做了各種嘗試。</p><p>這本書裡描述了數十年間，學者們的論述，這些教學機器的性能，發明者、廠商、學校等等各界人物間的互動與角力。每一次嘗試總是遇到各種挑戰和阻力，最後以失敗告終。</p><p>Watters 做的研究鉅細靡遺，引用了大量當事人的書信和著作，來還原歷史原貌。雖然只是在敘述歷史事件，但如果你近年來有在追蹤教育科技的發展的話，也許也會和我一樣，讀得手中捏一把冷汗。因為，這些這些大多已作古的人所說的話、所做的事，都這麼熟悉，都帶來強烈的既視感。</p><p>百年之中，一次又一次地見到人們說：</p><ul><li>教育長年一成不變，而現在，科技終於能完全改變學習的樣貌。</li><li>我們需要個人化學習，這些機器能讓每個學生用自己的步調學習。</li><li>用這些機器，學生可以遠比現在更輕鬆、更有效率地學會知識。</li><li>機器不是要取代老師！它們能輔助老師，省下時間做更有價值的教學。</li></ul><p>他們主張，透過詳細拆解每個技能，依照學生的反應來調整下一個說明或練習題目，使用統計數據來改進教材，這些機器會為教育帶來前所未有的革新。</p><p>反之，批評者說，這些機器是在剝奪自由與創意，是把孩子們當成機器、實驗動物一般制約。</p><p>耐人尋味的是，每一波的「創新者」常常都不太記得前人做過的嘗試，就像今日的我們沒意識到自己所說的話、做的事是和過去的他們多麼相像。或者，我們會用方便自己論述的方式去解讀過去。書的開頭，Watters 拿了可汗學院的 Sal Khan 所做的 <a href="https://www.youtube.com/watch?v=LqTwDDTjb6g">History of Education</a> 開刀（她以前也有寫過<a href="http://hackeducation.com/2012/11/01/history-of-education-khan-academy%E8%A3%A1">一篇文章</a>談這個影片），顯示教育科技界的人們常用偏頗的眼光來看過去，又多麼的健忘。的確，如今常有人批評現有教育是「工廠模式」，但越是對教育的歷史有所認識，就愈會覺得這種評價也許有失公平。過去百年的教育有著豐富多樣化的改革嘗試歷史，會有現在的樣貌也是其來有自。</p><p>當然，這一段段相似的故事，還是有不同的際遇。故事的走向會隨著大至戰爭、經濟和社會潮流，小至個人自尊心，被各種力量拉扯前進。書中佔了很大篇幅的 Skinner 是個個人色彩鮮明的人，他的各種事件也格外戲劇性。</p><p>對我來說，這些故事的吸引力除了本身的趣味，也在於它們是很有價值的參考。在二十一世紀嘗試用科技來個人化學習的我們，能從過去一百年的經驗學到什麼？過去的人是怎麼失敗的，我們是不是正在步其後塵？</p><h4>我的旅程</h4><p>對我來說，現在讀到這本書可以說是時機十分適切。</p><p>近十年前，我開始對教育科技起了興趣。那時可汗學院還新，臺灣有剛起步的均一平臺，Coursera 和 edX 等 MOOCs 喊著響亮的口號出世。我自己成了 MOOC 的受惠者，靠線上課程進入了程式工程的世界。</p><p>漸漸培養起技術能力的同時，我也日益熱衷於科技能如何輔助教育。這是「大數據」、數據科學如日中天的年代。既然數據這麼厲害，想必也能為教育帶來前所未有的進步吧！那時的確常見到這樣的論述。我也信了那些樂觀的預言，希望有一天能用自己的技術能力來幫忙推進這股創新潮流。</p><p>但在熱情之下，也有不少懷疑漸漸浮升。如果教育科技這麼好，為什麼教育界看起來並沒有多麼快速的在改變呢？有的時候，還會讀到像是 Watters 這類學者的「打臉文」，警告科技界不要太自以為是。我來回在對教育科技的熱情與質疑間徘徊。這次在讀 <em>Teaching Machines</em> 時，便清楚回憶起了那時的感受。</p><p>時間推進到大約四年前。這時的我仍覺得科技想必有辦法對教育帶來極大助益，但並不是純粹把科技丟進來，進步就會發生。至於要怎麼應用科技，才是「好」應用，才能最有效的幫助學習呢？我很懷疑我能憑自身之力找到好答案。這時，我聽說了 Learning Engineering，「學習工程」這個領域，也很幸運的得到機會，直接殺到 Carnegie Mellon University 去，投入了一年的時間來認識什麼是學習工程。</p><p>十年的心路歷程，讓我能夠以一個較平衡的角度來看 <em>Teaching Machines</em>。如果是幾年前的我，這本書可能會讓我對教育科技的信心大受打擊吧。但現在，我可以坦白的欽佩、感謝 Watters 為我們介紹這百年的歷史，讓我們引以為鑑，而且也感受到 Watters 看似相當嘲諷的文筆下，似乎也蘊含樂觀與期盼。</p><h4>It’s Complicated</h4><p>如果用科技來個人化學習的嘗試，像書裡所描述的，每一次都以慘敗收場，我們要怎麼看待近年來再度吹起的這股風潮呢？我們可以純粹相信有了更強大的電腦、AI，就會迎向不同的結局嗎？</p><p>現在的我會說：It’s complicated。</p><p>有多 complicated 呢？我們可以從許多不同面向，來看為何成敗難以定奪。我自己最近的讀物中，就剛好有幾個值得借用來思考這個問題。</p><p>一個是 Roediger 教授寫的 <a href="https://www.psychologicalscience.org/observer/what-happened-to-behaviorism">What Happened to Behaviorism</a>。Teaching machine 和 Skinner、行為主義多少有些關係，而行為主義為主的教育並非我們所樂見。但行為主義現在怎麼了？有一種說法是它過時了，但另一種說法卻是它已經「驘」了！就如行為主義，以前的 teaching machines 也許看似是被淘汰了，但也有一種可能，是它們的精神早已遍及我們的教育系統中，被視為理所當然。</p><p>另外是 Dewey vs. Thorndike，或各種教育理念間的拉鋸。可以參考 Guzdial 教授寫的 <a href="https://computinged.wordpress.com/2020/01/27/thorndike-won-dewey-lost-the-most-important-thing-to-know-about-the-us-education-system/">Thorndike won. Dewey Lost: The Most Important 4 Words about the US Education System</a>。Teaching machine 和任何方案一樣，都無法滿足所有人，有時更會受大環境的限制與影響。若要用科技實現個人化學習，能怎麼做、該怎麼做的衝突，也許可視為不同教育理念間的衝突所表現出來的一面。</p><p>再來是 Reich 教授的書，<a href="https://failuretodisrupt.com/">Failure to Disrupt</a>。這本書裡講到，任一教育科技若想推廣開來，總會有數種基本難關阻擋。如今的網路和 AI 非常強大，有的難關變容易了，但有些卻更困難了。現代的 teaching machine 最終能為個人化學習帶來多少改變，仍要看是如何執行。</p><p>整體來說，我依然對於個人化學習的科技保持著審慎樂觀。何況在所有科技輔助教育的大題目下，個人化學習也只是許多可能性之一。隨著許多人的耕耘努力，我相信我們能找到各種好方法，漸漸把這些可能性化為現實。像是前面提到的 Learning Engineering，就是能妥善結合科技與學習的有力框架之一。</p><h4>結語</h4><p><em>Teaching Machines</em> 是本有點小眾、會挑讀者的歷史故事書。我不會推薦給大部分的人，但自己相當喜歡。</p><p>如果你對現今的 Computer-Assisted Instruction (CAI)、Intelligent Tutoring Systems (ITS) 有所認識或很感興趣，或者剛好工作就與科技輔助個人化學習相關，那麼這本書提供了一般鮮少提及的時空背景，亦是一個反思自己理念的好機會。</p><p>在此之外，我想一般人只需要知道：有時科技界會被自己的力量沖昏頭，忘了過去人的經驗，以為自己做的是多麼空前的創新，卻不見得全然是那麼回事。所以聽到教育科技過於美好的論述時，我們不妨保持一點健康的懷疑態度。</p><p>最後想拋一個問題請大家一起思考。本書作者 Watters 似乎覺得，用「自動化」來「個人化」，及用「機械」來防止「機械化」，是很矛盾的。蒐集數據、歸納群體行為，本質上就與「個人化」背道而馳。你覺得呢？想用統計方法 — 像是取各種平均值 — 來實現個人化學習，是一種矛盾嗎？</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=93ca3e9c9c6" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Swift Protocols and Generics, Part 2: Protocol as Type 和 Type Erasure 有什麼關係？]]></title>
            <link>https://automorphism.medium.com/swift-protocols-and-generics-part-2-protocol-as-type-%E5%92%8C-type-erasure-%E6%9C%89%E4%BB%80%E9%BA%BC%E9%97%9C%E4%BF%82-d2c7ef29c011?source=rss-a2509555d989------2</link>
            <guid isPermaLink="false">https://medium.com/p/d2c7ef29c011</guid>
            <category><![CDATA[type-erasure]]></category>
            <category><![CDATA[protocol-existential-type]]></category>
            <category><![CDATA[swift]]></category>
            <dc:creator><![CDATA[Hsing Liu]]></dc:creator>
            <pubDate>Sun, 05 Jun 2022 10:31:52 GMT</pubDate>
            <atom:updated>2022-06-07T09:38:46.787Z</atom:updated>
            <content:encoded><![CDATA[<p>這是一系列以「建構基礎概念」為目標，希望能幫助 Swift 開發者更加瞭解 protocols 和 generics 的文章。文章索引、相關資源以及較詳細的介紹，請見 <a href="http://mathemusician.net/2022/04/protocols-generics-index">系列簡介</a>。希望讀者在看到文章裡提出問題的時候（請注意 <strong>[Q]</strong> 標示），能夠一起試著解釋看看，好深化你個人的理解。</p><p>上次在 <a href="https://medium.com/@es2mac/swift-protocols-and-generics-part-1-protocol-和其他-type-有什麼不一樣-cb05c4250ec1">Part 1</a> 裡，我們試著分辨 protocol 和其他的 type 有什麼不一樣。當 protocol 作為 type 使用時，它被稱為 existential type。本篇會假設讀者已經熟悉 Part 1 的內容。</p><p>這次的篇幅較長，但是與其拆成數篇，我想把閱讀方式交由讀者自己決定。如果這些對你來說是比較不熟悉的概念，我建議分次分段閱讀，這樣吸收的效果會更好。</p><h3>Essential Question</h3><p>如果有人問你這個問題，你會怎麼解釋？<strong>[Q]</strong></p><p><strong>核心問題：</strong></p><blockquote><em>Protocol as type，也就是 existential type，和 type erasure 之間有什麼關係？</em></blockquote><p><strong>延伸問題：</strong></p><blockquote><em>Type erasure 有什麼替代方案？它們和 type erasure 有什麼性質上的不同？</em></blockquote><h3>前言：為什麼要談 Type Erasure</h3><p>說不定你和我一樣，接觸到 type erasure 一詞的契機，是 compiler 那個經典的錯誤訊息。</p><pre>Protocol &#39;MyProtocol&#39; can only be used as a generic constraint because it has Self or associated type requirements</pre><p>如果用 Part 1 的口語說法，就是 compiler 不想幫我們做盒子，所以這個 protocol 只能當藍圖來用。這個 error 應該會在 Swift 5.7 走入歷史¹。順便一提，當 protocol 使用了 Self 或 associated type 時，通常俗稱為 PAT，但這次我會刻意不使用這個稱呼。</p><p>對我來說，有很長一段時間，type erasure 和 existential type 是兩個分開的概念。當我慢慢瞭解他們的關係時，發現這對我的理解有很大的幫助，而且多了一種方式來看待 protocol as type。</p><p>雖然再來會花不少篇幅討論 type erasure，但重點不會放在要怎麼實作它。我們真正目的，仍是要更深一層理解 protocols &amp; protocol existentials。畢竟，光是知道 existentials 是什麼還不夠。你知道它有什麼重要的特性，又該如何正確的使用嗎？</p><p>討論大致會分為三個階段。</p><ul><li>第一階段，分析現成的 type-erased wrappers，來組織 type erasure 的概念。</li><li>第二階段，自己寫寫看，來更熟悉它的特性。</li><li>第三階段，比對 existential type 和 type erasure。</li></ul><h3>英文小教室：型別橡皮擦</h3><p>在正式開始之前，先說明一件不是很重要，但也許會在你找資料時有幫助的事情，就是英文 type erasure 一詞的幾種變型。</p><ul><li>Erase：這個字大家應該都認識。在這個情境，我會偏好說是「消匿」，因為 type erasure 雖然有消除型別、讓它消失的意思，但更常像是把型別藏起來。</li><li>Type erasure：名詞，把 type 消匿的這個概念本身。</li><li>Type-erased：形容詞（過去分詞），型別「被」消匿的。因為是形容接在後面的名詞，所以兩字間加了連字號。用來做 type erasure 的容器、包裝通常稱為 type-erased wrapper。</li><li>Type-erasing：形容詞（動名詞），「去」消匿型別的。剛才提到的的 type-erased wrapper 也可能被稱為 type-erasing wrapper。至於這東西到底應該想成是被動的被消匿型別，還是主動去消匿型別？也許就隨你喜歡了。我私下都叫它 type eraser，但這個說法似乎不太流行呢，所以之後會簡稱為 wrapper。</li></ul><h3>一、從現成 Wrappers 認識 Type Erasure</h3><p>說到底，為什麼要有 type-erased wrapper 這種東西？它們能做到什麼事情，又是在什麼樣的情境下適用？<strong>[Q]</strong></p><p>Swift standard library 以及 Apple 提供的 libraries &amp; frameworks 裡，有一些取名為 Any* ，我稱作是「現成」的 type-erased wrappers。它們的文件平易近人，而且透露了很多的「why」。下面我們來取幾個摘要敘述，來試著拼湊出 type erasure 的樣貌。</p><p>請注意：雖然我簡化了敘述，但仍然不是每一句話都很重要。建議你可以用上面的那個問題做為引導來抓重點。</p><h3>AnyCollection 和它的朋友們</h3><p><a href="https://developer.apple.com/documentation/swift/collection">Collection</a> 是 Swift standard library 裡極為重要，也相當複雜的 protocol。它有一系列的 <a href="https://developer.apple.com/documentation/swift/swift_standard_library/collections/supporting_types">Supporting Types</a>，其中就有多達七個的 Any* type-erased wrappers。我們來看看其中的 <a href="https://developer.apple.com/documentation/swift/anycollection">AnyCollection</a> 。</p><p>AnyCollection 是個 generic struct，可以把遵循 Collection 的 instance 收藏起來。 AnyCollection 有一個 generic type parameter 叫做 Element，對應 Collection 的 Element associated type。文件說，它是一個 type-erased wrapper，當你叫它做什麼事的時候，它就把工作交給底下、細節被藏起來的 collection。</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/6d9fabc6faad72f70f72a7cc0763cf30/href">https://medium.com/media/6d9fabc6faad72f70f72a7cc0763cf30/href</a></iframe><p>上面這個例子裡， Array 不只是個 Collection，還是更強大的 RandomAccessCollection。 Set 除了是 Collection，同時也是 SetAlgebra。所以 Collection 好比是它們兩個的「最大公因數」。用 AnyCollection 包起來以後，各自的特殊功能就被隱藏了。</p><p>值得一提的是，如果你有試過自己做 Collection，就會知道它十分棘手。它居然有五個 associated types！可是，AnyCollection 只有把五個中的一個變成 generic type parameter，也就是最關鍵的 Element。其它四個都是特定的實體型別。看起來 wrapper 對 protocol 的 associated types 並沒有統一的處理方式。</p><h3>AnyHashable</h3><p><a href="https://developer.apple.com/documentation/swift/anyhashable">AnyHashable</a> 對應 <a href="https://developer.apple.com/documentation/swift/hashable">Hashable</a> protocol，也是 Collections supporting types 中的一員。 Hashable 在 Dictionary 和 Set 的使用上都是必要的。</p><p>就像 AnyCollection 可以收納 Collection 的東西，AnyHashable 可以收納 Hashable。不過，從文件可以看到，它有些奇特的性質。</p><ul><li>Hashable 繼承了 Equatable，所以 AnyHashable 也要能夠比對是否相等。原先是不同型別的值，在藏進 AnyHashable 以後的比對結果不見得會如我們預期。</li></ul><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/cbddd076ceba0ed0c335dcd40f4211ef/href">https://medium.com/media/cbddd076ceba0ed0c335dcd40f4211ef/href</a></iframe><ul><li>Compiler 給了它特別的待遇，會幫我們做自動 wrapping²：</li></ul><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ba2e8f47095ae78675b9eb92f12466da/href">https://medium.com/media/ba2e8f47095ae78675b9eb92f12466da/href</a></iframe><ul><li>它包裝得很「鬆」，你可以直接操作裡面的東西。</li></ul><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/64f54f5d4043234361977684f2c74ebd/href">https://medium.com/media/64f54f5d4043234361977684f2c74ebd/href</a></iframe><p>我覺得 AnyHashable 很有意思的地方，在於它的核心功能不完全合乎直覺。設計者決定了它的 hashing 和 == 要如何實現，還在文件裡特別說明它的行為。這突顯出一個 type-erased wrapper，有時不只是為它的 protocol 做機械式的包裝。</p><h3>AnyView</h3><p>寫 SwiftUI 多少會遇到 <a href="https://developer.apple.com/documentation/swiftui/anyview">AnyView</a> 這個 View protocol 的 wrapper。它只是個普通的 struct，非 generic type。它的 Body associated type 是 Never，就和 Text， Image 等許多內建的 View 一樣。</p><p>一個可能會用到 AnyView 的情境，是視情況產生不同的 view。</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/73320e5dec320bc18626c296cf832f5a/href">https://medium.com/media/73320e5dec320bc18626c296cf832f5a/href</a></iframe><p>問題是，使用 AnyView 有效能上的疑慮。文件裡警告說，當 AnyView 裡面包裝的東西只要改變型別，view hierarchy 就會摧毀重建。所以 AnyView 看似好用，但其實盡量避免比較好。也可以說，AnyView 給了我們一個 type-erased wrapper 雖然方便，但不理想的例子。</p><p>網路上有不少文章教你如何避免 AnyView。常見的替代方案有 Group，view builder 和 generics。這幾個方案表面上長得不一樣，但骨子裡卻很接近，因為 Group 是在 initializer 裡使用 view builder，而 view builder 則是自動生成 generic view 來同時接納不同種類的 views。</p><h3>AnyPublisher</h3><p><a href="https://developer.apple.com/documentation/combine/anypublisher/">AnyPublisher</a> 對應了 <a href="https://developer.apple.com/documentation/combine/publisher">Publisher</a> protocol，是在 Combine 裡發佈數值出來的一方。</p><p>和前幾個例子比起來， AnyPublisher 相當實用，幾乎可以說有用 Combine 就會用到它。老實說我自己根本沒用過 AnyCollection 或 AnyHashable， AnyView 則是通常不用比較好。但 AnyPublisher 不同。在 Publisher protocol 裡面，就提供了一個 <a href="https://developer.apple.com/documentation/combine/publisher/erasetoanypublisher()">eraseToAnyPublisher()</a> method，任何 Publisher 都可以輕易被消匿成 AnyPublisher 。</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/883114ecd8360305eb1dae3df3f57eb5/href">https://medium.com/media/883114ecd8360305eb1dae3df3f57eb5/href</a></iframe><p>上面的例子裡，看得出來有時一個東西的 type 會吐露出很多實作細節的資訊，或是非常複雜冗長。用 type-erased wrapper 藏起來，可以簡化使用，另外也還有別的優點。</p><p>在 AnyPublisher 和 eraseToAnyPublisher() 的文件裡，說明了這種 type erasure 的好處：維護 API 的抽象區隔，譬如讓不同 module 之間看不到對方的實作細節。也許我們可以說這樣的 wrapper 是一個 proxy：</p><ul><li>這讓實作上更有彈性，因為如果 API 對外只有 AnyPublisher 這個介面，那麼更改內部的實作時，也不會影響到外部使用。</li><li>這也是一種保護機制，因為如果使用者看不到實際上是哪種 publisher、是怎麼 publish 數值的，就很難介入 publish 的過程，造成意料外的影響。</li></ul><h3>AnyCancellable</h3><p>同樣在 Combine 裡的 <a href="https://developer.apple.com/documentation/combine/anycancellable">AnyCancellable</a> 對應了 <a href="https://developer.apple.com/documentation/combine/cancellable">Cancellable</a> protocol，是接收數值的一方。 AnyCancellable 和 Cancellable 這對組合，許多方面都可以說是有點特別：</p><ul><li>Cancellable 是個很單純的 protocol。它沒有 associated type，也沒有用到 Self。</li><li>Cancellable 有一個 method 叫做 store(in:) 就是把自己轉為 AnyCancellable，並且同時存進一個 Set（AnyCancellable 自己額外遵循了 Hashable）。</li><li>Publisher 的 methods 會直接回傳 AnyCancellable，而不是其他特定的 Cancellable types。我們其實不太有機會接觸到未被消匿成 AnyCancellable 的 Cancellable types。</li><li>AnyCancellable 是一個 class。它在 deinitialize 的時候會自動執行 cancel()。</li></ul><p>超濃縮的形容一下 Combine 的機制：當 Publisher 和 Subscriber 搭上線時，會產生一個 Subscription，可以讓 Subscriber 用來 request 下一個數值。 Subscription 使用者必需把它存留著，數值才會持續流動。 Subscription 繼承了 Cancellable，可以用來手動 cancel 結束數據流，但使用者不應該用它來 request 數值，那是 Subscriber 的工作。所以把 Subscription 藏進 AnyCancellable 後再讓使用者持有，就能隱藏 request 但允許 cancel，兩全其美。</p><p>也就是說， AnyCancellable 這個 type-erased wrapper 的功能是當 Subscription 的包裝盒，隱藏實際實作、只曝露一部份的介面，幫忙管理 subscription lifecycle 並且便於儲存。</p><p>看完這幾個 Any* type-erased wrappers 的代表，請你先試著整合一下。它們有什麼共通的用途、功能？有什麼特性？有什麼優缺點，適合用在什麼情境？下一段提供的是我自己的整理。<strong>[Q]</strong></p><h3>第一階段統整：關於現成 Wrappers</h3><p>這些 wrapper types 的基本功能非常相似。如果我們有個 protocol P，那對應它的 AnyP wrapper 可以把遵從 P 的型別的值給收藏在內。透過 AnyP instance 我們還是可以使用P 定義的功能，但可能看不到原來那個值，及它原有的型別。</p><p>因為 wrapper 本身是 struct 或 class 這種實體型別，它能幫我們繞過 compiler 的 static typing 限制。譬如，我們可以把原來不同型別的值放進一個 Array，或是從同一個 function 回傳。有一點 workaround 的味道。</p><p>另一方面，消匿 type 資訊這件事，也有更為正面的意義。這些 wrappers 可以隱藏實作細節，達到 decoupling 的效果，並限制 API 抽象層的外面能做的事情。從這個角度來看，type erasure 像是一種 design pattern，配合 protocol 來幫助我們設計更好維護的元件/模組。</p><p>消匿一個值它原有的型別資訊，有時會伴隨一些挑戰。譬如比較複雜的 protocol 可能有 associated types，那 wrapper 可能針對這點有不同的實作方式。或是像 AnyHashable 在比對 == 時會有特別的邏輯。另外像AnyView 則是妨礙了 SwiftUI 的效能優化。</p><p>不知道你的結論，和我上面所說的有沒有什麼差別？</p><p>做為認識 type erasure 的第一步，我們整理了一些現成 type-erased wrappers 的用途和特性。但是，很多眉角是光看成品難以發現的。所以接下來，我們也來試著實作一個 wrapper，看看還能得到什麼樣的啟發。</p><h3>二、動手做做看：AnyShape</h3><p>第一階段我們看了現成的 type-erased wrappers。在第二階段，我們也來自己寫個最簡單的 wrapper。</p><p>這次我還是請來了老朋友， Shape protocol：</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/62d5bb4d7fc43ace2a780b003ac73094/href">https://medium.com/media/62d5bb4d7fc43ace2a780b003ac73094/href</a></iframe><p>我們來寫一個 wrapper，叫做 AnyShape，可以把 Triangle 和 Square 的 instance 都藏起來，只曝露出 Shape 的功能。</p><p>什麼，你說那很蠢，不需要自己寫？哎呀，配合一下嘛。</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/272f6f49422de66d83bff5c44324d827/href">https://medium.com/media/272f6f49422de66d83bff5c44324d827/href</a></iframe><p>實作 type erasure 有幾種方法，其中最直覺的大概就是用 closure，以我們的需要來說也很足夠了。我們可以在 AnyShape 裡藏一個實際會做 draw() 這件事的 closure。</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/2a641cb837d48aab68870f1c8cd8976c/href">https://medium.com/media/2a641cb837d48aab68870f1c8cd8976c/href</a></iframe><p>到這裡，基礎功能已經完備了，只剩下它要如何生成的問題。那再來，我們只需要寫個 init，讓我們可以拿一個 Triangle 的 instance，或一個 Square 的 instance 來產生 AnyShape。</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ef01778a465f2bf3bb1337260f215c87/href">https://medium.com/media/ef01778a465f2bf3bb1337260f215c87/href</a></iframe><p>什麼，你說寫兩個 init 很蠢？哎呀，等一下再改進。畢竟目標已經達成了，像這樣。</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/19c87a15125eae3bef1ea4cb89777a79/href">https://medium.com/media/19c87a15125eae3bef1ea4cb89777a79/href</a></iframe><p>所以寫個基本的 type-erased wrapper 並不難。在繼續之前，不知道你對上面的兩個疑點，心裡是不是已經有了答案。<strong>[Q]</strong></p><ul><li>為什麼寫這個 AnyShape 是多此一舉？</li><li>重覆寫很像的 init 要怎麼改善？</li></ul><p>另外還有個伏筆：我沒說 AnyShape 遵遁 Shape 。</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/e71f16b67868c1940e67bab81d6e69cd/href">https://medium.com/media/e71f16b67868c1940e67bab81d6e69cd/href</a></iframe><p>這有影響我們使用 AnyShape 嗎？</p><h3>改進 Initializer</h3><p>我們生成 AnyShape 的方法，是每種 Shape 都寫一個專屬的 init。這當然是有點糟糕。如果看現成的 wrappers 是怎麼做的，就會發現它們的 init 是 generic function。我們也來試試。</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/275bfff60cbf2a5a31688300da2aa58f/href">https://medium.com/media/275bfff60cbf2a5a31688300da2aa58f/href</a></iframe><p>完成！不止原本的 Triangle 和 Square，再來任何 Shape 都可以用了。Swift 5.7 以後，我們還可以用 some 關鍵字再簡化一點寫法³，但這下次再說吧。</p><p>使用 generics 來實作 type erasure 這件事，總是讓我覺得有點奇妙。如果要說為什麼，也許是因為它們有點像，又不太一樣。譬如，在 init 裡使用的 T instance，和另一處的 AnyShape instance 有什麼相似與不同呢？<strong>[Q]</strong></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f33b500cf976c37b616a8c374b4f2409/href">https://medium.com/media/f33b500cf976c37b616a8c374b4f2409/href</a></iframe><h3>更複雜的 Shape</h3><p>Shape protocol 太單純了，我想幫它再加一個 method，看看會造成什麼影響。</p><p>我想規定同種 Shape 要能互相比較是否為相似圖形。這個想法和 Equatable 差不多，只是說兩個形狀如果在縮放、旋轉、反轉以後可以變成一樣的，那它們就相似。</p><p>下面我改了 Shape 以後，也幫 Triangle 和 Square 實作了新的 isSimilar(to:) method，但是還沒有去動到 AnyShape。 AnyShape 會需要做什麼調整呢？<strong>[Q]</strong></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/e40d114bbc501f0eed58fb15e426542d/href">https://medium.com/media/e40d114bbc501f0eed58fb15e426542d/href</a></iframe><p>isSimilar(to:) 讓 Shape 成了一個有使用到 Self 的 protocol。和 associated type 類似，這應該會讓這個 protocol 變得難搞一點。</p><p>可是，奇怪？雖然我沒有修改 AnyShape ，但 compiler 也沒有抱怨耶。而且使用上似乎也沒有問題。</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c2e4607d7536a6a7b4c2bc0bc44be9e3/href">https://medium.com/media/c2e4607d7536a6a7b4c2bc0bc44be9e3/href</a></iframe><p>不過 AnyShape 並沒有比對相似形狀的功能。但是沒關係，因為… 我沒有宣告 AnyShape 遵循 Shape！</p><p>什麼，你說這根本詐欺？好吧，我自首，但希望能從輕發落。 AnyShape 這個名字強烈暗示它是 Shape，實際上卻不是，有點騙人。但轉念想想，實際上，它現在還是能藏起一個 Shape instance⁴，而且也提供了 Shape 部份的功能。</p><p>如果我沒有需要比對兩個 AnyShape 是否相似，也沒有要把 AnyShape instance 用在要求 Shape的地方（譬如，拿一個 AnyShape 再去 init 一個 AnyShape），那它還是可以當個稱職的 wrapper。</p><p>到這裡有兩個未結疑案。一個是上面這個 AnyShape 不是 Shape 的問題，就這樣放著合理嗎？另一個是如果不想這樣放著，我們可以怎麼實作 isSimilar(to:)？<strong>[Q]</strong></p><h3>第二階段統整：關於手作 Wrapper</h3><p>簡單回顧一下。我們為 Shape protocol 手作了一個 type-erased wrapper，叫做 AnyShape。一開始 Shape 是個很簡單，只有 draw() 一個 method 的 protocol。</p><ul><li>首先用了 closure 來實作 draw() 的功能。</li><li>再來，用 generics 來改進 init，讓它接受任意 Shape 的 instance。</li><li>最後把 Shape 弄複雜，讓它多了一個用到 Self 的 method，但其實沒有再修改 AnyShape，也還是可以用。</li></ul><p>途中我們有遇到幾個疑點。</p><ul><li>在 Shape 變複雜之前，為什麼寫這個 AnyShape 是多此一舉？這個問題先不在這裡回答，但你可能心裡早就有底了。</li><li>Generic type &lt;T: Shape&gt; 的 instance 和 type-erased AnyShape 的 instance 有何異同？</li><li>一個 type-erased wrapper 應該要遵循它所對應的 protocol 嗎？AnyShape 要怎麼遵循有 Self requirement 的 Shape？</li></ul><p>做為第二階段的收尾，我們來談談後兩個疑點。</p><h3>Generics vs. Type Erasure</h3><p>做 generic init 的時候，我提到使用 generics 和 type erasure 用起來有點像，又不太一樣。</p><p>記得那個時候我們在兩個不同的地方用了叫做 shape 的 instance，但是一個是 T type，一個是 AnyShape type。它們有什麼差別？</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/6a27191e860da03c6ac6165a810598ab/href">https://medium.com/media/6a27191e860da03c6ac6165a810598ab/href</a></iframe><p>這兩個 shape 相像的地方，在於它們都可能代表了不同類型的 Shape。也就是說，兩個 shape 背後的型別都像是個變數，可以是 Triangle，可以是 Square，也可以是別種 Shape。兩個都可以執行 Shape 的功能，像是 draw() 。</p><p>不過，它們切換 &amp; 導向不同 Shape 類型的「時機」有明確的差異。一個是在早 compile 的時候就做好，另一個則是延緩到 runtime。這可能聽起來很抽象，所以我做了個（很不精確的）示意圖，等一下在討論時，可以對照著看。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/913/0*jnRI3nmRTasUxAMZ.png" /><figcaption><em>Generic 和 type erasure 從 compile time 到 runtime 的粗略流程</em></figcaption></figure><p>先來看第一種情況，shape 是 generic type T 的 instance。T 是一個 type 的變數，在每一個呼叫 init 的地方，T 就會「代入」某種確定的 type。我們可以說這是「type 層面的抽象化」。</p><p>譬如如果呼叫 function 時，代入的是一個 Triangle instance，那在這裡 T 就是 Triangle，而 shape 就是個如假包換的 Triangle instance。如果另一個地方 T 是 Square，那 shape 就是 Square instance，以此類推。</p><p>再反觀第二種情況，shape 是 AnyShape 的 instance。這次 shape 是一個 AnyShape instance，它會在 runtime 的時候，再來導向它底下的某種實體 Shape 執行功能。我們可以說這是「value 層面的抽象化」。</p><p>我覺得這樣比對兩個機制蠻有趣的。不過對很多人來說，還是會比較關心實用性吧。這裡我們可以自問，從 compile time vs. runtime 這個角度來看，type erasure 相較於 generics 可以如何權衡優劣？<strong>[Q]</strong></p><p>回到上面的示意圖，可以推論的是 generics 讓 compiler 要做比較多的工作，也可能產出比較大的 binary。這些缺點換來的好處，是 compiler 有比較多的資訊來檢查程式的正確性，和優化 runtime 效能。</p><p>Type erasure 這邊則是因為把不同 types 推遲到 runtime 做處理，所以某方面來說它可以更有彈性，但代價是損失一些讓 compiler 最佳化的空間，還有在 runtime 時耗費比較多資源。</p><p>用點術語來說的話，type erasure 是一種 runtime polymorphism，相對於 generics 的 compile-time polymorphism。</p><h3>Wrapper 與它的 Protocol</h3><p>在寫 AnyShape 時，我很賴皮的沒有宣告它有遵循 Shape。原本是舉手之勞，但在 Shape 裡面加了相似比較以後，再要遵循就有點困難了。不過，就算 AnyShape 只實作了半個 Shape 該有的功能，它還是可以用。</p><p>實作 draw() 的方法蠻無腦的，用幾個固定的步驟就可以做出來了。可是 isSimilar(to:) 呢？</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/4fcf29c54e15c188ef097fd975ad0c05/href">https://medium.com/media/4fcf29c54e15c188ef097fd975ad0c05/href</a></iframe><p>要比對兩個 AnyShape 時，消匿 type 的問題就跑出來了。我們看不到 self 和 other 裡面到底包了哪種 Shape，當然，也不知道是不是同一種，即使原本 protocol 裡面要求的是要同一種（即 Self）。Type erasure 消匿各個 instance 的 type 的同時，原本不同 instances 之間可能有的 type 的關係也消失了。</p><p>偷懶的話，乾脆一律 return false 好了。或者，我們在 AnyShape 裡面再加幾個 properties 好做比對。往另一個方向想，也許我們可以試著比對 draw 出來的圖形。回想起 AnyHashable，也是在 == 的行為上面做了些特別的決定⁵。 AnyShape 應該怎麼比對相似？這似乎沒有一個標準答案。</p><p>但其實「沒有標準答案」這件事，本身就很有意思。 isSimilar(to:) 因為用到了 Self，在 wrapper 裡不能像 draw() 那樣套用簡單的步驟來實作，而是我們必需多費一些工、做一些取捨。</p><p>這些取捨會直接影響到 wrapper 到底能不能、要不要遵循它的 protocol。譬如，如果我們決定不處理 isSimilar(to:)，像我原本做的那樣，那 AnyShape 就因此不能遵循 Shape。</p><p>類似的困擾，也適用於有 associated type 的 protocol。記得我們一開始看現成的 wrappers 時，就看到它們對 associated type 各有各的處理方式。</p><p>我想，到這裡我們已經對 type erasure 有了不少認識。該是時候回頭談談 protocol as type 了。</p><h3>三、Protocol as Type 與 Type Erasure</h3><p>先來簡單回顧一下。一開始，我們以現成的 type-erased wrappers 為例，來認識 type erasure。然後，我們動手做了一個 AnyShape wrapper，也討論到一些 type erasure 實作上的挑戰。現在，我們終於要回到一開始的問題。Type erasure 和 protocol as type / existential type 之間有什麼樣的關係呢？那就是--</p><p>Existential type 也是一種 type erasure。</p><p>這樣的話，那也許我們從第一階段的現成 wrappers 學到的事情，以及第二階段自己做一個 wrapper 發現的事情，都可以反過來對應在 existential type 上面。這也就是第三階段的主要目標，不過也請讀者不妨先自己整理看看，這樣的對比是否成立呢？<strong>[Q]</strong></p><h3>AnyShape 與 any Shape</h3><p>在 <a href="https://medium.com/@es2mac/swift-protocols-and-generics-part-1-protocol-和其他-type-有什麼不一樣-cb05c4250ec1">Part 1</a> 裡面，我們說拿 protocol 當成 type 使用時，compiler 會自動幫我們生成一種像是盒子一樣的 type，又稱 existential type，而且還會自動幫我們把東西裝進去。雖然我們不會直接看到它，但 existential type 是個實體的 type，我們可以對它呼叫 protocol 裡的方法。</p><p>換句話說， any Shape 豈不就是自動生成的 AnyShape ？它們在字面上這麼相似，顯然不會是巧合⁶。</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c0e268f2671da527470cd1f2d6ed56b9/href">https://medium.com/media/c0e268f2671da527470cd1f2d6ed56b9/href</a></iframe><p>可以說，existential type 是超方便的自動生成 type-erased wrapper。或者， Any* type-erased wrapper 是充滿人情味的手作 existential container。</p><p>回想起剛接觸 type erasure 的時候，我的認識是這樣的：</p><p>「如果一個 protocol 因為有 Self or associated type requirement 不能當成 type 使用，我可以用 type erasure 來解決這個問題。」</p><p>但現在看來，這個說法其實更貼切：</p><p>「如果一個 protocol 有 Self or associated type requirement，那 compiler 就不願意自動幫我做 type-erased wrapper，但我可以自己手動做一個。」</p><h3>到頭來，Type Erasure 是什麼？</h3><p>說起來，我們談了這麼久的 type erasure，卻從未明確定義它是什麼。如果有人問你「那 type erasure 到底是什麼」，你會怎麼回答他？<strong>[Q]</strong></p><p>打岔閒聊一下。我在為這篇文章做研究時，發現 type erasure 這個概念/模式/技巧，原來在不同的情境下，可能指的是非常不同的東西。C++ 圈子裡說的 type erasure 會被形容為一個實現像在 Python 下的 duck typing 的技巧。Java 和 TypeScript 裡則是某種 compiler 或 transpiler 的技術。一般在 Swift 世界裡所稱的 type erasure，應該是貼近 C++ 的世界。</p><p>回到正題。與其正面回答什麼是 type erasure，我想藉由比較的方式，來同時描述它是什麼 &amp; 不是什麼。比較的對象是其他 polymorphism 的形式：protocol，generics，和 class inheritance。</p><p><strong>Protocol:</strong> 與純做為藍圖的 protocol 不同，type-erased wrapper 是可以生出 instance 來的實體 type。一個 wrapper 通常會對應一個 protocol，但嚴格說起來，wrapper 對外的介面不一定要用 protocol 來定義。</p><p><strong>Generics:</strong> Generics 處理不同 types 是透過 type 變數，可以想成是在 compile time 發生。Type erasure 則是在 runtime 時，把不同 type 的 instance 用單一個 wrapper type 的 instance 給包藏起來，只透過 wrapper 曝露共通的介面。</p><p><strong>Class inheritance:</strong> Inheritance 和 type erasure 類似，可以在 runtime 把不同 types（也就是不同的 subclass）動態的藏在一個共通介面的實體 type（也就是共同的 superclass）後面。不過，使用 class inheritance 需要先決定 superclass 怎麼做，然後再讓 subclass 去繼承它。Type erasure 則是允許顛倒順序，先有一些相像的 types（甚至可以不是自己寫的），之後再創造一個 wrapper 來代表它們。另外，type erasure 也可以應用於 struct 和 enum 等等，不像 class inheritance 顧名思義是只能使用 class。</p><p>Type erasure 和這些不同的方法，有時各司其職，有時可以互相取代，有時相輔相成。像是 generics 和 class inheritance 其實都可能在實作 type erasure 時用到。</p><h3>回頭看 Existential Type 的基本特性</h3><p>如果 protocol as type / existential type 跟 Any* type-erased wrapper 這麼像，那麼我們對後者的認識，應該有很多能應用到 existentials 上面。</p><p>統整起來，我們可以說 existential type 它：</p><ul><li>是用一個實體 type 做 proxy⁷，讓我們可以把多種不同 types 放進同一個 variable 或 collection，或從同一個 function 回傳</li><li>會隱蔽 type 資訊，包括一個 instance 原本是什麼 type，它原本的 associated types 是什麼，或如兩個 instances 原本是不是同一種 type</li><li>是一種 runtime polymorphism，在 value 層面做 types 的抽象化</li><li>會需要付出一點效能代價，包括 wrapper instance 本身，它的轉接機制，和減少最佳化的機會</li><li>適當使用，可以降低介面耦合度，幫我們隱藏實作細節</li></ul><p>當你拿 protocol 來當成 type 使用時，你是否有主動利用、或至少意識到它的這些特性呢？<strong>[Q]</strong></p><p>如果讀者你在這篇文章裡，只有自己回答過一次問題，或是讀完只帶走一個觀念，我希望會是上面這個。</p><p>我想至少在降低耦合這方面，很多人原本就很熟悉了。因為許多使用 protocol 的經典情境，像是 delegate pattern 或 dependency injection，要的就是這個特性。至於隱蔽 type 資訊和 runtime 代價等方面，就比較有可能其實不是我們所要的，只是因為自動的 existential type 太過方便，就沒想這麼多的接受了。</p><h3>Existential Type 的限制</h3><p>另一方面，實作一個 type-erased wrapper 時會遇到的困難，也幫我們解釋了 existentials 的一些限制。</p><p>當然，我指的就是 protocol 用到 Self 或 associated type 的情況。這時，實作一個 wrapper 就必需多費一點心思。Associated type 如何決定？用到 Self 的 methods 該有什麼樣的行為？這些都很難用簡單的規則來決定。</p><p>把這些困難從 type-erased wrapper 對應到 existential type 上面，可以幫助我們思考兩個問題：</p><ul><li>如果一個 protocol 用到 Self 或 associated type，那 compiler 要怎麼自動生成 existential type？</li></ul><p>要不，就是忽視難做的部份。要不，就是乾脆都不要做。一直到 Swift 5.6，compiler 的做法是後者：不幫你做，告訴我們這個 protocol 不能當作 type 使用。但是，在 Swift 5.7 以後，compiler 會進步成會幫我們做 existential type，只是可能部分無法使用¹。就好像我們在實作 AnyShape 時忽略了 isSimilar(to:) 。</p><ul><li>Existential type 要遵循它對應的 protocol 嗎？</li></ul><p>如果像上面說的，compiler 做 existential type 時可能有一部分無法使用，那就無法符合遵循 protocol 的條件了。事實上，目前就算是很單純的 protocol，existential type 也沒有遵循它的 protocol（除少數例外⁸）。這就解釋了為什麼曾經有過「Protocol ‘P’ as a type cannot conform to the protocol itself」這種令人困惑的 compiler error。</p><h3>Existential Type 的替代方案</h3><p>那如果我們遇到 existential type 的限制，或者想要避免它的某些特性，有什麼樣的替代的方法呢？我想可以粗略分成幾類。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*ipW3ofebjUHdRsnS.png" /><figcaption><em>Existentials 相關的解答空間</em></figcaption></figure><p>首先自然是本篇文章的主題，自己寫一個 type-erased wrapper。這是最接近 existential type 的選項，避開限制、繼續使用 protocol + runtime polymorphism，但性質基本上是相同的。</p><p>再來是 generics。因為是 compile-time polymorphism，使用模式和特性都有所不同。但 existential type 和 generics 有點像是 protocol 的雙重人格，其實有不少互相對應之處。</p><p>許多情境下，不使用 protocol 也是合理的選項。例如 inheritance 仍然有它的長處，enum 可以使用 associated value 來容納多種不同 types（但僅限於有定義到的 case），使用 closure 有時會比 protocol 更有彈性… 等等。</p><p>Swift 是個不斷演進中的語言。在 Swift 5 到 6 的這段期間，有數個減低 existential type 的限制、讓它更強大的改進。但對於在思考 Swift 的未來的語言設計/開發者來說，仍舊有個揮之不去的隱憂，就是 existential type 用起來太方便，導致人們不瞭解它的特性、拿它做不適合的用途，忽視了其他設計可能。希望這次從 type erasure 的角度來探討 existential type，可以幫助我們避免掉進這個陷阱。</p><h3>回顧</h3><p>在 Swift 裡面，existential type 可以視為自動生成的 type-erased wrapper。它很適合用做 decoupling，不過作為 runtime polymorphism 它也有些執行時的代價。當對應的 protocol 比較複雜時，existential type 的使用也會受到一些限制。瞭解它的特性、限制和替代方案，我們就可以在設計程式時自問：這裡我需要/想要這樣的 runtime 動態彈性嗎？</p><p>在 <a href="https://medium.com/@es2mac/swift-protocols-and-generics-part-1-protocol-和其他-type-有什麼不一樣-cb05c4250ec1">Part 1</a> 裡，我寫到：「Existential type 雖然方便，但它其實是個還蠻複雜的解法，也不是那麼完美的」。這次算是繞了一大圈，來做一個較為完整的解釋。</p><p>如果我們想深入理解一個觀念，有幾個常理且有效的學習原則。在還不熟悉時，可以先研讀範例（worked examples），其後是動手實作（learning by doing）。在過程中試著自行解釋、回答問題，可以進一步加強觀念連結（self-explanation）。Existential type 是被隱藏在 compiler 後面的技術，不過可以透過看得到的 type-erased wrappers 來研究範例，和試著實作。</p><p>最後邀請你一起來回顧。<strong>[Q]</strong></p><p>關於這次的內容，在技術 &amp; 非技術層面，有哪裡是你覺得難以理解、與原本認知有異，或是不同意說法的嗎？</p><p>回頭來看一開始的「核心問題」。現在再次請你和別人解釋，答案會和之前有什麼不同呢？</p><p><strong>核心問題：</strong></p><blockquote><em>Protocol as type，也就是 existential type，和 type erasure 之間有什麼關係？</em></blockquote><p><strong>延伸問題：</strong> （跟開頭的版本稍有不同）</p><blockquote><em>Type erasure / existential type 有什麼替代方案？它們和 type erasure / existential type 有什麼性質上的不同？</em></blockquote><h3>Footnotes</h3><ol><li><a href="https://github.com/apple/swift-evolution/blob/main/proposals/0309-unlock-existential-types-for-all-protocols.md">SE-0309: Unlock existentials for all protocols</a></li><li><a href="https://stackoverflow.com/questions/42021207/how-are-int-and-string-accepted-as-anyhashable">StackOverflow: How are Int and String accepted as AnyHashable?</a></li><li><a href="https://github.com/apple/swift-evolution/blob/main/proposals/0341-opaque-parameters.md">SE-0341: Opaque Parameter Declarations</a></li><li>其實應該是「一個遵循 Shape 的 type 的 instance」而不是「一個 Shape instance」，因為 protocol 沒有 instance，但是每次用精準的說法好累</li><li><a href="https://developer.apple.com/documentation/swift/anyhashable/2432102">Apple’s documentation for</a> static func == (lhs: AnyHashable, rhs: AnyHashable) -&gt; Bool</li><li>並不是說 any 這個字有多麼特別，只是設計者顯然有意為之。許多現成 wrapper 也是後來才更名為 Any 開頭的。</li><li>至少在 Swift 裡面是這樣，不過單純從 type theory 來說的話，這大概算是 implementation detail。</li><li>例外有：<a href="https://github.com/apple/swift/blob/main/userdocs/diagnostics/protocol-type-non-conformance.md#exceptions">Error and @objc protocols with no static requirements</a>。</li></ol><p><em>Originally published at </em><a href="http://mathemusician.net/2022/06/protocols-generics-p2-type-erasure/"><em>http://mathemusician.net</em></a><em> on June 5, 2022.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d2c7ef29c011" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Swift Protocols and Generics, Part 1: Protocol 和其他 Type 有什麼不一樣？]]></title>
            <link>https://automorphism.medium.com/swift-protocols-and-generics-part-1-protocol-%E5%92%8C%E5%85%B6%E4%BB%96-type-%E6%9C%89%E4%BB%80%E9%BA%BC%E4%B8%8D%E4%B8%80%E6%A8%A3-cb05c4250ec1?source=rss-a2509555d989------2</link>
            <guid isPermaLink="false">https://medium.com/p/cb05c4250ec1</guid>
            <category><![CDATA[protocol-existential-type]]></category>
            <category><![CDATA[swift]]></category>
            <dc:creator><![CDATA[Hsing Liu]]></dc:creator>
            <pubDate>Sun, 10 Apr 2022 06:07:09 GMT</pubDate>
            <atom:updated>2022-04-10T09:42:00.891Z</atom:updated>
            <content:encoded><![CDATA[<p>這是一系列以「建構基礎概念」為目標，希望能幫助 Swift 開發者更加瞭解 protocols 和 generics 的文章。文章索引、相關資源以及較詳細的介紹，請見 <a href="http://mathemusician.net/2022/04/protocols-generics-index">系列簡介</a> 。</p><p>希望讀者在看到文章裡提出問題的時候（請注意 <strong>[Q]</strong> 標示），能夠一起試著解釋看看，好深化你個人的理解。</p><h3>Essential Questions</h3><p>如果有人問你這個問題，你會怎麼解釋？<strong>[Q]</strong></p><p><strong>核心問題：</strong></p><blockquote><em>Protocol 作為一個 type，和 enum, struct, class… 等等其他的 type 有什麼不一樣？</em></blockquote><p><strong>延伸問題：</strong></p><blockquote><em>在 Swift 5.6 以後，protocol type 的 variables 前面要加上 </em><em>any 一字。為什麼要改成這樣？</em></blockquote><h3>大家都認識的 Protocol</h3><p>什麼是 protocol？<strong>[Q]</strong></p><p>只要是寫 Swift 的開發者，想必沒有不認識 protocol 的吧。</p><p><a href="https://docs.swift.org/swift-book/LanguageGuide/Protocols.html">Protocol</a> 是一個藍圖，一個協議，一個規範。借用個 <a href="https://docs.swift.org/swift-book/LanguageGuide/OpaqueTypes.html">官方文件</a> 的例子：</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/34316bce05015f00e47957071b84a6bc/href">https://medium.com/media/34316bce05015f00e47957071b84a6bc/href</a></iframe><p>Shape 規定了一個符合它這個「藍圖」的 type 要能做到什麼事情。 Triangle 則昭告天下，說它符合這個藍圖。所以， Triangle 是一個 Shape 。</p><p>有一件事，說起來有點理所當然，但不知道你有沒有仔細想過。那就是， Triangle 可以生一個 instance（實例）出來，但 Shape 不行。</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/276610a245ea6908643615a09578c852/href">https://medium.com/media/276610a245ea6908643615a09578c852/href</a></iframe><p>不論是 struct，enum 還是 class，都可以生出 instance 來，但 protocol 不行。它只是藍圖，它不必說明要怎麼做到它規範的項目。這樣一說，感覺其它那些才能算是真的 types，但連個實體 instance 都不能做出來的 protocol 好像… 不太算？<strong>[Q]</strong></p><h3>聽說 Swift 是個「靜態型別」（Statically Typed）的語言</h3><p>在型別這方面，Swift 真的是相當嚴格。一個變數只要定義了它是什麼 type，那如果有任何差錯，compiler 是不會跟你客氣的。</p><p>更甚者，compiler 會要求任何變數都在 compile 的時候，就已經知道它的 type，不能說「喔我們程式執行的時候再走著瞧吧」。</p><p>當然我們對 Swift 熟一點，會知道它其實也是有辦法在 runtime 時做一些 dynamic 的事情。不過當我們想到 Swift 引以為傲的 type safety，這安全性無疑是源自 compiler 會在 compile time 知道 &amp; 檢查所有東西的型別。</p><h3>Compiler 的困擾</h3><p>根據到目前為止所說的，可以想像當 compiler 看到類似下面的寫法，可能會覺得相當困擾。</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/3affae3af0f750f8fcf232cdb39333fa/href">https://medium.com/media/3affae3af0f750f8fcf232cdb39333fa/href</a></iframe><p>「保羅，這個 myShape 是什麼 type？」</p><p>我停下敲鍵盤的手，「就是一個 Shape 啊。」</p><p>「呃，可是 Shape 是一個 protocol，它是無法生成 instance 的啊。」Compiler 不以為然的說。</p><p>我想了想，「嘛… 其實我是想先放一個 Triangle 。」</p><p>「那就…！」</p><p>「可是接下來可能會視情況換成一個 Circle 。」</p><p>Compiler 沉默了下來，我也大概知道原因。一個 Triangle 和一個 Circle 的 instance，分別屬於某種「遵從 Shape protocol」的 type，但那並不是「 Shape type」，也不是同一種 type。Compiler 不能允許 myShape 的型別變來變去。</p><p>「Compiler，就沒有什麼好方法嗎？我想用同一個 variable 來放任何 Shape。我有好幾種 Shape ，想保留一點彈性。」</p><p>Compiler 嘆了口氣，搔了搔頭。「我知道了，你就繼續把 Shape 當成 type 來用吧，總之我來處理。你想在 myShape 放 Triangle， Circle 還是什麼符合 Shape 的東西都可以。不過放進來以後，你就看不到它原本的 type 了喔！」</p><p>雖是這麼說，但 compiler 也不能違反它的原則。它在 compile 時就必需決定 myShape 的 type。但是，單純 Shape protocol 不足以直接拿來作為一個 type 使用。 Shape 空有規範，就連之後會放一個 struct 還是 class 都不知道，更別說是要保留多少記憶體等等。</p><p>（註：一直到 Swift 5.6，compiler 都不會再有怨言，但在那之後的版本會叫你加上關鍵字 any ，下面會討論。我們先假設 compiler 還在比較舊的版本。）</p><h3>Protocol 做為一個 Type</h3><p>為了給我們方便，讓我們能把 Shape 當成一個 type 使用，compiler 在幕後可是大費周章。</p><p>Compiler 默默做了一個新的 type，但它沒有特別跟我們講。這個 type 像是一個盒子，可以把 Triangle 也好， Circle 也好，任何遵循 Shape 的 type 的 instance 藏起來。</p><p>如果我要把 myShape 設為一個 Triangle instance，那 compiler 會另外加上生成盒子、把它裝進去的動作。就好像如果我想把某個 optional Triangle 的 variable 設為一個 Triangle instance，compiler 會幫我包成 optional 一樣。</p><p>我們雖然不知道 myShape 的真實身份是 compiler 做的新盒子，但我們還是可以拿 myShape 當成一個「可以執行 Shape 規範的方法」的東西來用。這是因為 compiler 在設計這個盒子的時候，就建好了機制，讓我們在對 myShape 呼叫 Shape 的方法時，正確的指揮被裝進盒子裡的那個 instance 來做事。</p><p>同樣的一行 code，從開發者的角度，和從 compiler 的角度看來，相當不一樣。</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/db361760545bbd51cb6a4a91505b11d4/href">https://medium.com/media/db361760545bbd51cb6a4a91505b11d4/href</a></iframe><h3>一個名字，兩種身份</h3><p>每當我們拿一個 protocol 當成 type 使用，compiler 就會默默幫我們做個盒子。所以這些時候，protocol 並非我們所想那個單純的「藍圖」。</p><p>可是，有的時候它的確是被做為規範其他 type 的藍圖使用。</p><p>也就是說，當我們在程式中用到一個 protocol 的名字時，它都有兩種可能的身分。我們怎麼知道哪個是哪個呢？它現在是藍圖，還是盒子呢？<strong>[Q]</strong></p><p>如果你目前還覺得很難分辨 protocol 是哪種使用方式，別急，再多想想。原則上，我們只要認出 protocol 被使用的情境是在「type 層面」，還是在「value 層面」，就知道答案了。前者它就還是藍圖，後者才是盒子。</p><p>在「type 層面」使用：針對 type，作為某些 type 的規範</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/6bd296ab76e603aa124f8c02d532e9b6/href">https://medium.com/media/6bd296ab76e603aa124f8c02d532e9b6/href</a></iframe><p>在「value 層面」使用：針對 value，作為某個值的 type，實際上變成盒子</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/9b11d7c8c49f6dbc88cfaf861c7d5863/href">https://medium.com/media/9b11d7c8c49f6dbc88cfaf861c7d5863/href</a></iframe><h3>盒子的學名</h3><p>Compiler 幫我們做的這個盒子，確切來說它的名稱是「existential type」。也有人叫它「existential container」，或是口語化的就叫它「existential」。</p><p>為什麼叫這個怪名字，難道它跟哲學有關？Swift 開發者也必需認識尼采嗎？</p><p>受到好奇心驅使，我也花了點時間研究（不是尼采），到了一知半解的程度。這個名字的由來，真的，真的不重要。但也許也有人也同樣好奇，所以就稍微提一下。</p><p>Existential 這個怪名字有點像是在說，「我這盒子裡存在（∃）某個符合你的 protocol 的 type」。Existential（∃）和 universal（∀）這兩個數學邏輯概念，被用在程式的 type theory 後，成了某種 type 概念的名字。</p><p>順道一提，看來與 existential type 相對的 universal type，在 Swift 裡面符合概念的是 generic type。</p><p>至於更準確的定義… 就請有興趣的人自行研究吧。雖然它在 type theory 裡面是一個更廣泛的概念，但至少當我們在討論 Swift 的時候，existential type 一詞指的是一個明確、特定的東西，也就是把 protocol 當作 type 時的這個盒子。</p><h3>拉近兩種思維</h3><p>言歸正傳。Existential type 雖然方便，但它其實是個還蠻複雜的解法，也不是那麼完美的。譬如說，有時我們會看到「protocol 不符合 protocol」的奇妙錯誤。多的一層盒子也會稍微影響執行效率。還有當 protocol 有 associated type 的時候，情況又更複雜。</p><p>不過，對於許多的進階使用者，以及正在研發、改進 Swift 語言本身的開發者們來說，還有另一個大問題，那就是它方便過頭了。直接拿 protocol 來當 type 這麼的方便，以至於很多人寫了很久的 Swift，都沒有意識到原來 compiler 還會要幫我們做盒子。因為沒有正確的認識，所以也難以發現，有時候它並不是最好的選擇。Swift 還有其他進階功能，像是 generics，更適合用在某些情境。</p><p>Swift 是個不斷在進步的語言。而這次的進步，代表的是有東西會不像以前那麼方便了。</p><p>預計從 Swift 6 開始，如果要使用 protocol existential type，必需在 protocol 的名字前面加上 any 一字。當然，這僅限在 protocol 被當作盒子，而非作為藍圖使用的時候。</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/3614c89c8338e852adf4c6df5ddcf4b0/href">https://medium.com/media/3614c89c8338e852adf4c6df5ddcf4b0/href</a></iframe><p>有點像把一個 type 的名字用 [] 圍起來會變成 Array、後面加上 ? 會變成 Optional 一樣，當 protocol 的名字前面加上 any 時，我們就知道，原來這是個 existential container 啊。</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/e010c116a45094e49200fc887c987513/href">https://medium.com/media/e010c116a45094e49200fc887c987513/href</a></iframe><h3>回顧</h3><p>在 Swift 裡面，一個 protocol 雖然不能像 struct 和 class 一樣生出 instance，但我們還是能把它當成 instance 的 type 來使用，來達到我們 polymorphism 的目的。實際上為了做到這件事，compiler 會在幕後幫我們產生 existential type。這雖然方便，但也反而造成一些困惑。預計在 Swift 6 之後，protocol existential type 會被要求前面加上一個 any 關鍵字。</p><p>最後邀請你一起來回顧。<strong>[Q]</strong></p><p>關於這次的內容，在技術 &amp; 非技術層面，有哪裡是你覺得難以理解、與原本認知有異，或是不同意說法的嗎？</p><p>回頭來看一開始的「核心問題」。現在再次請你和別人解釋，答案會和之前有什麼不同呢？</p><p><strong>核心問題：</strong></p><blockquote><em>Protocol 作為一個 type，和 enum, struct, class… 等等其他的 type 有什麼不一樣？</em></blockquote><p><strong>延伸問題：</strong></p><blockquote><em>在 Swift 5.6 以後，protocol type 的 variables 前面要加上 </em><em>any 一字。為什麼要改成這樣？</em></blockquote><p><em>Originally published at </em><a href="http://mathemusician.net/2022/04/protocols-generics-p1-existentials/"><em>http://mathemusician.net</em></a><em> on April 10, 2022.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=cb05c4250ec1" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Thoughts on Writing Good SRS Prompts, Part 2: Using SRS Beyond Memorization]]></title>
            <link>https://automorphism.medium.com/thoughts-on-writing-good-srs-prompts-part-2-using-srs-beyond-memorization-c0ebd450e30a?source=rss-a2509555d989------2</link>
            <guid isPermaLink="false">https://medium.com/p/c0ebd450e30a</guid>
            <category><![CDATA[spaced-repetition]]></category>
            <category><![CDATA[memorization]]></category>
            <category><![CDATA[understanding]]></category>
            <category><![CDATA[learning]]></category>
            <dc:creator><![CDATA[Hsing Liu]]></dc:creator>
            <pubDate>Thu, 24 Feb 2022 10:15:32 GMT</pubDate>
            <atom:updated>2022-02-24T10:50:50.342Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="Kid thinking on a bridge" src="https://cdn-images-1.medium.com/max/1024/0*ieESXMrLOQnKDnLY" /><figcaption>Photo by <a href="https://unsplash.com/@japhethmast?utm_source=medium&amp;utm_medium=referral">Japheth Mast</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>This is the second part of my thoughts on writing good SRS prompts after reading this <a href="https://andymatuschak.org/prompts/">guide</a> by Andy Matuschak. In <a href="https://medium.com/@es2mac/thoughts-on-writing-good-srs-prompts-part-1-what-is-a-good-prompt-6a70efcce359">part 1</a>, I mused on what it means for a prompt to be good. One aspect of judging whether a prompt is good has to do with what we intend to learn with the prompt. That is, how good is the learning goal underlying the learning tasks defined by the prompt? But given the diversity of goals that we might have, how do we know if the spaced repetition system is the right approach to achieve them?</p><p>To be more specific, SRS is tailor-made for memorization-type learning goals. But ideally, we want to learn things way beyond remembering facts, and it would be very exciting if SRS can be an effective tool for those as well. So this time, I want to focus on the very idea of using SRS beyond memorization. Do we have any reason to believe this is a good idea? What could go wrong? While I want to express certain concerns, I’ll also make some conjectures on how we might address those concerns.</p><h3>Using SRS for Memorization</h3><p>Spaced repetition systems are most well known for helping us remember facts. And indeed, there are some good explanations for why SRS are effective for memorization. So let’s start from there, before we go beyond memorization.</p><p>Spaced repetition systems are designed around two principles that help us remember things better: retrieval practice and optimal scheduling.</p><p><strong>Retrieval:</strong> Trying to recall something is a more effective way to memorize it, compared to studying it again. It is how flash cards work.</p><p><strong>Optimal scheduling:</strong> This is where a digital SRS diverges from a physical deck of flash cards. If we review cards randomly or at a fixed interval, we’ll be reviewing many cards either too early (so that it’s very easy and doesn’t reinforce our memory very much), or too late (we’ve forgotten too much about it so we fail to recall, and it’s a frustrating experience). So timing the reviews just right can make our review sessions more efficient. Typically, this means that the gaps between review sessions start short and get exponentially longer.</p><p>These two principles are most often associated with memorization, rather than sense-making. But we already know that SRS is good for remembering things. Do we also have reasons to suspect that SRS can be good for learning beyond memorization?</p><h3>Using SRS Beyond Memorization</h3><p>For one thing, remembering is fundamental to understanding.</p><h4>Memorization as a Foundation to Understanding</h4><p>Knowledge builds on top of other knowledge. Remembering facts is often a prerequisite to understanding deeper concepts.</p><p>Mathematical definitions and theorems are some of the first examples that come to my mind. I studied math at the graduate level. At some point, I naively thought that math is great for someone who hates to memorize stuff like me. Reasoning and understanding was all I need. But thinking back, this was not the case. I needed to remember the definitions of mathematical objects and operations before I can understand their properties. I then need to internalize those properties, and not having to reason them from scratch every time (although doing so occasionally is fine), to access the more intricate and interesting results. Although a lot of that memorization happened “naturally” rather than dedicated efforts to memorize, deeper understanding couldn’t happen without some memorization.</p><p>There are some learning-related models that embody this idea. In Bloom’s taxonomy, the six cognitive domains are: remember, understand, apply, analyze, evaluate, and create. Associated with this model is the idea that those lower levels are building blocks for the higher ones. In other words, remembering is fundamental to deeper understanding.</p><p>There is a related formulation in the <a href="https://www.researchgate.net/publication/223968321_The_Knowledge-Learning-Instruction_KLI_Framework_Toward_Bridging_the_Science-Practice_Chasm_to_Enhance_Robust_Student_Learning">Knowledge-Learning-Instruction (KLI) framework</a> by Dr. Koedinger. One idea that the KLI framework explores is the matching between learning processes and knowledge types. Some learning processes seem to be especially effective for low-complexity knowledge components, like facts, while others are great for high-complexity knowledge components, like understanding principles. What if we switch up the matching? The observation is that learning processes good for low-complexity knowledge are often also good for high-complexity knowledge (although maybe not to the same degree), but not vice versa.</p><p>What I infer from these is this: Even if SRS is primarily good for memorization, we’d still expect it to be helpful for deeper understanding.</p><h4>Other Good Learning Principles for SRS</h4><p>To go one step further considering the effectiveness of SRS beyond memorization, we might ask: does the use of SRS involve other good learning principles that benefit understanding? I think there are at least two that fit naturally here, which are self-explanation and interleaving.</p><p><strong>Self-explanation:</strong> Trying to explain something using our own words is an effective way to help understand it better. During SRS review sessions, we’d often engage in reasoning around a prompt and not merely recalling the answer. That could either be because it helps us synthesize the answer, or because we’ve explicitly asked “why” in our prompt.</p><p><strong>Interleaving:</strong> Alternating between different topics during practice sessions might help us learn better, versus “blocking,” or grouping the practice of the same topic together. In SRS we often have a big bucket of mixed prompts from multiple sources, so during a review session we’d be hopping from one topic to another, and that can be a good thing.</p><p>I’m glossing over all kinds of finer details, but if we write prompts that ask us to explain things or otherwise induce mental processes other than mere factual recall, then I can see how principles such as these are at play to help us develop deeper understanding.</p><h4>Salience</h4><p>Andy brought up the concept of salience for what he calls “salience prompts” in the guide. I find it really interesting and think it deserves special mention.</p><p>Salience is about what you notice. When something is fresh in your mind, you notice it more around you. This is a mechanism firmly rooted in our biological hardware: scientists have identified areas dubbed the “salience network” in the brain. It could be an underlying mechanism for the higher-level learning principles above, like interleaving.</p><p>Reviewing prompts in an SRS brings the things we review to our active attention. We might write prompts that do this more explicitly, asking us “what might you think about or notice in this situation?” But most likely, it’s not just these kinds of purpose-crafted prompts that affect the way we notice things in our lives. Rather, all prompts do.</p><p>This is quite an interesting effect of using an SRS, and it tells us how we might use SRS beyond memorization. By writing a prompt, we’re “brewing” ideas over time, by bringing them to our conscious attention on a schedule, and each time it will remain salient for some time. Those ideas will have more time and more opportunities to grow roots by connecting to other things we see and think, creating more enduring memories and a more well-connected expert knowledge network.</p><p>Sometimes we might even see serendipitous connections of ideas. It’s a cool feeling when you’ve reviewed a prompt, then the thought somehow “clicked” that day as you notice how it is relevant in a completely different context. However, this is jut a secondary effect. If this kind of connection-making is important, then I’d prefer more intentionality, such as explicitly trying to find different contexts in which the idea might apply.</p><p>In a sense, we can think of writing a prompt as a declaration of what ideas we want to pay more attention to. Our brains would then keep tap of those ideas even when we’re not consciously thinking about them. I think this fits well with the development of deeper understanding — giving ideas time to grow. Meanwhile, I wonder how much we know about this phenomenon. What are the chances that we might misuse this power?</p><h3>Should We Be Worried?</h3><p>So when we add a prompt into our SRS, the system would guarantee that we practice and remember it, think about it for a long time, and notice it more than other things. This is really amazing, but also alarming.</p><p>Of course, the concern is about whether we’re focusing all this energy and attention on the right things. When we use SRS, we are usually in a self-directed learning context. If we’re not making good decisions about what to write prompts for, then we’d be wasting time and effort. For example, remembering the US state capitals may not have much benefit for most people. But could there be situations where wasting efforts is not the worst outcome? Thinking in terms of salience, I worry that there might be situations where poor decisions could cause active harm.</p><p>Before getting into that, maybe we should ask, should we even doubt whether we know how to pick the right things to make prompts for? I suspect the answer is yes, especially when we’re still a novice in a subject area.</p><h4>Novices and the Difficulties of Self-Directed Learning</h4><p>When someone is a novice, not only do they know less about the subject, they also know less about how to advance. Our self-learning ability correlates with our expertise in the subject area that we’re learning.</p><p>As a software developer, I often see articles giving advice to novice developers. One common advice is to stop trying to memorize all the syntax of a language, or all APIs in a framework. Another one is to not fall prey to the “shiny object syndrome” — continuously chasing after new technologies and trends. In these cases, it’d be more beneficial for a novice to spend their time thinking through the patterns and approaches to problem-solving, than over-commit on the surface level knowledge.</p><p>As another example, when I first ventured into learning sciences, I found it difficult to decide which research papers to read, which parts to read, and how much time to spend reading them. As far as I can tell, this is a common problem for students new to any field of research.</p><p>Novices face a number of metacognitive challenges. A lot of knowledge is in the realm of “unconscious incompetence” for novices, or putting it more plainly, things they aren’t even aware that they don’t know. Some things are better internalized, others can be looked up on demand, but it’s hard to tell which is which. It’s also hard to differentiate something we like, e.g. shiny objects, from something that is harder and less interesting, but better for developing expert-level understanding.</p><p>So yes, we should be concerned that people will probably make some bad choices when deciding what to learn, and the problem is worse when one’s level of expertise is still low. In terms of SRS usage, that means creating ineffective prompts that are a waste of time to write and review. I don’t want to over-exaggerate this loss of efficiency as disastrous. That journey of exploring new knowledge can itself be a valuable thing. But I do want to run with this train of thought further and hypothesize what might be the worst-case scenario.</p><h4>Overfitting</h4><p>In machine learning, overfitting is a situation where an ML model “specializes” too much on a particular dataset, so that it performs really well on those data, but is terrible at generalizing the inference to data it hasn’t seen before. Overfitting might happen when the model is too powerful, the dataset is too small, and/or the training process is longer than needed.</p><p>Overfitting is what I keep thinking about regarding how using an SRS could go wrong, in light of its power described above. Given how it’s easy (especially for novices) to target the wrong things to learn, and how SRS would ensure that you keep coming back to them and maintain their salience, this feels just like a machine learning model being extensively trained on a small and noisy set of data. Such a model would probably perform poorly, and make unexpected mistakes in prediction.</p><p>I don’t love this analogy, though. One reason is that it has the smell of overthinking, something I’m prone to do. The other reason is that I don’t believe machine learning is a good source of inference/inspiration for human learning.</p><p>That said, with SRS, we can easily target a few ideas and incubate them. The opposite of that might be to expose oneself to a wide range of ideas, without dwelling too long on each. The challenge of when to go deep versus when to go broad is almost like a multi-armed bandit problem, and writing prompts correspond to “exploit,” where you double down on what you think is valuable based on limited information. We achieve optimal expected return only when we strike a good balance between exploitation and exploration.</p><p>In Andy’s guide, he mentioned how the “Baader-Meinhof Phenomenon” (namely, once you notice something, you’ll notice it more often) may be unhelpful sometimes. Meanwhile, looking at the neighboring concept of <em>deliberate practice</em>, one major consideration there is that while it helps people become more efficient at a skill, it might also make them less adaptive. These both have the same vibe as what I call overfitting, so perhaps lending some legitimacy to my concern.</p><p>How might it look in practice? Here’s an attempt of a crappy example by extending one (originally good example) taken from the guide. Say for a cooking prompt, we write that instead of adding water to a dish, we can consider using stock instead to get more flavor. We incubate that thought over time by reviewing the prompt. The next time we encounter a situation where we might use water, we immediately think of using stock, but that inhibits other ideas like condensing liquids or turning it into a different dish. Maybe the more fundamental concept here is managing fluids and layering on flavor, and we should’ve kept those in the forefront of our minds instead of looking for more opportunities to replace water with stock.</p><p>If you buy these arguments, then the follow-up question is: Given that we sometimes write ineffective prompts, what can we do about it?</p><h3>Remedies</h3><p>The most obvious, and I suspect probably the best remedy, is to have a robust routine for revising our prompts.</p><h4>Prompt Revisions</h4><p>What we want to avoid is the excessive incubation of ideas that aren’t helpful, but we’ve put into the system because of poor judgments. We’ve likely put them in because we didn’t know enough at the time yet to make better decisions.</p><p>Andy’s guide, as well as other SRS-related guides, recommends that we add prompts in several passes when we study a topic, flexibly pick things up as we go, and revise or discard any old prompts that are no longer appropriate. This makes perfect sense, since as our understanding of a topic keeps improving, we can course-correct and reorient our focus on what we need most.</p><p>However, I personally find this difficult to follow through in practice. The natural tendency is to keep moving forward and adding new prompts into the system. If we do our reviews in fragmented time slots on a smartphone, then when we notice that a prompt might need to be updated, the most we could do is to flag it and deal with it later. Updating old prompts seems like a skill of its own, and an additional process that we need to intentionally set time for.</p><h4>Other Supports</h4><p>Aside from having a routine to revise prompts, I think we simply need to be more conscious about keeping our prompts targeted on the right things. If we’re not the best judges ourselves, we could use some scaffolding or outside support. I’ve brainstormed a couple of ideas here.</p><p><strong>Evaluate confidence:</strong> Well-designed courses and guides give us some sense of what we should pay attention to, explicitly or implicitly. So if we’re making prompts based on those, we might not need to worry as much about targeting the right things. Meanwhile, if we’re writing prompts based on articles, papers, books and other unstructured materials with less of an instructional nature, then we might want to be more mindful about revising those prompts down the road.</p><p>Speaking of courses and guides, Andy goes a step further and offers author-written prompts. Using the integrated Orbit platform is quite an interesting experience, and I’ll try to organize my thoughts on that next time.</p><p><strong>Mentorship:</strong> Instead of trying to do everything right ourselves, perhaps we could consult human mentors. Have a mentor look at the prompts you’ve made, and they can quickly correct any misunderstandings, spot missed opportunities and advise future directions. It’d also be a great way to communicate what we’re thinking and learning.</p><p><strong>Self-reflection techniques and support:</strong> Tips for writing good prompts are naturally also useful for inspecting and revising old prompts. But are there specific strategies and actions that we can adopt, during review sessions or otherwise, that help us become better at revising our prompts? In part 1, I mentioned how I try to imagine the application context when I review prompts. This also helps me spot stale prompts due for maintenance. How else might we tell if a prompt we’re reviewing is no longer appropriate?</p><h3>Closing Thoughts</h3><p>It’s quite natural that we might try to expand the use of spaced repetition systems to learning beyond simple memorization. We might do this explicitly by writing prompts that promote higher-level thinking, or have it happen implicitly by the way our attention works.</p><p>As they say, SRS makes memory a choice. Along with that, it makes keeping ideas salient easy. So even for simpler memorization prompts, it is worth considering: Is this something I want to notice more, and have in my mind more often?</p><p>Self-directed learning has many challenges, especially when we’re a relative novice in a field. If Anki gives me the power to choose whatever I want to remember, do I naively abuse the fact that I could remember as much as I want, or do I have the wisdom to know what’s not worth reviewing repeatedly? If keeping ideas salient is easy, how do I know I’m not overdoing it, such that it skews my perspective or makes me think too rigidly? Do we understand the full implication of using SRS, and how it changes our thinking?</p><p>While I’m sure I would gain more insights into these questions as I continue to use SRS myself and reading others’ work, what I’d really like to see is more rigorous research efforts. One way that could happen is if we build SRS tools that incorporate the means for generating insights. This seems to be already happening with the Orbit system that Andy built and used for this guide. Next time, I’d like to collect some remaining thoughts about the experience of using Orbit, author-written prompts, and data-informed prompt improvement.</p><p><em>Originally published at </em><a href="http://mathemusician.net/2022/02/good-prompts-2/"><em>http://mathemusician.net</em></a><em> on February 24, 2022.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c0ebd450e30a" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Thoughts on Writing Good SRS Prompts, Part 1: What is a Good Prompt?]]></title>
            <link>https://automorphism.medium.com/thoughts-on-writing-good-srs-prompts-part-1-what-is-a-good-prompt-6a70efcce359?source=rss-a2509555d989------2</link>
            <guid isPermaLink="false">https://medium.com/p/6a70efcce359</guid>
            <category><![CDATA[spaced-repetition]]></category>
            <category><![CDATA[learning-science]]></category>
            <category><![CDATA[instructional-design]]></category>
            <category><![CDATA[anki]]></category>
            <dc:creator><![CDATA[Hsing Liu]]></dc:creator>
            <pubDate>Wed, 17 Nov 2021 02:00:55 GMT</pubDate>
            <atom:updated>2021-11-17T02:14:22.883Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*YNqGEq_nM585oUnl" /><figcaption>Photo by <a href="https://unsplash.com/@nicontents?utm_source=medium&amp;utm_medium=referral">Nicolò Canu</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>Some time ago, I worked through Andy Matuschak’s guide, <a href="https://andymatuschak.org/prompts/">How to write good prompts: using spaced repetition to create understanding</a>, and took some personal notes. Revisiting the notes, I decided to write a couple of blog posts to help me consolidate my thoughts.</p><p>For the uninitiated, the guide is about using spaced repetition systems (SRS). One of the most popular SRS is an app called Anki. I’ve been a moderate user of Anki for a few years myself. These apps are like supercharged flashcards. How they work is that you write your cards, i.e. question prompts with answers, and regularly review the cards. That may not sound impressive, but behind the scene, these apps schedule your card reviews in a way that optimizes learning gain, according to cognitive science principles.</p><p>SRS is theoretically sound, and it has an enthusiastic user base. But how much you get out of it depends a lot on how well you use it. A big part of that is whether you can write effective prompts. Including Andy, many smart people (e.g. Michael Nielsen, Piotr Wozniak) have written a lot more about SRS, so for those interested, check out their works related to topics such as the mnemonic medium and tools for thought. There you should find writings and discussions more mature than my own.</p><h3>What is a “Good” Prompt?</h3><p>One thing I kept thinking about as I worked through Andy’s guide is how there are two ways a prompt can be “good.” One is the “what,” that the prompt pertains to knowledge worth learning to you. The other is “how,” that it works well in an SRS (spaced repetition system), such that it helps you learn that knowledge efficiently. Andy also mentioned this distinction in the guide.</p><p>In practice, the two notions of goodness are intertwined. But to see how they differ, imagine having a prompt that is about some knowledge that’s really valuable to you, but written in a way that’s hard to review. Or conversely, imagine a prompt that helps you easily remember some useless facts.</p><p>From an instructional design perspective, we could say that this is about goal and instruction. Goal is the “what,” and instruction is the “how.” In this language, we could say that writing good prompts is simultaneously about:</p><ul><li>Defining good learning goals (write prompts worth reviewing)</li><li>Designing good instruction to reach goals (write prompts that are efficient to review)</li></ul><p>I tried to see if I could put Andy’s prompt-writing principles in one of the two categories. Take the 5 properties of effective prompts — focused, precise, consistent, tractable, effortful. These aren’t much concerned with what knowledge you’re trying to gain, but more about how the prompts should be to make your practice sessions more effective. If I have an unfocused prompt with too many details to recall, I could improve it by splitting it into multiple prompts, but I’ll be learning the same things. I’ve only changed the instruction.</p><p>For something that targets better goals, take the lenses for conceptual knowledge — attributes and tendencies, similarities and differences, and so on. These lenses make you think: What do I really want, when I say I want to know/understand this? In what way, to what degree? Because how much and how far you adopt these lenses correspond to the depth of understanding you seek. It’s also about making decisions about what is important for you. Looking for keywords in procedures is very much a practice of identifying what you find valuable to learn.</p><p>When I evaluate my own prompts, I put on the instructional designer hat and ask myself whether my prompts target the right goals and are instructionally effective. This gives me an additional layer of mental structure over the more concrete techniques like those in Andy’s guide.</p><p>Is being aware of the distinction between goal and instruction important for writing better SRS prompts? Since writing SRS prompts is giving ourselves recurring learning tasks, it is nothing short of doing instructional design. Then, it should be helpful, if not important, to make this distinction… or so I thought. But on the flip side, unless one is also knowledgeable about what makes good goals and instructions, being able to differentiate them may not amount to much. So perhaps for most people, a grab bag of tips and tricks is more helpful than theories you can’t use without a web of associated knowledge.</p><h3>My Guiding Principle for Writing Good Prompts</h3><p>I’ve been experimenting with another notion of what a “good” prompt is, and it has to do with transferability. In other words, I want to learn things in a way that I can apply them in life.</p><p>A bit of background. I’ve used Anki for about three years. During this time, I learned a few things about learning sciences and instructional design, and I experimented with my SRS prompt-writing process as an application of what I’ve learned. A key idea that informs my prompt-writing is called cognitive task analysis, or CTA. For a given task, we could try to break down the mental process of how to complete the task into smaller component parts. In particular, I’m most interested in extracting component procedural skills in terms of ACT-R theory’s knowledge representation, i.e. “production rules.”</p><p>Jargons aside, what I’m looking for in a good prompt, or more precisely, the practice task of answering a prompt, is that it should match some authentic context. It should look like a little piece of something I might actually do.</p><p>This is, in fact, a pretty low bar to clear. Perhaps a bit cheating, but one scenario I often go to is explaining what something is to someone during a conversation. For example, I can imagine myself talking to someone when the topic of learning style comes up, and I need to recall the term of what’s that thing being discredited about learning styles… oh yes, the meshing hypothesis (and there’s card 1). Then I might be asked what that means (card 2). This is enough of a realistic scenario for a couple prompts around the topic.</p><p>By evaluating prompts this way, I’m naturally thinking more about the application context of each prompt during review sessions. I think this is a helpful thing to do, both for catching ineffective prompts to improve upon, and for reviewing in a way that I have a better chance of applying the knowledge when an opportunity arises.</p><p>With that as a guiding principle, I’d consider methods like those in Andy’s guide as supplementary aids to strengthen the prompts. But I tend to not worry about the prompts being, e.g. focused and tractable, as much as being able to contextualize the prompts.</p><h3>Summary &amp; The Challenge of Learning to Use SRS Effectively</h3><p>So far, I’ve shared some thoughts on what a “good” prompt entails for me. From an instructional design perspective, good goals and good instruction are two general categories we could use to help frame the more specific tips and suggestions for writing prompts. I also shared how I use transferability as my primary guiding principle for writing better prompts.</p><p>Andy’s guide is quite optimistic about whether people can learn to use spaced repetition systems effectively. I share the same sentiment, but I also think it is intrinsically very challenging. The challenge isn’t specific to SRS per se, but common to any self-directed learning. Namely, it demands a great deal of metacognitive abilities. Developing metacognitive abilities is hard.</p><p>Effective use of SRS requires strong skills in self-reflection and self-direction. We need to constantly ask ourselves what matters for us to learn, and scaffold ourselves to learn effectively by writing better prompts. We need to introspect the way we think and act to break down a skill. We need to self-evaluate the results of our review sessions. We also need the discipline to maintain a regular routine for reviewing the prompts.</p><p>If we consistently use an SRS system and follow tips like those in Andy’s guide to improve the prompts, surely we’ll get better over time. But it won’t be simple, and we’d probably all face various challenges. For example, I have two problems that I don’t really have good answers to:</p><ul><li>How do we encourage and scaffold the revision of old prompts?</li><li>How do we really know what our prompts should target?</li></ul><p>The first problem comes from the fact that I often feel I should improve or update some prompts, but rarely actually do. The second problem is more theoretical, but quite serious, since we’re using SRS beyond simple memorization. There’s much more to say on that front. If I come around to write a part 2, I’ll share some thoughts on targeting goals and this idea about keeping things salient.</p><p><em>Originally published at </em><a href="http://mathemusician.net/2021/11/good-prompts-1/"><em>http://mathemusician.net</em></a><em> on November 17, 2021.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6a70efcce359" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[DrillAI 筆記：Overengineering vs. Scaffolding]]></title>
            <link>https://automorphism.medium.com/drillai-%E7%AD%86%E8%A8%98-overengineering-vs-scaffolding-b658e4b59c12?source=rss-a2509555d989------2</link>
            <guid isPermaLink="false">https://medium.com/p/b658e4b59c12</guid>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[tetris-ai]]></category>
            <category><![CDATA[ios]]></category>
            <dc:creator><![CDATA[Hsing Liu]]></dc:creator>
            <pubDate>Mon, 16 Aug 2021 20:11:00 GMT</pubDate>
            <atom:updated>2021-08-16T20:33:26.729Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ibFZ3tW3Jy6JeUEIDWBVOQ.png" /></figure><p>最近我把以前的一個 personal project 挖出來翻新，從中得到了許多奇奇怪怪的開發經驗。趁印象仍深，做點記錄。</p><p>對軟體工程師來說，這是個老掉牙的問題：實作一個功能的時候，要先保持簡單，避免 overengineer — 過度設計？還是預先拆設出架構、抽象化，來讓以後擴充功能的時候更順暢省力？</p><p>我想，雖然對於什麼是 “clean code” 普遍有各種原則，適當的平衡應該還是要視個人自身的經驗和能力和 project 的特性來拿捏。畢竟什麼東西是容易的，或是能判斷之後一定會用上的，並沒有標準答案。我其實也沒有什麼高見可以教別人，只是遇到了兩個相似的情況，產生了點想法，把它整理出來。</p><h3>DrillAI</h3><p>先描述一下這個 project 吧。</p><p>兩三年前，我用 (後來死掉了的) Swift for TensorFlow (S4TF) 在 Google Colab notebook 裡寫了個俄羅斯方塊的 AI。那時的想法，是透過實作去熟悉 AlphaGo 的核心原理，應用在一個我有興趣的問題上，順便玩玩新技術。寫著寫著它成了個還蠻巨大的 notebook，而隨著 S4TF 關門大吉，想延續這個 project 勢必要從 Colab 把 code 轉移出來。</p><p>最近終於付諸行動，而同時我也有了幾個新的學習目標：Swift protocols &amp; generics，testing，Swift 5.5 async/await，SwiftUI，以及 app architecture 等。原本的舊程式成了拿來 refactor &amp; 反思的好材料。從 git 看來實際開工大約是三週前吧，目前它長這個樣子：</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FKBZ806qmsTs%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DKBZ806qmsTs&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FKBZ806qmsTs%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/5798afdb0c1efab98dcd5d5ef21640fe/href">https://medium.com/media/5798afdb0c1efab98dcd5d5ef21640fe/href</a></iframe><h3>由簡入繁：Protocol-Oriented Programming</h3><p>Protocol / generics 的使用，就相當值得參考 KISS (keep it simple, stupid)、YAGNI (you’re not gonna need it) 的原則。Swift 問世不久時，在 WWDC 2015 有個著名的 “Crusty” talk ( <a href="https://developer.apple.com/videos/play/wwdc2015/408/">Protocol-Oriented Programming in Swift</a>)，奠定了 Swift protocol-oriented programming 的基礎。但講者 Dave Abrahams 後來澄清，並不是想做什麼都先丟一個 protocol 下去。建議的做法其實是先從單純的 value types 開始 (structs/enums)，如果需要 polymorphism 的時候才用 protocol 而避免用 classes 來做。可見 Rob Napier 更詳細的敘述( <a href="https://robnapier.net/start-with-a-protocol">Protocols I: “Start With a Protocol,” He Said</a>)。</p><p>…話是這麼說，但我就是刻意想寫些 generics，所以在改寫 AI 時，就把主要的幾個類別弄成了有 protocol constraints 的 generic types，雖然老實說，它們未來應該也不會需要是 polymorphic。</p><p>寫俄羅斯方塊 AI 時參考了 AlphaGo 也有使用的 Monte Carlo tree search (MCTS) + UCT 概念。這樣的 AI 我會形容是由兩大元件組成：一個 game tree，和一個評估遊戲狀態好壞的方法。</p><p>會寫程式的人應該多對 game tree 的概念不陌生。這棵樹裡的每個節點 (node) 都代表了遊戲的一種狀態 (state)。在這個狀態下，玩家有幾個可以執行的行動 (action)，而每一個行動都會把遊戲變成另一個未來的狀態。</p><p>譬如說下面這個圈圈叉叉遊戲，目前是在畫了三個 O、兩個 X 的狀態，而 X 有四個可能的行動，所以這棵「樹」的 root node 有四個 child nodes，各代表一種可能的未來狀態。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*NFgPPagebAs5C1AjF-f2WA.png" /><figcaption><em>A Tic-Tac-Toe Game Tree</em></figcaption></figure><p>MCTS 就是有這麼一棵樹，但它有些多加上去的邏輯，來判斷接下來「哪一個未來狀態最值得評估」。</p><p>評估一個狀態的好壞的方法，是這種 AI 的第二個大元件。對於圍棋這種雙方論輸驘的遊戲，「好」的定義基本上就是你輸我驘。也就是說，一個棋盤狀態對我的價值，就是我的勝利的可能性大小。AlphaGo 用 deep reinforcement learning 大量運算來培養出能精確評估棋盤的 neural network，造就超強 AI。</p><p>至於我的程式嘛，它其實只針對俄羅斯方塊這一種遊戲，但既然 tree search 應該是種泛用的策略，於是它成了</p><pre>protocol MCTSState {<br>    associatedtype Action<br>    // ...<br>}</pre><pre>protocol MCTSEvaluator {<br>    associatedtype State: MCTSState<br>    func evaluate(state: State) async -&gt; EvaluationResult // just a tuple<br>}</pre><pre>actor MCTSTree&lt;State: MCTSState&gt; {<br>    // ...<br>}</pre><p>然後我就開始了與 generic 相關的 compiler warning 的奮戰。開玩笑的。其實因為是逐步 refactor 舊程式碼一邊追加 unit tests，我發現這樣改並不困難。畢竟程式也沒有很龐大，原本也有把物件職責切分好。</p><p>這是 overengineering 嗎？雖然說了不會很難，我想仍然是的。我的 Tree 可能永遠沒有支援另一種 game state 的需求，所以這些 protocols 和 generics，I ain’t gonna need ‘em。使用這些 type 的地方都要把它們用同一種 state 來 specialize，不過是徒增複雜度而已。如果這是個商業產品，那過早帶進 polymorphism 的代價，大概就是浪費人力資源與維護成本吧。</p><p>不過從學習的角度而言，這是個不錯的經驗。除了比較習慣自己設計和使用 protocols with associated types，也感受到它的確對 testing 及 separation of concerns (SoC) 帶來好處。</p><p>雖說我的 Tree 沒有要支援另一種遊戲，但它的確支援 fake State。MCTS 的邏輯還是有些複雜的，所以為了確保它的行為如我預期，我可以在測試時做一個相對單純、容易調控的假遊戲 State 餵給它。要是它並非 generic，而我只能餵俄羅斯方塊的 State 來做測試，想必會變得十分困難。</p><p>在 SoC 方面，因為 protocol 的分隔，我必需重新思考這個是 Tree 的工作？還是這個遊戲本身的屬性？等等問題。</p><p>這裡有個意外的收穫，是在實作一個傳統方法的 Evaluator 的時候。我參考了一些 Tetris AI 的研究，複製其中對遊戲場地做評估的計算方法，作為在能做出 neural network evaluator 之前讓 AI 能動的替代方案 (前面的影片就是用傳統 evaluator)。但在實作時有個困擾：這個傳統計算的方法除了用到目前的狀態，還用到上一個狀態，和把上個狀態變成目前狀態的 action。</p><p>我先是想到兩種做法：最簡單的方法是在 Tree 裡補一個 method 來回傳這種「加強版」的資訊以供評估，麻煩的做法是把上一步的歷史加入到 State 的實作裡。前者好做，但後者概念上比較正確。此時，懶惰帶來創新，這讓我想通這個傳統評估法其實可以視為一個 action 的價值，加上 resulting state 的價值，也許我可以拆開使用。結果，因為 Tree/State/Evaluator 之間隔了 protocols 的 decoupling，逼我回思三者的 separation of concerns，反讓我想到我也許不用改 Tree 或 State，而是可以從 Evaluator 來下手。</p><p>好吧，既然有好處，那回到前一個問題：如果這是個商業產品，這樣做會利大於弊嗎？我還不知道。</p><h3>未雨綢繆：Swift Packages</h3><p>在剛開始整理舊 AI 的時候，我就希望之後同時能在 iOS 上執行，又能在電腦上生訓練用的資料。既然會這樣給不同的 app target 使用，很自然的是要做進一個 Swift Package。</p><p>另開 app project 並寫了一陣子 SwiftUI 界面後，在思考 SwiftUI app architecture 時讀到了 Matt Gallagher 的文章，大力推薦把 model layer 切出到一個 inline Swift Package ( <a href="https://www.cocoawithlove.com/blog/app-submodules.html">App architecture basics in SwiftUI Part 3: Module-separated layers</a>)。於是我的 app 就又多了一個 package，在 iOS target 專屬的 folder 底下剩下幾乎全都是 View，各個 View 把兩個 modules 給 import 進來使用 (View import AI module 都只是為了它的 type 定義)。</p><p>在 project 裡多切一個 module 出來，無疑是提高了架構的複雜度。寫 code 時，也十分有感 — 一開始當然是要處理 access level，哪些東西要從 internal 升級成 public。再來偶爾會做蠢事，寫 model code 時，咦，怎麼沒有辦法用這個 type，一直說找不到？原來是還在 app 那頭，忘了搬進 module。</p><p>而原來這些都是 Matt 推薦這個做法的好處：它不僅在視覺上造成 model 和 view 的切分 (兩組檔案在兩個不同地方)，更重要的是因為 access level 多一層，我必須去思考 view 到底需要對 model 知道得多詳細，而 model 呢，則是根本沒有辦法知道 view 任何事情 (因為我們只會從 view 那邊來 import model 所在的 module)。這真的有對維護 loose coupling 產生了正面的影響。</p><p>嗯，不過… 「我」同時知道 model 和 view 兩邊長什麼樣子，所以還是一定會為了 view 的需要去設計 model，反之亦然。兩邊增加的隔閡，其實也讓我更明確的感覺到我是怎麼樣忍不住不斷增加 model 和 view 在邏輯上的 coupling。</p><p>這方面最有感覺的，是在做「放下方塊 — 震動 — 填滿一行消失 — 重新組合」這一連串的動態呈現的時候。我想透過一連串的計時改變 view model 來達成這個視覺效果，而每個 view model 的改變，在 view 這一頭都有一個相對應的 animation 或 transition，但這每一步都要兩頭刻意串通好，才能做到現在 trigger 這個 animation，接下來 trigger 另一個 animation。這個現象到什麼程度是可允許的，以及針對這個例子可能的改善方式 (尤其是怎麼把某些動畫邏輯轉移到 view 層面)，都是我還在思考的問題。</p><h3>異中求同</h3><p>一個 deja vu 的感覺，讓我開始思考這兩件事的相似之處。</p><p>向上追溯，protocol 和 modules 各自的目標裡，一個明顯的共通點是 decoupling。兩者在做下去以後，就產生種種限制，促使、甚至是限制開發者去將兩個物件，或是兩個 architectural layers 之間的關係給釐清開來。</p><p>在一個 project 還小還單純的時候，引進某些 protocol 或 module 比較可能令人疑惑：有必要搞這麼複雜嗎？但它們帶來的限制，對開發者的思考而言有時就像一種 scaffolding，有點像是 Swift 的 strong typing 引導我們寫更安全的 code 一樣。</p><p>但在優劣權衡上，我想最終兩者還是有些差異。我這次這種 protocol 和 generic 的使用，實作下來的感覺是比較不值得的。它讓 code 到處冒出許多的&lt;尖角角&gt;和很長的型別名字或 typealias，也讓我感覺程式變複雜了。反之切 module 雖然會多出一些 public 和 import，在思考架構時反而會感覺變得容易、單純了點。</p><p>我好奇的是，隨著經驗增長，和遇到不同的 app 需求，自己抉擇的天平又會怎麼改變呢？總之在那之前，DrillAI 還會繼續挖下去。</p><p><em>Originally published at </em><a href="http://mathemusician.net/2021/08/drillai-01-overengineering-vs-scaffolding/"><em>http://mathemusician.net</em></a><em> on August 16, 2021.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b658e4b59c12" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[一段黑歷史：錄製 Auto Layout 課程]]></title>
            <link>https://automorphism.medium.com/%E4%B8%80%E6%AE%B5%E9%BB%91%E6%AD%B7%E5%8F%B2-%E9%8C%84%E8%A3%BD-auto-layout-%E8%AA%B2%E7%A8%8B-c6cf0298a2de?source=rss-a2509555d989------2</link>
            <guid isPermaLink="false">https://medium.com/p/c6cf0298a2de</guid>
            <category><![CDATA[education]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[autolayout]]></category>
            <category><![CDATA[instructional-design]]></category>
            <dc:creator><![CDATA[Hsing Liu]]></dc:creator>
            <pubDate>Wed, 01 Jan 2020 04:21:14 GMT</pubDate>
            <atom:updated>2020-01-01T04:49:45.530Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="YouTube Playlist Thumbnails" src="https://cdn-images-1.medium.com/max/1024/0*qoF6R9_bZST8V3Ei.png" /></figure><p>大約 2016 到 2018 年間內，我曾經試著開一堂 iOS Auto Layout 的線上課程。我做為 iOS 開發者的經驗有限，但依著自己的數學背景以及對教育的興趣，那時覺得自己在這方面能做點什麼。</p><p>直到現在我仍認為，Auto Layout 中等到進階的知識，在網路上過於欠缺且分散。這世上並不存在一堂我心目中的 Auto Layout 中高階課程。</p><p>我閉門錄了些影片，且把課程預定的規模縮編了幾次，但這堂課從未真正上架。在我做了最後一次縮編，覺得可以先上一版時，SwiftUI 問世。綜合一些其它因素，終於不再歹戲拖棚而停手。而這也是幾個月前的事了。</p><p>雖說是個丟臉的失敗史，但也不甘就此淡忘。最近終於有些閒，決定來為這整件事稍做紀錄。文底會提供現有影片的連結，歡迎自由取用。</p><h3>歷程</h3><p>做這個課程的想法在 2015 年就有了。那是我開始當 iOS 開發者的第一年。有幾次聽社群的朋友們提到對 Auto Layout 感到困難，我也意識到我從線性代數 &amp; 線性規劃的基礎上去瞭解 Auto Layout 的方式並不常見。</p><p>同時，做軟體開發一段時間後，我愈來愈期望自己能在教育領域貢獻心力，並確立了向教育科技發展的大方向。故，拿 Auto Layout 為素材做一堂線上課程，似乎有許多好處。一方面課程製作會是在教學和教育科技方面很棒的實務經驗，另一方面它也會是身為 iOS 開發者獨特的一個「作品」。加點妄想，說不定還有機會名利雙收？</p><p>於是，一段孤獨漫長的開發之路開始了，斷斷續續經過好多時間。到了 2018 年時幾乎完全停擺，而在 2019 年初時重啟，又稍有進度，直到 WWDC 為止。整體而言，在個人動力問題和糟糕的（瀑布式？）開發流程之間，效率一直都很低落。不過這經驗終究十分寶貴，在教學設計上面，我花了很多精力思考，也體認到一個人自學、單打獨鬥的限制。</p><p>現在的我回到全職學生的身份，進修教育科學 &amp; 教學設計。學習間我不斷回想起這段開發經驗。回頭看這個作品，可以判別出一些蠻要命的問題，但同時感到驚訝的是，當時憑直覺做的許多決定其實也很符合科學的學習原則。</p><p>整體評鑑，這套影片我現在覺得最大的缺失有：不夠清楚的學習目標（著重我如何教，大於學生如何學，過於自我滿足），極度缺乏動手做和評量（如練習題，測驗），沒有和目標學生做 CTA（Cognitive Task Analysis，分析真實需要及困難點），沒有經過設計疊代等。至於影片錄製剪輯的不專業倒不覺有大礙。</p><h3>開發</h3><p>製作過程中，除了花無數的時間思考，也花了無數時間找資料和做研究。除了再三確認自己的想法，也看看其他人都是怎麼教的。我把當時幾個找到的線上影片課程都上了一遍（Treehouse, BitFountain, Ray Wenderlich…)，前後翻了三本書（Steven Lipton, Erica Sadun, Keith Harrison），當然還有不知道多少的 blog posts，tutorials，YouTube 影片，及大部份相關的歷年 WWDC 影片。</p><p>雖說的確在研究過程中，思想不斷變得更完善，但這個研究的量明顯代表在專案管理上有點問題。回頭看，就算要多做研究，若能多著重於做 CTA 會更有幫助。</p><p>一開始的規劃範圍極廣，把能想到的題目都列出來，試著以一種半獨立單元的方式分類排序。</p><figure><img alt="2016 summer — 2017 list of topics" src="https://cdn-images-1.medium.com/max/896/0*FG2W3BHWYW7-TJH2.png" /></figure><p>之後大部份的心力在於把每個單元、每個影片的內容編排出來，寫成概要。前前後後共寫了 16 個單元（Lessons），各單元通常有固定的流程，實作示範和理論交雜。這個概要的檔案有三千多行字，離逐字稿還有些距離，但對於每個單元 &amp; 每個影片大概要長什麼樣子，可以說是已有個基本的描述，只缺把細節整理出來。</p><figure><img alt="Lessons Outline" src="https://cdn-images-1.medium.com/max/546/0*nm_Ce5yyBXYiT5ha.png" /></figure><p>至於影片錄製，我並沒有天真到以為能一步到位，所以就從頭錄起，自許以一個 alpha 品質做為起點，邊錄邊試著改進。</p><p>在錄前幾個單元的時候後，我想訓練自己看著概要，直接就即興錄製，希望習慣後可以加快進度。無奈不斷 NG 重錄反花更多時間且品質不如讀稿，試多次也無明顯進步，所以打消了這個念頭。這時錄的一、二、三章，被我歸類為 alpha 品質。接下來錄了單元四、五、六，都有重頭寫稿再錄，燈光也稍有改進，自評是 beta 品質。</p><p>不記得是到什麼時候，明顯意識到原先想做十幾章的規劃太不實際了，就把「第一版」的規劃不斷縮小。到最後 2019 年時，決定再來只要把單元一到三改進重錄，配合 beta 版的單元四到六，加個介紹就算初步完成。</p><p>遺憾的是，最後就停在這裡了。</p><h3>錄影</h3><p>錄影片這件事，雖說 DIY 對品質期望不高，但每次錄影的時候總是感到不小的壓力。對我來說，讓學生看到老師，就算隔著螢幕仍能產生一種連結，是一件重要的事。所以，很多影片都是我在對著 iPhone 比手劃腳說話。</p><p>有的時候，我會用實體道具做說明。某個影片拿了塊豆花來解釋「切割次元」，自己也覺得莫名其妙得好笑。</p><p>當然，少不了 Xcode 示範的螢幕錄製。另外，也有一些拿 iPad 當「黑版」的解說。可以說是各種不同的影片呈現方式，除了我做不到的電腦動畫之類以外，都依適合的情境用上了。</p><figure><img alt="Chapter 6 (beta) thumbnails" src="https://cdn-images-1.medium.com/max/557/0*8X2FavibIQOBSOsH.png" /></figure><p>影片編輯是用 ScreenFlow，聲音也簡單就在 ScreenFlow 裡處理。做出來的感覺 DIY 感濃厚，但如先前所說，這點我並不是很在意。</p><h3>分享</h3><p>早在 2015 年還沒決定要做之時，就在 iOS Taipei 聚會時分享了一次 Auto Layout &amp; Scroll View，是個難忘的經驗。後來在課程製作期間，我有兩次機會把當下的心得稍做整理，在不同場合跟人分享。</p><p>第一次是 2017 年八月的 Cocoaheads Taipei。那時我開始玩 Fate/Grand Order，想到「打素材最有效率的方式」可視為一個線性規劃問題，而 Auto Layout 就是個線性規劃的引擎。那，我們能不能反過來用 Auto Layout 來找最有效的素材打法呢？</p><figure><img alt="Cocoaheads FGOALT p.18" src="https://cdn-images-1.medium.com/max/856/0*GHnAWWe2zKUp0DKC.png" /></figure><p><a href="https://www.icloud.com/keynote/05QQ-OKwCRj0Blht62EVZSv2Q#FGO_Auto_Layout_Talk">FGO Auto Layout Talk Slides</a></p><p>第二次是 2018 年一月，在台中舉辦的 iOSDC 大會。我把標題取為「Auto Layout，你從哪裡來？要往何處去？」，算是把在做課程時的心得做了個總整理。我談到 Auto Layout 因它的運作原理而帶給使用者學習上的困難，以及未來期望看到的改變。那時就想，Apple 到底會不會全盤翻新，推出一個完全不同的架構呢？答案在一年多後揭曉：SwiftUI。終於，我們可能要慢慢跟 Auto Layout 說再見了。</p><figure><img alt="Auto Layout, Where Are You From p.5" src="https://cdn-images-1.medium.com/max/912/0*vpJo8Hqnm2DCUrYF.png" /></figure><p><a href="https://www.icloud.com/keynote/0zMQ0v2GhhWEuXsDW6g3zW2TA#Auto_Layout_Where_Are_You_From">Auto Layout Where Are You From Slides</a></p><p>這兩次分享，都有廣告「我在做課程喔！」以此激勵自己完成課程。很好笑的結果，就是不少朋友知道了我在做課程，但這堂課始終無聲無息。</p><h3>半成品</h3><p>如上所說，最後留下的有六個單元的影片。前半還在摸索的期間，後半則是稍微進入情況，但原本也沒有打算要做最終版本。粗估這樣約有六小時的影片。第六章長度最長、理論最深，也是我個人覺得最重要的一章。</p><p>YouTube playlists:</p><p><a href="https://www.youtube.com/playlist?list=PLYAmbnECx05SwWGLPirF9MjqdKBKAqOjL">第 1 章 (⍺)：牛刀小試 &amp; Auto Layout 之前的技術</a></p><p><a href="https://www.youtube.com/playlist?list=PLYAmbnECx05RtVw_eUdBhLMLVKkeThusQ">第 2 章 (⍺)：什麼是 Layout Constraint</a></p><p><a href="https://www.youtube.com/playlist?list=PLYAmbnECx05QdMsOxq5l-cEo4VLoeaP5r">第 3 章 (⍺)：用 Spacer &amp; Grouping Views 拓展可能</a></p><p><a href="https://www.youtube.com/playlist?list=PLYAmbnECx05Slpv03wUt5NxsKs82qEb_d">第 4 章 (β)：Layout Debugging：蟲與工具</a></p><p><a href="https://www.youtube.com/playlist?list=PLYAmbnECx05QQ55A7DTbAqvZ0Cc6Chkl5">第 5 章 (β)：邁向精熟 Auto Layout 之路</a></p><p><a href="https://www.youtube.com/playlist?list=PLYAmbnECx05TAxnEzVEbNBTHslW9YQeqa">第 6 章 (β)：Auto Layout 的理論基礎</a></p><h3>檢討</h3><p>好了，影片丟出來了，但這對別人有幫助嗎？</p><p>之前提到我自評這些影片有許多缺點。但它們對一般學習者有多少價值，我無法客觀判定，只能邀請大家來評論。我先稍微說說我的看法。</p><p>我認為使用 Auto Layout，理論和實務都很重要。絕大多數我看到的 Auto Layout 教學，會幾乎完全著重於實務，告訴你功能有哪些，使用的步驟是什麼。這些固然很重要，但並不足。與其相比，我花很多力氣在「解讀、分析」：為什麼這功能會設計成這個樣子？它背後的原理是什麼？如此一來什麼是可能較好的做法？</p><p>我的課程對於理論及個人解讀的偏重，一方面是個人偏好，另一方面也是因為我認為這是網路上所缺乏與過度分散的資源。甚至，我講的到底對不對還在其次，重要的是你可以和我一起思考，並構築你自己的看法。這是我希望當初自己在學的時候就存在的教材。</p><p>不過，這也代表了想要上這堂課的學生，至少要已經對 Auto Layout 有些實務經驗。理論和實務都重要，但我並沒安排足夠的實務解說和練習。另外，學生除了被動的看影片，也需要主動思考、自問、評估，這傢伙講得真的對嗎？我之前遇過跟這有關的情況嗎？並試著手做驗證，最好是在真實 App 中，但開個練習專案也行。實務和理論交會，才有最佳成效。</p><h3>結語</h3><p>SwiftUI 當紅，但 Auto Layout 可能還會再活幾年。所以，現在把這些影片釋出，應該還是有點意義。</p><p>雖然這堂課我視為是個失敗之作，但這不會是我最後一次做教學設計。所以，在這裡拜託大家，請大方的給我回饋，幫助我成長。不論是關於 Auto Layout 的技巧與理論，學習的方法和成效，或是影片錄製，任何的批評、建議和鼓勵，都歡迎留言給我。</p><p>另外要感謝：Richard, Keith, Aki, Pofat 以及許多其他朋友們，在開發過程中給予的建議和鼓勵。</p><p><em>Originally published at </em><a href="http://mathemusician.net/2019/12/reflection-auto-layout-course/"><em>http://mathemusician.net</em></a><em> on January 1, 2020.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c6cf0298a2de" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[從教育看開發者研討會：iPlayground 與會心得（下）]]></title>
            <link>https://automorphism.medium.com/%E5%BE%9E%E6%95%99%E8%82%B2%E7%9C%8B%E9%96%8B%E7%99%BC%E8%80%85%E7%A0%94%E8%A8%8E%E6%9C%83-iplayground-%E8%88%87%E6%9C%83%E5%BF%83%E5%BE%97-%E4%B8%8B-e2df005fdd88?source=rss-a2509555d989------2</link>
            <guid isPermaLink="false">https://medium.com/p/e2df005fdd88</guid>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[education]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[iplayground]]></category>
            <category><![CDATA[conference]]></category>
            <dc:creator><![CDATA[Hsing Liu]]></dc:creator>
            <pubDate>Mon, 26 Aug 2019 01:15:41 GMT</pubDate>
            <atom:updated>2019-08-26T01:31:38.651Z</atom:updated>
            <content:encoded><![CDATA[<p>一眨眼，不但心得分享的 <a href="https://medium.com/@es2mac/從教育看開發者研討會-iplayground-與會心得-上-e042e3bc7943">上半</a> 已經是幾個月前的事，連第二屆的 iPlayground 都快要開始了。第一屆的心得再不寫完，別人都要寫第二屆心得了。</p><p>事實上，原本要留到後半寫的內容中，有些動筆時才覺得邏輯過於粗糙，所以草稿被我棄置了。只是既然有了「上」，甚至還預告了內容，那不硬擠個「下」出來也太說不過去。所以我想還是來把手邊的舊心得筆記稍做整理，從簡隨意。文句不順之處也請見諒。</p><p>大致上主題還是在議程和 secret party 兩者，不過我想既然第二屆即將到來，與其講去年的心得，不如多分享一些個人建議。</p><h3>關於議程</h3><p>上次提到，議程中如果是認識的朋友上臺，我就去聽。講題內容都十分精彩，而內容在這邊就不再贅述，畢竟有段時間了，且大多有錄影。</p><p>雖不提內容，但有一點讓我十分感動，不得不說，那就是講者們都很尊重大家時間，我不記得有誰時間到了還不收尾。如此一致讓我不禁好奇是不是主辦方有特別提醒過，但不論如何，我真的覺得尊重聽眾時間非常重要，希望這在下一屆能傳承下去。</p><p>另外，怎麼樣能更進一步提升演講品質？這是個我蠻常思考的問題，不知不覺就累積成了一小個清單，歡迎下屆講者們參考看看。</p><p>不過說在前面：在研討會做分享，和在課堂內講課，這兩者相像但又似乎有很大不同。我的感覺是研討會分享的主要目的不見得是在傳播知識，但我想到的東西基本上都是以促進聽眾的學習成效為目標。所以，以下建議是否適用就得請大家自行判斷。</p><h3>保羅的技術分享演講原則</h3><ul><li>講者應該時常從聽眾的角度，自問：Why do I care？這關我 X 事，我幹嘛要聽這個？也許你的主題是 architecture，有的聽眾很清楚自己需要加強 architecture 的思維，所以他明確知道自己為何要聽這個。但也有的聽眾只有個模糊的概念，這時就可能需要你的幫助，來建構這個 session、這個段落、這個細節他所應該關注的原因。不然，半關機的腦子是不會從你的演講帶走什麼東西的。</li><li>輕鬆自然的用和聽眾對談的姿態說話。當人們在對話中時，會自然的去試著理解吸收另外一方說的話。演講雖然主要是一個人在講，但也可以用語法（不吝使用「你」和「我」）和視線方向來讓情境更像一個對話（甚至真的可以對話），增進聽眾的投入。</li><li>你今天上臺，是為了讓聽眾帶走東西，還是要炫耀自己的知識？其實我覺得後者並沒有錯，但也是個陷阱，如何取得平衡值得好好思考。</li><li>承上，準備講題時就以聽眾為本位，從問「我要講什麼」改為問「我的聽眾要帶走什麼」，如此發展出來的講題內容和結構可以很不一樣。</li><li>有的時候，less is more。少講一點說不定反而聽眾會記得更多。人要記憶東西，必需在短期記憶中做處理，來讓它進入長期記憶。問題是短期記憶空間非常的小，一下子爆炸的資訊量襲來的後果就是反而全都來不及處理。</li><li>當然，身為講者一定是有多到講不完的好料希望能帶給聽眾。改善講題結構可能會有幫助：把內容切成較小而明確的段落，有點像把一個大演講變成數個小演講，會更容易吸收。而段落之間的關連結構，可以在一開始先預告給大家心裡有個底，也可以在最後做個小複習，以避免感覺內容缺乏連貫性。</li><li>至於你想要聽眾帶走的東西，何不設個更有野心的目標？如果你的主題都是事實（如：Combine 和 SwiftUI 的 API 裡有甲，乙，丙…）或是程序（要實作什麼功能，先做這，在做那…），有沒有什麼值得讓聽眾去理解？讓他們有辦法回家後可以自己分析和評估自己專案的適用性？更能用資深工程師的思維想事情？覺得有趣到自己回家找資料繼續學，或是加入讀書會？用個常見的說法，看你要給聽眾魚，給釣竿，還是釣魚的動機。</li><li>除了前面幾點，可以進一步想想還有什麼做法能引導聽眾更願意投入，花更多腦筋主動思考你所講的主題，而不是被動的聽你說而已。</li></ul><p>如前所說，這些建議都是針對議題的教育功能，但我覺得議題也不只有教育功能，所以就自行取捨囉。</p><h3>Secret Party Panel Discussion</h3><p>回到 2018 的 iPlayground。說到 Secret Party，也許我該先解釋它是什麼。去年的 Secret Party 是第二天在議程結束後的派對，大家移動到另一個地點，吃吃喝喝，並請幾位資深工程師上臺做 panel 討論。很愛玩的主辦方把討論定位為不得錄影錄音的辛辣敏感題目。</p><p>印象中，這個 panel 討論算是有達到期望的「笑果」，但不能說是進行得很順暢。我其實蠻喜歡 panel 討論這種形式，因為可以聽到同一主題的數個觀點。那天有一個主題我印象特別深刻，下面再談。</p><p>如果之後還有辦 panel 討論，我想主辦可以讓主持人與講者們多做點事前的準備工作。之前有看到 Cassie Kozyrkov 寫的 <a href="https://hackernoon.com/top-10-panel-disasters-and-how-to-avoid-them-c37535a6438e">兩篇</a> <a href="https://hackernoon.com/public-speaking-masterclass-everything-in-moderation-ea286e0188d8">建議</a> ，值得參考。</p><h3>Apprenticeship</h3><p>回到那個令我印象深刻的主題：記得那時似乎問講者們的問題是，身為工程師，要怎麼樣才能快速進步？而 Zonble 和 Aki 的回答是一致的，那就是要找個好 mentor。</p><p>近年來教育界似乎有提高對學徒制的重視。同時，因為各種線上平台、教育科技的興起，大家都很好奇：我們會不會很快就不需要老師了？老師會被科技取代嗎？所以，在科技研討會聽到進步最快的方法就是找個好師傅，我感覺實在是很有意思。</p><p>「科技是否能取代老師」又是另一個寫不完的問題，這邊就不講太多。簡單說，我完全相信 Aki 和 Zonble 的說法。</p><p>目前教育科技取得較大成功的學科，首先就是基礎數學。這其實有個原因，那就是數學它十分有結構性，譬如說先學加法再學乘法，學會乘法再學除法。而因為知識結構相對明確，我們也才相對有辦法讓電腦來「教」。</p><p>與其相比，「資深工程師的能力」可就沒有什麼明確的結構。技術日新月異是個因素，但更棘手的，愈是厲害的工程師，就有愈多已經內化的知識和技能，以至於在教學時不會完整交待，是種所謂在教學上的「專家盲點」。所以網路上技術教學再多，也可能都打不到你個人的痛點，對成為更成熟的工程師助益不大。若不想自己碰碰撞撞累積經驗，那跟一位好師傅，實作，觀察，在師傅身上挖智慧，也許就是成長最快的方式。</p><h3>iPlayground 2019</h3><p><a href="https://iplayground.io/2019/">今年的盛會</a> 在 9/21-22，很可惜我無法共襄盛舉，只能坐等影片和回顧，並預祝大家收獲滿滿！</p><p><em>Originally published at </em><a href="http://mathemusician.net/2019/08/iplayground-2018-part-2/"><em>http://mathemusician.net</em></a><em> on August 26, 2019.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e2df005fdd88" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[從教育看開發者研討會：iPlayground 與會心得（上）]]></title>
            <link>https://automorphism.medium.com/%E5%BE%9E%E6%95%99%E8%82%B2%E7%9C%8B%E9%96%8B%E7%99%BC%E8%80%85%E7%A0%94%E8%A8%8E%E6%9C%83-iplayground-%E8%88%87%E6%9C%83%E5%BF%83%E5%BE%97-%E4%B8%8A-e042e3bc7943?source=rss-a2509555d989------2</link>
            <guid isPermaLink="false">https://medium.com/p/e042e3bc7943</guid>
            <category><![CDATA[education]]></category>
            <category><![CDATA[iplayground]]></category>
            <category><![CDATA[conference]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[software-development]]></category>
            <dc:creator><![CDATA[Hsing Liu]]></dc:creator>
            <pubDate>Sat, 09 Mar 2019 16:34:06 GMT</pubDate>
            <atom:updated>2019-03-09T16:49:45.081Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*98mrO14h-4x0acW0-cUVwQ.jpeg" /><figcaption>照片來源：<a href="https://www.facebook.com/theiPlayground/">iPlayground</a></figcaption></figure><p>去年十月份，我參加了一個 iOS 開發者大會，<a href="https://iplayground.io/">iPlayground</a>。事過數月，拖到現在才寫心得也蠻好笑的。但原本就想做點紀錄，再經過一些反思的時間，遲來總比不來好。</p><p>基於背景和興趣，我想試著從教育與學習的面向來回顧這次的經驗。iPlayground 這類專業研討會不只是好玩，還突顯了人際互動對於學習的重要性。參加這個活動再次提醒了我，教育有很大一部份難以用科技去取代或自動化。另外，未來的「個人化學習」必需小心不要成為「孤獨化學習」。</p><p>怕篇輻過長，我會把分享拆成上下兩篇。雖然是個人感想，但如果你是軟體開發者，也許這裡有些觀察能幫助你加速提升自己的能力。如果你是體制內的教育者，不知你看了會不會有所啟發？希望大家若有什麼想法也能不吝分享。</p><h3>關於 iPlayground，iOS 開發社群與我</h3><p><a href="https://iplayground.io/">iPlayground</a> 是一個 iOS App 開發者的研討大會。有幾位開發者朋友們，從國外的幾個大型研討會回來後，感慨這些這麼棒的活動還要出國才能參加，便決定自己在臺灣辦一個。</p><p>在臺灣，以 iOS 為主的大型研討會很少（另參加過 <a href="https://oasisforum.tw/topic/iosdc/">iOSDC</a>），而 iPlayground 應是第一次完全由社群成員主導籌劃的。組織團隊很有想法，他們試著集結其它研討會的經驗，擷取好的元素來融入這次活動。雖是第一次舉辦，但我覺得整體體驗很棒，偶有不順亦瑕不掩瑜。</p><p>我 2015 年開始寫 iOS，後來試著往機器學習 &amp; 教育科技的方向發展，近兩年已經沒有在寫 App 了。不過，我還是持續注意 Apple 每年推出的新技術。這次參加 iPlayground 也是因為想瞭解 iOS 業界最近的發展。</p><p>不，其實真的說起來，會去參加的更大理由，可能是因為我仍心繫這個社群，想來和朋友同樂，一起聊技術宅的話題吧。之前全職寫 iOS 時，參加數個開發者聚會，結識了好些朋友。這個社群給我的感覺是友善且非常有凝聚力的。也許因為成員大多是 Apple 粉絲，所以倍感親切？</p><p>相信在軟體開發界，如 iOS 這樣的社群必非特例。可惜我近來用的技術類別較雜，比較沒有接觸到個別技術的相關社群，無從比較。不過可以確定的是，如果我再次專注於某個領域的開發，我會試著複製在 iOS 領域的經驗，主動投入該領域的社群。</p><h3>歸屬感 (Belonging)</h3><p>我很內向。我雖不畏懼表現自己，也不怎麼害羞，但與人互動會消耗我大量的精力。若是如此，為何不多享受愉悅的獨處時光，跑去參加什麼開發者聚會和 iPlayground？回想起來，可能是因為我在其中得到了歸屬感。</p><p><a href="https://www.amazon.com/ABCs-How-Learn-Scientifically-Approaches/dp/0393709264">The ABCs of How We Learn</a> 一書中寫到，在學習科學領域，有許多研究顯示「歸屬感」對學習有顯著的影響。歸屬感指的是一個人是否感覺被一個團體接納、肯定，並身為那團體中的一員。同時，這也關係一個人的自我認同。所以，其他的 iOS 開發者是否接納我？我能自稱是個 iOS 開發者嗎？這些問題看似與我的專業能力無關，但其實不然。</p><p>歸屬感對學習造成的影響可以從兩個方面來看。首先，歸屬感讓人更願意努力投入學習。另外，它能減少會防礙我們進步的負面思維，如自卑和疏離感。</p><p>舉例來說，假設我在寫 iOS App 時遇到一個技術難題，而我必需決定要用草率的 workaround 解決，還是試著深入探索問題核心，來找出一個比較漂亮的解法。那麼這時，如果我自認是一個貨真價實的 iOS 開發者，或是我知道在必要時，有人能助我一臂之力，那我會有更高的意願投入心力、接受挑戰，並選擇後者。而這樣的選擇，會讓我在專業上成長更快速。反之，如果我覺得自己稱不上是個 iOS 開發者，並感覺孤立無援，那我追求卓越的動力會相對薄弱。</p><p>至於減少負面思維，一個絕佳的例子是 Imposter Syndrome。Imposter Syndrome 是當你覺得其他人都遠勝於你 — 畢業於 X 大學本科系，有 Y 年經驗，開發過有名的 Z 程式 — 而在他們之中，你只是個不自量力的冒牌貨。這種認知會帶來不必要的壓力與挫折，或是令人放棄自我成長的好機會。此時若能得到同儕的接納與肯定，就會降低這類負面情緒。</p><p>整體而言，我覺得 iOS 社群的接納度很高，在其中並不難找到歸屬感。我遇到許多謙遜的資深開發者，不擺架子，樂於分享知識，也不吝談論自己的歷程和失敗。大家似乎都明白，新朋友們需要知道每個人都是這樣走來的，也都在學習，也常做蠢事。在 iPlayground，不論是議題分享中，或是在講堂外的互動，這種風氣都顯而易見。</p><p>我接下第一份 iOS 工作時，幾乎所有軟體開發的知識都是自己在網路上拼湊自學的，真的很菜。做了幾個月後，偶然參加了 <a href="https://www.facebook.com/groups/ios.taipei/">iOS @ Taipei</a>，其後就視社群參與為我的專業成長上重要的一部份。從 iPlayground 創辦人之一的 Ethan 寫的<a href="https://medium.com/@ethanhuang13/身為-ios-工程師不該錯過的-iplayground-fd6ae4148478">推坑文</a>看來，我的經驗並不特殊。說起來，歸屬感不容易由自己產生，高接納度與包容度的社群也不見得會自然形成，所以這需要大家一起努力。</p><h3>雙軌議程</h3><p>iPlayground 為期兩天，主要場地是一大一小的會議廳/講堂，其外則有交流空間，贊助商攤位，和提供飲料的休息區。</p><p>大會的主軸是在會議廳舉辦的議題分享。主題十分多元，有許多是偏向軟體的設計模式，方法，開發或求職的經驗等等，而針對程式語言、framework 等技術性的內容倒比我預期得要少。</p><p>議題分享是雙軌同步進行，所以參加一個就會錯過另一個，但多半都會有現場錄影，可以等上線後補看。事實上，既然議題有錄影，你也可以兩個都不去，在外面跟人聊天。我感覺 iPlayground 主辦方的態度是，這不但被允許，甚至值得鼓勵。原因是在研討會交新朋友和面對面互動，可能比聽課還要有價值。</p><p>不過，大部份的參加者還是會盡量去聽議程。除了為了有興趣的主題，能現場一睹業界名人的風采其實也蠻有趣的。我自己另有個小規則，就是只要有認識的朋友上臺就去捧場。</p><p>雖然有錄影，但現場聽講顯然還是有很多益處。一方面，iPlayground 的講者都是業界朋友無償的來分享，如果能有一群友善而投入的聽眾，倒也不失為很好的鼓勵。另外，議程能讓大家有一些共同話題。你聽了哪一場？對剛才講的 testing 原則有什麼想法？有試過 Redux 架構嗎？像這樣的互動交流，能留下比聽課更深刻的印象。況且，對很多工程師來說，結識新朋友並不容易，但用一個技術話題來破冰，難度就降低許多。</p><h3>Ask Speaker</h3><p>為了不阻礙行程，每場分享結束後，講者會移動到會議廳外的一個 Ask Speaker 區，在這裡回答聽眾的問題。</p><p>我很喜歡這個區域的概念。講臺分享主要是單向的，且講者和聽眾之間有很大的距離。就算在現場預留問答時間，能做的互動仍很有限。在 Ask Speaker 區，講者和聽眾可以坐下來對話，讓聽眾更容易把自己的問題說清楚，講者也因此能得到即時的回饋。我想像如果我是講者，我會十分期待觀眾來分享他們聽後的感想。</p><p>比較可惜的是，就我的觀察，Ask Speaker 區的使用率偏低，可能在場地安排及說明推廣方面還有改善空間。</p><h3>小結 &amp; 待續</h3><p>我有時會想，如果教育的目的之一是培養未來所需的人才，那麼透過觀察軟體工程師，也許可以帶來一些啟發。</p><p>軟體技術翻新的速度極快，所以工程師們必需為「autodidact」，善於自學的人。這群人天天都在線上找資料解決問題，並規律性的自修，以提升專業能力。許多人認為像這樣的模式，在未來會愈來愈廣泛。另外，軟體圈內還有個共識，就是工程師應該常找 side projects，即個人有興趣的小型專案，做為學習新技術和展示能力的媒介。換言之，就是 Project-Based Learning。如此說來，軟體工程師們好似走在教育改革的最前線。</p><p>雖然如此，軟體工程師並不會把學習的管道侷限在數位世界裡。如 iPlayground 這類的實體技術研習，在各個軟體領域裡都十分受歡迎。雖說主因可能是好玩、可以拓展人脈，但我想同樣不容小覻的，是因實體活動能滿足學習所需的社會要素，而對專業成長所帶來的幫助。</p><p>下一篇我想紀錄關於這次議程的個人評價，第二天的 secret party，和其它學習相關的想法。</p><p><em>Originally published at </em><a href="http://mathemusician.net/2019/03/iplayground-2018-part-1/"><em>mathemusician.net</em></a><em> on March 9, 2019.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e042e3bc7943" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>