Conversation
Scala 概念(flatMap、Option/Either、IO monad、Ref、property-based testing 等)翻譯成 JS 可執行等價寫法;跟既有 fp/sicp/dmmf/fljs 章節 重疊主題(純函式、不可變性、Stream、併行)刻意換不同情境出題避免重複。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
複習分頁讓使用者按章節/關卡瀏覽重點整理(目前只有 Grokking Functional Programming 完整驗證,內容改寫自原書 12 章,其餘書籍待補)。收藏重點功能 讓使用者在每條重點旁點星號收藏,收藏狀態比照既有收藏題目的模式同步到 後端(新增 Prisma model SavedKeyPoint 並套用 migration),登入使用者可 跨裝置同步;同時修掉一個既有的畫面切換捲動位置未歸零的 bug。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The latest updates on your projects. Learn more about Vercel for GitHub.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughAdds the Grokking Functional Programming curriculum and recap data, introduces saved key-point progress persistence through Prisma and API synchronization, and adds web recap, saved-key-point screens, navigation, and styling. ChangesCurriculum and recap data
Saved key-point persistence
Recap and saved-key-point screens
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant RecapChapter
participant useProgress
participant SaveKeyPointToggle
participant Prisma
User->>RecapChapter: Select key-point star
RecapChapter->>useProgress: toggleSavedKeyPoint(keyPointId)
useProgress->>SaveKeyPointToggle: POST keyPointId
SaveKeyPointToggle->>Prisma: Create or delete SavedKeyPoint
SaveKeyPointToggle->>Prisma: Load full progress
SaveKeyPointToggle-->>useProgress: Return progress
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 10
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/web/src/app/api/progress/save-keypoint-toggle/route.ts`:
- Around line 23-35: Replace the blind toggle flow in
apps/web/src/app/api/progress/save-keypoint-toggle/route.ts lines 23-35 with an
idempotent mutation that accepts the desired saved state and serializes or
atomically applies the read/write; do not silently treat P2002/P2025 races as
successful. Update apps/web/src/hooks/useProgress.ts lines 154-166 to send that
desired state, sequence requests per key point, and reconcile stale or failed
responses so the persisted and displayed state remains correct.
- Around line 19-20: Validate incoming key-point IDs against the canonical IDs
defined in chapterSummaries before accepting them. Apply this validation in
save-keypoint-toggle/route.ts at lines 19-20 and migrate-local/route.ts at lines
44-46, rejecting invalid values before creating or persisting any records while
preserving the existing 400 response behavior.
In `@apps/web/src/screens/RecapChapter.tsx`:
- Around line 47-49: Update the key-point identity flow in RecapChapter and
keyPointId usage to use an immutable ID stored on each summary key point rather
than the mapped array index. Ensure saved-key-point lookup and persistence use
that stable ID, while retaining a migration/fallback path that recognizes
existing index-based saved IDs.
In `@packages/core/src/data/chapterSummaries.ts`:
- Line 48: Revise the product type explanation in chapterSummaries so the
JavaScript `{ name, year }` analogy is not described as inherently immutable.
Describe it as an object with a fixed product-shaped structure, or explicitly
qualify immutability as a convention/type-level guarantee unless the
implementation freezes the object or uses an immutable structure.
- Around line 142-143: Update keyPointId and the key-point data model so every
key point has an explicit immutable identifier rather than deriving its
persisted ID from the array index. Add migration handling for existing
levelId:index values before using the new IDs for saved-row lookup, preserving
existing saved key points and ensuring inserts, removals, or reordering do not
change their identities.
- Around line 87-90: Update the lazy-IO analogy in the affected chapter summary
to avoid presenting an unawaited Promise as equivalent to a deferred thunk,
since creating a Promise from fetch starts the work immediately. Keep the
existing thunk/IO-wrapper explanation, or explicitly qualify Promise as only an
approximation; preserve the surrounding descriptions of delayed execution and IO
composition.
In `@packages/core/src/data/questions/gfp-1-learning-fp.json`:
- Around line 90-105: Update the question around parseAge and categorizeAge so
the stated string-to-number signature does not imply safe composition when
parseAge can throw. Prefer changing parseAge to return an explicit total result
such as Option or Either and revise the prompt, correct answer, and explanation
to address the failure path; otherwise require callers to handle its exception
before composition.
In `@packages/core/src/data/questions/gfp-10-concurrent-programs.json`:
- Around line 115-127: Update the chunk helper to validate size before entering
its loop, rejecting any non-positive or non-integer value so i += size cannot
cause an infinite loop. Preserve the existing batching behavior for positive
integer sizes and ensure invalid inputs fail through the established error
behavior.
- Around line 70-85: Update the question’s `topic`, `prompt`, code labels,
`options`, `answer` explanation, and `verify` output around `safeBalance` so
`reduce` is described as sequential accumulation rather than concurrency-safe or
atomic updating. Preserve the distinction between the lost-update simulation and
the sequential result, and ensure all displayed text and expected output use the
revised terminology consistently.
In `@packages/core/src/data/questions/gfp-12-testing-fp-programs.json`:
- Around line 67-85: The question currently describes the hard-coded testCases
and every check as property-based testing. Relabel the topic, prompt, and
explanation as example-based testing, or replace testCases with an
arbitrary-array generator and property runner; ensure the answer and
verification remain consistent with the chosen implementation.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: c7d0b630-6938-436e-8353-ffc74b49e205
📒 Files selected for processing (32)
apps/mobile/hooks/useProgress.tsapps/web/prisma/migrations/20260727021508_add_saved_keypoint/migration.sqlapps/web/prisma/schema.prismaapps/web/src/App.tsxapps/web/src/app/api/progress/migrate-local/route.tsapps/web/src/app/api/progress/save-keypoint-toggle/route.tsapps/web/src/components/Navbar.tsxapps/web/src/hooks/useProgress.tsapps/web/src/index.cssapps/web/src/lib/progressLogic.tsapps/web/src/lib/progressStore.tsapps/web/src/screens/Notes.tsxapps/web/src/screens/Recap.tsxapps/web/src/screens/RecapChapter.tsxapps/web/src/screens/SavedKeyPoints.tsxdocs/curriculum.mdpackages/core/src/data/chapterSummaries.tspackages/core/src/data/chapters.tspackages/core/src/data/questions/gfp-1-learning-fp.jsonpackages/core/src/data/questions/gfp-10-concurrent-programs.jsonpackages/core/src/data/questions/gfp-11-designing-fp-programs.jsonpackages/core/src/data/questions/gfp-12-testing-fp-programs.jsonpackages/core/src/data/questions/gfp-2-pure-functions.jsonpackages/core/src/data/questions/gfp-3-immutable-values.jsonpackages/core/src/data/questions/gfp-4-functions-as-values.jsonpackages/core/src/data/questions/gfp-5-sequential-programs.jsonpackages/core/src/data/questions/gfp-6-error-handling.jsonpackages/core/src/data/questions/gfp-7-requirements-as-types.jsonpackages/core/src/data/questions/gfp-8-io-as-values.jsonpackages/core/src/data/questions/gfp-9-streams-as-values.jsonpackages/core/src/index.tspackages/core/src/types.ts
| if (typeof keyPointId !== 'string' || keyPointId.length === 0) { | ||
| return NextResponse.json({ error: 'invalid keyPointId' }, { status: 400 }) |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Validate key-point IDs at every server ingestion path. Both endpoints accept arbitrary non-empty strings, although packages/core/src/data/chapterSummaries.ts defines the canonical IDs. Invalid IDs become orphaned records and can incorrectly make a user appear to have migrated progress.
apps/web/src/app/api/progress/save-keypoint-toggle/route.ts#L19-L20: validate the ID against canonical chapter-summary data.apps/web/src/app/api/progress/migrate-local/route.ts#L44-L46: apply the same validation before creating Prisma rows.
📍 Affects 2 files
apps/web/src/app/api/progress/save-keypoint-toggle/route.ts#L19-L20(this comment)apps/web/src/app/api/progress/migrate-local/route.ts#L44-L46
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/web/src/app/api/progress/save-keypoint-toggle/route.ts` around lines 19
- 20, Validate incoming key-point IDs against the canonical IDs defined in
chapterSummaries before accepting them. Apply this validation in
save-keypoint-toggle/route.ts at lines 19-20 and migrate-local/route.ts at lines
44-46, rejecting invalid values before creating or persisting any records while
preserving the existing 400 response behavior.
| const existing = await prisma.savedKeyPoint.findUnique({ where: { userId_keyPointId: { userId, keyPointId } } }) | ||
| try { | ||
| if (existing) { | ||
| await prisma.savedKeyPoint.delete({ where: { userId_keyPointId: { userId, keyPointId } } }) | ||
| } else { | ||
| await prisma.savedKeyPoint.create({ data: { userId, keyPointId } }) | ||
| } | ||
| } catch (err) { | ||
| // 重複點擊造成兩個請求同時 toggle:一個 create 撞到另一個剛建好的紀錄(P2002), | ||
| // 或一個 delete 撞到已經被另一個請求刪掉的紀錄(P2025)——兩者都代表結果已經是預期狀態,忽略即可 | ||
| const isRaceCondition = | ||
| err instanceof Prisma.PrismaClientKnownRequestError && (err.code === 'P2002' || err.code === 'P2025') | ||
| if (!isRaceCondition) throw err |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Use an idempotent saved-state API instead of a blind toggle contract. The server uses a stale read-before-write, while the client sends only an ID and accepts responses in any order; concurrent clicks or retries can persist and display the wrong final state.
apps/web/src/app/api/progress/save-keypoint-toggle/route.ts#L23-L35: accept the desired state or serialize the read/write mutation; do not silently ignore races as success.apps/web/src/hooks/useProgress.ts#L154-L166: send the desired state, sequence requests per key point, and reconcile failed or stale responses.
📍 Affects 2 files
apps/web/src/app/api/progress/save-keypoint-toggle/route.ts#L23-L35(this comment)apps/web/src/hooks/useProgress.ts#L154-L166
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/web/src/app/api/progress/save-keypoint-toggle/route.ts` around lines 23
- 35, Replace the blind toggle flow in
apps/web/src/app/api/progress/save-keypoint-toggle/route.ts lines 23-35 with an
idempotent mutation that accepts the desired saved state and serializes or
atomically applies the read/write; do not silently treat P2002/P2025 races as
successful. Update apps/web/src/hooks/useProgress.ts lines 154-166 to send that
desired state, sequence requests per key point, and reconcile stale or failed
responses so the persisted and displayed state remains correct.
| {summary.keyPoints.map((point, index) => { | ||
| const id = keyPointId(summary.levelId, index) | ||
| const saved = !!savedKeyPointIds[id] |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Use stable IDs for persisted key points.
keyPointId(summary.levelId, index) is positional. Inserting or reordering summary bullets will make existing saved records point to different text. Store an immutable ID alongside each key point instead of deriving it from its array index, with a migration/fallback for existing saved IDs.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/web/src/screens/RecapChapter.tsx` around lines 47 - 49, Update the
key-point identity flow in RecapChapter and keyPointId usage to use an immutable
ID stored on each summary key point rather than the mapped array index. Ensure
saved-key-point lookup and persistence use that stable ID, while retaining a
migration/fallback path that recognizes existing index-based saved IDs.
| 'map、filter、foldLeft 都是「高階函數」(higher-order function:接受函式當參數,或回傳函式的函式)在 JS 裡的直接對應:map 對陣列每個元素套用同一個轉換函式產生等長新陣列;filter 用回傳布林值的判斷函式挑出符合條件的元素;foldLeft(對應 JS 的 reduce)則是帶著一個累加值,逐一走訪陣列元素把結果收斂成單一值,三者都不修改原陣列。', | ||
| '函式可以回傳函式:當一個函式需要固定住某些參數(例如評分演算法、清單)、只留一個參數(例如門檻值)晚點才決定時,可以讓外層函式回傳一個只接收剩餘參數的內層函式,避免每次呼叫都要重複傳同一組固定參數。', | ||
| 'currying(柯里化)就是把一個多參數函式,轉換成一連串每次只接收一個參數、彼此回傳彼此的單參數函式鏈(在 JS 就是連續回傳箭頭函式,如 a => b => c => 結果);重點不是語法炫技,而是讓呼叫端可以在程式的不同地方分別決定每個參數,減少重複傳遞相同引數的樣板碼。', | ||
| 'product type(乘積型別,Scala 用 case class 表達,對應到 JS 大致就是一個描述固定欄位形狀的物件,例如 { name, year })用來把兩個以上相關聯的資料綁在一起變成一個不可變值;由於它本身不可變,跟 map/filter 這類高階函數搭配時特別安全,可以直接用箭頭函式取出欄位(例如 lang => lang.name)而不用擔心欄位被意外修改。', |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Do not describe a plain JavaScript object as immutable.
{ name, year } is mutable unless the implementation freezes it or uses an immutable data structure. Reword this as a product-shaped object, or explicitly explain that immutability is only a convention/type-level guarantee in this JavaScript analogy.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/core/src/data/chapterSummaries.ts` at line 48, Revise the product
type explanation in chapterSummaries so the JavaScript `{ name, year }` analogy
is not described as inherently immutable. Describe it as an object with a fixed
product-shaped structure, or explicitly qualify immutability as a
convention/type-level guarantee unless the implementation freezes the object or
uses an immutable structure.
| '把一個會讀寫外部世界的不純函式(呼叫 API、讀寫終端機輸入輸出)包裝起來(概念上類似 JS 裡用一個 thunk 或尚未 await 的 Promise 描述「之後才要執行的動作」),包裝的當下並不會真的執行它,而只是建立一個值,描述「這是一個之後執行時會產生某個結果的計算」,真正的副作用被延後到程式裡明確呼叫一次「執行」的地方才會發生。', | ||
| '這種「延遲計算」的容器本身也有 map/flatMap,所以多個包好的 IO 步驟(例如分別取兩個人的行事曆再合併)可以用前面章節同一套 for 迴圈式管線串接起來——串接的過程完全不會真的執行任何副作用,只有在最後把整條組合值拿去執行時才會發生。', | ||
| '只要用到一個回傳 IO 的函式,呼叫端自己的回傳型別也會被迫變成 IO(書裡稱為 IO 有「傳染性」,類似 JS 裡 async 函式不管內部做什麼、回傳的一定是 Promise)——這讓型別簽名一眼就能看出程式裡哪些地方可能有副作用、哪些地方保證是純的。', | ||
| '重試一次、重試後改用預設值等復原策略,是透過在 IO 值上串接 .orElse(...) 來表達——跟第六章 Option/Either 的 orElse 是同一套語彙,不需要巢狀的 try/catch,讓重試/備援邏輯可以宣告式地跟主要商業邏輯分開放。', |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Use a thunk, not a Promise, for the lazy-IO analogy.
Creating a Promise from fetch() starts the underlying work; await only observes it and does not defer the side effect. Keep the thunk/IO-wrapper analogy, or clearly state that a Promise is only an approximation.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/core/src/data/chapterSummaries.ts` around lines 87 - 90, Update the
lazy-IO analogy in the affected chapter summary to avoid presenting an unawaited
Promise as equivalent to a deferred thunk, since creating a Promise from fetch
starts the work immediately. Keep the existing thunk/IO-wrapper explanation, or
explicitly qualify Promise as only an approximation; preserve the surrounding
descriptions of delayed execution and IO composition.
| // 重點文字沒有天生的唯一 id,用「關卡 id + 陣列索引」組出穩定識別碼給收藏功能存 | ||
| export const keyPointId = (levelId: string, index: number): string => `${levelId}:${index}` |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Make persisted key-point IDs independent of array position.
levelId:index is stable only while key points are never inserted, removed, or reordered. A future recap edit can make an existing saved row select different text or become orphaned. Give each key point an explicit immutable ID and migrate existing IDs before treating this as a persistence key.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/core/src/data/chapterSummaries.ts` around lines 142 - 143, Update
keyPointId and the key-point data model so every key point has an explicit
immutable identifier rather than deriving its persisted ID from the array index.
Add migration handling for existing levelId:index values before using the new
IDs for saved-row lookup, preserving existing saved key points and ensuring
inserts, removals, or reordering do not change their identities.
| "id": "gfp-1-q5", | ||
| "type": "concept", | ||
| "difficulty": 2, | ||
| "topic": "關注簽章勝過關注函式本體:組合函式時,你在乎的是型別對不對得起來", | ||
| "docs": "", | ||
| "story": "", | ||
| "prompt": "把 parseAge 的結果傳給 categorizeAge(age: number) 使用時,你需要先讀懂 parseAge 內部每一行程式碼在做什麼,才能正確使用它嗎?", | ||
| "code": "", | ||
| "options": [ | ||
| { "id": "a", "text": "不需要:只要知道 parseAge 的簽章是「輸入字串、輸出數字」,就能判斷它的輸出型別跟 categorizeAge 要求的輸入型別對得起來,可以直接組合使用,不需要深入讀懂 parseAge 內部每一行是怎麼算出這個數字的——FP 鼓勵在組合函式的時候,優先關注簽章,而不是鑽進函式本體的實作細節" }, | ||
| { "id": "b", "text": "一定要先把 parseAge 內部的每一行程式碼都讀懂,才能知道能不能跟 categorizeAge 組合使用" }, | ||
| { "id": "c", "text": "只要函式名稱聽起來合理,型別對不對得起來完全不重要" }, | ||
| { "id": "d", "text": "簽章只是裝飾用的標註,實際組合函式時完全不會參考它" } | ||
| ], | ||
| "answer": "a", | ||
| "explanation": "只要 parseAge 的簽章跟 categorizeAge 要求的輸入型別對得起來,就能直接組合使用,不需要深入讀懂 parseAge 內部每一行是怎麼算出這個數字的。FP 鼓勵在組合函式時優先關注簽章,這也是為什麼「函式不說謊」這麼重要——一旦簽章可以信任,組合時就能省下讀懂每個函式內部實作細節的心力。", |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Do not treat this parseAge signature as sufficient.
Lines 48–64 establish that parseAge throws, but this question says callers can safely compose it based only on string -> number. In JavaScript, that signature does not encode exceptions. Either return Option/Either (or another total result) or update the answer to require handling the failure path.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/core/src/data/questions/gfp-1-learning-fp.json` around lines 90 -
105, Update the question around parseAge and categorizeAge so the stated
string-to-number signature does not imply safe composition when parseAge can
throw. Prefer changing parseAge to return an explicit total result such as
Option or Either and revise the prompt, correct answer, and explanation to
address the failure path; otherwise require callers to handle its exception
before composition.
| "topic": "驗證:把多次「各自讀取、各自寫回」合併成一次安全的累加更新", | ||
| "docs": "https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce", | ||
| "story": "", | ||
| "prompt": "這段程式對照示範「模擬遺失更新」跟「用單一個 reduce 安全地累加更新」,處理同一批扣款請求。這段程式會印出什麼?", | ||
| "code": "let unsafeBalance = 100;\nconst withdrawals = [30, 40];\n\nconst snapshot = unsafeBalance;\nwithdrawals.forEach((amount) => {\n unsafeBalance = snapshot - amount;\n});\n\nconst safeBalance = withdrawals.reduce((balance, amount) => balance - amount, 100);\n\nconsole.log('模擬遺失更新的結果:', unsafeBalance);\nconsole.log('安全累加的結果:', safeBalance);", | ||
| "options": [ | ||
| { "id": "a", "text": "模擬遺失更新的結果: 60\n安全累加的結果: 30" }, | ||
| { "id": "b", "text": "模擬遺失更新的結果: 30\n安全累加的結果: 30" }, | ||
| { "id": "c", "text": "模擬遺失更新的結果: 60\n安全累加的結果: 60" }, | ||
| { "id": "d", "text": "報錯" } | ||
| ], | ||
| "answer": "a", | ||
| "explanation": "模擬遺失更新的版本,每一步都根據同一份「呼叫當下的舊值」計算,後面的寫入會直接覆蓋前面的結果,最終只反映了最後一筆扣款(40),30 這筆扣款完全消失,變成 60;安全累加的版本用 reduce,每一步都基於「上一步已經更新過的結果」繼續往下算,正確反映兩筆扣款,變成 30。", | ||
| "verify": { | ||
| "checks": [ | ||
| { "code": "let unsafeBalance = 100;\nconst withdrawals = [30, 40];\nconst snapshot = unsafeBalance;\nwithdrawals.forEach((amount) => {\n unsafeBalance = snapshot - amount;\n});\nconst safeBalance = withdrawals.reduce((balance, amount) => balance - amount, 100);\nconsole.log('模擬遺失更新的結果:', unsafeBalance);\nconsole.log('安全累加的結果:', safeBalance);", "expected": "模擬遺失更新的結果: 60\n安全累加的結果: 30" } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Do not present reduce as a concurrency-safe update.
This example only performs sequential computation over a local array; it does not provide atomicity for concurrent writers. Calling the result “safe” can teach users that reduce fixes shared-state races. Relabel it as sequential accumulation or demonstrate an actual atomic/Ref-based update.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/core/src/data/questions/gfp-10-concurrent-programs.json` around
lines 70 - 85, Update the question’s `topic`, `prompt`, code labels, `options`,
`answer` explanation, and `verify` output around `safeBalance` so `reduce` is
described as sequential accumulation rather than concurrency-safe or atomic
updating. Preserve the distinction between the lost-update simulation and the
sequential result, and ensure all displayed text and expected output use the
revised terminology consistently.
| "prompt": "chunk 把一個陣列切成固定大小的小批次,模擬「分批處理」的第一步。這段程式會印出什麼?", | ||
| "code": "function chunk(arr, size) {\n const result = [];\n for (let i = 0; i < arr.length; i += size) {\n result.push(arr.slice(i, i + size));\n }\n return result;\n}\n\nconst items = [1, 2, 3, 4, 5, 6, 7];\nconsole.log(chunk(items, 3));", | ||
| "options": [ | ||
| { "id": "a", "text": "[ [ 1, 2, 3 ], [ 4, 5, 6 ], [ 7 ] ]" }, | ||
| { "id": "b", "text": "[ [ 1, 2, 3, 4, 5, 6, 7 ] ]" }, | ||
| { "id": "c", "text": "[ [ 1 ], [ 2 ], [ 3 ], [ 4 ], [ 5 ], [ 6 ], [ 7 ] ]" }, | ||
| { "id": "d", "text": "報錯" } | ||
| ], | ||
| "answer": "a", | ||
| "explanation": "chunk 每次用 slice 切出固定大小(3 個元素)的一批,直到整個陣列都切完為止——7 個元素被切成 [1,2,3]、[4,5,6]、[7] 三個批次。分批處理的第一步,通常就是先把要處理的資料切成這種固定大小的批次,再依序(而不是全部同時)對每一批做併行處理。", | ||
| "verify": { | ||
| "checks": [ | ||
| { "code": "function chunk(arr, size) {\n const result = [];\n for (let i = 0; i < arr.length; i += size) {\n result.push(arr.slice(i, i + size));\n }\n return result;\n}\nconst items = [1, 2, 3, 4, 5, 6, 7];\nconsole.log(chunk(items, 3));", "expected": "[ [ 1, 2, 3 ], [ 4, 5, 6 ], [ 7 ] ]" } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
Reject non-positive chunk sizes.
With size === 0 or a negative value, i += size never reaches arr.length, so this helper loops forever. Add a positive-integer guard before the loop.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/core/src/data/questions/gfp-10-concurrent-programs.json` around
lines 115 - 127, Update the chunk helper to validate size before entering its
loop, rejecting any non-positive or non-integer value so i += size cannot cause
an infinite loop. Preserve the existing batching behavior for positive integer
sizes and ensure invalid inputs fail through the established error behavior.
| "id": "gfp-12-q4", | ||
| "type": "predict-output", | ||
| "difficulty": 2, | ||
| "topic": "驗證:對好幾份不同的清單,反轉兩次都回到原本的樣子", | ||
| "docs": "https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse", | ||
| "story": "", | ||
| "prompt": "這段程式對三份不同的清單,各自驗證「反轉兩次是否等於原本」這條屬性。這段程式會印出什麼?", | ||
| "code": "function reverseTwice(arr) {\n return [...arr].reverse().reverse();\n}\n\nconst testCases = [[1, 2, 3], [], ['a'], [5, 5, 5, 1]];\n\nconst allPass = testCases.every(\n (arr) => JSON.stringify(reverseTwice(arr)) === JSON.stringify(arr)\n);\n\nconsole.log(allPass);", | ||
| "options": [ | ||
| { "id": "a", "text": "true" }, | ||
| { "id": "b", "text": "false" }, | ||
| { "id": "c", "text": "undefined" }, | ||
| { "id": "d", "text": "報錯" } | ||
| ], | ||
| "answer": "a", | ||
| "explanation": "不管測試案例是有三個元素的陣列、空陣列、只有一個元素的陣列,還是有重複值的陣列,reverseTwice 都能正確回到原本的樣子,every 對所有測試案例都驗證通過,所以印出 true——這正是屬性測試的精神:同一條規律要對很多種不同形狀的輸入都成立,才算是真正驗證過這條屬性。", | ||
| "verify": { | ||
| "checks": [ | ||
| { "code": "function reverseTwice(arr) {\n return [...arr].reverse().reverse();\n}\nconst testCases = [[1, 2, 3], [], ['a'], [5, 5, 5, 1]];\nconst allPass = testCases.every(\n (arr) => JSON.stringify(reverseTwice(arr)) === JSON.stringify(arr)\n);\nconsole.log(allPass);", "expected": "true" } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Do not label fixed examples as property-based testing.
testCases is a hard-coded list and every checks only those four examples; no input generator or property runner is used. Either relabel this as example-based testing or change the code to generate arbitrary arrays so the explanation matches the implementation.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/core/src/data/questions/gfp-12-testing-fp-programs.json` around
lines 67 - 85, The question currently describes the hard-coded testCases and
every check as property-based testing. Relabel the topic, prompt, and
explanation as example-based testing, or replace testCases with an
arbitrary-array generator and property runner; ensure the answer and
verification remain consistent with the chosen implementation.
內容改寫自 SICP JS 原書 1.1~5.5 每個小節,涵蓋代換模型、遞迴過程、 資料抽象、環境模型、可變資料、併行、Stream、metacircular 直譯器、 惰性求值、非決定性計算、邏輯式程式設計、暫存器機器、垃圾回收、 編譯器等經典主題。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Uh oh!
There was an error while loading. Please reload this page.
Summary by CodeRabbit