From 63c6ad2d9abb470cf0a426ab83e2d8ed7e99ade8 Mon Sep 17 00:00:00 2001 From: na-trium-144 <100704180+na-trium-144@users.noreply.github.com> Date: Mon, 3 Aug 2026 00:40:55 +0900 Subject: [PATCH 1/6] add Dart MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - **API**: DartPad の `compileNewDDC` API エンドポイント (`https://stable.api.dartpad.dev/api/v3/compileNewDDC`) を使用。 - **実行環境**: iframe 内で RequireJS により `ddc_module_loader.js` および `dart_sdk_new.js` をロードし、`dartDevEmbedder.runMain` でコンパイル済み JS を安全に実行。 - **UI & 統合**: Ace Editor モード (`mode-dart`)、Dart SVG アイコン、テスト用サンプルコード (`main.dart`) を追加・統合。 --- app/terminal/editor.tsx | 1 + app/terminal/icons.tsx | 10 + app/terminal/page.tsx | 8 + app/terminal/samples/main.dart | 3 + packages/runtime/src/context.tsx | 8 +- packages/runtime/src/dart/runtime.tsx | 299 ++++++++++++++++++++++++ packages/runtime/src/languages.ts | 21 +- packages/runtime/tests/fileExecution.ts | 7 + packages/runtime/tests/repl.ts | 5 + packages/runtime/tests/utils.ts | 1 + 10 files changed, 358 insertions(+), 5 deletions(-) create mode 100644 app/terminal/samples/main.dart create mode 100644 packages/runtime/src/dart/runtime.tsx diff --git a/app/terminal/editor.tsx b/app/terminal/editor.tsx index 14d2547b..70e9b94e 100644 --- a/app/terminal/editor.tsx +++ b/app/terminal/editor.tsx @@ -26,6 +26,7 @@ const AceEditor = lazy(async () => { await import("ace-builds/src-min-noconflict/mode-json"); await import("ace-builds/src-min-noconflict/mode-csv"); await import("ace-builds/src-min-noconflict/mode-text"); + await import("ace-builds/src-min-noconflict/mode-dart"); return ace; } else { throw new Error("should not try SSR"); diff --git a/app/terminal/icons.tsx b/app/terminal/icons.tsx index 872ae930..9c356a0f 100644 --- a/app/terminal/icons.tsx +++ b/app/terminal/icons.tsx @@ -113,6 +113,16 @@ export function LanguageIcon(props: Props) { ); + case "dart": + return ( + + + + ); default: props.lang satisfies never; console.warn("unknown lang for LanguageIcon:", props.lang); diff --git a/app/terminal/page.tsx b/app/terminal/page.tsx index ba9a32ec..b40bef1c 100644 --- a/app/terminal/page.tsx +++ b/app/terminal/page.tsx @@ -26,6 +26,7 @@ import sub_h from "./samples/sub.h?raw"; import sub_cpp from "./samples/sub.cpp?raw"; import main2_rs from "./samples/main2.rs?raw"; import sub_rs from "./samples/sub.rs?raw"; +import main_dart from "./samples/main.dart?raw"; import { DaisyInfoIcon } from "@/daisyAlertIcon"; export default function RuntimeTestPage() { @@ -127,6 +128,13 @@ const sampleConfig: Record = { }, exec: ["main2.rs"], }, + dart: { + repl: false, + editor: { + "main.dart": main_dart, + }, + exec: ["main.dart"], + }, }; function RuntimeSample({ lang, diff --git a/app/terminal/samples/main.dart b/app/terminal/samples/main.dart new file mode 100644 index 00000000..a506f97c --- /dev/null +++ b/app/terminal/samples/main.dart @@ -0,0 +1,3 @@ +void main() { + print("Hello, Dart!"); +} diff --git a/packages/runtime/src/context.tsx b/packages/runtime/src/context.tsx index 9874812e..4a7e5ac4 100644 --- a/packages/runtime/src/context.tsx +++ b/packages/runtime/src/context.tsx @@ -3,6 +3,7 @@ import { ReactNode, useEffect } from "react"; import { RuntimeContext } from "./interface"; import { RuntimeLang } from "./languages"; +import { DartProvider, useDart } from "./dart/runtime"; import { TypeScriptProvider, useTypeScript } from "./typescript/runtime"; import { useWandbox, WandboxProvider } from "./wandbox/runtime"; import { JSEvalContext, useJSEval } from "./worker/jsEval"; @@ -34,6 +35,7 @@ export function useRuntimeAll(): Record { const typescript = useTypeScript(jsEval); const wandboxCpp = useWandbox("cpp"); const wandboxRust = useWandbox("rust"); + const dart = useDart(); // initはしない。呼び出し側でする必要がある return { @@ -43,6 +45,7 @@ export function useRuntimeAll(): Record { typescript: typescript, cpp: wandboxCpp, rust: wandboxRust, + dart: dart, }; } export function RuntimeProvider({ children }: { children: ReactNode }) { @@ -51,10 +54,13 @@ export function RuntimeProvider({ children }: { children: ReactNode }) { - {children} + + {children} + ); } + diff --git a/packages/runtime/src/dart/runtime.tsx b/packages/runtime/src/dart/runtime.tsx new file mode 100644 index 00000000..dabf52fa --- /dev/null +++ b/packages/runtime/src/dart/runtime.tsx @@ -0,0 +1,299 @@ +"use client"; + +import { + createContext, + ReactNode, + useCallback, + useContext, + useEffect, + useMemo, + useRef, +} from "react"; +import useSWR from "swr"; +import { + ReplOutput, + RuntimeContext, + RuntimeErrorHandler, + RuntimeInfo, + UpdatedFile, +} from "../interface"; + +const DART_PAD_API_BASE = "https://stable.api.dartpad.dev/api/v3"; +const DART_PAD_ARTIFACTS_BASE = "https://stable.api.dartpad.dev/artifacts"; + +interface DartVersionResponse { + dartVersion?: string; + flutterVersion?: string; +} + +const versionFetcher = async (url: string): Promise => { + const res = await fetch(url); + if (!res.ok) { + throw new Error(`Failed to fetch Dart version: ${res.statusText}`); + } + return res.json(); +}; + +const DartContext = createContext<{ + init: (onError?: RuntimeErrorHandler) => void; + ready: boolean; + dartVersion?: string; +}>({ + init: () => undefined, + ready: true, +}); + +export function DartProvider({ children }: { children: ReactNode }) { + const onErrorRef = useRef(undefined); + const init = useCallback((onError?: RuntimeErrorHandler) => { + onErrorRef.current = onError; + }, []); + + const { data, error } = useSWR( + `${DART_PAD_API_BASE}/version`, + versionFetcher + ); + + useEffect(() => { + if (error) { + console.error("Failed to fetch Dart version info:", error); + onErrorRef.current?.(error); + } + }, [error]); + + return ( + + {children} + + ); +} + +export function useDart(): RuntimeContext { + const { init: dartInit, ready, dartVersion } = useContext(DartContext); + const onErrorRef = useRef(undefined); + + const init = useCallback( + (onError?: RuntimeErrorHandler) => { + onErrorRef.current = onError; + dartInit(onError); + }, + [dartInit] + ); + + const runFiles = useCallback( + async ( + filenames: string[], + files: Readonly>, + onOutput: (output: ReplOutput | UpdatedFile) => void + ) => { + if (typeof window === "undefined") { + onOutput({ + type: "error", + message: "Dart runtime requires browser environment.", + }); + return; + } + + const filename = filenames[0] ?? Object.keys(files)[0]; + const source = files[filename] ?? Object.values(files)[0]; + + if (!source) { + onOutput({ type: "error", message: "No source code provided to run." }); + return; + } + + try { + const response = await fetch(`${DART_PAD_API_BASE}/compileNewDDC`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ source }), + }); + + if (!response.ok) { + const errorText = await response.text(); + onOutput({ + type: "error", + message: + errorText || + `Compilation failed with status ${response.status}`, + }); + return; + } + + const data = await response.json(); + if (!data.result) { + onOutput({ + type: "error", + message: "Compilation returned empty result.", + }); + return; + } + + const jsCode: string = data.result; + + // Execute compiled JS inside a temporary iframe + await new Promise((resolve) => { + const iframe = document.createElement("iframe"); + iframe.style.display = "none"; + document.body.appendChild(iframe); + + let resolved = false; + const cleanup = () => { + if (resolved) return; + resolved = true; + window.removeEventListener("message", handleMessage); + if (iframe.parentNode) { + iframe.parentNode.removeChild(iframe); + } + resolve(); + }; + + const handleMessage = (event: MessageEvent) => { + if (event.source !== iframe.contentWindow) return; + const msgData = event.data; + if (!msgData || msgData.sender !== "dart_frame") return; + + if (msgData.type === "stdout") { + onOutput({ type: "stdout", message: String(msgData.message) }); + } else if (msgData.type === "stderr") { + onOutput({ type: "stderr", message: String(msgData.message) }); + } else if (msgData.type === "done") { + cleanup(); + } else if (msgData.type === "error") { + onOutput({ type: "error", message: String(msgData.message) }); + cleanup(); + } + }; + + window.addEventListener("message", handleMessage); + + const iframeDoc = iframe.contentDocument; + if (!iframeDoc) { + onOutput({ + type: "error", + message: "Failed to access iframe document.", + }); + cleanup(); + return; + } + + const htmlContent = ` + + + + + + + +`; + + iframeDoc.open(); + iframeDoc.write(htmlContent); + iframeDoc.close(); + + setTimeout(() => { + cleanup(); + }, 15000); + }); + } catch (error) { + onErrorRef.current?.(error); + onOutput({ + type: "fatalError", + message: error instanceof Error ? error.message : String(error), + }); + } + }, + [] + ); + + const runtimeInfo = useMemo( + () => ({ + prettyLangName: "Dart", + version: dartVersion, + }), + [dartVersion] + ); + + return { + init, + ready, + runFiles, + getCommandlineStr, + runtimeInfo, + }; +} + +function getCommandlineStr(filenames: string[]) { + return `dart run ${filenames[0] ?? "main.dart"}`; +} diff --git a/packages/runtime/src/languages.ts b/packages/runtime/src/languages.ts index 3c2ceb72..2b4ff665 100644 --- a/packages/runtime/src/languages.ts +++ b/packages/runtime/src/languages.ts @@ -22,7 +22,8 @@ export type MarkdownLang = | "makefile" | "cmake" | "text" - | "txt"; + | "txt" + | "dart"; export type RuntimeLang = | "python" @@ -30,7 +31,8 @@ export type RuntimeLang = | "cpp" | "rust" | "javascript" - | "typescript"; + | "typescript" + | "dart"; export type LangConstants = { originalLang: MarkdownLang | undefined; @@ -50,7 +52,8 @@ export type LangConstants = { | "json" | "ini" | "makefile" - | "cmake"; + | "cmake" + | "dart"; } & ( | { // terminal/editor.tsx でimportする mode-xxxx.js のファイル名と、AceEditorの mode プロパティの値と対応する @@ -63,7 +66,8 @@ export type LangConstants = { | "typescript" | "json" | "csv" - | "text"; + | "text" + | "dart"; tabSize: number; } | { @@ -158,6 +162,14 @@ export function langConstants(lang: MarkdownLang | undefined): LangConstants { tabSize: 4, runtime: "rust", }; + case "dart": + return { + originalLang: lang, + rsh: "dart", + ace: "dart", + tabSize: 2, + runtime: "dart", + }; case "bash": case "sh": return { originalLang: lang, rsh: "bash" }; @@ -198,3 +210,4 @@ export function langConstants(lang: MarkdownLang | undefined): LangConstants { return { originalLang: lang }; } } + diff --git a/packages/runtime/tests/fileExecution.ts b/packages/runtime/tests/fileExecution.ts index ee1b9617..88542853 100644 --- a/packages/runtime/tests/fileExecution.ts +++ b/packages/runtime/tests/fileExecution.ts @@ -20,6 +20,7 @@ export const fileExecutionTests: Record< rust: ["test.rs", `fn main() {\n println!("${msg}");\n}\n`], javascript: ["test.js", `console.log("${msg}")`], typescript: ["test.ts", `console.log("${msg}")`], + dart: ["main.dart", `void main() {\n print("${msg}");\n}\n`], } satisfies Record )[lang]; if (!filename || !code) return null; @@ -53,6 +54,10 @@ export const fileExecutionTests: Record< rust: ["test_error.rs", `fn main() {\n panic!("${errorMsg}");\n}\n`], javascript: ["test_error.js", `throw new Error("${errorMsg}");\n`], typescript: ["test_error.ts", `throw new Error("${errorMsg}");\n`], + dart: [ + "test_error.dart", + `void main() {\n throw Exception("${errorMsg}");\n}\n`, + ], } satisfies Record )[lang]; if (!filename || !code) return null; @@ -114,6 +119,7 @@ export const fileExecutionTests: Record< ], javascript: [null, null], typescript: [null, null], + dart: [null, null], } satisfies Record< RuntimeLang, [Record, string[]] | [null, null] @@ -148,6 +154,7 @@ export const fileExecutionTests: Record< rust: [null, null], javascript: [null, null], typescript: [null, null], + dart: [null, null], } satisfies Record )[lang]; if (!filename || !code) return null; diff --git a/packages/runtime/tests/repl.ts b/packages/runtime/tests/repl.ts index e47d5da3..999ff2b4 100644 --- a/packages/runtime/tests/repl.ts +++ b/packages/runtime/tests/repl.ts @@ -19,6 +19,7 @@ export const replTests: Record TestBody | null> = rust: null, javascript: `console.log("${msg}")`, typescript: null, + dart: null, } satisfies Record )[lang]; if (!printCode) return null; @@ -49,6 +50,7 @@ export const replTests: Record TestBody | null> = `console.log(${varName})`, ], typescript: [null, null], + dart: [null, null], } satisfies Record )[lang]; if (!setIntVarCode || !printIntVarCode) return null; @@ -87,6 +89,7 @@ export const replTests: Record TestBody | null> = rust: null, javascript: `throw new Error("${errorMsg}")`, typescript: null, + dart: null, } satisfies Record )[lang]; if (!errorCode) return null; @@ -118,6 +121,7 @@ export const replTests: Record TestBody | null> = `console.log(testVar)`, ], typescript: [null, null, null], + dart: [null, null, null], } satisfies Record )[lang]; if (!setIntVarCode || !infLoopCode || !printIntVarCode) return null; @@ -168,6 +172,7 @@ export const replTests: Record TestBody | null> = rust: null, javascript: null, typescript: null, + dart: null, } satisfies Record )[lang]; if (!writeCode) return null; diff --git a/packages/runtime/tests/utils.ts b/packages/runtime/tests/utils.ts index f9878cb0..15c98d29 100644 --- a/packages/runtime/tests/utils.ts +++ b/packages/runtime/tests/utils.ts @@ -9,6 +9,7 @@ export const RUNTIME_TIMEOUTS: Record = { typescript: 2000, cpp: 10000, rust: 20000, + dart: 15000, }; export async function waitForRuntimeReady( From bab04f9497b0bbac8c9629558c959e7589516920 Mon Sep 17 00:00:00 2001 From: na-trium-144 <100704180+na-trium-144@users.noreply.github.com> Date: Mon, 3 Aug 2026 02:59:41 +0900 Subject: [PATCH 2/6] =?UTF-8?q?DartPad=20=E3=81=AE=E9=9D=99=E7=9A=84?= =?UTF-8?q?=E8=A7=A3=E6=9E=90=20API=E3=81=AE=E5=91=BC=E3=81=B3=E5=87=BA?= =?UTF-8?q?=E3=81=97=E5=87=A6=E7=90=86=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `performAnalysis` の追加 - 解析結果に含まれるエラー・警告・情報(`issues`)を、行・列番号および修正提案(`correction`)を含めたフォーマットで出力コールバック (`onOutput`) に渡します。 --- packages/runtime/src/dart/runtime.tsx | 71 ++++++++++++++++++++++++--- 1 file changed, 64 insertions(+), 7 deletions(-) diff --git a/packages/runtime/src/dart/runtime.tsx b/packages/runtime/src/dart/runtime.tsx index dabf52fa..13698da8 100644 --- a/packages/runtime/src/dart/runtime.tsx +++ b/packages/runtime/src/dart/runtime.tsx @@ -26,6 +26,25 @@ interface DartVersionResponse { flutterVersion?: string; } +interface AnalysisIssue { + kind: "error" | "warning" | "info"; + message: string; + location: { + charStart: number; + charLength: number; + line: number; + column: number; + }; + code?: string; + correction?: string; + url?: string; +} + +interface AnalysisResponse { + issues: AnalysisIssue[]; + imports?: unknown[]; +} + const versionFetcher = async (url: string): Promise => { const res = await fetch(url); if (!res.ok) { @@ -74,6 +93,41 @@ export function DartProvider({ children }: { children: ReactNode }) { ); } +async function performAnalysis( + source: string, + onOutput: (output: ReplOutput | UpdatedFile) => void +): Promise { + try { + const res = await fetch(`${DART_PAD_API_BASE}/analyze`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ source }), + }); + + if (!res.ok) return; + + const data: AnalysisResponse = await res.json(); + if (Array.isArray(data.issues)) { + for (const issue of data.issues) { + const line = issue.location?.line ?? 1; + const column = issue.location?.column ?? 1; + const kindStr = (issue.kind || "info").toUpperCase(); + const correctionStr = issue.correction ? ` (${issue.correction})` : ""; + const formattedMsg = `[ANALYZER ${kindStr}] line ${line}:${column} - ${issue.message}${correctionStr}`; + + onOutput({ + type: issue.kind === "error" ? "error" : "stderr", + message: formattedMsg, + }); + } + } + } catch (err) { + console.warn("Failed to perform Dart static analysis:", err); + } +} + export function useDart(): RuntimeContext { const { init: dartInit, ready, dartVersion } = useContext(DartContext); const onErrorRef = useRef(undefined); @@ -109,13 +163,16 @@ export function useDart(): RuntimeContext { } try { - const response = await fetch(`${DART_PAD_API_BASE}/compileNewDDC`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ source }), - }); + const [_, response] = await Promise.all([ + performAnalysis(source, onOutput), + fetch(`${DART_PAD_API_BASE}/compileNewDDC`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ source }), + }), + ]); if (!response.ok) { const errorText = await response.text(); From 0147fe2f65609b86e7650ed6659a593a2c87aba3 Mon Sep 17 00:00:00 2001 From: na-trium-144 <100704180+na-trium-144@users.noreply.github.com> Date: Mon, 3 Aug 2026 04:32:17 +0900 Subject: [PATCH 3/6] =?UTF-8?q?interrupt=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/runtime/src/dart/runtime.tsx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/runtime/src/dart/runtime.tsx b/packages/runtime/src/dart/runtime.tsx index 13698da8..b94bcdac 100644 --- a/packages/runtime/src/dart/runtime.tsx +++ b/packages/runtime/src/dart/runtime.tsx @@ -131,6 +131,7 @@ async function performAnalysis( export function useDart(): RuntimeContext { const { init: dartInit, ready, dartVersion } = useContext(DartContext); const onErrorRef = useRef(undefined); + const activeIframeRef = useRef(null); const init = useCallback( (onError?: RuntimeErrorHandler) => { @@ -140,6 +141,15 @@ export function useDart(): RuntimeContext { [dartInit] ); + const interrupt = useCallback(() => { + if (activeIframeRef.current) { + if (activeIframeRef.current.parentNode) { + activeIframeRef.current.parentNode.removeChild(activeIframeRef.current); + } + activeIframeRef.current = null; + } + }, []); + const runFiles = useCallback( async ( filenames: string[], @@ -201,12 +211,16 @@ export function useDart(): RuntimeContext { const iframe = document.createElement("iframe"); iframe.style.display = "none"; document.body.appendChild(iframe); + activeIframeRef.current = iframe; let resolved = false; const cleanup = () => { if (resolved) return; resolved = true; window.removeEventListener("message", handleMessage); + if (activeIframeRef.current === iframe) { + activeIframeRef.current = null; + } if (iframe.parentNode) { iframe.parentNode.removeChild(iframe); } @@ -346,6 +360,7 @@ export function useDart(): RuntimeContext { init, ready, runFiles, + interrupt, getCommandlineStr, runtimeInfo, }; From 00f7bf767bf77f62b7646bd1176907af9c151c2f Mon Sep 17 00:00:00 2001 From: na-trium-144 <100704180+na-trium-144@users.noreply.github.com> Date: Fri, 7 Aug 2026 03:50:31 +0900 Subject: [PATCH 4/6] =?UTF-8?q?html=E3=82=92=E5=88=86=E9=9B=A2=E3=80=81req?= =?UTF-8?q?uire.js=E3=81=AB=E3=81=A4=E3=81=84=E3=81=A6=E3=81=AFcdn?= =?UTF-8?q?=E3=82=92=E4=BD=BF=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/runtime/src/dart/dart-runner.html | 130 +++++++++++++++++++++ packages/runtime/src/dart/runtime.tsx | 110 +++-------------- 2 files changed, 144 insertions(+), 96 deletions(-) create mode 100644 packages/runtime/src/dart/dart-runner.html diff --git a/packages/runtime/src/dart/dart-runner.html b/packages/runtime/src/dart/dart-runner.html new file mode 100644 index 00000000..b5d80e01 --- /dev/null +++ b/packages/runtime/src/dart/dart-runner.html @@ -0,0 +1,130 @@ + + + + + + + + + + diff --git a/packages/runtime/src/dart/runtime.tsx b/packages/runtime/src/dart/runtime.tsx index b94bcdac..3db0e63a 100644 --- a/packages/runtime/src/dart/runtime.tsx +++ b/packages/runtime/src/dart/runtime.tsx @@ -17,9 +17,9 @@ import { RuntimeInfo, UpdatedFile, } from "../interface"; +import dartRunnerHtml from "./dart-runner.html?raw"; const DART_PAD_API_BASE = "https://stable.api.dartpad.dev/api/v3"; -const DART_PAD_ARTIFACTS_BASE = "https://stable.api.dartpad.dev/artifacts"; interface DartVersionResponse { dartVersion?: string; @@ -189,8 +189,7 @@ export function useDart(): RuntimeContext { onOutput({ type: "error", message: - errorText || - `Compilation failed with status ${response.status}`, + errorText || `Compilation failed with status ${response.status}`, }); return; } @@ -210,6 +209,7 @@ export function useDart(): RuntimeContext { await new Promise((resolve) => { const iframe = document.createElement("iframe"); iframe.style.display = "none"; + iframe.srcdoc = dartRunnerHtml; document.body.appendChild(iframe); activeIframeRef.current = iframe; @@ -234,11 +234,9 @@ export function useDart(): RuntimeContext { if (msgData.type === "stdout") { onOutput({ type: "stdout", message: String(msgData.message) }); - } else if (msgData.type === "stderr") { - onOutput({ type: "stderr", message: String(msgData.message) }); } else if (msgData.type === "done") { cleanup(); - } else if (msgData.type === "error") { + } else if (msgData.type === "jserr") { onOutput({ type: "error", message: String(msgData.message) }); cleanup(); } @@ -246,96 +244,16 @@ export function useDart(): RuntimeContext { window.addEventListener("message", handleMessage); - const iframeDoc = iframe.contentDocument; - if (!iframeDoc) { - onOutput({ - type: "error", - message: "Failed to access iframe document.", - }); - cleanup(); - return; - } - - const htmlContent = ` - - - - - - - -`; - - iframeDoc.open(); - iframeDoc.write(htmlContent); - iframeDoc.close(); - - setTimeout(() => { - cleanup(); - }, 15000); + // iframeが読み込まれたら、コンパイル済みのコードを送信して実行させる + iframe.onload = () => { + iframe.contentWindow?.postMessage( + { + type: "EXECUTE_DART", + code: jsCode, + }, + "*" + ); + }; }); } catch (error) { onErrorRef.current?.(error); From 6e5b52fb8f33af8a606180bdf356007804cb3f3f Mon Sep 17 00:00:00 2001 From: na-trium-144 <100704180+na-trium-144@users.noreply.github.com> Date: Fri, 14 Aug 2026 05:30:53 +0000 Subject: [PATCH 5/6] docs: add Dart tutorial with internal term links --- packages/runtime/src/dart/runtime.tsx | 2 +- public/docs/dart/0-intro/-intro.md | 5 ++ public/docs/dart/0-intro/1-0-features.md | 34 +++++++ public/docs/dart/0-intro/2-0-flutter.md | 40 +++++++++ public/docs/dart/0-intro/3-0-install.md | 64 +++++++++++++ public/docs/dart/0-intro/4-0-main.md | 57 ++++++++++++ public/docs/dart/1-basics/-intro.md | 4 + public/docs/dart/1-basics/1-0-variables.md | 62 +++++++++++++ .../dart/1-basics/1-1-var-dynamic-object.md | 51 +++++++++++ public/docs/dart/1-basics/1-2-final-const.md | 63 +++++++++++++ .../docs/dart/1-basics/2-0-builtin-types.md | 89 ++++++++++++++++++ public/docs/dart/1-basics/2-1-operators.md | 85 ++++++++++++++++++ public/docs/dart/1-basics/3-0-summary.md | 18 ++++ public/docs/dart/1-basics/3-1-practice1.md | 26 ++++++ public/docs/dart/1-basics/3-2-practice2.md | 26 ++++++ public/docs/dart/10-async-stream/-intro.md | 5 ++ .../10-async-stream/1-0-stream-concepts.md | 51 +++++++++++ .../dart/10-async-stream/2-0-await-for.md | 67 ++++++++++++++ .../10-async-stream/3-0-stream-controller.md | 56 ++++++++++++ .../10-async-stream/4-0-async-generator.md | 51 +++++++++++ .../docs/dart/10-async-stream/5-0-summary.md | 17 ++++ .../dart/10-async-stream/5-1-practice1.md | 28 ++++++ .../dart/10-async-stream/5-2-practice2.md | 28 ++++++ public/docs/dart/11-error-handling/-intro.md | 6 ++ .../dart/11-error-handling/1-0-try-catch.md | 65 ++++++++++++++ .../docs/dart/11-error-handling/2-0-assert.md | 49 ++++++++++ .../11-error-handling/3-0-result-pattern.md | 70 +++++++++++++++ .../dart/11-error-handling/4-0-summary.md | 16 ++++ .../dart/11-error-handling/4-1-practice1.md | 29 ++++++ .../dart/11-error-handling/4-2-practice2.md | 29 ++++++ .../dart/12-concurrency-isolate/-intro.md | 7 ++ .../1-0-single-thread-model.md | 42 +++++++++ .../2-0-isolate-intro.md | 54 +++++++++++ .../3-0-isolate-ports.md | 74 +++++++++++++++ .../12-concurrency-isolate/4-0-summary.md | 22 +++++ .../12-concurrency-isolate/4-1-practice1.md | 29 ++++++ .../12-concurrency-isolate/4-2-practice2.md | 30 +++++++ public/docs/dart/2-null-safety/-intro.md | 6 ++ public/docs/dart/2-null-safety/1-0-types.md | 67 ++++++++++++++ .../dart/2-null-safety/2-0-null-assertion.md | 43 +++++++++ .../2-null-safety/3-0-null-aware-operators.md | 83 +++++++++++++++++ public/docs/dart/2-null-safety/4-0-late.md | 79 ++++++++++++++++ public/docs/dart/2-null-safety/5-0-summary.md | 21 +++++ .../docs/dart/2-null-safety/5-1-practice1.md | 26 ++++++ .../docs/dart/2-null-safety/5-2-practice2.md | 26 ++++++ public/docs/dart/3-functions/-intro.md | 6 ++ .../docs/dart/3-functions/1-0-first-class.md | 78 ++++++++++++++++ .../docs/dart/3-functions/2-0-parameters.md | 40 +++++++++ .../dart/3-functions/2-1-named-positional.md | 69 ++++++++++++++ .../3-functions/3-0-anonymous-closures.md | 79 ++++++++++++++++ public/docs/dart/3-functions/4-0-summary.md | 18 ++++ public/docs/dart/3-functions/4-1-practice1.md | 30 +++++++ public/docs/dart/3-functions/4-2-practice2.md | 27 ++++++ .../docs/dart/4-collections-control/-intro.md | 6 ++ .../4-collections-control/1-0-collections.md | 88 ++++++++++++++++++ .../2-0-collection-features.md | 87 ++++++++++++++++++ .../4-collections-control/3-0-higher-order.md | 76 ++++++++++++++++ .../dart/4-collections-control/4-0-summary.md | 17 ++++ .../4-collections-control/4-1-practice1.md | 32 +++++++ .../4-collections-control/4-2-practice2.md | 34 +++++++ public/docs/dart/5-records-patterns/-intro.md | 6 ++ .../dart/5-records-patterns/1-0-records.md | 69 ++++++++++++++ .../5-records-patterns/2-0-destructuring.md | 63 +++++++++++++ .../3-0-switch-expressions.md | 81 +++++++++++++++++ .../dart/5-records-patterns/4-0-guards.md | 47 ++++++++++ .../dart/5-records-patterns/5-0-summary.md | 17 ++++ .../dart/5-records-patterns/5-1-practice1.md | 27 ++++++ .../dart/5-records-patterns/5-2-practice2.md | 30 +++++++ public/docs/dart/6-classes/-intro.md | 6 ++ public/docs/dart/6-classes/1-0-classes.md | 52 +++++++++++ .../docs/dart/6-classes/2-0-constructors.md | 68 ++++++++++++++ .../dart/6-classes/3-0-initializer-super.md | 76 ++++++++++++++++ .../4-0-encapsulation-getter-setter.md | 71 +++++++++++++++ .../6-classes/5-0-factory-constructors.md | 90 +++++++++++++++++++ public/docs/dart/6-classes/6-0-summary.md | 18 ++++ public/docs/dart/6-classes/6-1-practice1.md | 29 ++++++ public/docs/dart/6-classes/6-2-practice2.md | 29 ++++++ public/docs/dart/7-class-extension/-intro.md | 5 ++ .../1-0-extends-implements.md | 66 ++++++++++++++ .../docs/dart/7-class-extension/2-0-mixins.md | 62 +++++++++++++ .../3-0-extension-methods.md | 56 ++++++++++++ .../7-class-extension/4-0-enhanced-enums.md | 56 ++++++++++++ .../dart/7-class-extension/5-0-summary.md | 17 ++++ .../dart/7-class-extension/5-1-practice1.md | 28 ++++++ .../dart/7-class-extension/5-2-practice2.md | 28 ++++++ public/docs/dart/8-class-modifiers/-intro.md | 5 ++ .../8-class-modifiers/1-0-sealed-classes.md | 68 ++++++++++++++ .../8-class-modifiers/2-0-other-modifiers.md | 61 +++++++++++++ .../8-class-modifiers/3-0-domain-modeling.md | 72 +++++++++++++++ .../dart/8-class-modifiers/4-0-summary.md | 18 ++++ .../dart/8-class-modifiers/4-1-practice1.md | 32 +++++++ .../dart/8-class-modifiers/4-2-practice2.md | 28 ++++++ public/docs/dart/9-async-future/-intro.md | 5 ++ .../dart/9-async-future/1-0-event-loop.md | 44 +++++++++ public/docs/dart/9-async-future/2-0-future.md | 57 ++++++++++++ .../dart/9-async-future/3-0-async-await.md | 75 ++++++++++++++++ .../4-0-async-error-handling.md | 60 +++++++++++++ .../docs/dart/9-async-future/5-0-summary.md | 18 ++++ .../docs/dart/9-async-future/5-1-practice1.md | 29 ++++++ .../docs/dart/9-async-future/5-2-practice2.md | 29 ++++++ public/docs/dart/index.yml | 42 +++++++++ 101 files changed, 4228 insertions(+), 1 deletion(-) create mode 100644 public/docs/dart/0-intro/-intro.md create mode 100644 public/docs/dart/0-intro/1-0-features.md create mode 100644 public/docs/dart/0-intro/2-0-flutter.md create mode 100644 public/docs/dart/0-intro/3-0-install.md create mode 100644 public/docs/dart/0-intro/4-0-main.md create mode 100644 public/docs/dart/1-basics/-intro.md create mode 100644 public/docs/dart/1-basics/1-0-variables.md create mode 100644 public/docs/dart/1-basics/1-1-var-dynamic-object.md create mode 100644 public/docs/dart/1-basics/1-2-final-const.md create mode 100644 public/docs/dart/1-basics/2-0-builtin-types.md create mode 100644 public/docs/dart/1-basics/2-1-operators.md create mode 100644 public/docs/dart/1-basics/3-0-summary.md create mode 100644 public/docs/dart/1-basics/3-1-practice1.md create mode 100644 public/docs/dart/1-basics/3-2-practice2.md create mode 100644 public/docs/dart/10-async-stream/-intro.md create mode 100644 public/docs/dart/10-async-stream/1-0-stream-concepts.md create mode 100644 public/docs/dart/10-async-stream/2-0-await-for.md create mode 100644 public/docs/dart/10-async-stream/3-0-stream-controller.md create mode 100644 public/docs/dart/10-async-stream/4-0-async-generator.md create mode 100644 public/docs/dart/10-async-stream/5-0-summary.md create mode 100644 public/docs/dart/10-async-stream/5-1-practice1.md create mode 100644 public/docs/dart/10-async-stream/5-2-practice2.md create mode 100644 public/docs/dart/11-error-handling/-intro.md create mode 100644 public/docs/dart/11-error-handling/1-0-try-catch.md create mode 100644 public/docs/dart/11-error-handling/2-0-assert.md create mode 100644 public/docs/dart/11-error-handling/3-0-result-pattern.md create mode 100644 public/docs/dart/11-error-handling/4-0-summary.md create mode 100644 public/docs/dart/11-error-handling/4-1-practice1.md create mode 100644 public/docs/dart/11-error-handling/4-2-practice2.md create mode 100644 public/docs/dart/12-concurrency-isolate/-intro.md create mode 100644 public/docs/dart/12-concurrency-isolate/1-0-single-thread-model.md create mode 100644 public/docs/dart/12-concurrency-isolate/2-0-isolate-intro.md create mode 100644 public/docs/dart/12-concurrency-isolate/3-0-isolate-ports.md create mode 100644 public/docs/dart/12-concurrency-isolate/4-0-summary.md create mode 100644 public/docs/dart/12-concurrency-isolate/4-1-practice1.md create mode 100644 public/docs/dart/12-concurrency-isolate/4-2-practice2.md create mode 100644 public/docs/dart/2-null-safety/-intro.md create mode 100644 public/docs/dart/2-null-safety/1-0-types.md create mode 100644 public/docs/dart/2-null-safety/2-0-null-assertion.md create mode 100644 public/docs/dart/2-null-safety/3-0-null-aware-operators.md create mode 100644 public/docs/dart/2-null-safety/4-0-late.md create mode 100644 public/docs/dart/2-null-safety/5-0-summary.md create mode 100644 public/docs/dart/2-null-safety/5-1-practice1.md create mode 100644 public/docs/dart/2-null-safety/5-2-practice2.md create mode 100644 public/docs/dart/3-functions/-intro.md create mode 100644 public/docs/dart/3-functions/1-0-first-class.md create mode 100644 public/docs/dart/3-functions/2-0-parameters.md create mode 100644 public/docs/dart/3-functions/2-1-named-positional.md create mode 100644 public/docs/dart/3-functions/3-0-anonymous-closures.md create mode 100644 public/docs/dart/3-functions/4-0-summary.md create mode 100644 public/docs/dart/3-functions/4-1-practice1.md create mode 100644 public/docs/dart/3-functions/4-2-practice2.md create mode 100644 public/docs/dart/4-collections-control/-intro.md create mode 100644 public/docs/dart/4-collections-control/1-0-collections.md create mode 100644 public/docs/dart/4-collections-control/2-0-collection-features.md create mode 100644 public/docs/dart/4-collections-control/3-0-higher-order.md create mode 100644 public/docs/dart/4-collections-control/4-0-summary.md create mode 100644 public/docs/dart/4-collections-control/4-1-practice1.md create mode 100644 public/docs/dart/4-collections-control/4-2-practice2.md create mode 100644 public/docs/dart/5-records-patterns/-intro.md create mode 100644 public/docs/dart/5-records-patterns/1-0-records.md create mode 100644 public/docs/dart/5-records-patterns/2-0-destructuring.md create mode 100644 public/docs/dart/5-records-patterns/3-0-switch-expressions.md create mode 100644 public/docs/dart/5-records-patterns/4-0-guards.md create mode 100644 public/docs/dart/5-records-patterns/5-0-summary.md create mode 100644 public/docs/dart/5-records-patterns/5-1-practice1.md create mode 100644 public/docs/dart/5-records-patterns/5-2-practice2.md create mode 100644 public/docs/dart/6-classes/-intro.md create mode 100644 public/docs/dart/6-classes/1-0-classes.md create mode 100644 public/docs/dart/6-classes/2-0-constructors.md create mode 100644 public/docs/dart/6-classes/3-0-initializer-super.md create mode 100644 public/docs/dart/6-classes/4-0-encapsulation-getter-setter.md create mode 100644 public/docs/dart/6-classes/5-0-factory-constructors.md create mode 100644 public/docs/dart/6-classes/6-0-summary.md create mode 100644 public/docs/dart/6-classes/6-1-practice1.md create mode 100644 public/docs/dart/6-classes/6-2-practice2.md create mode 100644 public/docs/dart/7-class-extension/-intro.md create mode 100644 public/docs/dart/7-class-extension/1-0-extends-implements.md create mode 100644 public/docs/dart/7-class-extension/2-0-mixins.md create mode 100644 public/docs/dart/7-class-extension/3-0-extension-methods.md create mode 100644 public/docs/dart/7-class-extension/4-0-enhanced-enums.md create mode 100644 public/docs/dart/7-class-extension/5-0-summary.md create mode 100644 public/docs/dart/7-class-extension/5-1-practice1.md create mode 100644 public/docs/dart/7-class-extension/5-2-practice2.md create mode 100644 public/docs/dart/8-class-modifiers/-intro.md create mode 100644 public/docs/dart/8-class-modifiers/1-0-sealed-classes.md create mode 100644 public/docs/dart/8-class-modifiers/2-0-other-modifiers.md create mode 100644 public/docs/dart/8-class-modifiers/3-0-domain-modeling.md create mode 100644 public/docs/dart/8-class-modifiers/4-0-summary.md create mode 100644 public/docs/dart/8-class-modifiers/4-1-practice1.md create mode 100644 public/docs/dart/8-class-modifiers/4-2-practice2.md create mode 100644 public/docs/dart/9-async-future/-intro.md create mode 100644 public/docs/dart/9-async-future/1-0-event-loop.md create mode 100644 public/docs/dart/9-async-future/2-0-future.md create mode 100644 public/docs/dart/9-async-future/3-0-async-await.md create mode 100644 public/docs/dart/9-async-future/4-0-async-error-handling.md create mode 100644 public/docs/dart/9-async-future/5-0-summary.md create mode 100644 public/docs/dart/9-async-future/5-1-practice1.md create mode 100644 public/docs/dart/9-async-future/5-2-practice2.md create mode 100644 public/docs/dart/index.yml diff --git a/packages/runtime/src/dart/runtime.tsx b/packages/runtime/src/dart/runtime.tsx index 3db0e63a..0408ca5f 100644 --- a/packages/runtime/src/dart/runtime.tsx +++ b/packages/runtime/src/dart/runtime.tsx @@ -173,7 +173,7 @@ export function useDart(): RuntimeContext { } try { - const [_, response] = await Promise.all([ + const [, response] = await Promise.all([ performAnalysis(source, onOutput), fetch(`${DART_PAD_API_BASE}/compileNewDDC`, { method: "POST", diff --git a/public/docs/dart/0-intro/-intro.md b/public/docs/dart/0-intro/-intro.md new file mode 100644 index 00000000..4349a8a7 --- /dev/null +++ b/public/docs/dart/0-intro/-intro.md @@ -0,0 +1,5 @@ +他のプログラミング言語の経験がある皆さん、[[Dart]]の世界へようこそ。 + +DartはGoogleによって開発された、クライアントサイド(モバイル・Web・デスクトップ)およびサーバーサイドで幅広く活用されているモダンなオブジェクト指向言語です。特にクロスプラットフォームフレームワークである[[Flutter]]の開発言語として急速に普及しました。 + +この章では、Dartがどのような言語思想で作られ、どのような特徴を持っているかを概観し、Dartプログラムのエントリーポイントとなる [[`main`関数]] の書き方を学びます。 diff --git a/public/docs/dart/0-intro/1-0-features.md b/public/docs/dart/0-intro/1-0-features.md new file mode 100644 index 00000000..beac672d --- /dev/null +++ b/public/docs/dart/0-intro/1-0-features.md @@ -0,0 +1,34 @@ +--- +id: dart-intro-features +title: Dartの特徴(JIT/AOTコンパイル、UI最適化言語) +level: 2 +question: + - JITコンパイルとAOTコンパイルの両方をサポートしているメリットは何ですか? + - なぜDartはUI開発に最適化された言語と言われるのですか? + - JavaScriptやTypeScriptと比べたDartの強みは何ですか? +term: + - Dart + - JITコンパイル + - AOTコンパイル + - JIT + - AOT +--- + +## Dartの特徴(JIT/AOTコンパイル、UI最適化言語) + +[[Dart]]は、美しく高速なユーザーインターフェース(UI)を構築するために設計された、オブジェクト指向の型安全なプログラミング言語です。 + +### 1. JITとAOTのハイブリッドな実行基盤 + +Dart最大の特徴の1つは、**[[JITコンパイル]](Just-In-Time)** と **[[AOTコンパイル]](Ahead-Of-Time)** の両方に対応している点です。 + +* **開発時(JIT)**: ソースコードを実行時に即座に解釈・コンパイルします。これにより、コード変更を瞬時にアプリへ反映する**ホットリロード(Hot Reload)**が可能になり、開発サイクルが劇的に高速化します。 +* **リリース時(AOT)**: 各プラットフォーム(ARMやx86のネイティブ機械語、あるいは最適化されたJavaScript/WebAssembly)へ事前にコンパイルします。起動の速さとなめらかな描画フレームレート(60fps / 120fps)を実現します。 + +### 2. UI構築に最適化された言語機能 + +Dartはユーザーインターフェースのツリー構造を宣言的に記述しやすくするため、言語仕様レベルで多くの工夫が施されています。 + +* **オブジェクト生成時の `new` 省略**: ネストしたUIコンポーネントをスッキリ記述できます。 +* **コレクション要素内の制御構文**: 配列やリストの内部で直接 `if` や `for`、スプレッド演算子(`...`)が使えます([[./4]]で学習)。 +* **健全な型システムと[[Null Safety]]**: 実行時クラッシュの原因となりやすいNull関連エラーをコンパイル時に検知します([[./2]]で学習)。 diff --git a/public/docs/dart/0-intro/2-0-flutter.md b/public/docs/dart/0-intro/2-0-flutter.md new file mode 100644 index 00000000..1ff34125 --- /dev/null +++ b/public/docs/dart/0-intro/2-0-flutter.md @@ -0,0 +1,40 @@ +--- +id: dart-intro-flutter +title: DartとFlutter +level: 2 +question: + - DartとFlutterの関係はどうなっていますか? + - Flutter以外の場所でもDartは使えますか? + - Dart単体でサーバーサイドアプリケーションを書くことはできますか? +term: + - Flutter + - フラッター + - マルチプラットフォーム +--- + +## DartとFlutter + +[[Dart]]の存在を語る上で欠かせないのが、UIフレームワークである **[[Flutter]]** です。 + +Flutterは、単一のコードベースからiOS、Android、Web、macOS、Windows、Linux向けの高性能なアプリケーションをビルドできる[[マルチプラットフォーム]]フレームワークです。 + +``` ++-------------------------------------------------------------+ +| Flutter Framework (Dart製UIライブラリ) | +| (Widgets, Rendering, Animation, Material, Cupertino) | ++-------------------------------------------------------------+ +| Dart Platform (言語・ランタイム・標準ライブラリ) | +| (Core, Async, Collections, Math, I/O, Isolates) | ++-------------------------------------------------------------+ +| Flutter Engine (C++ / Skia・Impeller) | ++-------------------------------------------------------------+ +``` + +### DartがFlutterに選ばれた理由 + +1. **高速な開発体験**: JITによるミリ秒単位のステートフル・ホットリロード。 +2. **高い実行時パフォーマンス**: AOTコンパイルによるネイティブバイナリの生成と、UIの頻繁なオブジェクト生成・破棄に特化した世代別ガベージコレクション。 +3. **宣言的UIとの親和性**: 特別なマークアップ言語(XMLやJSXなど)を必要とせず、Dart言語そのものでUIツリーをきれいに記述可能。 + +> [!NOTE] +> DartはFlutter専用言語ではありません。`dart:io` や `shelf` などのライブラリを用いてCLIツールやバックエンドAPIサーバーの開発にも活用されています。 diff --git a/public/docs/dart/0-intro/3-0-install.md b/public/docs/dart/0-intro/3-0-install.md new file mode 100644 index 00000000..a34f1470 --- /dev/null +++ b/public/docs/dart/0-intro/3-0-install.md @@ -0,0 +1,64 @@ +--- +id: dart-intro-install +title: Dartのインストール +level: 2 +question: + - FlutterをインストールすればDart SDKも一緒に含まれますか? + - Dart公式のパッケージマネージャは何ですか? + - pubspec.yaml とは何をするファイルですか? +term: + - Dart SDK + - dart コマンド + - pub + - pubspec.yaml +--- + +## Dartのインストール + +ローカル環境で[[Dart]]を動作させるには、**[[Dart SDK]]** をインストールします。 + +> [!TIP] +> 既に [[Flutter]] をインストールしている場合、Flutter SDKにDart SDKがバンドルされているため、個別のDartインストールは不要です。 + +### 1. インストール方法 + +* **macOS (Homebrew)**: + ```bash + brew tap dart-lang/dart + brew install dart + ``` +* **Windows (Chocolatey / Scoop / Winget)**: + ```bash + winget install Dart.Dart-SDK + ``` +* **Linux (apt)**: + ```bash + sudo apt-get install dart + ``` + +### 2. インストールの確認 + +ターミナルで `dart` コマンドを実行し、バージョンが表示されるか確認します。 + +```bash +$ dart --version +Dart SDK version: 3.x.x +``` + +### 3. プロジェクトの作成と実行 + +Dartでは、プロジェクト作成や依存関係管理、コード整形、テストなどのツールチェーンがすべて `dart` コマンドに統合されています。 + +```bash +# 新しいコンソールプロジェクトを作成 +$ dart create my_dart_app + +# プロジェクトディレクトリへ移動 +$ cd my_dart_app + +# プログラムの実行 +$ dart run +Hello world: 42! +``` + +Dartのプロジェクト構成やライブラリの依存管理は、プロジェクトルートにある `pubspec.yaml` ファイルで行います。 diff --git a/public/docs/dart/0-intro/4-0-main.md b/public/docs/dart/0-intro/4-0-main.md new file mode 100644 index 00000000..10efad13 --- /dev/null +++ b/public/docs/dart/0-intro/4-0-main.md @@ -0,0 +1,57 @@ +--- +id: dart-intro-main +title: 'エントリーポイント: main() 関数' +level: 2 +question: + - main関数の戻り値型はvoid以外も使えますか? + - コマンドライン引数をmain関数で受け取るにはどうすればよいですか? + - print関数で改行なしの出力を行うことはできますか? +term: + - main関数 + - main() + - print + - print関数 +--- + +## エントリーポイント: `main()` 関数 + +C言語、Java、Rustなどと同様に、[[Dart]]プログラムはトップレベルの **[[main関数]](`main()`)** から実行が始まります。 + +まずは最もシンプルな "Hello, World!" プログラムを見てみましょう。 + +```dart:hello_world.dart +void main() { + print('Hello, Dart!'); +} +``` + +```dart-exec:hello_world.dart +Hello, Dart! +``` + +### コードの解説 + +* `void`: `main` 関数が値を返さないことを表します。 +* `main()`: アプリケーションのエントリーポイントとなる関数名です。 +* `print(...)`: 文字列などのオブジェクトを標準出力に出力し、末尾に改行を追加します。 +* `;` (セミコロン): Dartでは各ステートメントの末尾にセミコロンが必須です。 + +### コマンドライン引数の受け取り + +外部からの引数を受け取る場合は、引数に `List args` を指定します。 + +```dart:args_example.dart +void main(List args) { + if (args.isEmpty) { + print('引数が渡されていません。'); + } else { + print('受け取った引数: $args'); + } +} +``` + +```dart-exec:args_example.dart +引数が渡されていません。 +``` + +次の [[./1]] では、Dartの型システム、変数宣言、および基本的な演算子について詳しく学んでいきます。 diff --git a/public/docs/dart/1-basics/-intro.md b/public/docs/dart/1-basics/-intro.md new file mode 100644 index 00000000..5131fee1 --- /dev/null +++ b/public/docs/dart/1-basics/-intro.md @@ -0,0 +1,4 @@ +この章では、[[Dart]]の基本的な文法と型システムについて学びます。 + +Dartは静的型付け言語ですが、優れた[[型推論]]を備えており、冗長な型記述を避けつつ型安全性を保つことができます。 +また、他の言語とは一味違う `var`, `dynamic`, `Object`, `final`, `const` などのキーワードの使い分けを理解することで、堅牢で読みやすいコードの土台を築きます。 diff --git a/public/docs/dart/1-basics/1-0-variables.md b/public/docs/dart/1-basics/1-0-variables.md new file mode 100644 index 00000000..af04e9a8 --- /dev/null +++ b/public/docs/dart/1-basics/1-0-variables.md @@ -0,0 +1,62 @@ +--- +id: dart-basics-variables +title: 変数宣言と型推論 +level: 2 +question: + - 型を明示する場合とvarを使う場合の使い分けの目安はありますか? + - 初期化せずに変数宣言したときの初期値は何ですか? + - Dartで変数名に使える命名規則はどうなっていますか? +term: + - 変数 + - 変数宣言 + - 型推論 +--- + +## 変数宣言と型推論 + +[[Dart]]は静的型付け言語ですが、初期値から型を自動で推論する **[[型推論]]** を強力にサポートしています。 + +変数を宣言する方法は、主に以下の2通りがあります。 + +### 1. `var` による型推論 + +初期値が存在する場合、`var` キーワードを使うとコンパイラが自動的に型を決定します。 + +```dart:variables_intro.dart +void main() { + var name = 'Dart'; // String 型と推論される + var version = 3; // int 型と推論される + + print('$name のバージョン: $version'); +} +``` + +```dart-exec:variables_intro.dart +Dart のバージョン: 3 +``` + +一度型が推論された変数に、異なる型の値を代入しようとするとコンパイルエラーになります。 + +```dart +var score = 100; +// score = '満点'; // コンパイルエラー: A value of type 'String' can't be assigned to a variable of type 'int'. +``` + +### 2. 型を明示する宣言 + +型を明示的に記述することも可能です。変数の意図をコード上で強調したい場合や、初期値を後から代入する場合に使われます。 + +```dart:type_explicit.dart +void main() { + String message = 'Hello'; + int count = 10; + double price = 99.9; + bool isActive = true; + + print('$message: $count 件, 価格: $price 円, 有効: $isActive'); +} +``` + +```dart-exec:type_explicit.dart +Hello: 10 件, 価格: 99.9 円, 有効: true +``` diff --git a/public/docs/dart/1-basics/1-1-var-dynamic-object.md b/public/docs/dart/1-basics/1-1-var-dynamic-object.md new file mode 100644 index 00000000..010ead1b --- /dev/null +++ b/public/docs/dart/1-basics/1-1-var-dynamic-object.md @@ -0,0 +1,51 @@ +--- +id: dart-basics-var-dynamic-object +title: 'var、dynamic、Object の違い' +level: 3 +question: + - dynamicとObjectの違いは何ですか? + - dynamic型を使うべきシチュエーションはどのようなときですか? + - varで宣言した変数を初期化しなかった場合はどうなりますか? +term: + - var + - dynamic + - Object + - 動的型 +--- + +### `var`、`dynamic`、`Object` の違い + +Dartには一見似ているように思える `var`, `dynamic`, `Object` というキーワードが存在します。これらは型安全性において決定的な違いがあります。 + +| キーワード | 静的型チェック | 別の型の再代入 | メンバーアクセス | +| :--- | :--- | :--- | :--- | +| **`var` (初期化あり)** | あり (初期値から固定) | 不可 | 推論された型のメソッドのみ | +| **`dynamic`** | なし (実行時に解決) | 可能 | なんでも呼べる (存在しなければ実行時エラー) | +| **`Object` / `Object?`** | あり (すべての型の基底) | 可能 | `Object` が持つメソッド (`toString()` 等) のみ | + +```dart:dynamic_vs_object.dart +void main() { + // 1. dynamic: 静的型チェックを完全にバイパスする + dynamic value = 'Hello'; + print('dynamic: ${value.length}'); // 実行可能 + value = 123; // 異なる型の再代入もOK + print('dynamic(int): $value'); + + // 2. Object: すべての非Nullオブジェクトの基底クラス (型安全) + Object obj = 'World'; + // print(obj.length); // コンパイルエラー: Object型には length プロパティがない + if (obj is String) { + // 型チェック (is) を通すとスマートキャストされる + print('Object(String): ${obj.length}'); + } +} +``` + +```dart-exec:dynamic_vs_object.dart +dynamic: 5 +dynamic(int): 123 +Object(String): 5 +``` + +> [!WARNING] +> `dynamic` は実行時までエラーが発覚しないため、JSONのデコードなど型が未知の境界領域以外では極力使用を避け、型安全なコードを心がけましょう。 diff --git a/public/docs/dart/1-basics/1-2-final-const.md b/public/docs/dart/1-basics/1-2-final-const.md new file mode 100644 index 00000000..8a11ec58 --- /dev/null +++ b/public/docs/dart/1-basics/1-2-final-const.md @@ -0,0 +1,63 @@ +--- +id: dart-basics-final-const +title: final と const の使い分け +level: 3 +question: + - finalとconstの最も重要な違いは何ですか? + - DateTime.now()をconstに代入できないのはなぜですか? + - constコンストラクタを使うとFlutterでパフォーマンスが上がるのはなぜですか? +term: + - final + - const + - コンパイル時定数 + - 不変 +--- + +### `final` と `const` の使い分け + +Dartで値の再代入を禁止する変数(定数)を宣言するには、`final` または `const` を使用します。 + +### 1. `final`: 実行時定数(一度だけ代入可能) + +`final` で宣言された変数は、**実行時** に値が決まる定数です。一度代入した後は変更できません。 + +```dart +final currentTime = DateTime.now(); // 実行時の現在時刻を代入可能 +// currentTime = DateTime.now(); // エラー: 再代入不可 +``` + +### 2. `const`: コンパイル時定数 + +`const` は、**コンパイル時** に値が完全に確定している定数です。 + +```dart +const double pi = 3.1415926535; +const int secondsInMinute = 60; +// const currentTime = DateTime.now(); // コンパイルエラー: 実行時にしか決まらない +``` + +### 比較コード例 + +```dart:final_const.dart +void main() { + final now = DateTime.now(); + const maxItems = 100; + + // constリストは内容の変更も不可(ディープイミュータブル) + const list = [1, 2, 3]; + // list.add(4); // 実行時エラー (Unsupported operation) + + print('現在時刻 (final): $now'); + print('最大件数 (const): $maxItems'); + print('定数リスト: $list'); +} +``` + +```dart-exec:final_const.dart +現在時刻 (final): 2026-08-14 05:20:00.000 +最大件数 (const): 100 +定数リスト: [1, 2, 3] +``` + +> [!TIP] +> Flutterでは、不変なWidgetの生成に `const` を付与することで、フレーム再描画時にインスタンスが再生成されるのを防ぎ、メモリ消費とレンダリング負荷を大幅に削減できます。 diff --git a/public/docs/dart/1-basics/2-0-builtin-types.md b/public/docs/dart/1-basics/2-0-builtin-types.md new file mode 100644 index 00000000..c8a49dfe --- /dev/null +++ b/public/docs/dart/1-basics/2-0-builtin-types.md @@ -0,0 +1,89 @@ +--- +id: dart-basics-builtin-types +title: 組み込み型と文字列操作 +level: 2 +question: + - int型とdouble型の共通の親クラスは何ですか? + - Dartで文字列補間(String Interpolation)はどう書きますか? + - 複数行の文字列(ヒアドキュメント)はどう定義しますか? +term: + - 組み込み型 + - int + - double + - num + - String + - bool + - 文字列補間 +--- + +## 組み込み型と文字列操作 + +[[Dart]]のすべての値はオブジェクトであり、数値や真偽値も含めて `Object` を継承しています。 + +主要な組み込み型には以下があります。 + +* **`int`**: 任意精度または64ビット符号付き整数。 +* **`double`**: 64ビット倍精度浮動小数点数。 +* **`num`**: `int` と `double` の親クラス。整数と小数の両方を許容したい場合に使用。 +* **`String`**: UTF-16コードユニットのシーケンス。 +* **`bool`**: 真偽値(`true` または `false`)。 + +### 数値と型変換 + +```dart:numbers.dart +void main() { + int integer = 42; + double decimal = 3.14; + num both = 10; + both = 2.5; // num型ならdoubleも代入可能 + + // 文字列から数値への変換 + int parsedInt = int.parse('100'); + double parsedDouble = double.parse('12.34'); + + // 数値から文字列への変換 + String strInt = integer.toString(); + String fixedDec = decimal.toStringAsFixed(1); // "3.1" + + print('parsed: $parsedInt, $parsedDouble'); + print('converted: $strInt, $fixedDec'); +} +``` + +```dart-exec:numbers.dart +parsed: 100, 12.34 +converted: 42, 3.1 +``` + +### 文字列と文字列補間(String Interpolation) + +Dartでは、文字列リテラル内に `$変数名` や `${式}` を埋め込むことができます。シングルクォート `'` とダブルクォート `"` のどちらでも記述可能です。 + +```dart:strings.dart +void main() { + String language = 'Dart'; + int version = 3; + + // 文字列補間 + String greeting = 'Welcome to $language $version!'; + String calc = '1 + 1 = ${1 + 1}'; + + // 複数行文字列(トリプルクォート) + String multiLine = ''' +1行目のテキスト +2行目のテキスト +3行目のテキスト'''; + + print(greeting); + print(calc); + print(multiLine); +} +``` + +```dart-exec:strings.dart +Welcome to Dart 3! +1 + 1 = 2 +1行目のテキスト +2行目のテキスト +3行目のテキスト +``` diff --git a/public/docs/dart/1-basics/2-1-operators.md b/public/docs/dart/1-basics/2-1-operators.md new file mode 100644 index 00000000..0b2d9efa --- /dev/null +++ b/public/docs/dart/1-basics/2-1-operators.md @@ -0,0 +1,85 @@ +--- +id: dart-basics-operators +title: 基本的な演算子 +level: 2 +question: + - 整数除算を行う演算子は何ですか? + - is や is! 演算子は何のために使われますか? + - 三項演算子やカスケード記法はどのように使いますか? +term: + - 演算子 + - 算術演算子 + - 比較演算子 + - 論理演算子 + - 型テスト演算子 + - 整数除算 +--- + +## 基本的な演算子 + +Dartには一般的な言語と同様の算術・比較・論理演算子に加えて、Dart特有の便利な演算子があります。 + +### 1. 算術演算子と整数除算 (`~/`) + +通常の除算 `/` は常に `double` を返します。商の整数部分のみを取得したい場合は **`~/`**(整数除算演算子)を使います。 + +```dart:operators_arithmetic.dart +void main() { + int a = 10; + int b = 3; + + print('加算 (+): ${a + b}'); + print('通常除算 (/): ${a / b}'); // double (3.3333333333333335) + print('整数除算 (~/): ${a ~/ b}'); // int (3) + print('剰余 (%): ${a % b}'); // int (1) +} +``` + +```dart-exec:operators_arithmetic.dart +加算 (+): 13 +通常除算 (/): 3.3333333333333335 +整数除算 (~/): 3 +剰余 (%): 1 +``` + +### 2. 型テスト演算子 (`is`, `is!`, `as`) + +オブジェクトが特定の型であるかを判定します。 + +* `is`: 指定した型であれば `true` +* `is!`: 指定した型でなければ `true` +* `as`: 型キャスト(型が合わない場合は実行時エラー) + +```dart:type_test.dart +void main() { + Object value = 'Dart Programming'; + + if (value is String) { + // ifスコープ内では value が String にスマートキャストされる + print('文字列の長さ: ${value.length}'); + } + + if (value is! int) { + print('value は int ではありません'); + } +} +``` + +```dart-exec:type_test.dart +文字列の長さ: 16 +value は int ではありません +``` + +### 3. 三項条件演算子 (`condition ? expr1 : expr2`) + +```dart:ternary.dart +void main() { + int score = 85; + String result = score >= 60 ? '合格' : '不合格'; + print('結果: $result'); +} +``` + +```dart-exec:ternary.dart +結果: 合格 +``` diff --git a/public/docs/dart/1-basics/3-0-summary.md b/public/docs/dart/1-basics/3-0-summary.md new file mode 100644 index 00000000..3109410a --- /dev/null +++ b/public/docs/dart/1-basics/3-0-summary.md @@ -0,0 +1,18 @@ +--- +id: dart-basics-summary +title: この章のまとめ +level: 2 +question: [] +--- + +## この章のまとめ + +この章では、[[Dart]]の基本構文と型システムの基本を学びました。 + +* **型推論と変数宣言**: `var` を使うと初期値から静的型が自動推論される。型を明示することも可能。 +* **`dynamic` と `Object`**: `dynamic` は型チェックを無効化し、`Object` は全オブジェクトの基底型として安全に扱える。 +* **`final` と `const`**: `final` は実行時に一度だけ初期化される不変変数、`const` はコンパイル時に確定する完全な定数。 +* **組み込み型**: `int`, `double`, `num`, `String`, `bool` が基本。文字列補間 `${}` が便利。 +* **便利な演算子**: 整数除算 `~/` や型判定 `is` によるスマートキャストを活用する。 + +次の [[./2]] では、Dartを特徴づける最も重要な機能の1つである **[[Null Safety]]** について詳しく学びます。 diff --git a/public/docs/dart/1-basics/3-1-practice1.md b/public/docs/dart/1-basics/3-1-practice1.md new file mode 100644 index 00000000..64021081 --- /dev/null +++ b/public/docs/dart/1-basics/3-1-practice1.md @@ -0,0 +1,26 @@ +--- +id: dart-basics-practice1 +title: '練習問題1: 変数と定数の使い分け' +level: 3 +question: + - constとfinalを間違えて宣言した場合、コンパイラはどのようなエラーを出しますか? + - DateTime.now() の結果をconst変数に代入できない理由を復習したいです。 +--- + +### 練習問題1: 変数と定数の使い分け + +以下の仕様を満たすDartプログラムを作成してください。 + +1. 円周率を保持する定数 `pi` を `const` で定義(値は `3.14159`)。 +2. 半径を表す変数 `radius` を `var` で定義し、初期値 `5.0` を代入。 +3. 実行時の現在時刻を保持する定数 `createdAt` を `final` で定義(`DateTime.now()` を代入)。 +4. 円の面積($\text{radius} \times \text{radius} \times \pi$)を計算し、`createdAt` と共に文字列補間を使って出力する。 + +```dart:practice1_1.dart +void main() { + // ここにコードを書いてください +} +``` + +```dart-exec:practice1_1.dart +``` diff --git a/public/docs/dart/1-basics/3-2-practice2.md b/public/docs/dart/1-basics/3-2-practice2.md new file mode 100644 index 00000000..6e6872d7 --- /dev/null +++ b/public/docs/dart/1-basics/3-2-practice2.md @@ -0,0 +1,26 @@ +--- +id: dart-basics-practice2 +title: '練習問題2: 型変換と計算' +level: 3 +question: + - double.parse() で数値に変換できない文字列を渡すと何が起きますか? + - 整数除算 ~/ と通常の除算 / の使い分けを教えてください。 +--- + +### 練習問題2: 型変換と計算 + +文字列として受け取った価格と数量から、合計金額と一人あたりの支払額を計算するプログラムを作成してください。 + +1. 文字列変数 `priceStr = "1280"` と `quantityStr = "3"` を定義する。 +2. それぞれを `int.parse()` で `int` 型に変換する。 +3. 合計金額 `total = price * quantity` を計算する。 +4. 4人で均等に割り勘した場合の「一人あたりの金額(整数部分)」を `~/` 演算子で求め、「余り(端数)」を `%` 演算子で計算して出力する。 + +```dart:practice1_2.dart +void main() { + // ここにコードを書いてください +} +``` + +```dart-exec:practice1_2.dart +``` diff --git a/public/docs/dart/10-async-stream/-intro.md b/public/docs/dart/10-async-stream/-intro.md new file mode 100644 index 00000000..4dcc82f8 --- /dev/null +++ b/public/docs/dart/10-async-stream/-intro.md @@ -0,0 +1,5 @@ +[[Future]]が「1回だけ値を返す非同期処理」であるのに対し、**[[Stream]]** は「時間の経過に伴って複数回、非同期に連続して届くデータ(イベント列)」を扱います。 + +ユーザーのボタンタップ、センサー値の変動、WebSocketによるリアルタイム通信、ファイルのチャンク読み込みなど、現代のリアクティブなアプリ開発においてStreamは不可欠な概念です。 + +この章では、Streamの購読方法、非同期ジェネレータ(`async*` / `yield`)、および `StreamController` による自作ストリームの制御方法を学びます。 diff --git a/public/docs/dart/10-async-stream/1-0-stream-concepts.md b/public/docs/dart/10-async-stream/1-0-stream-concepts.md new file mode 100644 index 00000000..1c6b85ca --- /dev/null +++ b/public/docs/dart/10-async-stream/1-0-stream-concepts.md @@ -0,0 +1,51 @@ +--- +id: dart-stream-concepts +title: Stream の概念(Push型のデータフロー) +level: 2 +question: + - 単一購読ストリームとブロードキャストストリームの違いは何ですか? + - Streamはどのようにデータを送信側に要求する(または受信する)のですか? + - Streamのリスナー(listen)はどう使いますか? +term: + - Stream + - ストリーム + - 単一購読ストリーム + - ブロードキャストストリーム + - listen +--- + +## `Stream` の概念(Push型のデータフロー) + +**`Stream`** は、非同期に連続して流れてくる一連のデータ(データパイプライン)です。 + +### 1. 単一購読ストリーム(Single-subscription Stream) + +* デフォルトのStream。 +* ライフサイクルの中で**1つのリスナーだけ**が購読できます(例: ファイル読み込み)。 +* 2回以上 `listen()` しようとするとエラーになります。 + +### 2. ブロードキャストストリーム(Broadcast Stream) + +* **複数のリスナー**が同時に購読できます(例: UIのクリックイベント、マウス移動)。 +* `stream.asBroadcastStream()` または `StreamController.broadcast()` で作成します。 + +```dart:stream_listen.dart +void main() { + // 1から3までのデータを順番に流す Stream + final stream = Stream.fromIterable([1, 2, 3]); + + // listen でイベントを購読 + final subscription = stream.listen( + (data) => print('データ受信: $data'), + onError: (error) => print('エラー: $error'), + onDone: () => print('ストリーム終了 (Done)'), + ); +} +``` + +```dart-exec:stream_listen.dart +データ受信: 1 +データ受信: 2 +データ受信: 3 +ストリーム終了 (Done) +``` diff --git a/public/docs/dart/10-async-stream/2-0-await-for.md b/public/docs/dart/10-async-stream/2-0-await-for.md new file mode 100644 index 00000000..18732142 --- /dev/null +++ b/public/docs/dart/10-async-stream/2-0-await-for.md @@ -0,0 +1,67 @@ +--- +id: dart-stream-await-for +title: await for によるStreamの購読 +level: 2 +question: + - await for ループはいつ終了しますか? + - await for の中で break や return を使うとストリームはどうなりますか? + - Streamの高階メソッド(map, where, take)と組み合わせる方法は? +term: + - await for + - Stream購読 + - take +--- + +## `await for` によるStreamの購読 + +`async` 関数内では、**`await for` ループ** を使うことで、ストリームからデータが流れてくるたびに同期的な `for-in` ループのように直感的に処理できます。 + +ストリームが `onDone`(完了)を発行するまでループが継続します。 + +```dart:await_for_demo.dart +Future processStream(Stream stream) async { + print('--- 処理開始 ---'); + await for (final value in stream) { + print('受信値: $value (2倍: ${value * 2})'); + } + print('--- 全データ受信完了 ---'); +} + +void main() async { + // 10, 20, 30 を流すストリーム + final dataStream = Stream.fromIterable([10, 20, 30]); + await processStream(dataStream); +} +``` + +```dart-exec:await_for_demo.dart +--- 処理開始 --- +受信値: 10 (2倍: 20) +受信値: 20 (2倍: 40) +受信値: 30 (2倍: 60) +--- 全データ受信完了 --- +``` + +### Streamの変換オペレータ + +Streamも `List` と同様に `map` や `where`、`take` などのオペレータでパイプライン処理が可能です。 + +```dart:stream_operators.dart +void main() async { + final stream = Stream.fromIterable([1, 2, 3, 4, 5, 6]); + + final filtered = stream + .where((n) => n.isEven) + .map((n) => '偶数: $n'); + + await for (final item in filtered) { + print(item); + } +} +``` + +```dart-exec:stream_operators.dart +偶数: 2 +偶数: 4 +偶数: 6 +``` diff --git a/public/docs/dart/10-async-stream/3-0-stream-controller.md b/public/docs/dart/10-async-stream/3-0-stream-controller.md new file mode 100644 index 00000000..b439c895 --- /dev/null +++ b/public/docs/dart/10-async-stream/3-0-stream-controller.md @@ -0,0 +1,56 @@ +--- +id: dart-stream-controller +title: StreamController と StreamSubscription +level: 2 +question: + - StreamControllerの sink と stream の役割の違いは何ですか? + - StreamSubscription を使って購読を一時停止・再開・解除する方法は? + - StreamController を使い終わった後に close() を呼ぶべき理由は何ですか? +term: + - StreamController + - StreamSubscription + - sink + - close + - cancel +--- + +## `StreamController` と `StreamSubscription` + +### 1. `StreamController`: イベントの送信と管理 + +プログラムの任意の場所からデータを流し込みたい場合、**`StreamController`** を使用します。 + +* `controller.sink.add(value)`: データをストリームへ送信。 +* `controller.sink.addError(error)`: エラーイベントを送信。 +* `controller.close()`: ストリームの終了(完了)を通知(メモリリーク防止のため必須)。 +* `controller.stream`: 購読用の `Stream` オブジェクト。 + +```dart:stream_controller_demo.dart +import 'dart:async'; + +void main() async { + final controller = StreamController(); + + // 購読側の設定 + controller.stream.listen( + (message) => print('通知: $message'), + onDone: () => print('コントローラ停止'), + ); + + // イベントの発行 + controller.sink.add('第1報: サーバー起動'); + controller.sink.add('第2報: クライアント接続'); + + await controller.close(); // ストリームをクローズ +} +``` + +```dart-exec:stream_controller_demo.dart +通知: 第1報: サーバー起動 +通知: 第2報: クライアント接続 +コントローラ停止 +``` + +### 2. `StreamSubscription`: 購読の制御 + +`stream.listen()` の戻り値である `StreamSubscription` オブジェクトを使って、購読の中断・再開や、途中で購読を破棄(`subscription.cancel()`)できます。 diff --git a/public/docs/dart/10-async-stream/4-0-async-generator.md b/public/docs/dart/10-async-stream/4-0-async-generator.md new file mode 100644 index 00000000..4f44768b --- /dev/null +++ b/public/docs/dart/10-async-stream/4-0-async-generator.md @@ -0,0 +1,51 @@ +--- +id: dart-stream-generator +title: async* と yield(非同期ジェネレータ関数) +level: 2 +question: + - async* 関数と通常の async 関数の違いは何ですか? + - yield と yield* の使い分けはどうなりますか? + - 定期的に値を送信するストリームを async* で書く方法は? +term: + - 'async*' + - yield + - yield* + - 非同期ジェネレータ +--- + +## `async*` と `yield`(非同期ジェネレータ関数) + +**非同期ジェネレータ関数(`async*`)** を使用すると、複数の値を時間をかけて順番に生成・配信する `Stream` を手軽に構築できます。 + +* 関数宣言に `async*` を付け、戻り値型を `Stream` にします。 +* **`yield 値`**: データを1つストリームへ送出します。 +* **`yield* 別のStream`**: 別のストリームの全イベントをそのまま中継して送出します。 + +```dart:async_generator.dart +// 1からcountまで1秒おきにカウントアップする非同期ジェネレータ +Stream countStream(int max) async* { + for (int i = 1; i <= max; i++) { + await Future.delayed(const Duration(milliseconds: 50)); + yield i; // データを1件送出 + } +} + +void main() async { + print('カウントダウン開始'); + await for (final number in countStream(3)) { + print('カウント: $number'); + } + print('完了!'); +} +``` + +```dart-exec:async_generator.dart +カウントダウン開始 +カウント: 1 +カウント: 2 +カウント: 3 +完了! +``` + +> [!TIP] +> `StreamController` を手動で用意して `close()` を呼ぶ必要がないため、データの生成フローが明確な場合は `async*` と `yield` を使うのが最も安全でシンプルです。 diff --git a/public/docs/dart/10-async-stream/5-0-summary.md b/public/docs/dart/10-async-stream/5-0-summary.md new file mode 100644 index 00000000..20137ef4 --- /dev/null +++ b/public/docs/dart/10-async-stream/5-0-summary.md @@ -0,0 +1,17 @@ +--- +id: dart-async-stream-summary +title: この章のまとめ +level: 2 +question: [] +--- + +## この章のまとめ + +この章では、[[Dart]]のリアクティブな非同期データフローを支える **[[Stream]]** について学びました。 + +* **Streamの概念**: 時間軸に沿って複数回イベント(データ、エラー、完了)を伝達するパイプライン。 +* **`await for`**: ストリームから流れてくる要素を同期的なループ構文のように直感的に処理できる。 +* **`StreamController`**: `sink` 経由でデータを送信し、任意のタイミングでイベントを発行・クローズできる。 +* **`async*` と `yield`**: 非同期ジェネレータ関数を用いて、シンプルかつクリーンに自作ストリームを生成できる。 + +次の [[./11]] では、アプリケーションの信頼性を向上させる **エラーハンドリングとResultパターン** について学びます。 diff --git a/public/docs/dart/10-async-stream/5-1-practice1.md b/public/docs/dart/10-async-stream/5-1-practice1.md new file mode 100644 index 00000000..821c7ef1 --- /dev/null +++ b/public/docs/dart/10-async-stream/5-1-practice1.md @@ -0,0 +1,28 @@ +--- +id: dart-async-stream-practice1 +title: '練習問題1: async*を使ったカウントダウンストリーム' +level: 3 +question: + - async* 関数の中でループや条件分岐を組み合わせる方法を教えてください。 + - ストリームの途中でエラーを発生させたい場合はどう書きますか? +--- + +### 練習問題1: async*を使ったカウントダウンストリーム + +指定された秒数から0までカウントダウンし、最後に `'発射!'` という文字列を通知するストリームを作成してください。 + +1. `Stream countdown(int from)` を `async*` で定義する。 +2. `from` から `1` までの数値をループし、50ミリ秒待機しながら `'$i...'` を `yield` する。 +3. ループ終了後に `'発射!'` を `yield` する。 +4. `main()` で `await for` を使って `countdown(3)` を購読し、結果を出力する。 + +```dart:practice10_1.dart +// ここに関数を定義してください + +void main() async { + // ここで動作確認を行ってください +} +``` + +```dart-exec:practice10_1.dart +``` diff --git a/public/docs/dart/10-async-stream/5-2-practice2.md b/public/docs/dart/10-async-stream/5-2-practice2.md new file mode 100644 index 00000000..3137a7a5 --- /dev/null +++ b/public/docs/dart/10-async-stream/5-2-practice2.md @@ -0,0 +1,28 @@ +--- +id: dart-async-stream-practice2 +title: '練習問題2: StreamControllerを使ったイベント通知' +level: 3 +question: + - StreamControllerでエラーを流す sink.addError の使い方を教えてください。 + - listen の onError コールバックでエラーを処理する方法を復習したいです。 +--- + +### 練習問題2: StreamControllerを使ったイベント通知 + +タスクの進行状況(進捗率: 0〜100%)を通知する進捗トラッカーを実装してください。 + +1. `StreamController` を作成する。 +2. コントローラのストリームを `listen` し、`'進捗: $percent%'` と出力するリスナーを登録する。 +3. `sink.add` を使って、`25`, `50`, `75`, `100` を順番に送信する。 +4. 送信完了後に `await controller.close()` を呼び出し、ストリームを終了する。 + +```dart:practice10_2.dart +import 'dart:async'; + +void main() async { + // ここにコードを書いてください +} +``` + +```dart-exec:practice10_2.dart +``` diff --git a/public/docs/dart/11-error-handling/-intro.md b/public/docs/dart/11-error-handling/-intro.md new file mode 100644 index 00000000..b09f34f2 --- /dev/null +++ b/public/docs/dart/11-error-handling/-intro.md @@ -0,0 +1,6 @@ +堅牢なアプリケーションを開発するためには、予期せぬ実行時エラーや外部連携の失敗を適切に処理するエラーハンドリング設計が欠かせません。 + +[[Dart]]には、特定の例外型をピンポイントで捕捉する `on` 節やスタックトレースの取得、開発時の事前条件チェックを行う `assert` 文が用意されています。 +さらにDart 3の `sealed` クラスやレコードを活用することで、例外を投げずに型の戻り値として成功・失敗を明示的に表現する **Result型(Resultパターン)** を美しく実装できます。 + +この章では、Dartにおける例外処理の基礎から最新の関数型エラーハンドリング手法までを学びます。 diff --git a/public/docs/dart/11-error-handling/1-0-try-catch.md b/public/docs/dart/11-error-handling/1-0-try-catch.md new file mode 100644 index 00000000..98b4252c --- /dev/null +++ b/public/docs/dart/11-error-handling/1-0-try-catch.md @@ -0,0 +1,65 @@ +--- +id: dart-error-try-catch +title: try、catch、on、finally とカスタム例外 +level: 2 +question: + - Exception と Error の違いは何ですか? + - onキーワードを使って特定の例外だけをキャッチする方法は? + - rethrowキーワードはどのような場合に使われますか? +term: + - try-catch + - Exception + - Error + - on + - rethrow + - スタックトレース +--- + +## `try`、`catch`、`on`、`finally` とカスタム例外 + +[[Dart]]の例外システムでは、`Exception`(プログラムで回復可能なエラー)と `Error`(プログラミングミスなどの重大な欠陥)が区別されています。 + +### 1. `on` による型指定キャッチと `rethrow` + +* **`on 例外型`**: 特定の例外クラスのみを捕捉します。 +* **`catch (e, stackTrace)`**: 例外オブジェクトとスタックトレースを受け取ります。 +* **`rethrow`**: キャッチした例外をそのまま上位の呼び出し元へ再スローします。 + +```dart:exception_handling.dart +// 1. カスタム例外クラスの定義 (implements Exception) +class InsufficientFundsException implements Exception { + final int currentBalance; + final int requestedAmount; + InsufficientFundsException(this.currentBalance, this.requestedAmount); + + @override + String toString() => + '残高不足エラー: 現在残高 $currentBalance 円 に対し、$requestedAmount 円 の引き落とし要求がありました。'; +} + +void processWithdrawal(int balance, int amount) { + if (amount > balance) { + throw InsufficientFundsException(balance, amount); + } + print('引き落とし成功: $amount 円'); +} + +void main() { + try { + processWithdrawal(3000, 5000); + } on InsufficientFundsException catch (e) { + // 特定のカスタム例外を処理 + print('捕捉: $e'); + } catch (e, stack) { + // その他の未知の例外 + print('予期せぬエラー: $e'); + } finally { + print('トランザクション終了'); + } +} +``` + +```dart-exec:exception_handling.dart +捕捉: 残高不足エラー: 現在残高 3000 円 に対し、$5000 円 の引き落とし要求がありました。 +トランザクション終了 +``` diff --git a/public/docs/dart/11-error-handling/2-0-assert.md b/public/docs/dart/11-error-handling/2-0-assert.md new file mode 100644 index 00000000..34bf95c2 --- /dev/null +++ b/public/docs/dart/11-error-handling/2-0-assert.md @@ -0,0 +1,49 @@ +--- +id: dart-error-assert +title: assert による開発時のバグ検知 +level: 2 +question: + - assert 文は本番リリース時(プロダクションビルド)にも実行されますか? + - assert と if-throw の使い分け基準は何ですか? + - Flutterでassertが多用されている理由は何ですか? +term: + - assert + - アサーション + - デバッグ +--- + +## `assert` による開発時のバグ検知 + +**`assert(条件式, 'エラーメッセージ');`** は、開発・デバッグ時(Debug Mode)にのみ実行されるアサーション(前提条件チェック)文です。 + +* 条件が `true` であれば何も起きません。 +* 条件が `false` の場合、`AssertionError` がスローされ、即座に実行が中断します。 +* **リリースビルド(AOTコンパイルや本番モード)では自動的に完全に無視(コードから削除)される**ため、実行時パフォーマンスに一切影響を与えません。 + +```dart:assert_demo.dart +class User { + final String name; + final int age; + + User(this.name, this.age) + : assert(name.isNotEmpty, 'ユーザー名は空にできません'), + assert(age >= 0, '年齢は0歳以上である必要があります'); +} + +void main() { + final validUser = User('Alice', 20); + print('ユーザー作成成功: ${validUser.name}'); + + // デバッグ実行時、条件を満たさないと AssertionError が発生 + // final invalidUser = User('', -5); +} +``` + +```dart-exec:assert_demo.dart +ユーザー作成成功: Alice +``` + +> [!NOTE] +> **使い分けの基準**: +> * `assert`: プログラマ自身の内部的なミスやAPIの不正利用を開発中に防ぐ目的。 +> * `if-throw`(例外スロー): ユーザー入力エラーやネットワーク遮断など、本番環境でも発生し得る外部要因のエラー処理。 diff --git a/public/docs/dart/11-error-handling/3-0-result-pattern.md b/public/docs/dart/11-error-handling/3-0-result-pattern.md new file mode 100644 index 00000000..841c2606 --- /dev/null +++ b/public/docs/dart/11-error-handling/3-0-result-pattern.md @@ -0,0 +1,70 @@ +--- +id: dart-error-result-pattern +title: Result型(戻り値で成功/失敗を表現するパターン) +level: 2 +question: + - なぜ例外をthrowする代わりにResult型を使うアプローチが好まれるのですか? + - sealedクラスを使ってResult型(Success / Failure)を定義する方法は? + - switch式でResult型をハンドリングするメリットは何ですか? +term: + - Result型 + - Resultパターン + - Either + - 成功/失敗 + - 型安全なエラー処理 +--- + +## Result型(戻り値で成功/失敗を表現するパターン) + +例外を `throw` するアプローチは、関数の型シグネチャに「どのような例外が発生し得るか」が現れず、呼び出し側がエラー処理を忘れるリスクがあります。 + +Dart 3の **`sealed` クラス** を使って **[[Result型]](Result Pattern)** を自作すると、関数の戻り値の型として成功(`Success`)または失敗(`Failure`)を明示できます。 + +```dart:result_pattern_demo.dart +// 1. sealed クラスで Result 型を定義 +sealed class Result { + const Result(); +} + +final class Success extends Result { + final T value; + const Success(this.value); +} + +final class Failure extends Result { + final E error; + const Failure(this.error); +} + +// 2. 例外を投げず、Result型を返す安全な除算関数 +Result safeDivide(double a, double b) { + if (b == 0) { + return const Failure('0 で除算することはできません'); + } + return Success(a / b); +} + +void main() { + final results = [ + safeDivide(10, 2), + safeDivide(10, 0), + ]; + + for (final res in results) { + // switch式で網羅的にハンドリング (処理忘れをコンパイル時に防止) + final output = switch (res) { + Success(:var value) => '計算結果: $value', + Failure(:var error) => 'エラー通知: $error', + }; + print(output); + } +} +``` + +```dart-exec:result_pattern_demo.dart +計算結果: 5.0 +エラー通知: 0 で除算することはできません +``` + +> [!TIP] +> Result型を使うことで、呼び出し側は `switch` によるパターンマッチングで結果を取り出すことがコンパイラによって強制され、未処理エラーのバグが根絶されます。 diff --git a/public/docs/dart/11-error-handling/4-0-summary.md b/public/docs/dart/11-error-handling/4-0-summary.md new file mode 100644 index 00000000..8af9a7f9 --- /dev/null +++ b/public/docs/dart/11-error-handling/4-0-summary.md @@ -0,0 +1,16 @@ +--- +id: dart-error-handling-summary +title: この章のまとめ +level: 2 +question: [] +--- + +## この章のまとめ + +この章では、[[Dart]]のエラーハンドリングとモダンな堅牢設計について学びました。 + +* **`try-catch-on-finally`**: `on` による型限定の例外捕捉、スタックトレースの取得、`rethrow` による再スロー。 +* **`assert`**: デバッグ時にのみコードの事前条件を検証し、リリース時にはゼロオーバーヘッドで削除されるバグ検知機構。 +* **Resultパターン**: 例外を投げる代わりに `sealed class Result` で成功・失敗を戻り値型として表現し、コンパイラの網羅性チェックを活用する設計。 + +次の [[./12]] では、Dartチュートリアルの締めくくりとして、**並行処理の仕組みと `Isolate`** について学びます。 diff --git a/public/docs/dart/11-error-handling/4-1-practice1.md b/public/docs/dart/11-error-handling/4-1-practice1.md new file mode 100644 index 00000000..4c64a3e9 --- /dev/null +++ b/public/docs/dart/11-error-handling/4-1-practice1.md @@ -0,0 +1,29 @@ +--- +id: dart-error-handling-practice1 +title: '練習問題1: カスタム例外と適切な例外捕捉' +level: 3 +question: + - 複数の異なるカスタム例外を順番に on 節で捕捉する構文を教えてください。 + - カスタム例外クラスにエラーコードや詳細プロパティを持たせる方法を教えてください。 +--- + +### 練習問題1: カスタム例外と適切な例外捕捉 + +ユーザーのパスワード設定バリデーション関数と、それをテストするコードを作成してください。 + +1. `class WeakPasswordException implements Exception` を定義し、`final String reason;` を持たせる。 +2. `void validatePassword(String password)` 関数を定義する。 + * パスワードの長さが8文字未満の場合、`WeakPasswordException('8文字以上である必要があります')` をスローする。 + * 数字(`0`〜`9`)を含まない場合、`WeakPasswordException('少なくとも1つの数字を含む必要があります')` をスローする。 +3. `main()` でいくつかのテスト用パスワードを渡し、`on WeakPasswordException catch (e)` で捕捉して理由を出力する。 + +```dart:practice11_1.dart +// ここにコードを書いてください + +void main() { + // ここで動作確認を行ってください +} +``` + +```dart-exec:practice11_1.dart +``` diff --git a/public/docs/dart/11-error-handling/4-2-practice2.md b/public/docs/dart/11-error-handling/4-2-practice2.md new file mode 100644 index 00000000..f06c5474 --- /dev/null +++ b/public/docs/dart/11-error-handling/4-2-practice2.md @@ -0,0 +1,29 @@ +--- +id: dart-error-handling-practice2 +title: '練習問題2: sealedクラスによるResult型の実装' +level: 3 +question: + - Result型に map や flatMap のような便利メソッドを生やすことはできますか? + - 非同期処理 Future> と組み合わせるパターンの使いどころを教えてください。 +--- + +### 練習問題2: sealedクラスによるResult型の実装 + +文字列から整数への変換を安全に行う関数 `safeParseInt` を作成してください。 + +1. 本章で学習した `sealed class Result`(`Success` と `Failure`)を定義する。 +2. `Result safeParseInt(String input)` を作成する。 + * `int.tryParse(input)` を使い、変換成功時は `Success(value)` を返す。 + * 失敗時は `Failure('"$input" は有効な整数ではありません')` を返す。 +3. `main()` で `'123'` と `'abc'` を変換し、`switch` 式を使って結果を出力する。 + +```dart:practice11_2.dart +// ここにコードを書いてください + +void main() { + // ここで動作確認を行ってください +} +``` + +```dart-exec:practice11_2.dart +``` diff --git a/public/docs/dart/12-concurrency-isolate/-intro.md b/public/docs/dart/12-concurrency-isolate/-intro.md new file mode 100644 index 00000000..14828989 --- /dev/null +++ b/public/docs/dart/12-concurrency-isolate/-intro.md @@ -0,0 +1,7 @@ +[[Dart]]のコードはデフォルトでシングルスレッドのイベントループ上で実行されます。 +しかし、巨大なJSONの解析、画像の加工、複雑な暗号化や統計計算など、CPUを長時間占有する処理をそのまま実行するとUIがカクついてしまいます。 + +Dartでは、マルチコアCPUの性能をフルに活かして真の並列処理を行うために **[[Isolate]](アイソレート)** という仕組みを提供しています。 +各Isolateは完全に独立したメモリヒープを持っており、スレッド間の共有メモリに起因するデータ競合やデッドロックが原理的に発生しません。 + +この最終章では、Dartの並行処理アーキテクチャ、手軽に使える `Isolate.run()`、そしてポート通信を用いた高度な並行処理を学びます。 diff --git a/public/docs/dart/12-concurrency-isolate/1-0-single-thread-model.md b/public/docs/dart/12-concurrency-isolate/1-0-single-thread-model.md new file mode 100644 index 00000000..d6c80a50 --- /dev/null +++ b/public/docs/dart/12-concurrency-isolate/1-0-single-thread-model.md @@ -0,0 +1,42 @@ +--- +id: dart-concurrency-single-thread +title: Dartのシングルスレッドモデルとメモリ空間 +level: 2 +question: + - なぜDartは共有メモリスレッドではなくIsolateモデルを採用したのですか? + - メモリが分離されていることによるメリットとデメリットは何ですか? + - FutureとIsolateの使い分けの基準は何ですか? +term: + - シングルスレッド + - 並行処理 + - メモリ空間 + - データ競合 +--- + +## Dartのシングルスレッドモデルとメモリ空間 + +JavaやC++、C#などの一般的な言語では、複数のスレッドが同一のメモリ空間(ヒープ領域)を共有して動作します。この方式は高速ですが、**データ競合(Data Race)** や **デッドロック(Deadlock)**、複雑なロック(ミューテックス)管理が必要となり、バグの温床になりがちです。 + +一方、[[Dart]]では **[[Isolate]](隔離されたスレッド)** というモデルを採用しています。 + +``` ++--------------------------------+ +--------------------------------+ +| Main Isolate | | Worker Isolate | +| | | | +| +--------------------------+ | | +--------------------------+ | +| | 独立したメモリヒープ | | | | 独立したメモリヒープ | | +| +--------------------------+ | | +--------------------------+ | +| +--------------------------+ | | +--------------------------+ | +| | 独立したイベントループ | | | | 独立したイベントループ | | +| +--------------------------+ | | +--------------------------+ | ++---------------+----------------+ +----------------+---------------+ + | ^ + | メッセージパッシング (コピー/転送) | + +-----------------------------------------+ +``` + +### Isolateモデルの特徴 + +1. **完全なメモリ分離**: 1つのIsolate内の変数は、他のIsolateから直接読み書きできません。 +2. **ロックフリー**: メモリが共有されないため、ミューテックスやセマフォなどのロック機構が不要です。 +3. **安全なガベージコレクション**: 他のIsolateを停止させることなく、個々のIsolateが独立してガベージコレクション(GC)を実行できます。 diff --git a/public/docs/dart/12-concurrency-isolate/2-0-isolate-intro.md b/public/docs/dart/12-concurrency-isolate/2-0-isolate-intro.md new file mode 100644 index 00000000..7c33d185 --- /dev/null +++ b/public/docs/dart/12-concurrency-isolate/2-0-isolate-intro.md @@ -0,0 +1,54 @@ +--- +id: dart-concurrency-isolate-run +title: Isolate の概念と Isolate.run() による別スレッド処理 +level: 2 +question: + - Isolate.run() はどのような処理に向いていますか? + - Isolate.run() に渡せる関数や引数にどのような制限がありますか? + - compute() 関数(Flutter)と Isolate.run() の関係は何ですか? +term: + - Isolate + - Isolate.run + - アイソレート + - ヘビータスク +--- + +## `Isolate` の概念と `Isolate.run()` による別スレッド処理 + +Dart 2.19以降、単発の重い処理を別スレッドで実行して結果を受け取るための非常に手軽なAPI **`Isolate.run()`** が導入されました。 + +### `Isolate.run()` の使い方 + +`Isolate.run()` に実行したい関数(トップレベル関数、静的メソッド、あるいはクロージャ)を渡すだけで、自動的に新しいIsolateが立ち上がり、処理が完了すると結果を返して自動終了します。 + +```dart:isolate_run_demo.dart +import 'dart:isolate'; + +// 重い計算処理の例(大きな数値の合計) +int heavyCalculation(int count) { + int total = 0; + for (int i = 1; i <= count; i++) { + total += i; + } + return total; +} + +void main() async { + print('1. メイン処理開始'); + + // 別スレッド (Isolate) で重い処理をバックグラウンド実行 + final result = await Isolate.run(() => heavyCalculation(1000000)); + + print('2. 計算完了: $result'); + print('3. メイン処理終了'); +} +``` + +```dart-exec:isolate_run_demo.dart +1. メイン処理開始 +2. 計算完了: 500000500000 +3. メイン処理終了 +``` + +> [!NOTE] +> `Isolate.run()` に渡す引数や戻り値はメッセージとして転送可能なオブジェクト(基本型、コレクション、または特定条件を満たすオブジェクト)である必要があります。 diff --git a/public/docs/dart/12-concurrency-isolate/3-0-isolate-ports.md b/public/docs/dart/12-concurrency-isolate/3-0-isolate-ports.md new file mode 100644 index 00000000..26559205 --- /dev/null +++ b/public/docs/dart/12-concurrency-isolate/3-0-isolate-ports.md @@ -0,0 +1,74 @@ +--- +id: dart-concurrency-isolate-ports +title: ポート(ReceivePort、SendPort)による双方向メッセージ通信 +level: 2 +question: + - 長時間稼働するバックグラウンドワーカーを作るにはどうしますか? + - ReceivePort と SendPort の役割の違いは何ですか? + - Isolate.spawn() を使ったワーカーの起動方法は? +term: + - ReceivePort + - SendPort + - メッセージパッシング + - Isolate.spawn + - 双方向通信 +--- + +## ポート(`ReceivePort`、`SendPort`)による双方向メッセージ通信 + +`Isolate.run()` は単発の処理に適していますが、長時間常駐して継続的にメッセージを送受信するバックグラウンドワーカーを作成する場合は、**`ReceivePort`** と **`SendPort`** による **[[メッセージパッシング]]** を使用します。 + +* **`ReceivePort`**: メッセージの受信口(`Stream` として機能)。 +* **`SendPort`**: メッセージの送信先アドレス。 + +```dart:isolate_ports_demo.dart +import 'dart:isolate'; + +// ワーカースレッドのエントリーポイント +void worker(SendPort mainSendPort) { + // ワーカー側の受信ポートを作成 + final workerReceivePort = ReceivePort(); + + // ワーカーの送信ポートをメインスレッドに知らせる + mainSendPort.send(workerReceivePort.sendPort); + + // メインスレッドからの指示を待機 + workerReceivePort.listen((message) { + if (message is String) { + // 処理結果をメインスレッドに返信 + mainSendPort.send('処理完了: ${message.toUpperCase()}'); + } + }); +} + +void main() async { + // メイン側の受信ポート + final mainReceivePort = ReceivePort(); + + // ワーカーIsolateを起動 + final isolate = await Isolate.spawn(worker, mainReceivePort.sendPort); + + SendPort? workerSendPort; + + // メイン側でメッセージを受信 + mainReceivePort.listen((message) { + if (message is SendPort) { + // ワーカーの送信先を受け取ったら指示を送信 + workerSendPort = message; + workerSendPort?.send('hello from main'); + } else { + print('ワーカーからの返信: $message'); + + // クリーンアップ + mainReceivePort.close(); + isolate.kill(); + print('通信完了・Isolate破棄'); + } + }); +} +``` + +```dart-exec:isolate_ports_demo.dart +ワーカーからの返信: 処理完了: HELLO FROM MAIN +通信完了・Isolate破棄 +``` diff --git a/public/docs/dart/12-concurrency-isolate/4-0-summary.md b/public/docs/dart/12-concurrency-isolate/4-0-summary.md new file mode 100644 index 00000000..2b12a8f9 --- /dev/null +++ b/public/docs/dart/12-concurrency-isolate/4-0-summary.md @@ -0,0 +1,22 @@ +--- +id: dart-concurrency-isolate-summary +title: この章のまとめ +level: 2 +question: [] +--- + +## この章のまとめ + +この章では、[[Dart]]の並行処理とマルチスレッドアーキテクチャについて学びました。 + +* **シングルスレッドとIsolate**: メモリ空間を共有しない独立したスレッドモデルにより、データ競合やロックの複雑さを排除。 +* **`Isolate.run()`**: 単発の重いCPU処理(画像処理、暗号化、データ変換など)を数行で別スレッドにオフロード可能。 +* **ポート通信 (`ReceivePort` / `SendPort`)**: メッセージパッシングによって、常駐型ワーカーIsolateと安全に双方向通信ができる。 + +--- + +### Dartチュートリアル完走おめでとうございます! + +本チュートリアルを通じて、Dartの基本構文からSound Null Safety、関数・クロージャ、コレクション操作、レコードとパターンマッチング、クラス設計、クラス修飾子、非同期処理(Future・Stream)、エラーハンドリング、そして並行処理(Isolate)に至るまで、現代のDart開発に必要な知識を網羅しました。 + +これらの知識は、[[Flutter]]によるアプリ開発はもちろん、Dartを用いたWeb・サーバーサイド開発の強固な基盤となります。ぜひ学んだ知識を活かして、素晴らしいアプリケーションを作ってみてください! diff --git a/public/docs/dart/12-concurrency-isolate/4-1-practice1.md b/public/docs/dart/12-concurrency-isolate/4-1-practice1.md new file mode 100644 index 00000000..610e3ffb --- /dev/null +++ b/public/docs/dart/12-concurrency-isolate/4-1-practice1.md @@ -0,0 +1,29 @@ +--- +id: dart-concurrency-isolate-practice1 +title: '練習問題1: Isolate.run()による重い計算の並行実行' +level: 3 +question: + - Isolate.run() 内で例外が発生した場合、呼び出し側はどう処理すべきですか? + - 引数としてクロージャを渡す場合の変数キャプチャの注意点は何ですか? +--- + +### 練習問題1: Isolate.run()による重い計算の並行実行 + +素数の個数を数える計算を `Isolate.run()` を使って別スレッドで実行してください。 + +1. `bool isPrime(int n)` 関数を作成する(2以上の整数に対し素数判定を行う)。 +2. `int countPrimes(int max)` 関数を作成し、`1` から `max` までの素数の総数をカウントする。 +3. `main()` で `Isolate.run(() => countPrimes(50000))` を呼び出して非同期に結果を待機し、計算された素数の個数を出力する。 + +```dart:practice12_1.dart +import 'dart:isolate'; + +// ここに関数を定義してください + +void main() async { + // ここで動作確認を行ってください +} +``` + +```dart-exec:practice12_1.dart +``` diff --git a/public/docs/dart/12-concurrency-isolate/4-2-practice2.md b/public/docs/dart/12-concurrency-isolate/4-2-practice2.md new file mode 100644 index 00000000..6d3934e4 --- /dev/null +++ b/public/docs/dart/12-concurrency-isolate/4-2-practice2.md @@ -0,0 +1,30 @@ +--- +id: dart-concurrency-isolate-practice2 +title: '練習問題2: ポートを使った双方向ワーカーの作成' +level: 3 +question: + - Isolateでエラーが発生したときにメインスレッドに通知する onError ポートの設定方法は? + - 複数のワーカーをプールして管理する設計のポイントは何ですか? +--- + +### 練習問題2: ポートを使った双方向ワーカーの作成 + +メインスレッドから渡された文字列を逆順にして返信するワーカーIsolateを作成してください。 + +1. `void echoWorker(SendPort mainSendPort)` を作成する。 + * 自身の `ReceivePort` を作成し、その `sendPort` を `mainSendPort` に送信する。 + * 受信したメッセージが `String` であれば、文字列を逆順(`message.split('').reversed.join()`)にして `mainSendPort` に返信する。 +2. `main()` で `Isolate.spawn(echoWorker, mainReceivePort.sendPort)` を起動し、`'Flutter & Dart'` という文字列を送信して逆順になった文字列を受信・出力する。 + +```dart:practice12_2.dart +import 'dart:isolate'; + +// ここに関数を定義してください + +void main() async { + // ここで動作確認を行ってください +} +``` + +```dart-exec:practice12_2.dart +``` diff --git a/public/docs/dart/2-null-safety/-intro.md b/public/docs/dart/2-null-safety/-intro.md new file mode 100644 index 00000000..b5cbe11b --- /dev/null +++ b/public/docs/dart/2-null-safety/-intro.md @@ -0,0 +1,6 @@ +[[Dart]]はバージョン2.12より **健全なNull Safety(Sound Null Safety)** を言語レベルで導入しました。 + +現代のプログラミングにおいて、`NullPointerException`(Null参照エラー)は最も頻発するバグの1つです。 +Dartの健全な[[Null Safety]]は、コードを書いている最中やコンパイル時にNullになり得る箇所を厳密に区別し、実行時クラッシュを未然に防ぎます。 + +この章では、DartのNull Safetyの仕組み、各種Null認識演算子、そして `late` 修飾子の正しい使い方をマスターします。 diff --git a/public/docs/dart/2-null-safety/1-0-types.md b/public/docs/dart/2-null-safety/1-0-types.md new file mode 100644 index 00000000..1b454a23 --- /dev/null +++ b/public/docs/dart/2-null-safety/1-0-types.md @@ -0,0 +1,67 @@ +--- +id: dart-null-safety-types +title: Nullable型(?)とNon-nullable型 +level: 2 +question: + - デフォルトで変数がNull非許容(Non-nullable)であるメリットは何ですか? + - Nullableな変数はどのように宣言しますか? + - if文でnullチェックした後は自動的にNon-nullableとして扱われますか? +term: + - Null Safety + - null safety + - ヌル安全 + - Nullable + - Non-nullable + - null +--- + +## Nullable型(`?`)とNon-nullable型 + +[[Dart]]の型システムでは、すべての型がデフォルトで **[[Non-nullable]](Null非許容)** です。 + +値として `null` を許可したい場合のみ、型の後ろに `?` を付けて **[[Nullable]](Null許容)** として明示的に宣言します。 + +```dart:nullable_basics.dart +void main() { + // 1. Non-nullable型 (デフォルト): null を代入できない + String nonNullableText = 'Hello'; + // nonNullableText = null; // コンパイルエラー! + + // 2. Nullable型 (末尾に ? を付与): null を代入できる + String? nullableText = 'Hello'; + nullableText = null; // OK + + print('nonNullableText: $nonNullableText'); + print('nullableText: $nullableText'); +} +``` + +```dart-exec:nullable_basics.dart +nonNullableText: Hello +nullableText: null +``` + +### 型プロモーション(スマートキャスト) + +Nullableな変数であっても、`if` 文などで `null` でないことをチェック(フロー解析)すると、そのスコープ内では自動的にNon-nullable型へと昇格(プロモート)します。 + +```dart:flow_analysis.dart +void printLength(String? text) { + if (text != null) { + // このブロック内では text は String? ではなく String として扱われる + print('文字数: ${text.length}'); + } else { + print('テキストは null です'); + } +} + +void main() { + printLength('Dart Flutter'); + printLength(null); +} +``` + +```dart-exec:flow_analysis.dart +文字数: 12 +テキストは null です +``` diff --git a/public/docs/dart/2-null-safety/2-0-null-assertion.md b/public/docs/dart/2-null-safety/2-0-null-assertion.md new file mode 100644 index 00000000..80a5c22c --- /dev/null +++ b/public/docs/dart/2-null-safety/2-0-null-assertion.md @@ -0,0 +1,43 @@ +--- +id: dart-null-safety-assertion +title: Nullアサーション(!)とその危険性 +level: 2 +question: + - Nullアサーション演算子 (!) はどのような時に使うべきですか? + - ! を付けた変数が実際に null だった場合何が起こりますか? + - なぜ可能な限り ! を避けるべきなのですか? +term: + - Nullアサーション + - '!演算子' + - 実行時例外 +--- + +## Nullアサーション(`!`)とその危険性 + +**[[Nullアサーション]]演算子(`!`)** は、コンパイラに対して「この値はNullable型だが、実行時には絶対に `null` ではないとプログラマが保証する」と伝える演算子です。 + +```dart:null_assertion.dart +void main() { + String? maybeName = 'Alice'; + + // maybeName は String? 型だが、! を付けることで String 型として扱える + String definiteName = maybeName!; + print('名前: $definiteName'); +} +``` + +```dart-exec:null_assertion.dart +名前: Alice +``` + +### `!` の危険性: 実行時例外の発生 + +もし値が `null` であるにもかかわらず `!` を適用した場合、コンパイルは通過しますが、実行時に `TypeError` / `Null check operator used on a null value` 例外が発生してプログラムが強制終了します。 + +```dart +String? maybeNull; +// String forced = maybeNull!; // 実行時クラッシュ! +``` + +> [!CAUTION] +> `!` 演算子は、型推論やフロー解析が及ばない特定の場面(外部ライブラリとの連携など)を除き、安易に使うべきではありません。基本的には後述する **[[Null認識演算子]]** や明示的な `null` チェックを優先しましょう。 diff --git a/public/docs/dart/2-null-safety/3-0-null-aware-operators.md b/public/docs/dart/2-null-safety/3-0-null-aware-operators.md new file mode 100644 index 00000000..e434cf51 --- /dev/null +++ b/public/docs/dart/2-null-safety/3-0-null-aware-operators.md @@ -0,0 +1,83 @@ +--- +id: dart-null-safety-null-aware-operators +title: 'Null認識演算子(?.、??、??=)' +level: 2 +question: + - ?. 演算子(オプショナルチェーン)の返り値の型は何になりますか? + - ?? 演算子(Null合流演算子)はどう使いますか? + - ??= 演算子はどのような動作をしますか? +term: + - Null認識演算子 + - null認識演算子 + - オプショナルチェーン + - '??' + - '?.' + - '??=' +--- + +## Null認識演算子(`?.`、`??`、`??=`) + +[[Dart]]には、Nullableな値を安全かつ簡潔に処理するための **[[Null認識演算子]](Null-aware operators)** が用意されています。 + +### 1. 条件付きアクセス演算子 (`?.`) + +対象が `null` でなければプロパティやメソッドにアクセスし、`null` であれば `null` を返します。 + +```dart:null_aware_access.dart +void main() { + String? text; + // text が null なので、length にアクセスせず null を返す + int? length = text?.length; + print('length: $length'); + + text = 'Hello'; + length = text?.length; + print('length: $length'); +} +``` + +```dart-exec:null_aware_access.dart +length: null +length: 5 +``` + +### 2. Null合流演算子 (`??`) + +左辺が `null` でない場合は左辺の値を、左辺が `null` の場合は右辺のデフォルト値を返します(いわゆるElvis演算子やNullish coalescing演算子)。 + +```dart:null_coalescing.dart +void main() { + String? userName; + String displayName = userName ?? '名無しのユーザー'; + print('表示名: $displayName'); + + userName = 'Alice'; + displayName = userName ?? '名無しのユーザー'; + print('表示名: $displayName'); +} +``` + +```dart-exec:null_coalescing.dart +表示名: 名無しのユーザー +表示名: Alice +``` + +### 3. Null認識代入演算子 (`??=`) + +変数が `null` の場合のみ、右辺の値を代入します。 + +```dart:null_aware_assignment.dart +void main() { + int? count; + count ??= 10; // count は null だったので 10 が代入される + print('count: $count'); + + count ??= 20; // count は既に 10 なので 20 は代入されない + print('count: $count'); +} +``` + +```dart-exec:null_aware_assignment.dart +count: 10 +count: 10 +``` diff --git a/public/docs/dart/2-null-safety/4-0-late.md b/public/docs/dart/2-null-safety/4-0-late.md new file mode 100644 index 00000000..1405f50f --- /dev/null +++ b/public/docs/dart/2-null-safety/4-0-late.md @@ -0,0 +1,79 @@ +--- +id: dart-null-safety-late +title: late 修飾子の仕組みと使いどころ +level: 2 +question: + - late修飾子を付けると何が変わりますか? + - late変数を初期化前に参照するとどうなりますか? + - lateと遅延初期化(Lazy Initialization)の関係は何ですか? +term: + - late + - 遅延初期化 + - late修飾子 +--- + +## `late` 修飾子の仕組みと使いどころ + +**`late` 修飾子** は、Non-nullableな変数の初期化を「宣言時ではなく、後から(あるいは必要になった時に)行う」ことをコンパイラに宣言するキーワードです。 + +主な用途は以下の2点です。 + +### 1. 宣言時には値を代入できないNon-nullable変数の初期化 + +クラスの初期化メソッドやFlutterのライフサイクル(`initState`など)で後から値を設定する場合に使われます。 + +```dart:late_variables.dart +class UserProfile { + // 宣言時には値がないが、Non-nullableとして扱いたい + late String description; + + void setup() { + description = 'Dart & Flutter開発者'; + } + + void printProfile() { + print(description); + } +} + +void main() { + final profile = UserProfile(); + profile.setup(); + profile.printProfile(); +} +``` + +```dart-exec:late_variables.dart +Dart & Flutter開発者 +``` + +> [!WARNING] +> `late` で宣言した変数を初期化する前にアクセスすると、実行時に `LateInitializationError` がスローされます。 + +### 2. 遅延初期化(Lazy Initialization)によるパフォーマンス向上 + +`late` 変数に初期化式を記述すると、その変数に**初めてアクセスされた瞬間**にのみ計算が行われます。重い初期化処理をオンデマンドで実行したい場合に有効です。 + +```dart:late_lazy.dart +String heavyComputation() { + print('-> 重い計算を実行中...'); + return '計算結果: 42'; +} + +void main() { + print('プログラム開始'); + late String lazyData = heavyComputation(); // この時点ではまだ実行されない + + print('データが必要になりました:'); + print(lazyData); // ここで初めて heavyComputation が呼ばれる + print('二度目の参照: $lazyData'); // 既に計算済みなので再実行はされない +} +``` + +```dart-exec:late_lazy.dart +プログラム開始 +データが必要になりました: +-> 重い計算を実行中... +計算結果: 42 +二度目の参照: 計算結果: 42 +``` diff --git a/public/docs/dart/2-null-safety/5-0-summary.md b/public/docs/dart/2-null-safety/5-0-summary.md new file mode 100644 index 00000000..24703953 --- /dev/null +++ b/public/docs/dart/2-null-safety/5-0-summary.md @@ -0,0 +1,21 @@ +--- +id: dart-null-safety-summary +title: この章のまとめ +level: 2 +question: [] +--- + +## この章のまとめ + +この章では、[[Dart]]の堅牢性を支える **[[Null Safety]]** について学びました。 + +* **デフォルトでNon-nullable**: 通常の型は `null` を代入できない。`null` を許容したい場合は `String?` のように `?` を付ける。 +* **型プロモーション**: `if (x != null)` などのチェックを行うと、自動的にNon-nullable型にプロモートされる。 +* **Nullアサーション (`!`)**: 実行時エラーのリスクがあるため、どうしても必要な場合を除き使用を避ける。 +* **Null認識演算子**: + * `?.`: `null` でない時だけプロパティ/メソッドを呼び出す。 + * `??`: `null` の場合のデフォルト値を指定する。 + * `??=`: 変数が `null` の時だけ代入する。 +* **`late` 修飾子**: Non-nullable変数の初期化を遅延させたり、初回アクセス時の遅延評価(Lazy load)を行う。 + +次の [[./3]] では、Dartにおける関数、名前付き引数、およびクロージャについて学びます。 diff --git a/public/docs/dart/2-null-safety/5-1-practice1.md b/public/docs/dart/2-null-safety/5-1-practice1.md new file mode 100644 index 00000000..435247b6 --- /dev/null +++ b/public/docs/dart/2-null-safety/5-1-practice1.md @@ -0,0 +1,26 @@ +--- +id: dart-null-safety-practice1 +title: '練習問題1: Null安全なデータ処理' +level: 3 +question: + - nullableなオブジェクトから安全に値を取り出すベストプラクティスは何ですか? + - ?? 演算子を複数チェーンさせることはできますか? +--- + +### 練習問題1: Null安全なデータ処理 + +ユーザープロフィールからメールアドレスのドメイン部分を取得し、取得できない場合はデフォルト値を返すプログラムを作成してください。 + +1. `String? email` を定義し、最初は `'user@example.com'` を代入する。 +2. `email` が `null` の場合は `'ドメイン不明'` を返す安全な処理を書く。 + * ヒント: `email?.split('@').last ?? 'ドメイン不明'` +3. 次に `email = null` を代入し、再度同じ処理を実行して `'ドメイン不明'` が出力されることを確認する。 + +```dart:practice2_1.dart +void main() { + // ここにコードを書いてください +} +``` + +```dart-exec:practice2_1.dart +``` diff --git a/public/docs/dart/2-null-safety/5-2-practice2.md b/public/docs/dart/2-null-safety/5-2-practice2.md new file mode 100644 index 00000000..0b503f14 --- /dev/null +++ b/public/docs/dart/2-null-safety/5-2-practice2.md @@ -0,0 +1,26 @@ +--- +id: dart-null-safety-practice2 +title: '練習問題2: lateとNull認識演算子の活用' +level: 3 +question: + - late変数をクラス外のトップレベルで定義した場合も遅延評価されますか? + - ??= 演算子を使ってキャッシュ処理を書く場合の注意点は何ですか? +--- + +### 練習問題2: lateとNull認識演算子の活用 + +設定マップの初期化と、値の取得・更新を行うプログラムを作成してください。 + +1. `late String appTitle` を定義し、初回アクセス時に `'My Dart App'` を生成する初期化式を記述する。 +2. `Map? config` 変数を `null` で定義する。 +3. `??=` 演算子を使って、`config` が `null` の場合に `{'theme': 'dark'}` を代入する。 +4. `appTitle` と `config` の中身を出力する。 + +```dart:practice2_2.dart +void main() { + // ここにコードを書いてください +} +``` + +```dart-exec:practice2_2.dart +``` diff --git a/public/docs/dart/3-functions/-intro.md b/public/docs/dart/3-functions/-intro.md new file mode 100644 index 00000000..69d19635 --- /dev/null +++ b/public/docs/dart/3-functions/-intro.md @@ -0,0 +1,6 @@ +[[Dart]]において、関数は単なる実行コードのまとまりではなく、真の **[[第一級オブジェクト]]** です。 + +関数を変数に代入したり、他の関数の引数として渡したり、戻り値として返したりすることができます。 +また、[[Flutter]]のWidgetツリー構築において必須となる「名前付き引数」や「アロー構文」、状態をカプセル化する「クロージャ」など、Dartの関数表現は非常に洗練されています。 + +この章では、Dartの関数の定義方法、パラメータの柔軟な指定方法、そしてクロージャの仕組みを詳しく学びます。 diff --git a/public/docs/dart/3-functions/1-0-first-class.md b/public/docs/dart/3-functions/1-0-first-class.md new file mode 100644 index 00000000..09ee80a4 --- /dev/null +++ b/public/docs/dart/3-functions/1-0-first-class.md @@ -0,0 +1,78 @@ +--- +id: dart-functions-first-class +title: 第一級オブジェクトとしての関数とアロー構文 +level: 2 +question: + - アロー構文(=>)と通常の関数本体({})の使い分けは何ですか? + - Dartで関数の型(Function型)はどのように表現しますか? + - トップレベル関数とクラスメソッドに関数の扱いの違いはありますか? +term: + - 第一級オブジェクト + - アロー関数 + - '=>' + - 関数型 + - Function +--- + +## 第一級オブジェクトとしての関数とアロー構文 + +[[Dart]]の関数は **[[第一級オブジェクト]]** であり、`Function` 型の値として変数に代入したり、高階関数の引数として渡すことができます。 + +### 1. 関数の基本定義 + +```dart:function_basics.dart +int add(int a, int b) { + return a + b; +} + +void main() { + int result = add(3, 5); + print('3 + 5 = $result'); +} +``` + +```dart-exec:function_basics.dart +3 + 5 = 8 +``` + +### 2. アロー構文(`=>`) + +関数本体が単一の式(Expression)のみで構成される場合、波括弧 `{ return ...; }` の代わりに **`=>`(アロー構文)** を使って簡潔に記述できます。 + +```dart:arrow_syntax.dart +// 通常の関数定義 +int multiplyNormal(int a, int b) { + return a * b; +} + +// アロー構文による定義 +int multiplyArrow(int a, int b) => a * b; + +void main() { + print('multiplyNormal: ${multiplyNormal(4, 5)}'); + print('multiplyArrow: ${multiplyArrow(4, 5)}'); +} +``` + +```dart-exec:arrow_syntax.dart +multiplyNormal: 20 +multiplyArrow: 20 +``` + +### 3. 関数を変数に代入する + +```dart:first_class.dart +void main() { + // 関数を変数に代入 + int Function(int, int) operation = (a, b) => a + b; + print('変数から呼び出し: ${operation(10, 20)}'); + + operation = (a, b) => a * b; + print('乗算に変更: ${operation(10, 20)}'); +} +``` + +```dart-exec:first_class.dart +変数から呼び出し: 30 +乗算に変更: 200 +``` diff --git a/public/docs/dart/3-functions/2-0-parameters.md b/public/docs/dart/3-functions/2-0-parameters.md new file mode 100644 index 00000000..5402b013 --- /dev/null +++ b/public/docs/dart/3-functions/2-0-parameters.md @@ -0,0 +1,40 @@ +--- +id: dart-functions-parameters +title: パラメータ(引数)の種類 +level: 2 +question: + - 位置パラメータと名前付きパラメータの混在は可能ですか? + - 引数のデフォルト値はどのように指定しますか? + - Dartのパラメータ設計のベストプラクティスは何ですか? +term: + - 引数 + - パラメータ + - 位置パラメータ + - デフォルト引数 +--- + +## パラメータ(引数)の種類 + +[[Dart]]の関数のパラメータには、大きく分けて以下の2つの分類があります。 + +1. **必須の位置パラメータ(Required Positional Parameters)**: + 通常の引数。渡す順番と型が固定されます。 +2. **オプショナルパラメータ(Optional Parameters)**: + 省略可能な引数。さらに以下の2種類に分かれます。 + * **[[名前付き引数]](Named Parameters)**: `{}` で囲む。呼び出し時に `name: value` で指定。 + * **[[位置指定引数]](Optional Positional Parameters)**: `[]` で囲む。順番通りに省略可能。 + +```dart:parameter_types.dart +// 1. 必須の位置引数 +void greet(String name, String message) { + print('$nameさん、$message'); +} + +void main() { + greet('Alice', 'こんにちは'); +} +``` + +```dart-exec:parameter_types.dart +Aliceさん、こんにちは +``` diff --git a/public/docs/dart/3-functions/2-1-named-positional.md b/public/docs/dart/3-functions/2-1-named-positional.md new file mode 100644 index 00000000..e19cc71e --- /dev/null +++ b/public/docs/dart/3-functions/2-1-named-positional.md @@ -0,0 +1,69 @@ +--- +id: dart-functions-named-positional +title: '名前付き引数({})と位置指定引数([])' +level: 3 +question: + - Flutterで名前付き引数が多用される理由は何ですか? + - requiredキーワードを付けるとどうなりますか? + - 名前付き引数にデフォルト値を設定する方法を教えてください。 +term: + - 名前付き引数 + - 位置指定引数 + - optional parameter + - required + - デフォルト値 +--- + +### 名前付き引数(`{}`)と位置指定引数(`[]`) + +### 1. 名前付き引数(Named Parameters) + +引数を `{}` で囲むと、呼び出し側で引数名を明示して渡すことができるようになります。引数の順番は自由です。 + +* デフォルトで省略可能(Nullableか、デフォルト値が必要)。 +* **`required`** を付けると、名前付き引数でありながら省略不可(必須)にできます。 + +```dart:named_parameters.dart +// required で必須、デフォルト値付きで省略可能 +void createUser({ + required String username, + String role = 'member', + int? age, +}) { + print('ユーザー: $username, 権限: $role, 年齢: ${age ?? "未設定"}'); +} + +void main() { + // 引数名を指定して呼び出す(順番は自由) + createUser(username: 'Alice'); + createUser(role: 'admin', username: 'Bob', age: 30); +} +``` + +```dart-exec:named_parameters.dart +ユーザー: Alice, 権限: member, 年齢: 未設定 +ユーザー: Bob, 権限: admin, 年齢: 30 +``` + +> [!TIP] +> [[Flutter]]のWidgetコンストラクタはほぼすべて名前付き引数で設計されています。設定項目が多くなってもコードの可読性が損なわれません。 + +### 2. オプショナルな位置指定引数(Optional Positional Parameters) + +引数を `[]` で囲むと、順番通りのオプショナル引数を定義できます。 + +```dart:optional_positional.dart +String formatMessage(String from, String msg, [String? appName = 'MyChat']) { + return '[$appName] $from: $msg'; +} + +void main() { + print(formatMessage('Alice', 'Hello')); + print(formatMessage('Bob', 'Hi', 'FlutterApp')); +} +``` + +```dart-exec:optional_positional.dart +[MyChat] Alice: Hello +[FlutterApp] Bob: Hi +``` diff --git a/public/docs/dart/3-functions/3-0-anonymous-closures.md b/public/docs/dart/3-functions/3-0-anonymous-closures.md new file mode 100644 index 00000000..537ad279 --- /dev/null +++ b/public/docs/dart/3-functions/3-0-anonymous-closures.md @@ -0,0 +1,79 @@ +--- +id: dart-functions-anonymous-closures +title: 無名関数とクロージャ +level: 2 +question: + - 無名関数(ラムダ式)の書き方はどのようになりますか? + - クロージャ(変数のキャプチャ)とは何ですか? + - コレクションのforEachやmapメソッドに渡す無名関数の実例を見たいです。 +term: + - 無名関数 + - 匿名関数 + - ラムダ式 + - クロージャ + - closure + - 変数のキャプチャ +--- + +## 無名関数とクロージャ + +### 1. 無名関数(Anonymous Functions / Lambdas) + +関数名を付けずに定義する関数を **[[無名関数]]** と呼びます。イベントハンドラやコレクションの操作([[./4]]参照)に頻繁に利用されます。 + +```dart:anonymous_functions.dart +void main() { + final fruits = ['apple', 'banana', 'orange']; + + // (item) { ... } という無名関数を forEach に渡す + fruits.forEach((fruit) { + print('フルーツ: $fruit'); + }); + + // アロー構文を用いた無名関数 + fruits.forEach((fruit) => print('UPPER: ${fruit.toUpperCase()}')); +} +``` + +```dart-exec:anonymous_functions.dart +フルーツ: apple +フルーツ: banana +フルーツ: orange +UPPER: APPLE +UPPER: BANANA +UPPER: ORANGE +``` + +### 2. クロージャ(Closures) + +**[[クロージャ]]** とは、関数が定義されたスコープの外側の変数を「キャプチャ(保持)」し、関数が別のスコープで実行されてもその変数にアクセス・変更できる仕組みです。 + +```dart:closures.dart +// カウンター関数を生成する高階関数 +Function makeCounter() { + int count = 0; // クロージャによってキャプチャされるローカル変数 + + return () { + count++; + return count; + }; +} + +void main() { + final counter1 = makeCounter(); + final counter2 = makeCounter(); + + print('counter1: ${counter1()}'); // 1 + print('counter1: ${counter1()}'); // 2 + + print('counter2: ${counter2()}'); // 1 (独立した状態を持つ) + print('counter1: ${counter1()}'); // 3 +} +``` + +```dart-exec:closures.dart +counter1: 1 +counter1: 2 +counter2: 1 +counter1: 3 +``` diff --git a/public/docs/dart/3-functions/4-0-summary.md b/public/docs/dart/3-functions/4-0-summary.md new file mode 100644 index 00000000..0107298b --- /dev/null +++ b/public/docs/dart/3-functions/4-0-summary.md @@ -0,0 +1,18 @@ +--- +id: dart-functions-summary +title: この章のまとめ +level: 2 +question: [] +--- + +## この章のまとめ + +この章では、[[Dart]]の関数システムと表現力について学びました。 + +* **第一級オブジェクト**: 関数は値として変数に代入したり、関数の引数・戻り値として扱える。 +* **アロー構文 (`=>`)**: 単一式を返す関数を簡潔に書ける。 +* **名前付き引数 (`{}`)**: 呼び出し時に引数名を明示でき、`required` で必須指定、デフォルト値設定が可能。 +* **位置指定引数 (`[]`)**: 順番に従って省略可能なオプショナル引数を定義できる。 +* **無名関数とクロージャ**: コールバック関数を即座に記述でき、外側のスコープの変数を安全にカプセル化(キャプチャ)できる。 + +次の [[./4]] では、日常の開発で多用する `List`, `Set`, `Map` などのコレクションと強力なコレクション構文について学びます。 diff --git a/public/docs/dart/3-functions/4-1-practice1.md b/public/docs/dart/3-functions/4-1-practice1.md new file mode 100644 index 00000000..8b4a4cb8 --- /dev/null +++ b/public/docs/dart/3-functions/4-1-practice1.md @@ -0,0 +1,30 @@ +--- +id: dart-functions-practice1 +title: '練習問題1: 名前付き引数を持つ計算関数' +level: 3 +question: + - 名前付き引数でデフォルト値とrequiredを組み合わせることはできますか? + - 消費税計算のような関数を設計する際のベストプラクティスを教えてください。 +--- + +### 練習問題1: 名前付き引数を持つ計算関数 + +商品の税込金額を計算する関数 `calculateTotal` を作成してください。 + +1. 関数 `calculateTotal` は以下の引数を持ちます。 + * `price`: 商品本体価格(`int`, 必須の名前付き引数 `required`) + * `taxRate`: 消費税率(`double`, デフォルト値 `0.10` の名前付き引数) + * `discount`: 割引額(`int`, デフォルト値 `0` の名前付き引数) +2. 計算式: $(\text{price} - \text{discount}) \times (1.0 + \text{taxRate})$ の整数部分(`~/ 1` または `.toInt()`)を返す。 +3. `main()` 関数で、デフォルト税率での計算と、割引・軽減税率(例: `taxRate: 0.08`)を指定した計算を実行して結果を出力する。 + +```dart:practice3_1.dart +// ここに関数を定義してください + +void main() { + // ここで動作確認を行ってください +} +``` + +```dart-exec:practice3_1.dart +``` diff --git a/public/docs/dart/3-functions/4-2-practice2.md b/public/docs/dart/3-functions/4-2-practice2.md new file mode 100644 index 00000000..4092bc80 --- /dev/null +++ b/public/docs/dart/3-functions/4-2-practice2.md @@ -0,0 +1,27 @@ +--- +id: dart-functions-practice2 +title: '練習問題2: カウンタークロージャの作成' +level: 3 +question: + - クロージャでキャプチャされた変数の寿命(ライフタイム)はどうなりますか? + - クロージャを引数として受け取る高階関数の作り方を教えてください。 +--- + +### 練習問題2: カウンタークロージャの作成 + +指定したステップ数ずつ増加するカスタムカウンターを生成する関数 `createStepCounter` を作成してください。 + +1. `int Function() createStepCounter(int step)` を定義する。 +2. 内部で初期値 `0` の変数 `current` を保持し、呼び出されるたびに `step` ずつ加算してその値を返す無名関数を返却する。 +3. `main()` でステップ数 `5` のカウンターを作成し、3回呼び出して `5`, `10`, `15` と出力されることを確認する。 + +```dart:practice3_2.dart +// ここに関数を定義してください + +void main() { + // ここで動作確認を行ってください +} +``` + +```dart-exec:practice3_2.dart +``` diff --git a/public/docs/dart/4-collections-control/-intro.md b/public/docs/dart/4-collections-control/-intro.md new file mode 100644 index 00000000..202976cb --- /dev/null +++ b/public/docs/dart/4-collections-control/-intro.md @@ -0,0 +1,6 @@ +[[Dart]]には標準で強力なコレクション(データ構造)が備わっています。 + +順序付きリスト `List`、重複を許さない集合 `Set`、キーと値のペア `Map` は、いずれも[[ジェネリクス]]によって厳密に型安全です。 +さらにDart独自の特徴として、リストやマップの定義内で直接条件分岐やループを展開できる **コレクション `if` / `for`** や **スプレッド演算子** があり、[[Flutter]]のWidgetツリーを構築する上で不可欠なテクニックとなっています。 + +この章では、コレクションの基礎から高度なデータ操作・変換までを学びます。 diff --git a/public/docs/dart/4-collections-control/1-0-collections.md b/public/docs/dart/4-collections-control/1-0-collections.md new file mode 100644 index 00000000..40fa8625 --- /dev/null +++ b/public/docs/dart/4-collections-control/1-0-collections.md @@ -0,0 +1,88 @@ +--- +id: dart-collections-types +title: List、Set、Map とジェネリクス +level: 2 +question: + - List、Set、Map の使い分けの基準は何ですか? + - コレクションのリテラル記法はどうなっていますか? + - 型推論で要素の型が固定される仕組みを教えてください。 +term: + - List + - Set + - Map + - コレクション + - ジェネリクス +--- + +## `List`、`Set`、`Map` とジェネリクス + +[[Dart]]の代表的なコレクション型には、**`List`**、**`Set`**、**`Map`** があります。すべて[[ジェネリクス]](``)に対応しており、要素の型が厳格にチェックされます。 + +### 1. `List`: 順序付きの配列 + +角括弧 `[]` を使ってリテラルを定義します。 + +```dart:list_basics.dart +void main() { + // 型推論により List になる + var fruits = ['apple', 'banana', 'orange']; + + fruits.add('grape'); + print('要素数: ${fruits.length}'); + print('0番目: ${fruits[0]}'); + print('全要素: $fruits'); +} +``` + +```dart-exec:list_basics.dart +要素数: 4 +0番目: apple +全要素: [apple, banana, orange, grape] +``` + +### 2. `Set`: 重複のない集合 + +波括弧 `{}` を使い、要素のみをカンマ区切りで並べます。 + +```dart:set_basics.dart +void main() { + // Set + var numbers = {1, 2, 3, 2, 1}; + numbers.add(4); + numbers.add(3); // 重複は無視される + + print('Setの要素: $numbers'); + print('2を含むか: ${numbers.contains(2)}'); +} +``` + +```dart-exec:set_basics.dart +Setの要素: {1, 2, 3, 4} +2を含むか: true +``` + +### 3. `Map`: キーと値のペア(連想配列) + +波括弧 `{}` を使い、`key: value` 形式で記述します。 + +```dart:map_basics.dart +void main() { + // Map + var scores = { + 'Alice': 95, + 'Bob': 80, + }; + + scores['Charlie'] = 88; // 要素の追加・更新 + print('Aliceの点数: ${scores['Alice']}'); + print('存在しないキー: ${scores['Dave']}'); // null が返る +} +``` + +```dart-exec:map_basics.dart +Aliceの点数: 95 +存在しないキー: null +``` + +> [!NOTE] +> 空の波括弧 `{}` はデフォルトで `Map` とみなされます。空のSetを作りたい場合は `{}` や `Set()` のように型を明示します。 diff --git a/public/docs/dart/4-collections-control/2-0-collection-features.md b/public/docs/dart/4-collections-control/2-0-collection-features.md new file mode 100644 index 00000000..04082aa5 --- /dev/null +++ b/public/docs/dart/4-collections-control/2-0-collection-features.md @@ -0,0 +1,87 @@ +--- +id: dart-collections-features +title: コレクション if、for とスプレッド演算子 +level: 2 +question: + - コレクション if と三項演算子の違いは何ですか? + - スプレッド演算子 (...) と Null認識スプレッド演算子 (...?) の使い分けは何ですか? + - コレクション for を使うとどのようなコードが簡潔になりますか? +term: + - コレクションif + - コレクションfor + - スプレッド演算子 + - '...' + - '...?' +--- + +## コレクション `if`、`for` とスプレッド演算子 + +[[Dart]]のコレクションリテラル内では、要素の生成ロジックとして `if`、`for`、スプレッド演算子を直接埋め込むことができます。 + +### 1. コレクション `if` + +条件が `true` の場合のみ要素をコレクションに含めます。 + +```dart:collection_if.dart +void main() { + bool isAdmin = true; + bool isGuest = false; + + var navItems = [ + 'ホーム', + 'プロフィール', + if (isAdmin) '管理者パネル', + if (isGuest) 'ログイン案内', + ]; + + print(navItems); +} +``` + +```dart-exec:collection_if.dart +[ホーム, プロフィール, 管理者パネル] +``` + +### 2. コレクション `for` + +ループを使って複数の要素を動的に展開してコレクションを構築します。 + +```dart:collection_for.dart +void main() { + var numbers = [1, 2, 3]; + var stringList = [ + '#0', + for (var n in numbers) '#$n', + ]; + + print(stringList); +} +``` + +```dart-exec:collection_for.dart +[#0, #1, #2, #3] +``` + +### 3. スプレッド演算子 (`...` / `...?`) + +既存のコレクションの全要素を別のコレクション内に展開して埋め込みます。対象が `null` になり得る場合は **`...?`(Null認識スプレッド演算子)** を使用します。 + +```dart:spread_operator.dart +void main() { + var baseList = [1, 2]; + List? extraList; // null + + var combined = [ + 0, + ...baseList, + ...?extraList, // null なので展開を安全にスキップ + 3, + ]; + + print(combined); +} +``` + +```dart-exec:spread_operator.dart +[0, 1, 2, 3] +``` diff --git a/public/docs/dart/4-collections-control/3-0-higher-order.md b/public/docs/dart/4-collections-control/3-0-higher-order.md new file mode 100644 index 00000000..2c745adf --- /dev/null +++ b/public/docs/dart/4-collections-control/3-0-higher-order.md @@ -0,0 +1,76 @@ +--- +id: dart-collections-higher-order +title: '高階関数によるデータ変換(map、where、reduce、fold)' +level: 2 +question: + - Iterable と List の関係は何ですか? + - map や where の結果を List に変換するにはどうすればよいですか? + - reduce と fold の違いは何ですか? +term: + - 高階関数 + - map + - where + - reduce + - fold + - Iterable + - toList +--- + +## 高階関数によるデータ変換(`map`、`where`、`reduce`、`fold`) + +[[Dart]]のコレクション(`Iterable`)には、関数型プログラミングスタイルでデータを操作・集計するための便利な高階メソッドが用意されています。 + +### 1. `where` (フィルタリング) と `map` (要素変換) + +* `where`: 条件を満たす(コールバックが `true` を返す)要素のみを抽出します。 +* `map`: 各要素を変換関数で別の値に写像します。 +* `toList()`: 遅延評価される `Iterable` を `List` に確定します。 + +```dart:map_where.dart +void main() { + final numbers = [1, 2, 3, 4, 5, 6]; + + // 偶数だけを抽出し、それぞれを2乗してListに変換 + final result = numbers + .where((n) => n.isEven) + .map((n) => n * n) + .toList(); + + print('元のリスト: $numbers'); + print('変換後: $result'); +} +``` + +```dart-exec:map_where.dart +元のリスト: [1, 2, 3, 4, 5, 6] +変換後: [4, 16, 36] +``` + +### 2. `reduce` と `fold` (畳み込み集計) + +* `reduce`: リストの先頭要素を初期値として要素を1つの値にまとめます(空リストではエラー)。 +* `fold`: 明示的な初期値を指定して集計を開始します(空リストでも安全、型変換も可能)。 + +```dart:reduce_fold.dart +void main() { + final numbers = [10, 20, 30, 40]; + + // reduce による合計 + final sum = numbers.reduce((acc, curr) => acc + curr); + print('合計 (reduce): $sum'); + + // fold による初期値 100 からの加算 + final totalWithBase = numbers.fold(100, (acc, curr) => acc + curr); + print('ベース値付き合計 (fold): $totalWithBase'); + + // fold で文字列結合 + final joined = numbers.fold('値:', (acc, curr) => '$acc $curr'); + print('文字列 (fold): $joined'); +} +``` + +```dart-exec:reduce_fold.dart +合計 (reduce): 100 +ベース値付き合計 (fold): 200 +文字列 (fold): 値: 10 20 30 40 +``` diff --git a/public/docs/dart/4-collections-control/4-0-summary.md b/public/docs/dart/4-collections-control/4-0-summary.md new file mode 100644 index 00000000..537f3899 --- /dev/null +++ b/public/docs/dart/4-collections-control/4-0-summary.md @@ -0,0 +1,17 @@ +--- +id: dart-collections-summary +title: この章のまとめ +level: 2 +question: [] +--- + +## この章のまとめ + +この章では、[[Dart]]のコレクションと強力な制御構文について学びました。 + +* **3大コレクション**: 順序配列の `List`、一意集合の `Set`、キー値ペアの `Map` を提供。 +* **コレクション `if` / `for`**: リテラル宣言の中で条件分岐やループを展開し、宣言的なリスト生成が可能。 +* **スプレッド演算子 (`...`, `...?`)**: コレクション要素の展開や、Null安全なリスト結合を実現。 +* **高階関数**: `where`(絞り込み)、`map`(変換)、`reduce` / `fold`(集計・畳み込み)をチェーンして簡潔にデータ加工ができる。 + +次の [[./5]] では、Dart 3で導入された目玉機能である **レコード(Records)とパターンマッチング** について学びます。 diff --git a/public/docs/dart/4-collections-control/4-1-practice1.md b/public/docs/dart/4-collections-control/4-1-practice1.md new file mode 100644 index 00000000..9749141d --- /dev/null +++ b/public/docs/dart/4-collections-control/4-1-practice1.md @@ -0,0 +1,32 @@ +--- +id: dart-collections-practice1 +title: '練習問題1: コレクション操作と条件付き要素追加' +level: 3 +question: + - コレクション if の中で else を使う構文はどう書きますか? + - Setから重複を取り除いたListを作成するにはどうすればよいですか? +--- + +### 練習問題1: コレクション操作と条件付き要素追加 + +ユーザー権限と追加オプションに応じたメニュー一覧のリストを生成してください。 + +1. 以下の変数を定義する。 + * `bool isPremium = true;` + * `List? betaFeatures = ['AIアシスタント', '高速検索'];` +2. コレクション `if`、コレクション `for`、スプレッド演算子を活用して、以下の要素を持つ `List menu` を生成する。 + * `'ホーム'` + * `'設定'` + * `isPremium` が `true` の場合のみ `'プレミアム限定動画'` + * `betaFeatures` が `null` でなければその全要素を展開 + * `'ログアウト'` +3. `menu` の中身を出力する。 + +```dart:practice4_1.dart +void main() { + // ここにコードを書いてください +} +``` + +```dart-exec:practice4_1.dart +``` diff --git a/public/docs/dart/4-collections-control/4-2-practice2.md b/public/docs/dart/4-collections-control/4-2-practice2.md new file mode 100644 index 00000000..b58fe903 --- /dev/null +++ b/public/docs/dart/4-collections-control/4-2-practice2.md @@ -0,0 +1,34 @@ +--- +id: dart-collections-practice2 +title: '練習問題2: 高階関数を使った集計処理' +level: 3 +question: + - foldメソッドの初期値の型と戻り値の型の関係を教えてください。 + - メソッドチェーンで可読性を保つインデントの書き方を教えてください。 +--- + +### 練習問題2: 高階関数を使った集計処理 + +商品データのリストから条件に合う商品を抽出し、合計金額を算出するプログラムを作成してください。 + +1. 以下の商品リスト(`Map` のリスト)を用意する。 + ```dart + final items = [ + {'name': 'ノートPC', 'price': 120000, 'inStock': true}, + {'name': 'マウス', 'price': 3000, 'inStock': false}, + {'name': 'キーボード', 'price': 15000, 'inStock': true}, + {'name': 'モニター', 'price': 45000, 'inStock': true}, + ]; + ``` +2. `where` を使って在庫がある(`inStock == true`)商品のみに絞り込む。 +3. `map` で各商品の価格(`price` as `int`)を取り出す。 +4. `fold` を使って価格の総額を計算し、`'在庫あり商品の合計金額: xxx 円'` と出力する。 + +```dart:practice4_2.dart +void main() { + // ここにコードを書いてください +} +``` + +```dart-exec:practice4_2.dart +``` diff --git a/public/docs/dart/5-records-patterns/-intro.md b/public/docs/dart/5-records-patterns/-intro.md new file mode 100644 index 00000000..28667857 --- /dev/null +++ b/public/docs/dart/5-records-patterns/-intro.md @@ -0,0 +1,6 @@ +[[Dart]] 3.0で導入された最大の機能刷新が、**[[レコード]](Records)** と **[[パターンマッチング]](Pattern Matching)** です。 + +従来のオブジェクト指向言語では、複数の値をまとめて返したい場合に一時的なデータクラスを定義する必要がありました。 +Dart 3では、型安全かつ軽量なレコード型や、データの形状と値に応じて直感的に分岐・分解できるパターンマッチング、そして式として評価できる `switch` 式が利用可能になりました。 + +この章では、現代のDart開発においてコードを劇的に簡潔にするこれらの最新機能をマスターします。 diff --git a/public/docs/dart/5-records-patterns/1-0-records.md b/public/docs/dart/5-records-patterns/1-0-records.md new file mode 100644 index 00000000..f411179d --- /dev/null +++ b/public/docs/dart/5-records-patterns/1-0-records.md @@ -0,0 +1,69 @@ +--- +id: dart-records-intro +title: レコード(Records)による複数戻り値の実現 +level: 2 +question: + - レコードとクラス(Class)の違いは何ですか? + - 位置指定フィールドと名前付きフィールドを持つレコードはどう書きますか? + - レコードのフィールド値を取得するための構文($1, $2, フィールド名)を教えてください。 +term: + - レコード + - Records + - record + - タプル +--- + +## レコード(Records)による複数戻り値の実現 + +**[[レコード]](Records)** は、複数の値を1つにまとめることができる匿名かつ不変(イミュータブル)な集約型(いわゆるタプル)です。 + +専用のクラスを定義することなく、関数から複数の値を型安全に返すことができます。 + +### 1. 位置指定フィールド(Positional Fields) + +丸括弧 `()` で値を囲むことでレコードを作成します。各フィールドには `$1`, `$2` でアクセスします。 + +```dart:positional_records.dart +// (String, int) 型のレコードを返す関数 +(String, int) getUserInfo() { + return ('Alice', 25); +} + +void main() { + final user = getUserInfo(); + print('名前 (\$1): ${user.$1}'); + print('年齢 (\$2): ${user.$2}'); +} +``` + +```dart-exec:positional_records.dart +名前 ($1): Alice +年齢 ($2): 25 +``` + +### 2. 名前付きフィールド(Named Fields) + +波括弧 `{}` を使うことで、フィールドに名前を付けることができます。 + +```dart:named_records.dart +// 名前付きフィールドを持つレコード型 +({String name, int age, bool isAdmin}) getDetailedUser() { + return (name: 'Bob', age: 30, isAdmin: true); +} + +void main() { + final user = getDetailedUser(); + print('名前: ${user.name}'); + print('年齢: ${user.age}'); + print('管理者: ${user.isAdmin}'); +} +``` + +```dart-exec:named_records.dart +名前: Bob +年齢: 30 +管理者: true +``` + +> [!NOTE] +> レコードは値の同一性(Equality)を持ちます。同じフィールドと値を持つ2つのレコードは `==` で比較した際に `true` になります。 diff --git a/public/docs/dart/5-records-patterns/2-0-destructuring.md b/public/docs/dart/5-records-patterns/2-0-destructuring.md new file mode 100644 index 00000000..b1e9e729 --- /dev/null +++ b/public/docs/dart/5-records-patterns/2-0-destructuring.md @@ -0,0 +1,63 @@ +--- +id: dart-patterns-destructuring +title: 分解(Destructuring)によるデータの抽出 +level: 2 +question: + - レコードやListから直接変数に分解代入する方法はどう書きますか? + - 分解時に一部の値を無視するにはどうすればよいですか? + - Mapの分解代入はどのように行いますか? +term: + - パターン分解 + - 分解代入 + - Destructuring + - ワイルドカード +--- + +## 分解(Destructuring)によるデータの抽出 + +Dart 3のパターン構文を使用すると、レコード、List、Mapなどの複合データ構造を宣言的に分解(**[[分解代入]]**)して個別のローカル変数に抽出できます。 + +### 1. レコードの分解 + +```dart:destructure_records.dart +(String, int) getCoords() => ('Tokyo', 100); +({int x, int y}) getPoint() => (x: 10, y: 20); + +void main() { + // 位置レコードの分解 + final (city, population) = getCoords(); + print('都市: $city, 人口: $population 万人'); + + // 名前付きレコードの分解 (フィールド名と同名の変数にバインド) + final (:x, :y) = getPoint(); + print('座標: x=$x, y=$y'); +} +``` + +```dart-exec:destructure_records.dart +都市: Tokyo, 人口: 100 万人 +座標: x=10, y=20 +``` + +### 2. List と Map の分解 + +リストの要素数や中身に一致するパターンを使って値を抽出できます。不要な要素は `_`(ワイルドカード)で無視できます。 + +```dart:destructure_collections.dart +void main() { + // List の分解 + final numbers = [1, 2, 3, 4]; + final [first, second, _, fourth] = numbers; + print('1番目: $first, 2番目: $second, 4番目: $fourth'); + + // Map の分解 + final json = {'id': 'user_123', 'status': 'active'}; + final {'id': String userId, 'status': String userStatus} = json; + print('ユーザーID: $userId, ステータス: $userStatus'); +} +``` + +```dart-exec:destructure_collections.dart +1番目: 1, 2番目: 2, 4番目: 4 +ユーザーID: user_123, ステータス: active +``` diff --git a/public/docs/dart/5-records-patterns/3-0-switch-expressions.md b/public/docs/dart/5-records-patterns/3-0-switch-expressions.md new file mode 100644 index 00000000..ca17b36b --- /dev/null +++ b/public/docs/dart/5-records-patterns/3-0-switch-expressions.md @@ -0,0 +1,81 @@ +--- +id: dart-patterns-switch +title: パターンマッチングと switch 式 +level: 2 +question: + - switch文とswitch式の違いは何ですか? + - switch式での網羅性チェック(Exhaustiveness checking)とは何ですか? + - switch式の中でパターンマッチングを使って型判定と値の取り出しを同時に行う方法は? +term: + - パターンマッチング + - switch式 + - 網羅性チェック + - switch文 +--- + +## パターンマッチングと `switch` 式 + +従来の `switch` 文(Statement)に加え、Dart 3では評価結果の値を返す **`switch` 式(Expression)** が導入されました。 + +### 1. `switch` 式の基本構文 + +* `case` や `break` キーワードが不要になり、`パターン => 式` の簡潔な構文になります。 +* すべてのケースが網羅されているかコンパイラが厳密に検証する **[[網羅性チェック]]** が働きます。 + +```dart:switch_expression.dart +String describeHttpCode(int statusCode) { + return switch (statusCode) { + 200 => '成功 (OK)', + 400 => '不正なリクエスト (Bad Request)', + 404 => '未検出 (Not Found)', + 500 => 'サーバーエラー (Internal Server Error)', + _ => '不明なステータスコード ($statusCode)', // デフォルトケース + }; +} + +void main() { + print(describeHttpCode(200)); + print(describeHttpCode(404)); + print(describeHttpCode(418)); +} +``` + +```dart-exec:switch_expression.dart +成功 (OK) +未検出 (Not Found) +不明なステータスコード (418) +``` + +### 2. オブジェクトやコレクションのパターンマッチング + +`switch` 式の中でデータの形状を検証しながら変数を取り出すことができます。 + +```dart:pattern_matching_complex.dart +String formatData(dynamic data) { + return switch (data) { + // 整数で 0 の場合 + 0 => 'ゼロ', + // 正の整数の場合 (関係演算子パターン) + int n && > 0 => '正の整数: $n', + // 2要素のリストの場合 + [var a, var b] => '2要素リスト: ($a, $b)', + // 特定のキーを持つマップの場合 + {'name': String name, 'age': int age} => '名前: $name, 年齢: $age', + _ => 'その他のデータ', + }; +} + +void main() { + print(formatData(10)); + print(formatData(['apple', 'banana'])); + print(formatData({'name': 'Alice', 'age': 20})); + print(formatData(false)); +} +``` + +```dart-exec:pattern_matching_complex.dart +正の整数: 10 +2要素リスト: (apple, banana) +名前: Alice, 年齢: 20 +その他のデータ +``` diff --git a/public/docs/dart/5-records-patterns/4-0-guards.md b/public/docs/dart/5-records-patterns/4-0-guards.md new file mode 100644 index 00000000..0dbdda79 --- /dev/null +++ b/public/docs/dart/5-records-patterns/4-0-guards.md @@ -0,0 +1,47 @@ +--- +id: dart-patterns-guards +title: Guard句(when)を使った条件分岐 +level: 2 +question: + - when句(ガード節)はパターンマッチのどの位置に記述しますか? + - when句の条件が満たされなかった場合、処理はどう流れますか? + - 複雑なビジネスロジックでwhen句を活用する具体例を見たいです。 +term: + - Guard句 + - when + - ガード節 +--- + +## Guard句(`when`)を使った条件分岐 + +パターンマッチングに **`when` 節(Guard句)** を組み合わせることで、パターンの形状が一致した上でさらに任意のブール条件を課すことができます。 + +条件が `false` であれば次のマッチング候補へとフォールスルー(移動)します。 + +```dart:guards_example.dart +String evaluateScore((String, int) student) { + return switch (student) { + (var name, var score) when score == 100 => '$nameさん: 満点!素晴らしい!', + (var name, var score) when score >= 80 => '$nameさん: 優秀です (点数: $score)', + (var name, var score) when score >= 60 => '$nameさん: 合格 (点数: $score)', + (var name, var score) => '$nameさん: 要再試験 (点数: $score)', + }; +} + +void main() { + print(evaluateScore(('Alice', 100))); + print(evaluateScore(('Bob', 85))); + print(evaluateScore(('Charlie', 62))); + print(evaluateScore(('Dave', 45))); +} +``` + +```dart-exec:guards_example.dart +Aliceさん: 満点!素晴らしい! +Bobさん: 優秀です (点数: 85) +Charlieさん: 合格 (点数: 62) +Daveさん: 要再試験 (点数: 45) +``` + +> [!TIP] +> `when` 句を活用することで、ネストした `if-else` 文を平坦で美しい宣言的パターンに置き換えることができます。 diff --git a/public/docs/dart/5-records-patterns/5-0-summary.md b/public/docs/dart/5-records-patterns/5-0-summary.md new file mode 100644 index 00000000..cd8e0a61 --- /dev/null +++ b/public/docs/dart/5-records-patterns/5-0-summary.md @@ -0,0 +1,17 @@ +--- +id: dart-records-patterns-summary +title: この章のまとめ +level: 2 +question: [] +--- + +## この章のまとめ + +この章では、[[Dart]] 3で導入された **[[レコード]]** と **[[パターンマッチング]]** について学びました。 + +* **レコード(Records)**: クラスを自作することなく、複数の値を型安全に返却・グループ化できる軽量なデータ構造。位置指定・名前付きフィールドに対応。 +* **分解(Destructuring)**: レコード、List、Mapなどの複合データから必要な変数だけを一度に抽出できる。 +* **`switch` 式**: 式として評価され、コンパイラによる網羅性チェックが働く安全でコンパクトな条件分岐。 +* **Guard句 (`when`)**: パターンの形状一致に加えて追加のブール条件をスマートに記述できる。 + +次の [[./6]] では、オブジェクト指向プログラミングの中核である **クラスと各種コンストラクタ** について学びます。 diff --git a/public/docs/dart/5-records-patterns/5-1-practice1.md b/public/docs/dart/5-records-patterns/5-1-practice1.md new file mode 100644 index 00000000..abf6be6e --- /dev/null +++ b/public/docs/dart/5-records-patterns/5-1-practice1.md @@ -0,0 +1,27 @@ +--- +id: dart-records-patterns-practice1 +title: '練習問題1: レコードを使った座標計算' +level: 3 +question: + - 名前付きフィールドを持つレコードの型注釈はどう書きますか? + - レコードの分解代入時に型を明示することはできますか? +--- + +### 練習問題1: レコードを使った座標計算 + +2点間のマンハッタン距離と中点の座標を同時に計算して返す関数を作成してください。 + +1. 2次元座標を表すレコード型 `({int x, int y})` を引数として2つ受け取る関数 `analyzePoints` を定義する。 +2. 戻り値として、マンハッタン距離 `distance`($|x_1 - x_2| + |y_1 - y_2|$)と中点 `midpoint`(レコード `(double x, double y)`)を含む名前付きレコード `({int distance, (double, double) midpoint})` を返す。 +3. `main()` で `p1 = (x: 0, y: 0)` と `p2 = (x: 4, y: 6)` を渡して呼び出し、分解代入で結果を受け取って出力する。 + +```dart:practice5_1.dart +// ここに関数を定義してください + +void main() { + // ここで動作確認を行ってください +} +``` + +```dart-exec:practice5_1.dart +``` diff --git a/public/docs/dart/5-records-patterns/5-2-practice2.md b/public/docs/dart/5-records-patterns/5-2-practice2.md new file mode 100644 index 00000000..44b2b1cf --- /dev/null +++ b/public/docs/dart/5-records-patterns/5-2-practice2.md @@ -0,0 +1,30 @@ +--- +id: dart-records-patterns-practice2 +title: '練習問題2: switch式とパターンマッチによるコマンドパーサー' +level: 3 +question: + - switch式でListの要素数をチェックするパターンの書き方を教えてください。 + - when句を使って引数の値のバリデーションを行う例を教えてください。 +--- + +### 練習問題2: switch式とパターンマッチによるコマンドパーサー + +CLIツールに入力された文字列コマンド(引数リスト `List`)を解析する関数 `handleCommand` を作成してください。 + +1. `handleCommand(List command)` を定義し、`switch` 式を使って以下のパターンを処理する。 + * `['help']` => `'ヘルプを表示します'` + * `['view', var id]` => `'ID: $id の詳細を表示します'` + * `['create', var name, var countStr]` when `int.tryParse(countStr) != null` => `'新規作成: $name ($countStr 個)'` + * `_` => `'無効なコマンドです'` +2. `main()` でそれぞれのコマンドパターンを渡し、正しく分岐されることを確認する。 + +```dart:practice5_2.dart +// ここに関数を定義してください + +void main() { + // ここで動作確認を行ってください +} +``` + +```dart-exec:practice5_2.dart +``` diff --git a/public/docs/dart/6-classes/-intro.md b/public/docs/dart/6-classes/-intro.md new file mode 100644 index 00000000..0320f603 --- /dev/null +++ b/public/docs/dart/6-classes/-intro.md @@ -0,0 +1,6 @@ +[[Dart]]は純粋なオブジェクト指向プログラミング言語であり、すべてのデータ型はクラスを基盤としています。 + +Dartのクラス設計には、コードのボイラープレート(定型文)を大幅に削減するための強力な仕組みが揃っています。 +例えば、インスタンス変数の自動初期化糖衣構文(`this.field`)、名前付きコンストラクタ、ライブラリ単位のカプセル化、そしてインスタンス生成ロジックを柔軟に制御できる `factory` コンストラクタなどです。 + +この章では、Dartにおけるクラスの基礎から実践的なオブジェクトモデリングまでを徹底解説します。 diff --git a/public/docs/dart/6-classes/1-0-classes.md b/public/docs/dart/6-classes/1-0-classes.md new file mode 100644 index 00000000..bd39ce35 --- /dev/null +++ b/public/docs/dart/6-classes/1-0-classes.md @@ -0,0 +1,52 @@ +--- +id: dart-classes-basics +title: クラスの定義とインスタンス化(newの省略) +level: 2 +question: + - なぜDartではnewキーワードを省略できるのですか? + - コンストラクタで this.field を使う構文のメリットは何ですか? + - メソッド内で this を明示する必要があるのはどのような場合ですか? +term: + - クラス + - class + - インスタンス + - new + - メソッド +--- + +## クラスの定義とインスタンス化(`new`の省略) + +[[Dart]]でオブジェクトの設計図となる **[[クラス]](`class`)** を定義し、インスタンスを生成する基本的な構文を見てみましょう。 + +### 1. クラス定義とコンストラクタの糖衣構文 + +Dartでは、引数をそのままフィールドに代入する場合、`Point(this.x, this.y);` のように宣言するだけで初期化処理が完了します。 + +```dart:point_class.dart +class Point { + // フィールド(インスタンス変数) + final double x; + final double y; + + // コンストラクタ(糖衣構文 this.x, this.y) + Point(this.x, this.y); + + // メソッド + void printCoordinates() { + print('Point($x, $y)'); + } +} + +void main() { + // new キーワードは完全に省略可能 + final p1 = Point(3.0, 4.0); + p1.printCoordinates(); +} +``` + +```dart-exec:point_class.dart +Point(3.0, 4.0) +``` + +> [!NOTE] +> Dart 2以降、インスタンス生成時の `new` キーワードは省略するのが公式スタイルガイドの標準です(`new Point(...)` と書くことも文法上は可能ですが、通常は書きません)。 diff --git a/public/docs/dart/6-classes/2-0-constructors.md b/public/docs/dart/6-classes/2-0-constructors.md new file mode 100644 index 00000000..242b1a5a --- /dev/null +++ b/public/docs/dart/6-classes/2-0-constructors.md @@ -0,0 +1,68 @@ +--- +id: dart-classes-constructors +title: 様々なコンストラクタ(名前付き、リダイレクト) +level: 2 +question: + - 名前付きコンストラクタはどのような時に便利ですか? + - リダイレクトコンストラクタの構文はどう書きますか? + - constコンストラクタを定義するための要件は何ですか? +term: + - コンストラクタ + - 名前付きコンストラクタ + - リダイレクトコンストラクタ + - constコンストラクタ +--- + +## 様々なコンストラクタ(名前付き、リダイレクト) + +Dartでは、1つのクラスに複数の異なる初期化方法を提供するために **[[名前付きコンストラクタ]]** や **[[リダイレクトコンストラクタ]]** を作成できます。 + +### 1. 名前付きコンストラクタ(Named Constructors) + +`ClassName.constructorName(...)` の形式で定義します。JavaやC++のオーバーロードと異なり、コンストラクタの意図が名前によって明確になります。 + +### 2. リダイレクトコンストラクタ(Redirecting Constructors) + +別のコンストラクタに初期化処理を委譲(リダイレクト)するコンストラクタです。コロン `:` に続けて `this(...)` を呼び出します。 + +### 3. `const` コンストラクタ + +すべてのフィールドが `final` で構成されている場合、コンストラクタに `const` を付与してコンパイル時定数インスタンスを生成できます。 + +```dart:constructors_demo.dart +class User { + final String name; + final int age; + + // 基本コンストラクタ (const対応) + const User(this.name, this.age); + + // 1. 名前付きコンストラクタ + User.guest() + : name = 'ゲスト', + age = 0; + + // 2. リダイレクトコンストラクタ (基本コンストラクタを呼び出す) + User.adult(String name) : this(name, 20); + + void display() { + print('ユーザー: $name (年齢: $age)'); + } +} + +void main() { + const u1 = User('Alice', 25); + final u2 = User.guest(); + final u3 = User.adult('Bob'); + + u1.display(); + u2.display(); + u3.display(); +} +``` + +```dart-exec:constructors_demo.dart +ユーザー: Alice (年齢: 25) +ユーザー: ゲスト (年齢: 0) +ユーザー: Bob (年齢: 20) +``` diff --git a/public/docs/dart/6-classes/3-0-initializer-super.md b/public/docs/dart/6-classes/3-0-initializer-super.md new file mode 100644 index 00000000..56807135 --- /dev/null +++ b/public/docs/dart/6-classes/3-0-initializer-super.md @@ -0,0 +1,76 @@ +--- +id: dart-classes-initializer-super +title: '初期化子リスト(:)と super の呼び出し' +level: 2 +question: + - 初期化子リストとコンストラクタ本体の実行順序はどうなっていますか? + - superパラメータ(super.field)を使うとコードがどう短縮されますか? + - 初期化子リスト内でassertを使って引数を検証する方法を教えてください。 +term: + - 初期化子リスト + - super + - superパラメータ + - 継承 +--- + +## 初期化子リスト(`:`)と `super` の呼び出し + +### 1. 初期化子リスト(Initializer List) + +コンストラクタ本体 `{}` が実行される前に、フィールドの計算や `assert` による引数検証を行うには、引数リストの後にコロン `:` を付けて **[[初期化子リスト]]** を記述します。 + +```dart:initializer_list.dart +class Rectangle { + final double width; + final double height; + final double area; + + // 初期化子リストで面積 (area) を事前計算 + Rectangle(this.width, this.height) + : area = width * height, + assert(width > 0, 'width は正の数である必要があります'), + assert(height > 0, 'height は正の数である必要があります'); + + void display() { + print('幅: $width, 高さ: $height, 面積: $area'); + } +} + +void main() { + final rect = Rectangle(5.0, 8.0); + rect.display(); +} +``` + +```dart-exec:initializer_list.dart +幅: 5.0, 高さ: 8.0, 面積: 40.0 +``` + +### 2. 親クラスのコンストラクタ呼び出し (`super` / `super.field`) + +子クラスのコンストラクタから親クラスのコンストラクタに値を渡すには、初期化子リストで `: super(...)` を呼び出すか、Dart 2.17で追加された **`super.field`(Super Parameters)** を使います。 + +```dart:super_params.dart +class Person { + final String name; + Person(this.name); +} + +class Employee extends Person { + final String department; + + // super.name で親クラスのコンストラクタへ引数を直接転送 + Employee(super.name, this.department); + + void info() => print('社員: $name, 部署: $department'); +} + +void main() { + final emp = Employee('Charlie', '開発部'); + emp.info(); +} +``` + +```dart-exec:super_params.dart +社員: Charlie, 部署: 開発部 +``` diff --git a/public/docs/dart/6-classes/4-0-encapsulation-getter-setter.md b/public/docs/dart/6-classes/4-0-encapsulation-getter-setter.md new file mode 100644 index 00000000..c6b089ac --- /dev/null +++ b/public/docs/dart/6-classes/4-0-encapsulation-getter-setter.md @@ -0,0 +1,71 @@ +--- +id: dart-classes-encapsulation +title: カプセル化(_ によるプライベート化と get / set) +level: 2 +question: + - Dartには private や public キーワードがないのですか? + - アンダースコア _ でプライベート化されるスコープの単位は何ですか? + - ゲッター(get)とセッター(set)の構文はどう書きますか? +term: + - カプセル化 + - プライベート変数 + - ゲッター + - セッター + - getter + - setter + - ライブラリスコープ +--- + +## カプセル化(`_` によるプライベート化と `get` / `set`) + +### 1. `_` によるプライベート化 + +[[Dart]]には `public`、`private`、`protected` といったアクセス修飾子キーワードがありません。 +識別子の先頭に **アンダースコア `_`** を付けることで、その要素は **ライブラリ(同一ファイル)プライベート** になります。 + +> [!IMPORTANT] +> Dartのプライベート化は「クラス単位」ではなく「ファイル(ライブラリ)単位」です。同一ファイル内であれば別クラスからでも `_` の付いた要素にアクセスできますが、別ファイルから `import` された場合は完全に非公開になります。 + +### 2. ゲッター(`get`)とセッター(`set`) + +プロパティアクセスのように振る舞うメソッドとして、`get` と `set` を定義できます。 + +```dart:bank_account.dart +class BankAccount { + // プライベートフィールド + double _balance = 0.0; + + // ゲッター + double get balance => _balance; + + // セッター + set balance(double value) { + if (value < 0) { + print('エラー: 残高を負の値にすることはできません'); + return; + } + _balance = value; + } + + void deposit(double amount) { + if (amount > 0) _balance += amount; + } +} + +void main() { + final account = BankAccount(); + account.deposit(5000); + print('残高: ${account.balance} 円'); + + account.balance = 8000; // セッターの呼び出し + print('更新後残高: ${account.balance} 円'); + + account.balance = -100; // 不正な値 +} +``` + +```dart-exec:bank_account.dart +残高: 5000.0 円 +更新後残高: 8000.0 円 +エラー: 残高を負の値にすることはできません +``` diff --git a/public/docs/dart/6-classes/5-0-factory-constructors.md b/public/docs/dart/6-classes/5-0-factory-constructors.md new file mode 100644 index 00000000..1d0a1aeb --- /dev/null +++ b/public/docs/dart/6-classes/5-0-factory-constructors.md @@ -0,0 +1,90 @@ +--- +id: dart-classes-factory +title: factory コンストラクタ(シングルトンやJSONパースの実装) +level: 2 +question: + - 通常のコンストラクタと factory コンストラクタの決定的な違いは何ですか? + - factory コンストラクタを使ってシングルトンパターンを実装する方法を教えてください。 + - fromJson などのファクトリメソッドが factory として定義される理由は何ですか? +term: + - factoryコンストラクタ + - factory + - ファクトリコンストラクタ + - シングルトン + - fromJson +--- + +## `factory` コンストラクタ(シングルトンやJSONパースの実装) + +通常のコンストラクタは常にそのクラスの「新しいインスタンス」を生成しますが、**`factory` コンストラクタ** を使うと、コンストラクタ構文でありながら以下の制御が可能になります。 + +1. 既存のキャッシュ済みインスタンス(**[[シングルトン]]**)を返す。 +2. サブクラスのインスタンスを生成して返す。 +3. 条件に応じたインスタンス生成ロジック(JSONからのデシリアライズなど)をカプセル化する。 + +### 1. シングルトンパターンの実装 + +```dart:singleton_demo.dart +class DatabaseService { + final String dbName; + + // プライベートな静的インスタンス + static DatabaseService? _instance; + + // プライベートな通常コンストラクタ + DatabaseService._internal(this.dbName); + + // factory コンストラクタ: 常に同一のインスタンスを返す + factory DatabaseService({String dbName = 'main.db'}) { + _instance ??= DatabaseService._internal(dbName); + return _instance!; + } +} + +void main() { + final db1 = DatabaseService(); + final db2 = DatabaseService(); + + print('同一インスタンスか: ${identical(db1, db2)}'); +} +``` + +```dart-exec:singleton_demo.dart +同一インスタンスか: true +``` + +### 2. JSONパース用 `fromJson` ファクトリ + +```dart:factory_from_json.dart +class Product { + final String id; + final String title; + final int price; + + const Product({ + required this.id, + required this.title, + required this.price, + }); + + // Map から Product を構築する factory コンストラクタ + factory Product.fromJson(Map json) { + return Product( + id: json['id'] as String, + title: json['title'] as String, + price: json['price'] as int, + ); + } +} + +void main() { + final jsonMap = {'id': 'p_01', 'title': 'メカニカルキーボード', 'price': 14800}; + final product = Product.fromJson(jsonMap); + + print('商品: ${product.title} (¥${product.price})'); +} +``` + +```dart-exec:factory_from_json.dart +商品: メカニカルキーボード (¥14800) +``` diff --git a/public/docs/dart/6-classes/6-0-summary.md b/public/docs/dart/6-classes/6-0-summary.md new file mode 100644 index 00000000..82344dc3 --- /dev/null +++ b/public/docs/dart/6-classes/6-0-summary.md @@ -0,0 +1,18 @@ +--- +id: dart-classes-summary +title: この章のまとめ +level: 2 +question: [] +--- + +## この章のまとめ + +この章では、[[Dart]]のオブジェクト指向の根幹となるクラスとコンストラクタについて学びました。 + +* **クラスとインスタンス化**: `this.field` による簡潔な初期化が可能で、`new` キーワードは省略するのが標準。 +* **多彩なコンストラクタ**: 名前付きコンストラクタ(`User.guest()`)、リダイレクトコンストラクタ(`: this(...)`)、定数生成のための `const` コンストラクタ。 +* **初期化子リストと `super`**: コロン `:` に続くフィールド事前計算やバリデーション、`super.field` による親クラスへの引数転送。 +* **カプセル化**: アンダースコア `_` でライブラリ単位のプライベート化を実現し、`get` / `set` で安全にプロパティを公開。 +* **`factory` コンストラクタ**: キャッシュ済みインスタンスの返却(シングルトン)や、`fromJson` などのカスタム生成ロジックを実現。 + +次の [[./7]] では、継承、インターフェース、Mixin、拡張メソッド(Extension)による **クラスの拡張と高度な設計パターン** を学びます。 diff --git a/public/docs/dart/6-classes/6-1-practice1.md b/public/docs/dart/6-classes/6-1-practice1.md new file mode 100644 index 00000000..a69eecbe --- /dev/null +++ b/public/docs/dart/6-classes/6-1-practice1.md @@ -0,0 +1,29 @@ +--- +id: dart-classes-practice1 +title: '練習問題1: カプセル化されたBankAccountクラス' +level: 3 +question: + - ゲッターのみを定義して読み取り専用プロパティを作るメリットは何ですか? + - コンストラクタで初期残高を受け取りつつ、負の値を防ぐにはどうすればよいですか? +--- + +### 練習問題1: カプセル化されたBankAccountクラス + +口座名義と残高を管理する `BankAccount` クラスを実装してください。 + +1. プライベート変数 `String _owner` と `int _balance` を持つ。 +2. コンストラクタ `BankAccount(this._owner, this._balance)` を定義する(初期残高が負でないことを `assert` または初期化子リストで確認)。 +3. 読み取り専用ゲッター `owner` と `balance` を定義する。 +4. メソッド `void withdraw(int amount)` を定義し、残高が十分な場合は引き落としを行い、不足している場合は `'残高不足です'` と出力する。 +5. `main()` でインスタンスを生成し、引き落とし処理をテストする。 + +```dart:practice6_1.dart +// ここにクラスを定義してください + +void main() { + // ここで動作確認を行ってください +} +``` + +```dart-exec:practice6_1.dart +``` diff --git a/public/docs/dart/6-classes/6-2-practice2.md b/public/docs/dart/6-classes/6-2-practice2.md new file mode 100644 index 00000000..fd17f5d1 --- /dev/null +++ b/public/docs/dart/6-classes/6-2-practice2.md @@ -0,0 +1,29 @@ +--- +id: dart-classes-practice2 +title: '練習問題2: factoryコンストラクタによるJSONパース' +level: 3 +question: + - JSONパースでnullが渡される可能性がある場合の安全な書き方を教えてください。 + - toString() メソッドをオーバーライドするメリットは何ですか? +--- + +### 練習問題2: factoryコンストラクタによるJSONパース + +Web APIから受信したユーザー設定データを表す `UserSettings` クラスを実装してください。 + +1. フィールドとして `final String theme`(テーマ名)と `final bool notificationsEnabled`(通知有無)を持つ。 +2. 通常のコンストラクタ `const UserSettings({required this.theme, required this.notificationsEnabled});` を定義する。 +3. `factory UserSettings.fromJson(Map json)` を実装し、Mapから各プロパティをパースしてインスタンスを返す。 + * `theme` が未指定の場合は `'light'`、`notificationsEnabled` が未指定の場合は `true` をデフォルト値とする。 +4. `main()` でJSONマップを渡し、パースされた設定内容を出力する。 + +```dart:practice6_2.dart +// ここにクラスを定義してください + +void main() { + // ここで動作確認を行ってください +} +``` + +```dart-exec:practice6_2.dart +``` diff --git a/public/docs/dart/7-class-extension/-intro.md b/public/docs/dart/7-class-extension/-intro.md new file mode 100644 index 00000000..56bf2ee9 --- /dev/null +++ b/public/docs/dart/7-class-extension/-intro.md @@ -0,0 +1,5 @@ +[[Dart]]のクラス設計は、単なる単一継承にとどまらず、柔軟で再利用性の高いコード構造を実現するための高度な機能を豊富に備えています。 + +すべてのクラスが暗黙的にインターフェースとして機能する「暗黙的インターフェース」、多重継承のメリットを安全に享受できる「Mixin(ミキシン)」、既存のSDKクラスに新しいメソッドを生やすことができる「拡張メソッド(Extension)」、そしてメソッドやプロパティを持てる「Enhanced Enum」などです。 + +この章では、Dartならではのオブジェクト設計のベストプラクティスを学びます。 diff --git a/public/docs/dart/7-class-extension/1-0-extends-implements.md b/public/docs/dart/7-class-extension/1-0-extends-implements.md new file mode 100644 index 00000000..8bf680c6 --- /dev/null +++ b/public/docs/dart/7-class-extension/1-0-extends-implements.md @@ -0,0 +1,66 @@ +--- +id: dart-classes-extends-implements +title: extends(継承)と implements(インターフェース実装)の違い +level: 2 +question: + - なぜDartには interface キーワードが別途不要だったのですか? + - implements を使った場合、親クラスの実装コードは引き継がれますか? + - 多重継承が禁止されている一方で implements を複数指定できる理由は何ですか? +term: + - extends + - 継承 + - implements + - 暗黙的インターフェース + - インターフェース + - '' +--- + +## `extends`(継承)と `implements`(インターフェース実装)の違い + +[[Dart]]では、すべてのクラスが自動的に **[[暗黙的インターフェース]](Implicit Interface)** を定義しています。これにより、任意のクラスを `implements` の対象として利用できます。 + +### 1. `extends` (単一継承) + +親クラスの実装(メソッドの処理やフィールド)をそのまま引き継ぎます。Dartでは多重継承(複数のクラスを `extends` すること)はできません。 + +### 2. `implements` (インターフェース実装) + +親クラスの「型のシグネチャ(メソッド名や引数・戻り値の型)」だけを満たすことを約束します。親クラスの実装コードは**一切引き継がれず、すべてのメソッドを再定義(`@override`)する必要があります**。カンマ区切りで複数のインターフェースを実装可能です。 + +```dart:extends_implements.dart +class Animal { + void speak() { + print('動物が鳴きます'); + } +} + +// 1. extends: 実装を引き継ぎ、必要に応じてオーバーライド +class Dog extends Animal { + @override + void speak() { + print('ワンワン!'); + } +} + +// 2. implements: 型だけを流用し、全メソッドを自前で再実装 +class RobotDog implements Animal { + @override + void speak() { + print('ビープ!ワンワン (電子音)'); + } +} + +void makeNoise(Animal animal) { + animal.speak(); +} + +void main() { + makeNoise(Dog()); + makeNoise(RobotDog()); +} +``` + +```dart-exec:extends_implements.dart +ワンワン! +ビープ!ワンワン (電子音) +``` diff --git a/public/docs/dart/7-class-extension/2-0-mixins.md b/public/docs/dart/7-class-extension/2-0-mixins.md new file mode 100644 index 00000000..c6470ca8 --- /dev/null +++ b/public/docs/dart/7-class-extension/2-0-mixins.md @@ -0,0 +1,62 @@ +--- +id: dart-classes-mixins +title: Mixin(mixin、with)による機能の注入 +level: 2 +question: + - Mixinと継承の違いは何ですか? + - onキーワードを使ってMixinを特定の基底クラスに限定する方法は? + - 複数のMixinを with で組み合わせた場合のメソッド解決順序はどうなりますか? +term: + - Mixin + - mixin + - with + - 機能の注入 + - on +--- + +## Mixin(`mixin`、`with`)による機能の注入 + +**[[Mixin]]** は、クラス階層に縛られることなく、複数のクラス間で実装コード(メソッドやプロパティ)を共有・注入するための仕組みです。 + +`mixin` キーワードで定義し、クラスに `with` キーワードで適用します。 + +```dart:mixins_demo.dart +// 1. ロギング機能を提供する Mixin +mixin Logger { + void log(String message) { + print('[LOG ${DateTime.now().hour}:${DateTime.now().minute}] $message'); + } +} + +// 2. 永続化機能を提供する Mixin +mixin Serializable { + Map toJson(); +} + +// クラスに with で複数の Mixin を合成 +class AppUser with Logger, Serializable { + final String name; + AppUser(this.name); + + void login() { + log('ユーザー $name がログインしました'); + } + + @override + Map toJson() => {'name': name}; +} + +void main() { + final user = AppUser('Alice'); + user.login(); + print('JSON: ${user.toJson()}'); +} +``` + +```dart-exec:mixins_demo.dart +[LOG 5:20] ユーザー Alice がログインしました +JSON: {name: Alice} +``` + +> [!TIP] +> Mixin内で特定のクラスの機能を利用したい場合は、`mixin MyMixin on SomeBaseClass` のように `on` 節を指定して制約をかけることができます。 diff --git a/public/docs/dart/7-class-extension/3-0-extension-methods.md b/public/docs/dart/7-class-extension/3-0-extension-methods.md new file mode 100644 index 00000000..fd9cc52d --- /dev/null +++ b/public/docs/dart/7-class-extension/3-0-extension-methods.md @@ -0,0 +1,56 @@ +--- +id: dart-classes-extensions +title: Extension(拡張メソッド)による既存クラスへの機能追加 +level: 2 +question: + - 拡張メソッドを使うと標準ライブラリのクラス(Stringやintなど)にメソッドを追加できますか? + - 拡張メソッドの定義構文はどう書きますか? + - ジェネリクスやゲッターをExtensionで定義することはできますか? +term: + - Extension + - 拡張メソッド + - extension + - extension on +--- + +## Extension(拡張メソッド)による既存クラスへの機能追加 + +**[[Extension]](拡張メソッド)** を使うと、既存のクラス(Dartの組み込み型やサードパーティ製ライブラリのクラス)のソースコードを変更することなく、新しいメソッドやゲッター、演算子を追加できます。 + +構文は `extension ExtensionName on TargetType { ... }` です。 + +```dart:extension_methods.dart +// String クラスに拡張メソッドを追加 +extension StringExtensions on String { + // ゲッター: 先頭文字を大文字にする + String get capitalize { + if (isEmpty) return this; + return this[0].toUpperCase() + substring(1); + } + + // メソッド: 安全に整数に変換する + int? toIntOrNull() => int.tryParse(this); +} + +// List に特化した拡張 +extension NumberListExtension on List { + int get sum => fold(0, (a, b) => a + b); +} + +void main() { + String word = 'flutter'; + print('capitalize: ${word.capitalize}'); + + String numStr = '42'; + print('toIntOrNull: ${numStr.toIntOrNull()}'); + + List numbers = [10, 20, 30]; + print('sum: ${numbers.sum}'); +} +``` + +```dart-exec:extension_methods.dart +capitalize: Flutter +toIntOrNull: 42 +sum: 60 +``` diff --git a/public/docs/dart/7-class-extension/4-0-enhanced-enums.md b/public/docs/dart/7-class-extension/4-0-enhanced-enums.md new file mode 100644 index 00000000..c6164730 --- /dev/null +++ b/public/docs/dart/7-class-extension/4-0-enhanced-enums.md @@ -0,0 +1,56 @@ +--- +id: dart-classes-enhanced-enums +title: 高機能な列挙型(Enhanced Enum) +level: 2 +question: + - 通常のenumとEnhanced Enumの違いは何ですか? + - Enumにフィールドやメソッド、コンストラクタを持たせるにはどう書きますか? + - Enhanced Enumとswitch式を組み合わせるメリットは何ですか? +term: + - Enum + - 列挙型 + - Enhanced Enum + - enum + - enumコンストラクタ +--- + +## 高機能な列挙型(Enhanced Enum) + +Dart 2.17以降の **[[Enhanced Enum]]** では、列挙型の各値にフィールド(値)、コンストラクタ、ゲッター、メソッドを持たせることができます。 + +単なる定数の列挙を超えて、型安全で表現力豊かなドメインモデルを簡潔に定義できます。 + +```dart:enhanced_enums.dart +enum HttpStatus { + ok(200, 'Success'), + notFound(404, 'Not Found'), + serverError(500, 'Internal Server Error'); + + // フィールド + final int code; + final String description; + + // const コンストラクタ + const HttpStatus(this.code, this.description); + + // ゲッター + bool get isSuccess => code >= 200 && code < 300; + + // メソッド + void log() { + print('[$code] $description (成功: $isSuccess)'); + } +} + +void main() { + final status = HttpStatus.notFound; + status.log(); + + print('HttpStatus.ok isSuccess: ${HttpStatus.ok.isSuccess}'); +} +``` + +```dart-exec:enhanced_enums.dart +[404] Not Found (成功: false) +HttpStatus.ok isSuccess: true +``` diff --git a/public/docs/dart/7-class-extension/5-0-summary.md b/public/docs/dart/7-class-extension/5-0-summary.md new file mode 100644 index 00000000..b430f2cb --- /dev/null +++ b/public/docs/dart/7-class-extension/5-0-summary.md @@ -0,0 +1,17 @@ +--- +id: dart-class-extension-summary +title: この章のまとめ +level: 2 +question: [] +--- + +## この章のまとめ + +この章では、[[Dart]]の高度なオブジェクト指向設計機能について学びました。 + +* **`extends` vs `implements`**: `extends` は実装コードを引き継ぎ、`implements` は暗黙的インターフェースの型契約のみを満たす。 +* **Mixin (`mixin`, `with`)**: 継承階層とは直交して、複数クラスへ横断的な機能・振る舞いを注入する。 +* **Extension (`extension on`)**: 既存のクラスを変更することなく、便利メソッドやプロパティを外付けで拡張する。 +* **Enhanced Enum**: フィールド、コンストラクタ、メソッドを持つ高機能な列挙型。 + +次の [[./8]] では、Dart 3で導入された `sealed`, `base`, `interface`, `final` などの **クラス修飾子** を使った厳密なドメインモデリングを学びます。 diff --git a/public/docs/dart/7-class-extension/5-1-practice1.md b/public/docs/dart/7-class-extension/5-1-practice1.md new file mode 100644 index 00000000..634a439d --- /dev/null +++ b/public/docs/dart/7-class-extension/5-1-practice1.md @@ -0,0 +1,28 @@ +--- +id: dart-class-extension-practice1 +title: '練習問題1: Mixinを用いたロギング機能の追加' +level: 3 +question: + - Mixinの中で自身を特定のクラス型として扱うにはどうしますか? + - クラス名を取得する runtimeType プロパティの使い方を教えてください。 +--- + +### 練習問題1: Mixinを用いたロギング機能の追加 + +イベントの発生ログを出力する `PrintableLogger` Mixin を作成し、クラスに適用してください。 + +1. `mixin PrintableLogger` を定義する。 + * メソッド `void logEvent(String eventName)` を持ち、`'[${runtimeType}] イベント発生: $eventName'` と出力する。 +2. `class OrderService with PrintableLogger` を定義し、メソッド `void checkout(String itemId)` 内で `logEvent('注文完了: $itemId')` を呼び出す。 +3. `main()` で `OrderService` のインスタンスを作成し、`checkout('item_999')` を実行する。 + +```dart:practice7_1.dart +// ここにコードを書いてください + +void main() { + // ここで動作確認を行ってください +} +``` + +```dart-exec:practice7_1.dart +``` diff --git a/public/docs/dart/7-class-extension/5-2-practice2.md b/public/docs/dart/7-class-extension/5-2-practice2.md new file mode 100644 index 00000000..4b3c11df --- /dev/null +++ b/public/docs/dart/7-class-extension/5-2-practice2.md @@ -0,0 +1,28 @@ +--- +id: dart-class-extension-practice2 +title: '練習問題2: Extensionを用いた文字列ユーティリティ' +level: 3 +question: + - Extensionのスコープ(ライブラリ内でのみ有効かエクスポート可能か)について教えてください。 + - 拡張メソッド内で元のオブジェクト(this)を変更することはできますか? +--- + +### 練習問題2: Extensionを用いた文字列ユーティリティ + +`String` クラスに対して、特定の文字列操作を行う拡張メソッドを実装してください。 + +1. `extension StringUtils on String` を定義する。 + * ゲッター `bool get isEmail`: 文字列に `@` と `.` が含まれているかを簡易判定する。 + * メソッド `String mask({int visibleCount = 2})`: 先頭から `visibleCount` 文字だけ残し、以降を `*` でマスクした文字列を返す(文字数が `visibleCount` 以下の場合はそのまま返す)。 +2. `main()` で `'user@example.com'` に対する `isEmail` の判定結果と、`mask()` によるマスク文字列(例: `'us**************'`)を出力する。 + +```dart:practice7_2.dart +// ここにコードを書いてください + +void main() { + // ここで動作確認を行ってください +} +``` + +```dart-exec:practice7_2.dart +``` diff --git a/public/docs/dart/8-class-modifiers/-intro.md b/public/docs/dart/8-class-modifiers/-intro.md new file mode 100644 index 00000000..dca9658a --- /dev/null +++ b/public/docs/dart/8-class-modifiers/-intro.md @@ -0,0 +1,5 @@ +[[Dart]] 3.0では、ライブラリの作者がクラスの継承・実装・インスタンス化の権限をきめ細かく制御できるように、**[[クラス修飾子]](Class Modifiers)** が導入されました。 + +特に **`sealed` クラス** は、RustのEnumやKotlinのSealed Class、TypeScriptのTagged Union(判別可能なUnion型)に相当する **[[代数的データ型]](ADT)** をDartで美しく実現し、`switch` 式と組み合わせることで網羅性チェックの恩恵を最大限に引き出します。 + +この章では、`sealed`, `base`, `interface`, `final` 修飾子の役割と、堅牢なドメインモデル設計を学びます。 diff --git a/public/docs/dart/8-class-modifiers/1-0-sealed-classes.md b/public/docs/dart/8-class-modifiers/1-0-sealed-classes.md new file mode 100644 index 00000000..e38695cb --- /dev/null +++ b/public/docs/dart/8-class-modifiers/1-0-sealed-classes.md @@ -0,0 +1,68 @@ +--- +id: dart-class-modifiers-sealed +title: sealed クラスによる代数的データ型(ADT)とSwitchの組み合わせ +level: 2 +question: + - sealedクラスを定義する主な目的は何ですか? + - sealedクラスのサブクラスはどこで定義する必要がありますか? + - switch式でsealedクラスの全パターンを網羅しないとどうなりますか? +term: + - sealed + - sealedクラス + - 代数的データ型 + - ADT + - 網羅性 +--- + +## `sealed` クラスによる代数的データ型(ADT)とSwitchの組み合わせ + +**`sealed` クラス** は、そのクラスを直接インスタンス化できず(暗黙的に `abstract`)、**サブクラスの定義を同一ライブラリ(同一ファイル)内のみに限定する** 修飾子です。 + +コンパイラは「そのクラスのサブクラスの全パターン」を完全に把握できるため、`switch` 式で**[[網羅性チェック]]**が働きます。 + +```dart:sealed_demo.dart +// 1. sealed クラスでUI状態の基底クラスを定義 +sealed class UiState {} + +class InitialState extends UiState {} +class LoadingState extends UiState {} +class SuccessState extends UiState { + final List data; + SuccessState(this.data); +} +class ErrorState extends UiState { + final String message; + ErrorState(this.message); +} + +// 2. switch式で状態に応じたレンダリング文字列を生成 +String render(UiState state) { + // 全サブクラスが網羅されているため、default (_) 節が不要! + return switch (state) { + InitialState() => '待機中...', + LoadingState() => '読み込み中...', + SuccessState(:var data) => 'データ取得成功: ${data.join(', ')}', + ErrorState(:var message) => 'エラー発生: $message', + }; +} + +void main() { + UiState state = LoadingState(); + print(render(state)); + + state = SuccessState(['Dart', 'Flutter']); + print(render(state)); + + state = ErrorState('ネットワーク接続に失敗しました'); + print(render(state)); +} +``` + +```dart-exec:sealed_demo.dart +読み込み中... +データ取得成功: Dart, Flutter +エラー発生: ネットワーク接続に失敗しました +``` + +> [!TIP] +> 新しい状態(例: `OfflineState`)を後から追加した際、`render` 関数内でそのケースを書き忘れていると、コンパイラがビルド時に「すべてのケースが網羅されていません」と即座にエラーを教えてくれます。 diff --git a/public/docs/dart/8-class-modifiers/2-0-other-modifiers.md b/public/docs/dart/8-class-modifiers/2-0-other-modifiers.md new file mode 100644 index 00000000..96598b41 --- /dev/null +++ b/public/docs/dart/8-class-modifiers/2-0-other-modifiers.md @@ -0,0 +1,61 @@ +--- +id: dart-class-modifiers-base-interface-final +title: base、interface、final 修飾子の使い分け +level: 2 +question: + - base修飾子を付けると外部パッケージでどのような制約が課されますか? + - interface修飾子と通常のclassの違いは何ですか? + - final修飾子をクラスに付けた場合、継承や実装はどう制限されますか? +term: + - base + - interface修飾子 + - final修飾子 + - クラス修飾子 + - abstract +--- + +## `base`、`interface`、`final` 修飾子の使い分け + +[[Dart]] 3では、ライブラリ境界外(外部パッケージや別ファイル)からのクラス利用方法を制限するために、以下のクラス修飾子が提供されています。 + +| 修飾子 | 外部でのインスタンス化 | 外部での `extends` (継承) | 外部での `implements` (実装) | 外部での `with` (Mixin) | +| :--- | :---: | :---: | :---: | :---: | +| **`class` (無印)** | ○ | ○ | ○ | × | +| **`base`** | ○ | **○ (`base` 必須)** | × | × | +| **`interface`** | ○ | × | **○** | × | +| **`final`** | ○ | × | × | × | +| **`sealed`** | × (abstract) | × (同ファイル内のみ) | × (同ファイル内のみ) | × | + +### 1. `base`: 継承のみを許可し、暗黙的インターフェースのimplementsを禁止 + +クラスに新しいメソッドを追加しても、外部のサブクラスが壊れないように設計したい場合に利用します。 + +```dart +// 外部ライブラリ側 +base class Vehicle { + void move() => print('移動中'); +} + +// 利用側 +// class Car implements Vehicle {} // コンパイルエラー: implements 不可 +base class Car extends Vehicle {} // OK (子クラスも base/final/sealed が必要) +``` + +### 2. `interface`: 実装(implements)のみを許可し、継承を禁止 + +APIの型シグネチャのみを提供し、内部実装の継承による依存を防ぎたい場合に利用します。 + +```dart +interface class StorageService { + void save(String key, String value) {} +} +// class LocalStorage extends StorageService {} // エラー: 外部から extends 不可 +class LocalStorage implements StorageService { // OK: implements のみ許可 + @override + void save(String key, String value) => print('Saved $key'); +} +``` + +### 3. `final`: 外部からの継承・実装を完全に禁止 + +クラスの動作を完全に固定し、サブクラス化を一切許さない場合に使用します。 diff --git a/public/docs/dart/8-class-modifiers/3-0-domain-modeling.md b/public/docs/dart/8-class-modifiers/3-0-domain-modeling.md new file mode 100644 index 00000000..5e862562 --- /dev/null +++ b/public/docs/dart/8-class-modifiers/3-0-domain-modeling.md @@ -0,0 +1,72 @@ +--- +id: dart-class-modifiers-domain-modeling +title: ドメインモデルの堅牢な設計方法 +level: 2 +question: + - アプリケーションの状態管理でsealedクラスを活用するベストプラクティスは何ですか? + - 不正な状態の表現を型レベルで不可能にするにはどうすればよいですか? + - イミュータブルなデータモデルを作るコツを教えてください。 +term: + - ドメインモデル + - 状態モデリング + - イミュータブル + - 型安全 +--- + +## ドメインモデルの堅牢な設計方法 + +クラス修飾子(特に `sealed`)とDart 3のパターンマッチングを組み合わせることで、**「不正な状態を型レベルで表現不可能にする(Make Impossible States Impossible)」** 堅牢な[[ドメインモデル]]が構築できます。 + +### 認証状態のモデリング例 + +フラグ変数(`bool isLoggedIn`, `String? token`, `String? errorMessage`)をバラバラに持つ代わりに、状態そのものを `sealed` クラスの階層として定義します。 + +```dart:auth_state_modeling.dart +sealed class AuthState { + const AuthState(); +} + +class AuthUnauthenticated extends AuthState { + const AuthUnauthenticated(); +} + +class AuthAuthenticating extends AuthState { + const AuthAuthenticating(); +} + +class AuthAuthenticated extends AuthState { + final String userId; + final String token; + const AuthAuthenticated({required this.userId, required this.token}); +} + +class AuthError extends AuthState { + final String errorMessage; + const AuthError(this.errorMessage); +} + +void printAuthAction(AuthState state) { + final action = switch (state) { + AuthUnauthenticated() => 'ログインボタンを表示します', + AuthAuthenticating() => 'スピナーを表示して待機します', + AuthAuthenticated(:var userId) => 'ユーザー $userId のマイページを表示します', + AuthError(:var errorMessage) => 'エラーダイアログを表示: $errorMessage', + }; + print(action); +} + +void main() { + AuthState state = const AuthAuthenticating(); + printAuthAction(state); + + state = const AuthAuthenticated(userId: 'u_777', token: 'jwt_abc123'); + printAuthAction(state); +} +``` + +```dart-exec:auth_state_modeling.dart +スピナーを表示して待機します +ユーザー u_777 のマイページを表示します +``` + +この設計により、「ログイン中なのにトークンが `null` である」といった不整合な状態が存在し得なくなり、UIのバグを大幅に減らすことができます。 diff --git a/public/docs/dart/8-class-modifiers/4-0-summary.md b/public/docs/dart/8-class-modifiers/4-0-summary.md new file mode 100644 index 00000000..06039277 --- /dev/null +++ b/public/docs/dart/8-class-modifiers/4-0-summary.md @@ -0,0 +1,18 @@ +--- +id: dart-class-modifiers-summary +title: この章のまとめ +level: 2 +question: [] +--- + +## この章のまとめ + +この章では、[[Dart]] 3のクラス修飾子によるモデリングの厳密化について学びました。 + +* **`sealed` クラス**: 同一ファイル内のみでサブクラス定義を制限し、代数的データ型(ADT)と `switch` 式の完全な網羅性チェックを実現。 +* **`base` 修飾子**: 継承のみを許可し、暗黙的インターフェースとしての `implements` を防ぐ。 +* **`interface` 修飾子**: インターフェース実装(`implements`)のみを許可し、実装の継承を防ぐ。 +* **`final` 修飾子**: 外部からの継承と実装を両方禁止する。 +* **ドメインモデリング**: 状態をフラグではなく型として分割定義し、不正な状態を排除する設計手法。 + +次の [[./9]] では、Dartのイベント駆動アーキテクチャの中核である **非同期処理の基礎(Futureとasync/await)** を学びます。 diff --git a/public/docs/dart/8-class-modifiers/4-1-practice1.md b/public/docs/dart/8-class-modifiers/4-1-practice1.md new file mode 100644 index 00000000..04612113 --- /dev/null +++ b/public/docs/dart/8-class-modifiers/4-1-practice1.md @@ -0,0 +1,32 @@ +--- +id: dart-class-modifiers-practice1 +title: '練習問題1: sealedクラスを用いたUI状態のモデリング' +level: 3 +question: + - sealedクラスを基底にした場合のジェネリクス型の指定方法はどうなりますか? + - switch式で各サブクラスのフィールドをパターンマッチで取り出す際の注意点は? +--- + +### 練習問題1: sealedクラスを用いたUI状態のモデリング + +天気予報アプリの画面状態を表す `WeatherState` を `sealed` クラスで実装してください。 + +1. `sealed class WeatherState` を定義する。 +2. 以下のサブクラスを作成する。 + * `class WeatherInitial extends WeatherState` + * `class WeatherLoading extends WeatherState` + * `class WeatherSuccess extends WeatherState`: フィールド `final String city`, `final double temperature` を持つ。 + * `class WeatherFailure extends WeatherState`: フィールド `final String error` を持つ。 +3. `String getWeatherMessage(WeatherState state)` 関数を `switch` 式で実装し、全状態に応じた適切なメッセージ文字列を返す。 +4. `main()` で各状態を作成し、メッセージを出力する。 + +```dart:practice8_1.dart +// ここにクラスと関数を定義してください + +void main() { + // ここで動作確認を行ってください +} +``` + +```dart-exec:practice8_1.dart +``` diff --git a/public/docs/dart/8-class-modifiers/4-2-practice2.md b/public/docs/dart/8-class-modifiers/4-2-practice2.md new file mode 100644 index 00000000..42805fe3 --- /dev/null +++ b/public/docs/dart/8-class-modifiers/4-2-practice2.md @@ -0,0 +1,28 @@ +--- +id: dart-class-modifiers-practice2 +title: '練習問題2: クラス修飾子による安全なAPI設計' +level: 3 +question: + - abstract interface class と書くことの意味は何ですか? + - baseクラスを継承するクラスに付けなければならない修飾子のルールを復習したいです。 +--- + +### 練習問題2: クラス修飾子による安全なAPI設計 + +キャッシュストレージのインターフェースと、基底となるローカルストレージ実装を設計してください。 + +1. `abstract interface class CacheRepository` を定義し、メソッド `T? get(String key);` と `void set(String key, T value);` を宣言する。 +2. `base class MemoryCacheRepository implements CacheRepository` を実装し、内部の `Map` にデータを保存・取得する処理を実装する。 +3. `final class ExpiringMemoryCache extends MemoryCacheRepository` を定義する。 +4. `main()` で `ExpiringMemoryCache` を生成し、データの追加と取得をテストする。 + +```dart:practice8_2.dart +// ここにクラスを定義してください + +void main() { + // ここで動作確認を行ってください +} +``` + +```dart-exec:practice8_2.dart +``` diff --git a/public/docs/dart/9-async-future/-intro.md b/public/docs/dart/9-async-future/-intro.md new file mode 100644 index 00000000..6c4cde33 --- /dev/null +++ b/public/docs/dart/9-async-future/-intro.md @@ -0,0 +1,5 @@ +ネットワーク通信、データベース読み書き、ファイルの入出力など、完了までに時間のかかる処理をUIスレッドで同期的に実行すると、アプリ画面がフリーズしてしまいます。 + +[[Dart]]はシングルスレッドのイベントループモデルを採用しており、処理をブロックすることなく非同期にタスクを実行するための仕組みとして **`Future`** と **`async` / `await`** を提供しています。 + +この章では、Dartのイベントループの概念から、非同期関数の書き方、エラーハンドリングまでを学びます。 diff --git a/public/docs/dart/9-async-future/1-0-event-loop.md b/public/docs/dart/9-async-future/1-0-event-loop.md new file mode 100644 index 00000000..a79687e9 --- /dev/null +++ b/public/docs/dart/9-async-future/1-0-event-loop.md @@ -0,0 +1,44 @@ +--- +id: dart-async-event-loop +title: 同期処理と非同期処理の違い(イベントループの概念) +level: 2 +question: + - Dartのシングルスレッドモデルで非同期処理が並行して進む仕組みは何ですか? + - イベントキューとマイクロタスクキューの違いは何ですか? + - JavaScriptのイベントループとDartのイベントループの違いはありますか? +term: + - イベントループ + - マイクロタスク + - イベントキュー + - 非同期処理 + - シングルスレッド +--- + +## 同期処理と非同期処理の違い(イベントループの概念) + +[[Dart]]コードは基本的に単一のスレッド(**[[シングルスレッド]]**)上で動作します。処理が重い計算でスレッドを長時間占有すると、描画やタップ入力がブロックされてしまいます。 + +Dartはこの問題を **[[イベントループ]](Event Loop)** によって解決しています。 + +``` + +----------------------------+ + | 現在実行中のコード | + +-------------+--------------+ + | (完了) + v ++---------------------------+---------------------------+ +| イベントループ (Event Loop) | +| | +| 1. マイクロタスクキュー (Microtask Queue) - 最優先 | +| [ Microtask 1 ] -> [ Microtask 2 ] | +| | +| 2. イベントキュー (Event Queue) - 通常の非同期タスク | +| [ I/O完了 ] -> [ タイマー発火 ] -> [ タップイベント ] | ++-------------------------------------------------------+ +``` + +1. **実行中タスク**: 現在実行しているDartコードが完了するまで走り切ります。 +2. **マイクロタスクキュー**: 最優先で処理される内部タスクのキュー。 +3. **イベントキュー**: 外部からのI/Oイベント、タイマー、描画リクエスト、UI操作などを処理するキュー。 + +非同期処理(`Future`)をスケジューリングすると、現在のコードの実行が終わった後、イベントループが順番にそれを取り出して実行します。 diff --git a/public/docs/dart/9-async-future/2-0-future.md b/public/docs/dart/9-async-future/2-0-future.md new file mode 100644 index 00000000..916662f1 --- /dev/null +++ b/public/docs/dart/9-async-future/2-0-future.md @@ -0,0 +1,57 @@ +--- +id: dart-async-future +title: Future の仕組み +level: 2 +question: + - Futureとは具体的に何を表すオブジェクトですか? + - Futureの状態(Uncompleted, Completed with data, Completed with error)について教えてください。 + - then() と catchError() を使ったコールバック記法はどう書きますか? +term: + - Future + - Future.value + - Future.delayed + - then + - コールバック +--- + +## `Future` の仕組み + +**`Future`** は、「将来のある時点で値 `T` またはエラーを返す非同期処理の結果」を表すオブジェクトです(JavaScriptの `Promise` や Rustの `Future` に相当します)。 + +### Futureの3つの状態 + +1. **未完了(Uncompleted)**: 非同期処理が実行中で、まだ結果が出ていない状態。 +2. **完了(Completed with data)**: 処理が正常に完了し、値が得られた状態。 +3. **エラー完了(Completed with error)**: 例外が発生して失敗した状態。 + +### Futureの生成と `then()` による購読 + +```dart:future_basics.dart +Future fetchUserGreeting() { + // 100ミリ秒後に完了する Future を生成 + return Future.delayed( + const Duration(milliseconds: 100), + () => 'こんにちは、ユーザーさん!', + ); +} + +void main() { + print('1. 処理開始'); + + fetchUserGreeting().then((greeting) { + print('3. データ受信: $greeting'); + }).catchError((error) { + print('エラー: $error'); + }); + + print('2. メイン関数の同期処理完了'); +} +``` + +```dart-exec:future_basics.dart +1. 処理開始 +2. メイン関数の同期処理完了 +3. データ受信: こんにちは、ユーザーさん! +``` + +`then()` コールバックをチェーンするスタイルも可能ですが、コードがネストしやすいため、通常は次に解説する `async` / `await` を使用します。 diff --git a/public/docs/dart/9-async-future/3-0-async-await.md b/public/docs/dart/9-async-future/3-0-async-await.md new file mode 100644 index 00000000..3c329d80 --- /dev/null +++ b/public/docs/dart/9-async-future/3-0-async-await.md @@ -0,0 +1,75 @@ +--- +id: dart-async-async-await +title: async / await による可読性の高い非同期コード +level: 2 +question: + - asyncキーワードを付けた関数の戻り値型は何になりますか? + - awaitキーワードはどこで使用できますか? + - Future.waitを使って複数の非同期タスクを並行実行する方法を教えてください。 +term: + - async + - await + - async/await + - Future.wait + - 並行実行 +--- + +## `async` / `await` による可読性の高い非同期コード + +**`async`** と **`await`** キーワードを使うことで、非同期コードを同期コードと同じような直線的で読みやすいフローで記述できます。 + +* 関数宣言の後に `async` を付けると、その関数は自動的に `Future` を返す非同期関数になります。 +* `async` 関数内でのみ、`Future` の完了を待機する **`await`** 式が使えます。 + +```dart:async_await_demo.dart +Future fetchUserId() async { + await Future.delayed(const Duration(milliseconds: 50)); + return 42; +} + +Future fetchUserName(int id) async { + await Future.delayed(const Duration(milliseconds: 50)); + return 'Alice (ID: $id)'; +} + +Future displayUser() async { + print('ユーザー情報を取得中...'); + final id = await fetchUserId(); + final name = await fetchUserName(id); + print('取得完了: $name'); +} + +void main() async { + await displayUser(); +} +``` + +```dart-exec:async_await_demo.dart +ユーザー情報を取得中... +取得完了: Alice (ID: 42) +``` + +### 複数の非同期処理を並行実行する (`Future.wait`) + +互いに依存しない複数の非同期処理を同時に実行したい場合は、`Future.wait` を使います。 + +```dart:future_wait.dart +Future fetchPostTitle() async => 'Dart 3の紹介'; +Future fetchLikeCount() async => 128; + +void main() async { + // 並行して実行し、両方の完了を待つ + final results = await Future.wait([ + fetchPostTitle(), + fetchLikeCount(), + ]); + + print('タイトル: ${results[0]}'); + print('いいね数: ${results[1]}'); +} +``` + +```dart-exec:future_wait.dart +タイトル: Dart 3の紹介 +いいね数: 128 +``` diff --git a/public/docs/dart/9-async-future/4-0-async-error-handling.md b/public/docs/dart/9-async-future/4-0-async-error-handling.md new file mode 100644 index 00000000..a477bd89 --- /dev/null +++ b/public/docs/dart/9-async-future/4-0-async-error-handling.md @@ -0,0 +1,60 @@ +--- +id: dart-async-error-handling +title: 非同期処理のエラーハンドリング(try-catch-finally) +level: 2 +question: + - async/await でのエラーハンドリングは通常の try-catch と同じですか? + - 非同期例外を確実に捕捉するための注意点は何ですか? + - finally ブロックはどのような場面で活用されますか? +term: + - 非同期エラーハンドリング + - catchError + - try-catch-finally + - try + - catch + - finally +--- + +## 非同期処理のエラーハンドリング(`try-catch-finally`) + +`async` / `await` を使った非同期処理では、同期コードと全く同じ **`try-catch-finally`** 構文でエラーを捕捉できます。 + +```dart:async_try_catch.dart +Future loadDataFromServer({required bool shouldFail}) async { + await Future.delayed(const Duration(milliseconds: 50)); + if (shouldFail) { + throw Exception('ネットワーク接続が切断されました'); + } + return '正常データレスポンス'; +} + +Future handleRequest(bool shouldFail) async { + try { + print('リクエスト送信...'); + final data = await loadDataFromServer(shouldFail: shouldFail); + print('成功: $data'); + } catch (e) { + print('例外をキャッチ: $e'); + } finally { + print('リソースクリーンアップ完了\n'); + } +} + +void main() async { + await handleRequest(false); + await handleRequest(true); +} +``` + +```dart-exec:async_try_catch.dart +リクエスト送信... +成功: 正常データレスポンス +リソースクリーンアップ完了 + +リクエスト送信... +例外をキャッチ: Exception: ネットワーク接続が切断されました +リソースクリーンアップ完了 +``` + +> [!TIP] +> `await` を付け忘れた `Future` で例外が発生すると、`try-catch` ブロックをすり抜けて未処理の非同期例外(Uncaught asynchronous error)となるため注意してください。 diff --git a/public/docs/dart/9-async-future/5-0-summary.md b/public/docs/dart/9-async-future/5-0-summary.md new file mode 100644 index 00000000..f39ed56d --- /dev/null +++ b/public/docs/dart/9-async-future/5-0-summary.md @@ -0,0 +1,18 @@ +--- +id: dart-async-future-summary +title: この章のまとめ +level: 2 +question: [] +--- + +## この章のまとめ + +この章では、[[Dart]]における非同期処理の基礎について学びました。 + +* **シングルスレッドとイベントループ**: DartはイベントループによってUIスレッドを止めずに非同期タスクを実行する。 +* **`Future`**: 将来確定する単一の非同期処理結果を表すオブジェクト。 +* **`async` / `await`**: 直感的な構文で非同期コードを同期コードのように直線的に記述できる。 +* **`Future.wait`**: 複数の非同期処理を並行して実行し、すべての完了を待機する。 +* **非同期エラー処理**: `try-catch-finally` 構文で同期処理と同様に例外をハンドリングできる。 + +次の [[./10]] では、単一の値ではなく「時間の経過とともに連続して発生するイベントのストリーム」を扱う **`Stream`** について学びます。 diff --git a/public/docs/dart/9-async-future/5-1-practice1.md b/public/docs/dart/9-async-future/5-1-practice1.md new file mode 100644 index 00000000..209bd511 --- /dev/null +++ b/public/docs/dart/9-async-future/5-1-practice1.md @@ -0,0 +1,29 @@ +--- +id: dart-async-future-practice1 +title: '練習問題1: 非同期データフェッチのシミュレーション' +level: 3 +question: + - Future.delayed を使ったモック関数の作成方法を教えてください。 + - 非同期関数が例外をスローした場合の try-catch の動作を復習したいです。 +--- + +### 練習問題1: 非同期データフェッチのシミュレーション + +サーバーからユーザーデータを非同期に取得する関数 `fetchUser` を作成してください。 + +1. `Future> fetchUser(int id)` を定義する。 +2. `Future.delayed(Duration(milliseconds: 100))` で遅延を発生させる。 +3. `id <= 0` の場合は `Exception('無効なユーザーIDです: $id')` をスローする。 +4. 正しいIDの場合は `{'id': id, 'name': 'User_$id', 'points': 1500}` を返す。 +5. `main()` で正常系(`id: 1`)と異常系(`id: -1`)の呼び出しを `try-catch` で処理し、結果を出力する。 + +```dart:practice9_1.dart +// ここに関数を定義してください + +void main() async { + // ここで動作確認を行ってください +} +``` + +```dart-exec:practice9_1.dart +``` diff --git a/public/docs/dart/9-async-future/5-2-practice2.md b/public/docs/dart/9-async-future/5-2-practice2.md new file mode 100644 index 00000000..3a3d84d7 --- /dev/null +++ b/public/docs/dart/9-async-future/5-2-practice2.md @@ -0,0 +1,29 @@ +--- +id: dart-async-future-practice2 +title: '練習問題2: 複数の非同期処理の並行実行' +level: 3 +question: + - Future.wait で一部のFutureが失敗した場合の挙動はどうなりますか? + - 並行実行と逐次実行の処理時間の違いを教えてください。 +--- + +### 練習問題2: 複数の非同期処理の並行実行 + +複数のAPIエンドポイントから並行してデータを取得し、まとめるプログラムを作成してください。 + +1. 以下の2つの非同期関数を作成する。 + * `Future fetchConfig()`: 100ミリ秒後に `'AppConfig: v2.0'` を返す。 + * `Future> fetchNotifications()`: 150ミリ秒後に `['メンテ予告', '新着メッセージ']` を返す。 +2. `main()` 内で `Future.wait` を使用して両方を並行して実行・待機する。 +3. 取得した設定と通知一覧を整形して画面に出力する。 + +```dart:practice9_2.dart +// ここに関数を定義してください + +void main() async { + // ここで動作確認を行ってください +} +``` + +```dart-exec:practice9_2.dart +``` diff --git a/public/docs/dart/index.yml b/public/docs/dart/index.yml new file mode 100644 index 00000000..80867126 --- /dev/null +++ b/public/docs/dart/index.yml @@ -0,0 +1,42 @@ +name: Dart +description: Googleによって開発された、Flutterの基盤となるUI最適化・マルチプラットフォーム言語 +pages: +- slug: 0-intro + name: Dartへようこそ + title: Dartへようこそ +- slug: 1-basics + name: 基本構文 + title: 基本構文と組み込み型 +- slug: 2-null-safety + name: Null Safety + title: Null Safetyの完全理解 +- slug: 3-functions + name: 関数とクロージャ + title: 関数とクロージャ +- slug: 4-collections-control + name: コレクションと制御構文 + title: コレクションと制御構文 +- slug: 5-records-patterns + name: レコードとパターンマッチ + title: レコードとパターンマッチング +- slug: 6-classes + name: クラスとコンストラクタ + title: クラスとコンストラクタ +- slug: 7-class-extension + name: クラスの拡張と高度な設計 + title: クラスの拡張と高度な設計 +- slug: 8-class-modifiers + name: クラス修飾子とモデリング + title: クラス修飾子による厳密なモデリング +- slug: 9-async-future + name: '非同期処理: Future' + title: 非同期処理の基礎 (Futureとasync/await) +- slug: 10-async-stream + name: '非同期処理: Stream' + title: 非同期処理の応用 (Stream) +- slug: 11-error-handling + name: エラーハンドリング + title: エラーハンドリングとResultパターン +- slug: 12-concurrency-isolate + name: 並行処理とIsolate + title: 並行処理とIsolate From 67ef1642a8c44ffba91c919ea8ccb039743f17dc Mon Sep 17 00:00:00 2001 From: na-trium-144 <100704180+na-trium-144@users.noreply.github.com> Date: Fri, 14 Aug 2026 10:07:58 +0000 Subject: [PATCH 6/6] docs: split Dart docs into 1 heading per markdown file --- public/docs/dart/0-intro/1-0-features.md | 22 +---- public/docs/dart/0-intro/1-1-jit-aot.md | 21 +++++ public/docs/dart/0-intro/1-2-ui-optimized.md | 18 ++++ public/docs/dart/0-intro/2-0-flutter.md | 10 --- public/docs/dart/0-intro/2-1-why-flutter.md | 19 +++++ public/docs/dart/0-intro/3-0-install.md | 44 ---------- .../docs/dart/0-intro/3-1-install-methods.md | 36 ++++++++ public/docs/dart/0-intro/3-2-create-run.md | 29 +++++++ public/docs/dart/0-intro/4-0-main.md | 27 +----- public/docs/dart/0-intro/4-1-args.md | 31 +++++++ public/docs/dart/1-basics/1-0-variables.md | 46 +--------- .../dart/1-basics/1-1-var-type-inference.md | 44 ++++++++++ public/docs/dart/1-basics/1-2-final-const.md | 63 -------------- ...ic-object.md => 1-2-var-dynamic-object.md} | 11 +-- public/docs/dart/1-basics/1-3-final-const.md | 43 ++++++++++ .../docs/dart/1-basics/2-0-builtin-types.md | 72 +--------------- public/docs/dart/1-basics/2-1-numbers.md | 41 +++++++++ public/docs/dart/1-basics/2-1-operators.md | 85 ------------------- public/docs/dart/1-basics/2-2-strings.md | 44 ++++++++++ public/docs/dart/1-basics/3-0-operators.md | 17 ++++ public/docs/dart/1-basics/3-1-arithmetic.md | 35 ++++++++ public/docs/dart/1-basics/3-2-type-test.md | 42 +++++++++ .../{3-0-summary.md => 4-0-summary.md} | 0 .../{3-1-practice1.md => 4-1-practice1.md} | 0 .../{3-2-practice2.md => 4-2-practice2.md} | 0 public/docs/dart/10-async-stream/-intro.md | 6 +- .../10-async-stream/1-0-stream-concepts.md | 53 ++++-------- .../dart/10-async-stream/1-1-stream-types.md | 21 +++++ .../dart/10-async-stream/2-0-await-for.md | 63 +++++--------- .../10-async-stream/2-1-stream-operators.md | 36 ++++++++ .../10-async-stream/3-0-stream-controller.md | 50 ++--------- .../3-1-stream-subscription.md | 53 ++++++++++++ .../10-async-stream/4-0-async-generator.md | 39 ++++----- .../docs/dart/10-async-stream/5-0-summary.md | 14 +-- .../dart/10-async-stream/5-1-practice1.md | 13 ++- .../dart/10-async-stream/5-2-practice2.md | 21 +++-- public/docs/dart/11-error-handling/-intro.md | 7 +- .../dart/11-error-handling/1-0-try-catch.md | 66 +++++++------- .../docs/dart/11-error-handling/2-0-assert.md | 38 +++------ .../11-error-handling/3-0-result-pattern.md | 49 +++++------ .../dart/11-error-handling/4-0-summary.md | 13 +-- .../dart/11-error-handling/4-1-practice1.md | 20 ++--- .../dart/11-error-handling/4-2-practice2.md | 21 ++--- .../dart/12-concurrency-isolate/-intro.md | 10 +-- .../1-0-single-thread-model.md | 53 ++++++------ .../2-0-isolate-intro.md | 43 ++++------ .../3-0-isolate-ports.md | 61 +++++-------- .../12-concurrency-isolate/4-0-summary.md | 19 ++--- .../12-concurrency-isolate/4-1-practice1.md | 16 ++-- .../12-concurrency-isolate/4-2-practice2.md | 17 ++-- public/docs/dart/2-null-safety/1-0-types.md | 26 ------ .../dart/2-null-safety/1-1-flow-analysis.md | 37 ++++++++ .../dart/2-null-safety/2-0-null-assertion.md | 14 +-- .../2-null-safety/3-0-null-aware-operators.md | 68 +-------------- .../2-null-safety/3-1-conditional-access.md | 33 +++++++ .../dart/2-null-safety/3-2-null-coalescing.md | 33 +++++++ .../dart/2-null-safety/3-3-null-assignment.md | 31 +++++++ public/docs/dart/2-null-safety/4-0-late.md | 64 +------------- .../docs/dart/2-null-safety/4-1-late-init.md | 43 ++++++++++ .../docs/dart/2-null-safety/5-1-practice1.md | 2 +- .../docs/dart/2-null-safety/5-2-practice2.md | 2 +- .../docs/dart/3-functions/1-0-first-class.md | 53 +----------- .../docs/dart/3-functions/1-1-arrow-syntax.md | 36 ++++++++ .../docs/dart/3-functions/2-0-parameters.md | 12 +-- ...amed-positional.md => 2-1-named-params.md} | 33 +------ .../dart/3-functions/2-2-positional-params.md | 31 +++++++ .../3-functions/3-0-anonymous-closures.md | 68 +-------------- public/docs/dart/3-functions/3-1-anonymous.md | 38 +++++++++ public/docs/dart/3-functions/3-2-closures.md | 45 ++++++++++ .../4-collections-control/1-0-collections.md | 72 +--------------- .../dart/4-collections-control/1-1-list.md | 33 +++++++ .../dart/4-collections-control/1-2-set.md | 35 ++++++++ .../dart/4-collections-control/1-3-map.md | 34 ++++++++ .../2-0-collection-features.md | 79 +---------------- .../2-1-collection-if.md | 34 ++++++++ .../2-2-collection-for.md | 30 +++++++ .../2-3-spread-operator.md | 36 ++++++++ .../4-collections-control/3-0-higher-order.md | 69 +-------------- .../4-collections-control/3-1-map-where.md | 38 +++++++++ .../4-collections-control/3-2-reduce-fold.md | 41 +++++++++ .../dart/5-records-patterns/1-0-records.md | 52 +----------- .../1-1-positional-records.md | 33 +++++++ .../5-records-patterns/1-2-named-records.md | 33 +++++++ .../5-records-patterns/2-0-destructuring.md | 51 +---------- .../2-1-destructure-records.md | 32 +++++++ .../2-2-destructure-collections.md | 35 ++++++++ .../3-0-switch-expressions.md | 47 +--------- .../3-1-pattern-matching.md | 45 ++++++++++ .../dart/5-records-patterns/4-0-guards.md | 1 - public/docs/dart/6-classes/1-0-classes.md | 12 +-- .../docs/dart/6-classes/2-0-constructors.md | 63 ++------------ .../dart/6-classes/2-1-named-redirecting.md | 56 ++++++++++++ .../dart/6-classes/3-0-initializer-super.md | 69 +-------------- .../docs/dart/6-classes/3-1-super-params.md | 45 ++++++++++ .../4-0-encapsulation-getter-setter.md | 55 +----------- .../dart/6-classes/4-1-private-get-set.md | 57 +++++++++++++ .../6-classes/5-0-factory-constructors.md | 78 +---------------- public/docs/dart/6-classes/5-1-singleton.md | 41 +++++++++ public/docs/dart/6-classes/5-2-from-json.md | 48 +++++++++++ .../1-0-extends-implements.md | 50 +---------- .../1-1-extends-implements-details.md | 54 ++++++++++++ .../docs/dart/7-class-extension/2-0-mixins.md | 7 +- .../3-0-extension-methods.md | 10 --- .../7-class-extension/4-0-enhanced-enums.md | 2 - .../8-class-modifiers/1-0-sealed-classes.md | 2 +- .../8-class-modifiers/2-0-other-modifiers.md | 42 +-------- .../8-class-modifiers/2-1-base-modifier.md | 25 ++++++ .../2-2-interface-modifier.md | 29 +++++++ .../8-class-modifiers/2-3-final-modifier.md | 26 ++++++ .../8-class-modifiers/3-0-domain-modeling.md | 9 +- .../dart/9-async-future/1-0-event-loop.md | 7 +- public/docs/dart/9-async-future/2-0-future.md | 43 +--------- .../dart/9-async-future/2-1-future-details.md | 47 ++++++++++ .../dart/9-async-future/3-0-async-await.md | 28 ------ .../dart/9-async-future/3-1-future-wait.md | 36 ++++++++ .../4-0-async-error-handling.md | 6 +- .../docs/dart/9-async-future/5-2-practice2.md | 19 ++--- 117 files changed, 2143 insertions(+), 1924 deletions(-) create mode 100644 public/docs/dart/0-intro/1-1-jit-aot.md create mode 100644 public/docs/dart/0-intro/1-2-ui-optimized.md create mode 100644 public/docs/dart/0-intro/2-1-why-flutter.md create mode 100644 public/docs/dart/0-intro/3-1-install-methods.md create mode 100644 public/docs/dart/0-intro/3-2-create-run.md create mode 100644 public/docs/dart/0-intro/4-1-args.md create mode 100644 public/docs/dart/1-basics/1-1-var-type-inference.md delete mode 100644 public/docs/dart/1-basics/1-2-final-const.md rename public/docs/dart/1-basics/{1-1-var-dynamic-object.md => 1-2-var-dynamic-object.md} (77%) create mode 100644 public/docs/dart/1-basics/1-3-final-const.md create mode 100644 public/docs/dart/1-basics/2-1-numbers.md delete mode 100644 public/docs/dart/1-basics/2-1-operators.md create mode 100644 public/docs/dart/1-basics/2-2-strings.md create mode 100644 public/docs/dart/1-basics/3-0-operators.md create mode 100644 public/docs/dart/1-basics/3-1-arithmetic.md create mode 100644 public/docs/dart/1-basics/3-2-type-test.md rename public/docs/dart/1-basics/{3-0-summary.md => 4-0-summary.md} (100%) rename public/docs/dart/1-basics/{3-1-practice1.md => 4-1-practice1.md} (100%) rename public/docs/dart/1-basics/{3-2-practice2.md => 4-2-practice2.md} (100%) create mode 100644 public/docs/dart/10-async-stream/1-1-stream-types.md create mode 100644 public/docs/dart/10-async-stream/2-1-stream-operators.md create mode 100644 public/docs/dart/10-async-stream/3-1-stream-subscription.md create mode 100644 public/docs/dart/2-null-safety/1-1-flow-analysis.md create mode 100644 public/docs/dart/2-null-safety/3-1-conditional-access.md create mode 100644 public/docs/dart/2-null-safety/3-2-null-coalescing.md create mode 100644 public/docs/dart/2-null-safety/3-3-null-assignment.md create mode 100644 public/docs/dart/2-null-safety/4-1-late-init.md create mode 100644 public/docs/dart/3-functions/1-1-arrow-syntax.md rename public/docs/dart/3-functions/{2-1-named-positional.md => 2-1-named-params.md} (54%) create mode 100644 public/docs/dart/3-functions/2-2-positional-params.md create mode 100644 public/docs/dart/3-functions/3-1-anonymous.md create mode 100644 public/docs/dart/3-functions/3-2-closures.md create mode 100644 public/docs/dart/4-collections-control/1-1-list.md create mode 100644 public/docs/dart/4-collections-control/1-2-set.md create mode 100644 public/docs/dart/4-collections-control/1-3-map.md create mode 100644 public/docs/dart/4-collections-control/2-1-collection-if.md create mode 100644 public/docs/dart/4-collections-control/2-2-collection-for.md create mode 100644 public/docs/dart/4-collections-control/2-3-spread-operator.md create mode 100644 public/docs/dart/4-collections-control/3-1-map-where.md create mode 100644 public/docs/dart/4-collections-control/3-2-reduce-fold.md create mode 100644 public/docs/dart/5-records-patterns/1-1-positional-records.md create mode 100644 public/docs/dart/5-records-patterns/1-2-named-records.md create mode 100644 public/docs/dart/5-records-patterns/2-1-destructure-records.md create mode 100644 public/docs/dart/5-records-patterns/2-2-destructure-collections.md create mode 100644 public/docs/dart/5-records-patterns/3-1-pattern-matching.md create mode 100644 public/docs/dart/6-classes/2-1-named-redirecting.md create mode 100644 public/docs/dart/6-classes/3-1-super-params.md create mode 100644 public/docs/dart/6-classes/4-1-private-get-set.md create mode 100644 public/docs/dart/6-classes/5-1-singleton.md create mode 100644 public/docs/dart/6-classes/5-2-from-json.md create mode 100644 public/docs/dart/7-class-extension/1-1-extends-implements-details.md create mode 100644 public/docs/dart/8-class-modifiers/2-1-base-modifier.md create mode 100644 public/docs/dart/8-class-modifiers/2-2-interface-modifier.md create mode 100644 public/docs/dart/8-class-modifiers/2-3-final-modifier.md create mode 100644 public/docs/dart/9-async-future/2-1-future-details.md create mode 100644 public/docs/dart/9-async-future/3-1-future-wait.md diff --git a/public/docs/dart/0-intro/1-0-features.md b/public/docs/dart/0-intro/1-0-features.md index beac672d..7ff96a23 100644 --- a/public/docs/dart/0-intro/1-0-features.md +++ b/public/docs/dart/0-intro/1-0-features.md @@ -3,32 +3,14 @@ id: dart-intro-features title: Dartの特徴(JIT/AOTコンパイル、UI最適化言語) level: 2 question: - - JITコンパイルとAOTコンパイルの両方をサポートしているメリットは何ですか? - - なぜDartはUI開発に最適化された言語と言われるのですか? + - なぜDartはクライアント開発と相性が良いのですか? - JavaScriptやTypeScriptと比べたDartの強みは何ですか? term: - Dart - - JITコンパイル - - AOTコンパイル - - JIT - - AOT --- ## Dartの特徴(JIT/AOTコンパイル、UI最適化言語) [[Dart]]は、美しく高速なユーザーインターフェース(UI)を構築するために設計された、オブジェクト指向の型安全なプログラミング言語です。 -### 1. JITとAOTのハイブリッドな実行基盤 - -Dart最大の特徴の1つは、**[[JITコンパイル]](Just-In-Time)** と **[[AOTコンパイル]](Ahead-Of-Time)** の両方に対応している点です。 - -* **開発時(JIT)**: ソースコードを実行時に即座に解釈・コンパイルします。これにより、コード変更を瞬時にアプリへ反映する**ホットリロード(Hot Reload)**が可能になり、開発サイクルが劇的に高速化します。 -* **リリース時(AOT)**: 各プラットフォーム(ARMやx86のネイティブ機械語、あるいは最適化されたJavaScript/WebAssembly)へ事前にコンパイルします。起動の速さとなめらかな描画フレームレート(60fps / 120fps)を実現します。 - -### 2. UI構築に最適化された言語機能 - -Dartはユーザーインターフェースのツリー構造を宣言的に記述しやすくするため、言語仕様レベルで多くの工夫が施されています。 - -* **オブジェクト生成時の `new` 省略**: ネストしたUIコンポーネントをスッキリ記述できます。 -* **コレクション要素内の制御構文**: 配列やリストの内部で直接 `if` や `for`、スプレッド演算子(`...`)が使えます([[./4]]で学習)。 -* **健全な型システムと[[Null Safety]]**: 実行時クラッシュの原因となりやすいNull関連エラーをコンパイル時に検知します([[./2]]で学習)。 +クライアントサイド(モバイル、Web、デスクトップ)の開発体験を極限まで高めるため、実行基盤や言語構文に独自の工夫が施されています。 diff --git a/public/docs/dart/0-intro/1-1-jit-aot.md b/public/docs/dart/0-intro/1-1-jit-aot.md new file mode 100644 index 00000000..5cdcca95 --- /dev/null +++ b/public/docs/dart/0-intro/1-1-jit-aot.md @@ -0,0 +1,21 @@ +--- +id: dart-intro-jit-aot +title: JITとAOTのハイブリッドな実行基盤 +level: 3 +question: + - JITコンパイルとAOTコンパイルの両方をサポートしているメリットは何ですか? + - ホットリロードはどのような仕組みで動いていますか? +term: + - JITコンパイル + - AOTコンパイル + - JIT + - AOT + - ホットリロード +--- + +### JITとAOTのハイブリッドな実行基盤 + +Dart最大の特徴の1つは、**[[JITコンパイル]](Just-In-Time)** と **[[AOTコンパイル]](Ahead-Of-Time)** の両方に対応している点です。 + +* **開発時(JIT)**: ソースコードを実行時に即座に解釈・コンパイルします。これにより、コード変更を瞬時にアプリへ反映する **[[ホットリロード]](Hot Reload)** が可能になり、開発サイクルが劇的に高速化します。 +* **リリース時(AOT)**: 各プラットフォーム(ARMやx86のネイティブ機械語、あるいは最適化されたJavaScript/WebAssembly)へ事前にコンパイルします。起動の速さとなめらかな描画フレームレート(60fps / 120fps)を実現します。 diff --git a/public/docs/dart/0-intro/1-2-ui-optimized.md b/public/docs/dart/0-intro/1-2-ui-optimized.md new file mode 100644 index 00000000..acbd9579 --- /dev/null +++ b/public/docs/dart/0-intro/1-2-ui-optimized.md @@ -0,0 +1,18 @@ +--- +id: dart-intro-ui-optimized +title: UI構築に最適化された言語機能 +level: 3 +question: + - なぜDartはUI開発に最適化された言語と言われるのですか? + - オブジェクト生成時のnew省略はどのようにUIコードをすっきりさせますか? +term: + - UI最適化 +--- + +### UI構築に最適化された言語機能 + +Dartはユーザーインターフェースのツリー構造を宣言的に記述しやすくするため、言語仕様レベルで多くの工夫が施されています。 + +* **オブジェクト生成時の `new` 省略**: ネストしたUIコンポーネントをスッキリ記述できます。 +* **コレクション要素内の制御構文**: 配列やリストの内部で直接 `if` や `for`、スプレッド演算子(`...`)が使えます([[./4]]で学習)。 +* **健全な型システムと[[Null Safety]]**: 実行時クラッシュの原因となりやすいNull関連エラーをコンパイル時に検知します([[./2]]で学習)。 diff --git a/public/docs/dart/0-intro/2-0-flutter.md b/public/docs/dart/0-intro/2-0-flutter.md index 1ff34125..c553b232 100644 --- a/public/docs/dart/0-intro/2-0-flutter.md +++ b/public/docs/dart/0-intro/2-0-flutter.md @@ -5,7 +5,6 @@ level: 2 question: - DartとFlutterの関係はどうなっていますか? - Flutter以外の場所でもDartは使えますか? - - Dart単体でサーバーサイドアプリケーションを書くことはできますか? term: - Flutter - フラッター @@ -29,12 +28,3 @@ Flutterは、単一のコードベースからiOS、Android、Web、macOS、Wind | Flutter Engine (C++ / Skia・Impeller) | +-------------------------------------------------------------+ ``` - -### DartがFlutterに選ばれた理由 - -1. **高速な開発体験**: JITによるミリ秒単位のステートフル・ホットリロード。 -2. **高い実行時パフォーマンス**: AOTコンパイルによるネイティブバイナリの生成と、UIの頻繁なオブジェクト生成・破棄に特化した世代別ガベージコレクション。 -3. **宣言的UIとの親和性**: 特別なマークアップ言語(XMLやJSXなど)を必要とせず、Dart言語そのものでUIツリーをきれいに記述可能。 - -> [!NOTE] -> DartはFlutter専用言語ではありません。`dart:io` や `shelf` などのライブラリを用いてCLIツールやバックエンドAPIサーバーの開発にも活用されています。 diff --git a/public/docs/dart/0-intro/2-1-why-flutter.md b/public/docs/dart/0-intro/2-1-why-flutter.md new file mode 100644 index 00000000..859a3858 --- /dev/null +++ b/public/docs/dart/0-intro/2-1-why-flutter.md @@ -0,0 +1,19 @@ +--- +id: dart-intro-why-flutter +title: DartがFlutterに選ばれた理由 +level: 3 +question: + - なぜFlutterはJavaScriptやC++ではなくDartを選んだのですか? + - Dart単体でサーバーサイドアプリケーションを書くことはできますか? +term: + - 宣言的UI +--- + +### DartがFlutterに選ばれた理由 + +1. **高速な開発体験**: JITによるミリ秒単位のステートフル・ホットリロード。 +2. **高い実行時パフォーマンス**: AOTコンパイルによるネイティブバイナリの生成と、UIの頻繁なオブジェクト生成・破棄に特化した世代別ガベージコレクション。 +3. **[[宣言的UI]]との親和性**: 特別なマークアップ言語(XMLやJSXなど)を必要とせず、Dart言語そのものでUIツリーをきれいに記述可能。 + +> [!NOTE] +> DartはFlutter専用言語ではありません。`dart:io` や `shelf` などのライブラリを用いてCLIツールやバックエンドAPIサーバーの開発にも活用されています。 diff --git a/public/docs/dart/0-intro/3-0-install.md b/public/docs/dart/0-intro/3-0-install.md index a34f1470..6ae506b7 100644 --- a/public/docs/dart/0-intro/3-0-install.md +++ b/public/docs/dart/0-intro/3-0-install.md @@ -4,7 +4,6 @@ title: Dartのインストール level: 2 question: - FlutterをインストールすればDart SDKも一緒に含まれますか? - - Dart公式のパッケージマネージャは何ですか? - pubspec.yaml とは何をするファイルですか? term: - Dart SDK @@ -19,46 +18,3 @@ term: > [!TIP] > 既に [[Flutter]] をインストールしている場合、Flutter SDKにDart SDKがバンドルされているため、個別のDartインストールは不要です。 - -### 1. インストール方法 - -* **macOS (Homebrew)**: - ```bash - brew tap dart-lang/dart - brew install dart - ``` -* **Windows (Chocolatey / Scoop / Winget)**: - ```bash - winget install Dart.Dart-SDK - ``` -* **Linux (apt)**: - ```bash - sudo apt-get install dart - ``` - -### 2. インストールの確認 - -ターミナルで `dart` コマンドを実行し、バージョンが表示されるか確認します。 - -```bash -$ dart --version -Dart SDK version: 3.x.x -``` - -### 3. プロジェクトの作成と実行 - -Dartでは、プロジェクト作成や依存関係管理、コード整形、テストなどのツールチェーンがすべて `dart` コマンドに統合されています。 - -```bash -# 新しいコンソールプロジェクトを作成 -$ dart create my_dart_app - -# プロジェクトディレクトリへ移動 -$ cd my_dart_app - -# プログラムの実行 -$ dart run -Hello world: 42! -``` - -Dartのプロジェクト構成やライブラリの依存管理は、プロジェクトルートにある `pubspec.yaml` ファイルで行います。 diff --git a/public/docs/dart/0-intro/3-1-install-methods.md b/public/docs/dart/0-intro/3-1-install-methods.md new file mode 100644 index 00000000..94f5a0a3 --- /dev/null +++ b/public/docs/dart/0-intro/3-1-install-methods.md @@ -0,0 +1,36 @@ +--- +id: dart-intro-install-methods +title: インストール方法 +level: 3 +question: + - macOSやWindowsでのDartのインストールコマンドは何ですか? + - インストールが成功したか確認する方法を教えてください。 +term: + - Homebrew + - Chocolatey +--- + +### インストール方法 + +主要なOSでのインストール手順は以下の通りです。 + +* **macOS (Homebrew)**: + ```bash + brew tap dart-lang/dart + brew install dart + ``` +* **Windows (Winget / Chocolatey)**: + ```bash + winget install Dart.Dart-SDK + ``` +* **Linux (apt)**: + ```bash + sudo apt-get install dart + ``` + +ターミナルで `dart` コマンドを実行し、バージョンが表示されれば完了です。 + +```bash +$ dart --version +Dart SDK version: 3.x.x +``` diff --git a/public/docs/dart/0-intro/3-2-create-run.md b/public/docs/dart/0-intro/3-2-create-run.md new file mode 100644 index 00000000..9a35fac3 --- /dev/null +++ b/public/docs/dart/0-intro/3-2-create-run.md @@ -0,0 +1,29 @@ +--- +id: dart-intro-create-run +title: プロジェクトの作成と実行 +level: 3 +question: + - dart create コマンドで新規プロジェクトを作成する方法は? + - プロジェクトの設定やライブラリ追加はどのファイルで行いますか? +term: + - dart create + - dart run +--- + +### プロジェクトの作成と実行 + +Dartでは、プロジェクト作成や依存関係管理、コード整形、テストなどのツールチェーンがすべて `dart` コマンドに統合されています。 + +```bash +# 新しいコンソールプロジェクトを作成 +$ dart create my_dart_app + +# プロジェクトディレクトリへ移動 +$ cd my_dart_app + +# プログラムの実行 +$ dart run +Hello world: 42! +``` + +Dartのプロジェクト構成やライブラリの依存管理は、プロジェクトルートにある `pubspec.yaml` ファイルで行います。 diff --git a/public/docs/dart/0-intro/4-0-main.md b/public/docs/dart/0-intro/4-0-main.md index 10efad13..a8071a08 100644 --- a/public/docs/dart/0-intro/4-0-main.md +++ b/public/docs/dart/0-intro/4-0-main.md @@ -4,8 +4,7 @@ title: 'エントリーポイント: main() 関数' level: 2 question: - main関数の戻り値型はvoid以外も使えますか? - - コマンドライン引数をmain関数で受け取るにはどうすればよいですか? - - print関数で改行なしの出力を行うことはできますか? + - print関数で出力した文字列の末尾には自動的に改行が入りますか? term: - main関数 - main() @@ -17,8 +16,6 @@ term: C言語、Java、Rustなどと同様に、[[Dart]]プログラムはトップレベルの **[[main関数]](`main()`)** から実行が始まります。 -まずは最もシンプルな "Hello, World!" プログラムを見てみましょう。 - ```dart:hello_world.dart void main() { print('Hello, Dart!'); @@ -29,29 +26,7 @@ void main() { Hello, Dart! ``` -### コードの解説 - * `void`: `main` 関数が値を返さないことを表します。 * `main()`: アプリケーションのエントリーポイントとなる関数名です。 * `print(...)`: 文字列などのオブジェクトを標準出力に出力し、末尾に改行を追加します。 * `;` (セミコロン): Dartでは各ステートメントの末尾にセミコロンが必須です。 - -### コマンドライン引数の受け取り - -外部からの引数を受け取る場合は、引数に `List args` を指定します。 - -```dart:args_example.dart -void main(List args) { - if (args.isEmpty) { - print('引数が渡されていません。'); - } else { - print('受け取った引数: $args'); - } -} -``` - -```dart-exec:args_example.dart -引数が渡されていません。 -``` - -次の [[./1]] では、Dartの型システム、変数宣言、および基本的な演算子について詳しく学んでいきます。 diff --git a/public/docs/dart/0-intro/4-1-args.md b/public/docs/dart/0-intro/4-1-args.md new file mode 100644 index 00000000..252d1cb3 --- /dev/null +++ b/public/docs/dart/0-intro/4-1-args.md @@ -0,0 +1,31 @@ +--- +id: dart-intro-args +title: コマンドライン引数の受け取り +level: 3 +question: + - コマンドライン引数をmain関数で受け取るにはどうすればよいですか? + - argsの型は何になりますか? +term: + - コマンドライン引数 + - args +--- + +### コマンドライン引数の受け取り + +外部からの引数を受け取る場合は、引数に `List args` を指定します。 + +```dart:args_example.dart +void main(List args) { + if (args.isEmpty) { + print('引数が渡されていません。'); + } else { + print('受け取った引数: $args'); + } +} +``` + +```dart-exec:args_example.dart +引数が渡されていません。 +``` + +次の [[./1]] では、Dartの型システム、変数宣言、および基本的な演算子について詳しく学んでいきます。 diff --git a/public/docs/dart/1-basics/1-0-variables.md b/public/docs/dart/1-basics/1-0-variables.md index af04e9a8..d175b723 100644 --- a/public/docs/dart/1-basics/1-0-variables.md +++ b/public/docs/dart/1-basics/1-0-variables.md @@ -5,7 +5,6 @@ level: 2 question: - 型を明示する場合とvarを使う場合の使い分けの目安はありますか? - 初期化せずに変数宣言したときの初期値は何ですか? - - Dartで変数名に使える命名規則はどうなっていますか? term: - 変数 - 変数宣言 @@ -16,47 +15,4 @@ term: [[Dart]]は静的型付け言語ですが、初期値から型を自動で推論する **[[型推論]]** を強力にサポートしています。 -変数を宣言する方法は、主に以下の2通りがあります。 - -### 1. `var` による型推論 - -初期値が存在する場合、`var` キーワードを使うとコンパイラが自動的に型を決定します。 - -```dart:variables_intro.dart -void main() { - var name = 'Dart'; // String 型と推論される - var version = 3; // int 型と推論される - - print('$name のバージョン: $version'); -} -``` - -```dart-exec:variables_intro.dart -Dart のバージョン: 3 -``` - -一度型が推論された変数に、異なる型の値を代入しようとするとコンパイルエラーになります。 - -```dart -var score = 100; -// score = '満点'; // コンパイルエラー: A value of type 'String' can't be assigned to a variable of type 'int'. -``` - -### 2. 型を明示する宣言 - -型を明示的に記述することも可能です。変数の意図をコード上で強調したい場合や、初期値を後から代入する場合に使われます。 - -```dart:type_explicit.dart -void main() { - String message = 'Hello'; - int count = 10; - double price = 99.9; - bool isActive = true; - - print('$message: $count 件, 価格: $price 円, 有効: $isActive'); -} -``` - -```dart-exec:type_explicit.dart -Hello: 10 件, 価格: 99.9 円, 有効: true -``` +Dartでの変数宣言は、`var` による型推論と、型を明示する宣言の2通りが基本となります。 diff --git a/public/docs/dart/1-basics/1-1-var-type-inference.md b/public/docs/dart/1-basics/1-1-var-type-inference.md new file mode 100644 index 00000000..59ef7381 --- /dev/null +++ b/public/docs/dart/1-basics/1-1-var-type-inference.md @@ -0,0 +1,44 @@ +--- +id: dart-basics-type-inference +title: var による型推論と型明示 +level: 3 +question: + - varで宣言した変数に異なる型の値を再代入できますか? + - 型を明示するべき場面はどのようなときですか? +term: + - var + - 静的型付け +--- + +### `var` による型推論と型明示 + +初期値が存在する場合、`var` キーワードを使うとコンパイラが自動的に型を決定します。 + +```dart:variables_intro.dart +void main() { + var name = 'Dart'; // String 型と推論される + var version = 3; // int 型と推論される + + print('$name のバージョン: $version'); +} +``` + +```dart-exec:variables_intro.dart +Dart のバージョン: 3 +``` + +一度型が推論された変数に異なる型の値を代入しようとすると、コンパイルエラーになります。 + +型を明示的に記述することも可能です。 + +```dart:type_explicit.dart +void main() { + String message = 'Hello'; + int count = 10; + print('$message: $count 件'); +} +``` + +```dart-exec:type_explicit.dart +Hello: 10 件 +``` diff --git a/public/docs/dart/1-basics/1-2-final-const.md b/public/docs/dart/1-basics/1-2-final-const.md deleted file mode 100644 index 8a11ec58..00000000 --- a/public/docs/dart/1-basics/1-2-final-const.md +++ /dev/null @@ -1,63 +0,0 @@ ---- -id: dart-basics-final-const -title: final と const の使い分け -level: 3 -question: - - finalとconstの最も重要な違いは何ですか? - - DateTime.now()をconstに代入できないのはなぜですか? - - constコンストラクタを使うとFlutterでパフォーマンスが上がるのはなぜですか? -term: - - final - - const - - コンパイル時定数 - - 不変 ---- - -### `final` と `const` の使い分け - -Dartで値の再代入を禁止する変数(定数)を宣言するには、`final` または `const` を使用します。 - -### 1. `final`: 実行時定数(一度だけ代入可能) - -`final` で宣言された変数は、**実行時** に値が決まる定数です。一度代入した後は変更できません。 - -```dart -final currentTime = DateTime.now(); // 実行時の現在時刻を代入可能 -// currentTime = DateTime.now(); // エラー: 再代入不可 -``` - -### 2. `const`: コンパイル時定数 - -`const` は、**コンパイル時** に値が完全に確定している定数です。 - -```dart -const double pi = 3.1415926535; -const int secondsInMinute = 60; -// const currentTime = DateTime.now(); // コンパイルエラー: 実行時にしか決まらない -``` - -### 比較コード例 - -```dart:final_const.dart -void main() { - final now = DateTime.now(); - const maxItems = 100; - - // constリストは内容の変更も不可(ディープイミュータブル) - const list = [1, 2, 3]; - // list.add(4); // 実行時エラー (Unsupported operation) - - print('現在時刻 (final): $now'); - print('最大件数 (const): $maxItems'); - print('定数リスト: $list'); -} -``` - -```dart-exec:final_const.dart -現在時刻 (final): 2026-08-14 05:20:00.000 -最大件数 (const): 100 -定数リスト: [1, 2, 3] -``` - -> [!TIP] -> Flutterでは、不変なWidgetの生成に `const` を付与することで、フレーム再描画時にインスタンスが再生成されるのを防ぎ、メモリ消費とレンダリング負荷を大幅に削減できます。 diff --git a/public/docs/dart/1-basics/1-1-var-dynamic-object.md b/public/docs/dart/1-basics/1-2-var-dynamic-object.md similarity index 77% rename from public/docs/dart/1-basics/1-1-var-dynamic-object.md rename to public/docs/dart/1-basics/1-2-var-dynamic-object.md index 010ead1b..5a8b48f9 100644 --- a/public/docs/dart/1-basics/1-1-var-dynamic-object.md +++ b/public/docs/dart/1-basics/1-2-var-dynamic-object.md @@ -5,9 +5,7 @@ level: 3 question: - dynamicとObjectの違いは何ですか? - dynamic型を使うべきシチュエーションはどのようなときですか? - - varで宣言した変数を初期化しなかった場合はどうなりますか? term: - - var - dynamic - Object - 動的型 @@ -27,15 +25,14 @@ Dartには一見似ているように思える `var`, `dynamic`, `Object` とい void main() { // 1. dynamic: 静的型チェックを完全にバイパスする dynamic value = 'Hello'; - print('dynamic: ${value.length}'); // 実行可能 - value = 123; // 異なる型の再代入もOK + print('dynamic: ${value.length}'); + value = 123; // 異なる型の再代入もOK print('dynamic(int): $value'); // 2. Object: すべての非Nullオブジェクトの基底クラス (型安全) Object obj = 'World'; - // print(obj.length); // コンパイルエラー: Object型には length プロパティがない if (obj is String) { - // 型チェック (is) を通すとスマートキャストされる + // is チェックでスマートキャストされる print('Object(String): ${obj.length}'); } } @@ -48,4 +45,4 @@ Object(String): 5 ``` > [!WARNING] -> `dynamic` は実行時までエラーが発覚しないため、JSONのデコードなど型が未知の境界領域以外では極力使用を避け、型安全なコードを心がけましょう。 +> `dynamic` は実行時までエラーが発覚しないため、JSONのデコードなど型が未知の境界領域以外では極力使用を避けましょう。 diff --git a/public/docs/dart/1-basics/1-3-final-const.md b/public/docs/dart/1-basics/1-3-final-const.md new file mode 100644 index 00000000..876cced5 --- /dev/null +++ b/public/docs/dart/1-basics/1-3-final-const.md @@ -0,0 +1,43 @@ +--- +id: dart-basics-final-const +title: final と const の使い分け +level: 3 +question: + - finalとconstの最も重要な違いは何ですか? + - DateTime.now()をconstに代入できないのはなぜですか? +term: + - final + - const + - コンパイル時定数 + - 不変 +--- + +### `final` と `const` の使い分け + +Dartで値の再代入を禁止する変数(定数)を宣言するには、`final` または `const` を使用します。 + +* **`final`(実行時定数)**: 実行時に一度だけ初期化され、以降は変更できません。`DateTime.now()` などの実行時計算値も代入可能です。 +* **`const`(コンパイル時定数)**: コンパイル時に値が完全に確定している定数です。 + +```dart:final_const.dart +void main() { + final now = DateTime.now(); // 実行時に値が確定 + const maxItems = 100; // コンパイル時に確定 + + // constリストは要素の変更も不可(完全なイミュータブル) + const list = [1, 2, 3]; + + print('現在時刻 (final): $now'); + print('最大件数 (const): $maxItems'); + print('定数リスト: $list'); +} +``` + +```dart-exec:final_const.dart +現在時刻 (final): 2026-08-14 05:20:00.000 +最大件数 (const): 100 +定数リスト: [1, 2, 3] +``` + +> [!TIP] +> Flutterでは不変なWidgetの生成に `const` を付与することで、再描画時のインスタンス再生成を防止してパフォーマンスを高めます。 diff --git a/public/docs/dart/1-basics/2-0-builtin-types.md b/public/docs/dart/1-basics/2-0-builtin-types.md index c8a49dfe..b39f055f 100644 --- a/public/docs/dart/1-basics/2-0-builtin-types.md +++ b/public/docs/dart/1-basics/2-0-builtin-types.md @@ -4,8 +4,7 @@ title: 組み込み型と文字列操作 level: 2 question: - int型とdouble型の共通の親クラスは何ですか? - - Dartで文字列補間(String Interpolation)はどう書きますか? - - 複数行の文字列(ヒアドキュメント)はどう定義しますか? + - Dartの組み込み型はすべてオブジェクトですか? term: - 組み込み型 - int @@ -13,77 +12,10 @@ term: - num - String - bool - - 文字列補間 --- ## 組み込み型と文字列操作 [[Dart]]のすべての値はオブジェクトであり、数値や真偽値も含めて `Object` を継承しています。 -主要な組み込み型には以下があります。 - -* **`int`**: 任意精度または64ビット符号付き整数。 -* **`double`**: 64ビット倍精度浮動小数点数。 -* **`num`**: `int` と `double` の親クラス。整数と小数の両方を許容したい場合に使用。 -* **`String`**: UTF-16コードユニットのシーケンス。 -* **`bool`**: 真偽値(`true` または `false`)。 - -### 数値と型変換 - -```dart:numbers.dart -void main() { - int integer = 42; - double decimal = 3.14; - num both = 10; - both = 2.5; // num型ならdoubleも代入可能 - - // 文字列から数値への変換 - int parsedInt = int.parse('100'); - double parsedDouble = double.parse('12.34'); - - // 数値から文字列への変換 - String strInt = integer.toString(); - String fixedDec = decimal.toStringAsFixed(1); // "3.1" - - print('parsed: $parsedInt, $parsedDouble'); - print('converted: $strInt, $fixedDec'); -} -``` - -```dart-exec:numbers.dart -parsed: 100, 12.34 -converted: 42, 3.1 -``` - -### 文字列と文字列補間(String Interpolation) - -Dartでは、文字列リテラル内に `$変数名` や `${式}` を埋め込むことができます。シングルクォート `'` とダブルクォート `"` のどちらでも記述可能です。 - -```dart:strings.dart -void main() { - String language = 'Dart'; - int version = 3; - - // 文字列補間 - String greeting = 'Welcome to $language $version!'; - String calc = '1 + 1 = ${1 + 1}'; - - // 複数行文字列(トリプルクォート) - String multiLine = ''' -1行目のテキスト -2行目のテキスト -3行目のテキスト'''; - - print(greeting); - print(calc); - print(multiLine); -} -``` - -```dart-exec:strings.dart -Welcome to Dart 3! -1 + 1 = 2 -1行目のテキスト -2行目のテキスト -3行目のテキスト -``` +主要な組み込み型には、整数 `int`、浮動小数点数 `double`、その共通親型 `num`、文字列 `String`、真偽値 `bool` があります。 diff --git a/public/docs/dart/1-basics/2-1-numbers.md b/public/docs/dart/1-basics/2-1-numbers.md new file mode 100644 index 00000000..82f580c8 --- /dev/null +++ b/public/docs/dart/1-basics/2-1-numbers.md @@ -0,0 +1,41 @@ +--- +id: dart-basics-numbers +title: 数値と型変換 +level: 3 +question: + - 文字列からintやdoubleにパースする方法は? + - doubleを小数点第1位までに丸めて文字列にする方法は? +term: + - 型変換 + - int.parse + - double.parse +--- + +### 数値と型変換 + +`int` と `double` は相互に変換でき、文字列との相互変換もメソッドが用意されています。 + +```dart:numbers.dart +void main() { + int integer = 42; + double decimal = 3.14; + num both = 10; + both = 2.5; // num型ならdoubleも代入可能 + + // 文字列から数値への変換 + int parsedInt = int.parse('100'); + double parsedDouble = double.parse('12.34'); + + // 数値から文字列への変換 + String strInt = integer.toString(); + String fixedDec = decimal.toStringAsFixed(1); // "3.1" + + print('parsed: $parsedInt, $parsedDouble'); + print('converted: $strInt, $fixedDec'); +} +``` + +```dart-exec:numbers.dart +parsed: 100, 12.34 +converted: 42, 3.1 +``` diff --git a/public/docs/dart/1-basics/2-1-operators.md b/public/docs/dart/1-basics/2-1-operators.md deleted file mode 100644 index 0b2d9efa..00000000 --- a/public/docs/dart/1-basics/2-1-operators.md +++ /dev/null @@ -1,85 +0,0 @@ ---- -id: dart-basics-operators -title: 基本的な演算子 -level: 2 -question: - - 整数除算を行う演算子は何ですか? - - is や is! 演算子は何のために使われますか? - - 三項演算子やカスケード記法はどのように使いますか? -term: - - 演算子 - - 算術演算子 - - 比較演算子 - - 論理演算子 - - 型テスト演算子 - - 整数除算 ---- - -## 基本的な演算子 - -Dartには一般的な言語と同様の算術・比較・論理演算子に加えて、Dart特有の便利な演算子があります。 - -### 1. 算術演算子と整数除算 (`~/`) - -通常の除算 `/` は常に `double` を返します。商の整数部分のみを取得したい場合は **`~/`**(整数除算演算子)を使います。 - -```dart:operators_arithmetic.dart -void main() { - int a = 10; - int b = 3; - - print('加算 (+): ${a + b}'); - print('通常除算 (/): ${a / b}'); // double (3.3333333333333335) - print('整数除算 (~/): ${a ~/ b}'); // int (3) - print('剰余 (%): ${a % b}'); // int (1) -} -``` - -```dart-exec:operators_arithmetic.dart -加算 (+): 13 -通常除算 (/): 3.3333333333333335 -整数除算 (~/): 3 -剰余 (%): 1 -``` - -### 2. 型テスト演算子 (`is`, `is!`, `as`) - -オブジェクトが特定の型であるかを判定します。 - -* `is`: 指定した型であれば `true` -* `is!`: 指定した型でなければ `true` -* `as`: 型キャスト(型が合わない場合は実行時エラー) - -```dart:type_test.dart -void main() { - Object value = 'Dart Programming'; - - if (value is String) { - // ifスコープ内では value が String にスマートキャストされる - print('文字列の長さ: ${value.length}'); - } - - if (value is! int) { - print('value は int ではありません'); - } -} -``` - -```dart-exec:type_test.dart -文字列の長さ: 16 -value は int ではありません -``` - -### 3. 三項条件演算子 (`condition ? expr1 : expr2`) - -```dart:ternary.dart -void main() { - int score = 85; - String result = score >= 60 ? '合格' : '不合格'; - print('結果: $result'); -} -``` - -```dart-exec:ternary.dart -結果: 合格 -``` diff --git a/public/docs/dart/1-basics/2-2-strings.md b/public/docs/dart/1-basics/2-2-strings.md new file mode 100644 index 00000000..f3341eff --- /dev/null +++ b/public/docs/dart/1-basics/2-2-strings.md @@ -0,0 +1,44 @@ +--- +id: dart-basics-strings +title: 文字列と文字列補間 +level: 3 +question: + - Dartで文字列補間(String Interpolation)はどう書きますか? + - 複数行文字列(トリプルクォート)の使い方は? +term: + - 文字列補間 + - 複数行文字列 +--- + +### 文字列と文字列補間 + +Dartでは、文字列リテラル内に `$変数名` や `${式}` を埋め込むことができます。シングルクォート `'` とダブルクォート `"` のどちらでも記述可能です。 + +```dart:strings.dart +void main() { + String language = 'Dart'; + int version = 3; + + // 文字列補間 + String greeting = 'Welcome to $language $version!'; + String calc = '1 + 1 = ${1 + 1}'; + + // 複数行文字列(トリプルクォート) + String multiLine = ''' +1行目のテキスト +2行目のテキスト +3行目のテキスト'''; + + print(greeting); + print(calc); + print(multiLine); +} +``` + +```dart-exec:strings.dart +Welcome to Dart 3! +1 + 1 = 2 +1行目のテキスト +2行目のテキスト +3行目のテキスト +``` diff --git a/public/docs/dart/1-basics/3-0-operators.md b/public/docs/dart/1-basics/3-0-operators.md new file mode 100644 index 00000000..b5b2bbdc --- /dev/null +++ b/public/docs/dart/1-basics/3-0-operators.md @@ -0,0 +1,17 @@ +--- +id: dart-basics-operators +title: 基本的な演算子 +level: 2 +question: + - Dart特有の便利な演算子には何がありますか? + - 整数除算演算子の記号は何ですか? +term: + - 演算子 + - 算術演算子 + - 比較演算子 + - 論理演算子 +--- + +## 基本的な演算子 + +Dartには一般的な言語と同様の算術・比較・論理演算子に加えて、整数除算や型判定などDart特有の便利な演算子が用意されています。 diff --git a/public/docs/dart/1-basics/3-1-arithmetic.md b/public/docs/dart/1-basics/3-1-arithmetic.md new file mode 100644 index 00000000..9dc7f919 --- /dev/null +++ b/public/docs/dart/1-basics/3-1-arithmetic.md @@ -0,0 +1,35 @@ +--- +id: dart-basics-arithmetic +title: 算術演算子と整数除算(~/) +level: 3 +question: + - 通常の除算 / と整数除算 ~/ の違いは何ですか? + - 剰余演算子 % の使い方は? +term: + - 整数除算 + - '~/' + - 剰余 +--- + +### 算術演算子と整数除算(`~/`) + +通常の除算 `/` は常に `double` を返します。商の整数部分のみを取得したい場合は **`~/`**(整数除算演算子)を使います。 + +```dart:operators_arithmetic.dart +void main() { + int a = 10; + int b = 3; + + print('加算 (+): ${a + b}'); + print('通常除算 (/): ${a / b}'); // double (3.3333333333333335) + print('整数除算 (~/): ${a ~/ b}'); // int (3) + print('剰余 (%): ${a % b}'); // int (1) +} +``` + +```dart-exec:operators_arithmetic.dart +加算 (+): 13 +通常除算 (/): 3.3333333333333335 +整数除算 (~/): 3 +剰余 (%): 1 +``` diff --git a/public/docs/dart/1-basics/3-2-type-test.md b/public/docs/dart/1-basics/3-2-type-test.md new file mode 100644 index 00000000..583a659d --- /dev/null +++ b/public/docs/dart/1-basics/3-2-type-test.md @@ -0,0 +1,42 @@ +--- +id: dart-basics-type-test +title: 型テスト演算子と三項条件演算子 +level: 3 +question: + - is や is! 演算子は何のために使われますか? + - 型テストを行った後に自動でキャストされる仕組み(スマートキャスト)とは? +term: + - 型テスト演算子 + - 'is' + - 'is!' + - 三項演算子 + - スマートキャスト +--- + +### 型テスト演算子と三項条件演算子 + +オブジェクトが特定の型であるかを判定するには、`is` や `is!` を使います。 + +* `is`: 指定した型であれば `true`(スコープ内で自動的にスマートキャストされる) +* `is!`: 指定した型でなければ `true` + +```dart:type_test.dart +void main() { + Object value = 'Dart Programming'; + + if (value is String) { + // ifスコープ内では value が String にスマートキャストされる + print('文字列の長さ: ${value.length}'); + } + + // 三項条件演算子 + int score = 85; + String result = score >= 60 ? '合格' : '不合格'; + print('判定: $result'); +} +``` + +```dart-exec:type_test.dart +文字列の長さ: 16 +判定: 合格 +``` diff --git a/public/docs/dart/1-basics/3-0-summary.md b/public/docs/dart/1-basics/4-0-summary.md similarity index 100% rename from public/docs/dart/1-basics/3-0-summary.md rename to public/docs/dart/1-basics/4-0-summary.md diff --git a/public/docs/dart/1-basics/3-1-practice1.md b/public/docs/dart/1-basics/4-1-practice1.md similarity index 100% rename from public/docs/dart/1-basics/3-1-practice1.md rename to public/docs/dart/1-basics/4-1-practice1.md diff --git a/public/docs/dart/1-basics/3-2-practice2.md b/public/docs/dart/1-basics/4-2-practice2.md similarity index 100% rename from public/docs/dart/1-basics/3-2-practice2.md rename to public/docs/dart/1-basics/4-2-practice2.md diff --git a/public/docs/dart/10-async-stream/-intro.md b/public/docs/dart/10-async-stream/-intro.md index 4dcc82f8..9a0a6e24 100644 --- a/public/docs/dart/10-async-stream/-intro.md +++ b/public/docs/dart/10-async-stream/-intro.md @@ -1,5 +1,5 @@ -[[Future]]が「1回だけ値を返す非同期処理」であるのに対し、**[[Stream]]** は「時間の経過に伴って複数回、非同期に連続して届くデータ(イベント列)」を扱います。 +[[Future]]が「1つの値を将来返す非同期処理」であるのに対し、**`Stream`** は「時間の経過とともに**複数の値やイベントが次々と流れてくる**非同期のデータシーケンス」です。 -ユーザーのボタンタップ、センサー値の変動、WebSocketによるリアルタイム通信、ファイルのチャンク読み込みなど、現代のリアクティブなアプリ開発においてStreamは不可欠な概念です。 +WebSocketのメッセージ受信、ファイルの逐次読み込み、ボタンの連続タップイベント、センサー情報のリアルタイム受信、そして[[Flutter]]の状態管理(BLoCパターンやRxDartなど)において `Stream` は極めて重要な役割を果たします。 -この章では、Streamの購読方法、非同期ジェネレータ(`async*` / `yield`)、および `StreamController` による自作ストリームの制御方法を学びます。 +この章では、`Stream` の基本概念、`await for` による購読、`StreamController`、そして非同期ジェネレータ(`async*` / `yield`)を学びます。 diff --git a/public/docs/dart/10-async-stream/1-0-stream-concepts.md b/public/docs/dart/10-async-stream/1-0-stream-concepts.md index 1c6b85ca..f82dc555 100644 --- a/public/docs/dart/10-async-stream/1-0-stream-concepts.md +++ b/public/docs/dart/10-async-stream/1-0-stream-concepts.md @@ -1,51 +1,32 @@ --- -id: dart-stream-concepts +id: dart-streams-concepts title: Stream の概念(Push型のデータフロー) level: 2 question: - - 単一購読ストリームとブロードキャストストリームの違いは何ですか? - - Streamはどのようにデータを送信側に要求する(または受信する)のですか? - - Streamのリスナー(listen)はどう使いますか? + - FutureとStreamの最も重要な違いは何ですか? + - Streamから流れてくる3種類の通知とは何ですか? term: - Stream - ストリーム - - 単一購読ストリーム - - ブロードキャストストリーム - - listen + - Push型 + - リアクティブ --- ## `Stream` の概念(Push型のデータフロー) -**`Stream`** は、非同期に連続して流れてくる一連のデータ(データパイプライン)です。 +**`Stream`** は、非同期に次々と発生するデータ(またはエラー)のパイプラインです。 -### 1. 単一購読ストリーム(Single-subscription Stream) +購読者(リスナー)はデータが準備できたタイミングで通知を受け取ります(**Push型**)。 -* デフォルトのStream。 -* ライフサイクルの中で**1つのリスナーだけ**が購読できます(例: ファイル読み込み)。 -* 2回以上 `listen()` しようとするとエラーになります。 - -### 2. ブロードキャストストリーム(Broadcast Stream) - -* **複数のリスナー**が同時に購読できます(例: UIのクリックイベント、マウス移動)。 -* `stream.asBroadcastStream()` または `StreamController.broadcast()` で作成します。 - -```dart:stream_listen.dart -void main() { - // 1から3までのデータを順番に流す Stream - final stream = Stream.fromIterable([1, 2, 3]); - - // listen でイベントを購読 - final subscription = stream.listen( - (data) => print('データ受信: $data'), - onError: (error) => print('エラー: $error'), - onDone: () => print('ストリーム終了 (Done)'), - ); -} ``` - -```dart-exec:stream_listen.dart -データ受信: 1 -データ受信: 2 -データ受信: 3 -ストリーム終了 (Done) ++--------------------------------------------------------+ +| Stream | +| ---[ 1 ]-----[ 2 ]------[ 3 ]------| (完了 / Done) | +| 1秒後 2秒後 3秒後 | ++--------------------------------------------------------+ ``` + +Streamは以下の3つの通知をリスナーへ送信します。 +1. **データイベント(Data Event)**: 送信された値(`T`)。 +2. **エラーイベント(Error Event)**: 発生した例外。 +3. **完了イベント(Done Event)**: ストリームが終了し、これ以上データが来ない通知。 diff --git a/public/docs/dart/10-async-stream/1-1-stream-types.md b/public/docs/dart/10-async-stream/1-1-stream-types.md new file mode 100644 index 00000000..6de0e739 --- /dev/null +++ b/public/docs/dart/10-async-stream/1-1-stream-types.md @@ -0,0 +1,21 @@ +--- +id: dart-streams-types +title: 単一購読ストリームとブロードキャストストリーム +level: 3 +question: + - 単一購読ストリーム(Single-subscription)とブロードキャストストリーム(Broadcast)の違いは何ですか? + - 単一購読ストリームを複数回listenするとどうなりますか? +term: + - 単一購読ストリーム + - ブロードキャストストリーム + - asBroadcastStream +--- + +### 単一購読ストリームとブロードキャストストリーム + +DartのStreamには2つのモードが存在します。 + +* **単一購読ストリーム(Single-subscription Stream)**: + デフォルトのStream。最初から最後までのイベント順序が保証され、**同時に1つのリスナーのみ** が購読(`listen`)できます。ファイルの読み込みやHTTPレスポンスなどに適しています。 +* **ブロードキャストストリーム(Broadcast Stream)**: + **複数のリスナーが同時に購読可能** なStream。リスナーが購読を開始した瞬間以降のイベントのみを受信します。UIのクリックイベントや通知システムに適しています。`stream.asBroadcastStream()` で変換できます。 diff --git a/public/docs/dart/10-async-stream/2-0-await-for.md b/public/docs/dart/10-async-stream/2-0-await-for.md index 18732142..8ede23d3 100644 --- a/public/docs/dart/10-async-stream/2-0-await-for.md +++ b/public/docs/dart/10-async-stream/2-0-await-for.md @@ -1,67 +1,44 @@ --- -id: dart-stream-await-for +id: dart-streams-await-for title: await for によるStreamの購読 level: 2 question: + - await for 文を使うとどのようなメリットがありますか? - await for ループはいつ終了しますか? - - await for の中で break や return を使うとストリームはどうなりますか? - - Streamの高階メソッド(map, where, take)と組み合わせる方法は? term: - await for - - Stream購読 - - take + - Stream.fromIterable --- ## `await for` によるStreamの購読 -`async` 関数内では、**`await for` ループ** を使うことで、ストリームからデータが流れてくるたびに同期的な `for-in` ループのように直感的に処理できます。 +`async` 関数内では、**`await for`** ループを使って `Stream` から送られてくるデータを同期ループのように1件ずつ順次処理できます。 -ストリームが `onDone`(完了)を発行するまでループが継続します。 +ストリームが完了(Done)するまでループが継続します。 ```dart:await_for_demo.dart -Future processStream(Stream stream) async { - print('--- 処理開始 ---'); - await for (final value in stream) { - print('受信値: $value (2倍: ${value * 2})'); +Stream countStream(int max) async* { + for (int i = 1; i <= max; i++) { + yield i; } - print('--- 全データ受信完了 ---'); } void main() async { - // 10, 20, 30 を流すストリーム - final dataStream = Stream.fromIterable([10, 20, 30]); - await processStream(dataStream); -} -``` - -```dart-exec:await_for_demo.dart ---- 処理開始 --- -受信値: 10 (2倍: 20) -受信値: 20 (2倍: 40) -受信値: 30 (2倍: 60) ---- 全データ受信完了 --- -``` - -### Streamの変換オペレータ + print('Stream受信開始'); -Streamも `List` と同様に `map` や `where`、`take` などのオペレータでパイプライン処理が可能です。 - -```dart:stream_operators.dart -void main() async { - final stream = Stream.fromIterable([1, 2, 3, 4, 5, 6]); - - final filtered = stream - .where((n) => n.isEven) - .map((n) => '偶数: $n'); - - await for (final item in filtered) { - print(item); + // await for による順次受信 + await for (final number in countStream(3)) { + print('受信データ: $number'); } + + print('Stream完了'); } ``` -```dart-exec:stream_operators.dart -偶数: 2 -偶数: 4 -偶数: 6 +```dart-exec:await_for_demo.dart +Stream受信開始 +受信データ: 1 +受信データ: 2 +受信データ: 3 +Stream完了 ``` diff --git a/public/docs/dart/10-async-stream/2-1-stream-operators.md b/public/docs/dart/10-async-stream/2-1-stream-operators.md new file mode 100644 index 00000000..8ff01ecd --- /dev/null +++ b/public/docs/dart/10-async-stream/2-1-stream-operators.md @@ -0,0 +1,36 @@ +--- +id: dart-streams-operators +title: Streamの変換オペレータ(where, map) +level: 3 +question: + - Streamに対してもwhereやmapなどのオペレータを適用できますか? + - Streamを変換した結果の型はどうなりますか? +term: + - Stream変換 + - リアクティブプログラミング +--- + +### Streamの変換オペレータ(where, map) + +`Iterable` と同様に、`Stream` に対しても `where`(フィルタリング)、`map`(変換)、`take`(指定件数取得)などのパイプライン処理をメソッドチェーンで適用できます。 + +```dart:stream_operators.dart +void main() async { + final numbersStream = Stream.fromIterable([1, 2, 3, 4, 5, 6]); + + // 偶数だけを抽出し、10倍に変換して最初の2件を取得 + final transformedStream = numbersStream + .where((n) => n.isEven) + .map((n) => n * 10) + .take(2); + + await for (final val in transformedStream) { + print('変換後データ: $val'); + } +} +``` + +```dart-exec:stream_operators.dart +変換後データ: 20 +変換後データ: 40 +``` diff --git a/public/docs/dart/10-async-stream/3-0-stream-controller.md b/public/docs/dart/10-async-stream/3-0-stream-controller.md index b439c895..fe454850 100644 --- a/public/docs/dart/10-async-stream/3-0-stream-controller.md +++ b/public/docs/dart/10-async-stream/3-0-stream-controller.md @@ -1,56 +1,18 @@ --- -id: dart-stream-controller +id: dart-streams-controller title: StreamController と StreamSubscription level: 2 question: - - StreamControllerの sink と stream の役割の違いは何ですか? - - StreamSubscription を使って購読を一時停止・再開・解除する方法は? - - StreamController を使い終わった後に close() を呼ぶべき理由は何ですか? + - 自分で新しいイベントをストリームに流すにはどのクラスを使いますか? + - StreamController を使い終わった後に close() を呼ぶ必要がある理由は何ですか? term: - StreamController - StreamSubscription - sink - - close - - cancel + - listen --- ## `StreamController` と `StreamSubscription` -### 1. `StreamController`: イベントの送信と管理 - -プログラムの任意の場所からデータを流し込みたい場合、**`StreamController`** を使用します。 - -* `controller.sink.add(value)`: データをストリームへ送信。 -* `controller.sink.addError(error)`: エラーイベントを送信。 -* `controller.close()`: ストリームの終了(完了)を通知(メモリリーク防止のため必須)。 -* `controller.stream`: 購読用の `Stream` オブジェクト。 - -```dart:stream_controller_demo.dart -import 'dart:async'; - -void main() async { - final controller = StreamController(); - - // 購読側の設定 - controller.stream.listen( - (message) => print('通知: $message'), - onDone: () => print('コントローラ停止'), - ); - - // イベントの発行 - controller.sink.add('第1報: サーバー起動'); - controller.sink.add('第2報: クライアント接続'); - - await controller.close(); // ストリームをクローズ -} -``` - -```dart-exec:stream_controller_demo.dart -通知: 第1報: サーバー起動 -通知: 第2報: クライアント接続 -コントローラ停止 -``` - -### 2. `StreamSubscription`: 購読の制御 - -`stream.listen()` の戻り値である `StreamSubscription` オブジェクトを使って、購読の中断・再開や、途中で購読を破棄(`subscription.cancel()`)できます。 +プログラム側から能動的にイベントを生成・送信(Push)したい場合は、`dart:async` ライブラリの **`StreamController`** を使用します。 +また、購読の開始と停止を制御するために **`StreamSubscription`** を管理します。 diff --git a/public/docs/dart/10-async-stream/3-1-stream-subscription.md b/public/docs/dart/10-async-stream/3-1-stream-subscription.md new file mode 100644 index 00000000..01969e7b --- /dev/null +++ b/public/docs/dart/10-async-stream/3-1-stream-subscription.md @@ -0,0 +1,53 @@ +--- +id: dart-streams-subscription +title: StreamController によるイベント送信と購読制御 +level: 3 +question: + - StreamControllerのsinkプロパティの役割は何ですか? + - StreamSubscriptionをcancel()しないと何が発生しますか(メモリリーク)? +term: + - メモリリーク + - キャンセル + - cancel +--- + +### StreamController によるイベント送信と購読制御 + +`controller.sink.add(value)` でデータを送信し、購読は `stream.listen(...)` で行います。 + +```dart:stream_controller_demo.dart +import 'dart:async'; + +void main() async { + // StreamController の作成 + final controller = StreamController(); + + // 購読を開始 (StreamSubscription を保持) + final subscription = controller.stream.listen( + (message) => print('受信: $message'), + onError: (error) => print('エラー受信: $error'), + onDone: () => print('ストリーム終了 (Done)'), + ); + + // イベントの発行 (sink経由) + controller.sink.add('第1通知: ユーザーログイン'); + controller.sink.add('第2通知: メッセージ受信'); + controller.sink.addError('第3通知: 接続不安定警告'); + + // ストリームを閉じる + await controller.close(); + + // 不要になったら購読を破棄(メモリリーク防止) + await subscription.cancel(); +} +``` + +```dart-exec:stream_controller_demo.dart +受信: 第1通知: ユーザーログイン +受信: 第2通知: メッセージ受信 +エラー受信: 第3通知: 接続不安定警告 +ストリーム終了 (Done) +``` + +> [!WARNING] +> 不要になった `StreamSubscription` は必ず `cancel()` し、`StreamController` は `close()` してメモリリークを防ぎましょう。 diff --git a/public/docs/dart/10-async-stream/4-0-async-generator.md b/public/docs/dart/10-async-stream/4-0-async-generator.md index 4f44768b..d3f2a135 100644 --- a/public/docs/dart/10-async-stream/4-0-async-generator.md +++ b/public/docs/dart/10-async-stream/4-0-async-generator.md @@ -1,51 +1,42 @@ --- -id: dart-stream-generator +id: dart-streams-async-generator title: async* と yield(非同期ジェネレータ関数) level: 2 question: - - async* 関数と通常の async 関数の違いは何ですか? - - yield と yield* の使い分けはどうなりますか? - - 定期的に値を送信するストリームを async* で書く方法は? + - sync* と async* の違いは何ですか? + - yield* の使いどころを教えてください。 term: - - 'async*' + - async* - yield - yield* - 非同期ジェネレータ + - sync* --- ## `async*` と `yield`(非同期ジェネレータ関数) -**非同期ジェネレータ関数(`async*`)** を使用すると、複数の値を時間をかけて順番に生成・配信する `Stream` を手軽に構築できます。 - -* 関数宣言に `async*` を付け、戻り値型を `Stream` にします。 -* **`yield 値`**: データを1つストリームへ送出します。 -* **`yield* 別のStream`**: 別のストリームの全イベントをそのまま中継して送出します。 +**`async*`(非同期ジェネレータ)** を使うと、関数の内部から時間の経過とともに複数の値を `yield` で順次ストリームへ送り出すことができます。 ```dart:async_generator.dart -// 1からcountまで1秒おきにカウントアップする非同期ジェネレータ -Stream countStream(int max) async* { - for (int i = 1; i <= max; i++) { +Stream tickStream(int count) async* { + for (int i = 1; i <= count; i++) { await Future.delayed(const Duration(milliseconds: 50)); - yield i; // データを1件送出 + yield 'Tick #$i'; } } void main() async { - print('カウントダウン開始'); - await for (final number in countStream(3)) { - print('カウント: $number'); + await for (final tick in tickStream(3)) { + print(tick); } - print('完了!'); } ``` ```dart-exec:async_generator.dart -カウントダウン開始 -カウント: 1 -カウント: 2 -カウント: 3 -完了! +Tick #1 +Tick #2 +Tick #3 ``` > [!TIP] -> `StreamController` を手動で用意して `close()` を呼ぶ必要がないため、データの生成フローが明確な場合は `async*` と `yield` を使うのが最も安全でシンプルです。 +> 別のStream全体をそのまま委譲して流したい場合は、**`yield* otherStream;`** を使用します。 diff --git a/public/docs/dart/10-async-stream/5-0-summary.md b/public/docs/dart/10-async-stream/5-0-summary.md index 20137ef4..bd51a42a 100644 --- a/public/docs/dart/10-async-stream/5-0-summary.md +++ b/public/docs/dart/10-async-stream/5-0-summary.md @@ -7,11 +7,13 @@ question: [] ## この章のまとめ -この章では、[[Dart]]のリアクティブな非同期データフローを支える **[[Stream]]** について学びました。 +この章では、[[Dart]]のリアクティブプログラミングを支える `Stream` について学びました。 -* **Streamの概念**: 時間軸に沿って複数回イベント(データ、エラー、完了)を伝達するパイプライン。 -* **`await for`**: ストリームから流れてくる要素を同期的なループ構文のように直感的に処理できる。 -* **`StreamController`**: `sink` 経由でデータを送信し、任意のタイミングでイベントを発行・クローズできる。 -* **`async*` と `yield`**: 非同期ジェネレータ関数を用いて、シンプルかつクリーンに自作ストリームを生成できる。 +* **`Stream`**: 連続する非同期イベントを流すデータパイプライン(Push型)。 +* **単一購読 vs ブロードキャスト**: 単一購読は1つのリスナー専用、ブロードキャストは複数リスナーで共有可能。 +* **`await for`**: ストリームから流れてくる要素をループで簡潔に順次受信。 +* **ストリーム変換**: `where`, `map`, `take` などのオペレータでイベントシーケンスを加工。 +* **`StreamController`**: 能動的にイベントを発行し、`StreamSubscription` で購読とキャンセルを管理。 +* **`async*` / `yield`**: 非同期ジェネレータ関数で宣言的にストリームを生成。 -次の [[./11]] では、アプリケーションの信頼性を向上させる **エラーハンドリングとResultパターン** について学びます。 +次の [[./11]] では、実務において不可欠な **エラーハンドリング(例外処理とResult型パターン)** について学びます。 diff --git a/public/docs/dart/10-async-stream/5-1-practice1.md b/public/docs/dart/10-async-stream/5-1-practice1.md index 821c7ef1..056f8783 100644 --- a/public/docs/dart/10-async-stream/5-1-practice1.md +++ b/public/docs/dart/10-async-stream/5-1-practice1.md @@ -3,18 +3,17 @@ id: dart-async-stream-practice1 title: '練習問題1: async*を使ったカウントダウンストリーム' level: 3 question: - - async* 関数の中でループや条件分岐を組み合わせる方法を教えてください。 - - ストリームの途中でエラーを発生させたい場合はどう書きますか? + - async* 関数内で例外をスローするとストリームはどうなりますか? + - await for ループを途中で break した場合の挙動を教えてください。 --- ### 練習問題1: async*を使ったカウントダウンストリーム -指定された秒数から0までカウントダウンし、最後に `'発射!'` という文字列を通知するストリームを作成してください。 +指定された秒数からゼロまでカウントダウンするストリーム関数を作成してください。 -1. `Stream countdown(int from)` を `async*` で定義する。 -2. `from` から `1` までの数値をループし、50ミリ秒待機しながら `'$i...'` を `yield` する。 -3. ループ終了後に `'発射!'` を `yield` する。 -4. `main()` で `await for` を使って `countdown(3)` を購読し、結果を出力する。 +1. `Stream countdown(int from)` を `async*` で定義する。 +2. `from` から `0` まで1ずつ減らしながら `yield` する(各ステップで `Future.delayed(Duration(milliseconds: 50))` を待つ)。 +3. `main()` で `countdown(3)` を呼び出し、`await for` で受け取って `'残り: X秒'`、最後に `'カウントダウン終了!'` と出力する。 ```dart:practice10_1.dart // ここに関数を定義してください diff --git a/public/docs/dart/10-async-stream/5-2-practice2.md b/public/docs/dart/10-async-stream/5-2-practice2.md index 3137a7a5..28e80879 100644 --- a/public/docs/dart/10-async-stream/5-2-practice2.md +++ b/public/docs/dart/10-async-stream/5-2-practice2.md @@ -3,24 +3,29 @@ id: dart-async-stream-practice2 title: '練習問題2: StreamControllerを使ったイベント通知' level: 3 question: - - StreamControllerでエラーを流す sink.addError の使い方を教えてください。 - - listen の onError コールバックでエラーを処理する方法を復習したいです。 + - ブロードキャストストリームにするための .asBroadcastStream() の使い方を教えてください。 + - 購読リスナーが誰もいない状態でsinkにデータを送るとどうなりますか? --- ### 練習問題2: StreamControllerを使ったイベント通知 -タスクの進行状況(進捗率: 0〜100%)を通知する進捗トラッカーを実装してください。 +メッセージ通知システムを `StreamController` を使って構築してください。 -1. `StreamController` を作成する。 -2. コントローラのストリームを `listen` し、`'進捗: $percent%'` と出力するリスナーを登録する。 -3. `sink.add` を使って、`25`, `50`, `75`, `100` を順番に送信する。 -4. 送信完了後に `await controller.close()` を呼び出し、ストリームを終了する。 +1. `class NotificationHub` を作成する。 + * 内部に `StreamController` を保持する。 + * メソッド `void sendNotification(String message)` でイベントを送信する。 + * ゲッター `Stream get onNotification` でストリームを公開する。 + * メソッド `Future dispose()` でコントローラを閉じる。 +2. `main()` でインスタンスを作成し、`onNotification` を `listen` して受信ログを出力する。 +3. 2件のメッセージを送信後、`dispose()` を呼ぶ。 ```dart:practice10_2.dart import 'dart:async'; +// ここにクラスを定義してください + void main() async { - // ここにコードを書いてください + // ここで動作確認を行ってください } ``` diff --git a/public/docs/dart/11-error-handling/-intro.md b/public/docs/dart/11-error-handling/-intro.md index b09f34f2..48466b7e 100644 --- a/public/docs/dart/11-error-handling/-intro.md +++ b/public/docs/dart/11-error-handling/-intro.md @@ -1,6 +1,5 @@ -堅牢なアプリケーションを開発するためには、予期せぬ実行時エラーや外部連携の失敗を適切に処理するエラーハンドリング設計が欠かせません。 +ソフトウェア開発において、想定外のエラーや例外的な状況を安全に処理することは、信頼性の高いアプリケーションを作るための必須要件です。 -[[Dart]]には、特定の例外型をピンポイントで捕捉する `on` 節やスタックトレースの取得、開発時の事前条件チェックを行う `assert` 文が用意されています。 -さらにDart 3の `sealed` クラスやレコードを活用することで、例外を投げずに型の戻り値として成功・失敗を明示的に表現する **Result型(Resultパターン)** を美しく実装できます。 +[[Dart]]は、JavaやC++に似た伝統的な `try-catch` / `on` 構文による例外処理機構を持つと同時に、開発時の事前条件検証を行う `assert`、そして現代的な関数型アプローチである **[[Result型]]パターン(代数的データ型とパターンマッチの活用)** を両方サポートしています。 -この章では、Dartにおける例外処理の基礎から最新の関数型エラーハンドリング手法までを学びます。 +この章では、Dartにおける適切なエラーハンドリング戦略を体系的に学びます。 diff --git a/public/docs/dart/11-error-handling/1-0-try-catch.md b/public/docs/dart/11-error-handling/1-0-try-catch.md index 98b4252c..090fa13e 100644 --- a/public/docs/dart/11-error-handling/1-0-try-catch.md +++ b/public/docs/dart/11-error-handling/1-0-try-catch.md @@ -1,65 +1,59 @@ --- id: dart-error-try-catch -title: try、catch、on、finally とカスタム例外 +title: 'try、catch、on、finally とカスタム例外' level: 2 question: - - Exception と Error の違いは何ですか? - - onキーワードを使って特定の例外だけをキャッチする方法は? - - rethrowキーワードはどのような場合に使われますか? + - catch 節の第2引数(StackTrace)の使い方は? + - rethrow キーワードはどのような場面で使用しますか? term: - - try-catch + - 例外処理 + - 'on' + - rethrow - Exception - Error - - on - - rethrow - - スタックトレース + - StackTrace --- ## `try`、`catch`、`on`、`finally` とカスタム例外 -[[Dart]]の例外システムでは、`Exception`(プログラムで回復可能なエラー)と `Error`(プログラミングミスなどの重大な欠陥)が区別されています。 - -### 1. `on` による型指定キャッチと `rethrow` +[[Dart]]では、任意のオブジェクトを例外として `throw` できますが、通常は `Exception` または `Error` を実装したクラスをスローします。 -* **`on 例外型`**: 特定の例外クラスのみを捕捉します。 -* **`catch (e, stackTrace)`**: 例外オブジェクトとスタックトレースを受け取ります。 -* **`rethrow`**: キャッチした例外をそのまま上位の呼び出し元へ再スローします。 +* **`on ExceptionType`**: 特定の例外型だけを指定してキャッチします。 +* **`catch (e, stackTrace)`**: 例外オブジェクトとスタックトレースを取得します。 +* **`rethrow`**: キャッチした例外を処理した後、再度上位の呼び出し元へ再スローします。 +* **`finally`**: 例外の有無にかかわらず、最後に必ず実行されるクリーンアップブロックです。 -```dart:exception_handling.dart -// 1. カスタム例外クラスの定義 (implements Exception) -class InsufficientFundsException implements Exception { - final int currentBalance; - final int requestedAmount; - InsufficientFundsException(this.currentBalance, this.requestedAmount); +```dart:custom_exception_demo.dart +class ValidationException implements Exception { + final String message; + ValidationException(this.message); @override - String toString() => - '残高不足エラー: 現在残高 $currentBalance 円 に対し、$requestedAmount 円 の引き落とし要求がありました。'; + String toString() => 'ValidationException: $message'; } -void processWithdrawal(int balance, int amount) { - if (amount > balance) { - throw InsufficientFundsException(balance, amount); +void validateAge(int age) { + if (age < 0) { + throw ValidationException('年齢は0以上である必要があります'); } - print('引き落とし成功: $amount 円'); } void main() { try { - processWithdrawal(3000, 5000); - } on InsufficientFundsException catch (e) { - // 特定のカスタム例外を処理 - print('捕捉: $e'); + print('年齢チェック開始'); + validateAge(-5); + } on ValidationException catch (e) { + print('検証エラーをキャッチ: $e'); } catch (e, stack) { - // その他の未知の例外 - print('予期せぬエラー: $e'); + print('予期せぬエラー: $e\n$stack'); } finally { - print('トランザクション終了'); + print('検証処理終了(finallyブロック実行)'); } } ``` -```dart-exec:exception_handling.dart -捕捉: 残高不足エラー: 現在残高 3000 円 に対し、$5000 円 の引き落とし要求がありました。 -トランザクション終了 +```dart-exec:custom_exception_demo.dart +年齢チェック開始 +検証エラーをキャッチ: ValidationException: 年齢は0以上である必要があります +検証処理終了(finallyブロック実行) ``` diff --git a/public/docs/dart/11-error-handling/2-0-assert.md b/public/docs/dart/11-error-handling/2-0-assert.md index 34bf95c2..85f09655 100644 --- a/public/docs/dart/11-error-handling/2-0-assert.md +++ b/public/docs/dart/11-error-handling/2-0-assert.md @@ -3,47 +3,35 @@ id: dart-error-assert title: assert による開発時のバグ検知 level: 2 question: - - assert 文は本番リリース時(プロダクションビルド)にも実行されますか? - - assert と if-throw の使い分け基準は何ですか? - - Flutterでassertが多用されている理由は何ですか? + - assert 文は本番リリースビルド時にも実行されますか? + - assert と通常の例外スローの使い分けは何ですか? term: - assert - アサーション - - デバッグ + - デバッグモード --- ## `assert` による開発時のバグ検知 -**`assert(条件式, 'エラーメッセージ');`** は、開発・デバッグ時(Debug Mode)にのみ実行されるアサーション(前提条件チェック)文です。 +**`assert(条件, メッセージ)`** は、開発中(デバッグモード)にプログラムの不変条件や関数の前提条件を検証するための文です。 -* 条件が `true` であれば何も起きません。 -* 条件が `false` の場合、`AssertionError` がスローされ、即座に実行が中断します。 -* **リリースビルド(AOTコンパイルや本番モード)では自動的に完全に無視(コードから削除)される**ため、実行時パフォーマンスに一切影響を与えません。 +**リリース(AOTコンパイルや本番ビルド)時にはコード自体が完全に無視(除去)される** ため、本番環境の実行速度に影響を与えません。 ```dart:assert_demo.dart -class User { - final String name; - final int age; - - User(this.name, this.age) - : assert(name.isNotEmpty, 'ユーザー名は空にできません'), - assert(age >= 0, '年齢は0歳以上である必要があります'); +void setPercentage(double rate) { + // 開発時のみチェックされ、不正なら AssertionError を発生させる + assert(rate >= 0.0 && rate <= 1.0, 'rate は 0.0 〜 1.0 の間である必要があります'); + print('設定されたレート: ${(rate * 100).toStringAsFixed(1)}%'); } void main() { - final validUser = User('Alice', 20); - print('ユーザー作成成功: ${validUser.name}'); - - // デバッグ実行時、条件を満たさないと AssertionError が発生 - // final invalidUser = User('', -5); + setPercentage(0.75); } ``` ```dart-exec:assert_demo.dart -ユーザー作成成功: Alice +設定されたレート: 75.0% ``` -> [!NOTE] -> **使い分けの基準**: -> * `assert`: プログラマ自身の内部的なミスやAPIの不正利用を開発中に防ぐ目的。 -> * `if-throw`(例外スロー): ユーザー入力エラーやネットワーク遮断など、本番環境でも発生し得る外部要因のエラー処理。 +> [!TIP] +> ユーザーの入力不正など本番でも検知・回復すべきエラーには `throw Exception` を使い、プログラマの実装ミスや前提条件の違反検知には `assert` を使い分けます。 diff --git a/public/docs/dart/11-error-handling/3-0-result-pattern.md b/public/docs/dart/11-error-handling/3-0-result-pattern.md index 841c2606..337ec1f6 100644 --- a/public/docs/dart/11-error-handling/3-0-result-pattern.md +++ b/public/docs/dart/11-error-handling/3-0-result-pattern.md @@ -3,22 +3,18 @@ id: dart-error-result-pattern title: Result型(戻り値で成功/失敗を表現するパターン) level: 2 question: - - なぜ例外をthrowする代わりにResult型を使うアプローチが好まれるのですか? - - sealedクラスを使ってResult型(Success / Failure)を定義する方法は? - - switch式でResult型をハンドリングするメリットは何ですか? + - なぜ例外を投げる代わりにResult型を使うアプローチが好まれるのですか? + - sealedクラスとswitch式を使ったResult型の実装方法は? term: - Result型 - - Resultパターン - - Either - - 成功/失敗 - - 型安全なエラー処理 + - 成功/失敗パターン --- ## Result型(戻り値で成功/失敗を表現するパターン) -例外を `throw` するアプローチは、関数の型シグネチャに「どのような例外が発生し得るか」が現れず、呼び出し側がエラー処理を忘れるリスクがあります。 +例外を `throw` するアプローチは、関数の呼び出し側がエラー処理を忘れてしまうリスクがあります。 -Dart 3の **`sealed` クラス** を使って **[[Result型]](Result Pattern)** を自作すると、関数の戻り値の型として成功(`Success`)または失敗(`Failure`)を明示できます。 +Dart 3の `sealed` クラス([[./8]]参照)を活用すると、RustやSwiftのような **[[Result型]]** を型安全に自作でき、エラーハンドリングをコンパイル時に強制できます。 ```dart:result_pattern_demo.dart // 1. sealed クラスで Result 型を定義 @@ -26,45 +22,40 @@ sealed class Result { const Result(); } -final class Success extends Result { +class Success extends Result { final T value; const Success(this.value); } -final class Failure extends Result { +class Failure extends Result { final E error; const Failure(this.error); } -// 2. 例外を投げず、Result型を返す安全な除算関数 -Result safeDivide(double a, double b) { +// 2. 例外をスローせず Result 型を返す関数 +Result divide(int a, int b) { if (b == 0) { - return const Failure('0 で除算することはできません'); + return const Failure('0 で割ることはできません'); } - return Success(a / b); + return Success(a ~/ b); } void main() { - final results = [ - safeDivide(10, 2), - safeDivide(10, 0), - ]; + final res1 = divide(10, 2); + final res2 = divide(10, 0); - for (final res in results) { - // switch式で網羅的にハンドリング (処理忘れをコンパイル時に防止) - final output = switch (res) { + for (final res in [res1, res2]) { + // switch式で Success と Failure を完全網羅 + final msg = switch (res) { Success(:var value) => '計算結果: $value', - Failure(:var error) => 'エラー通知: $error', + Failure(:var error) => 'エラー: $error', }; - print(output); + print(msg); } } ``` ```dart-exec:result_pattern_demo.dart -計算結果: 5.0 -エラー通知: 0 で除算することはできません +計算結果: 5 +エラー: 0 で割ることはできません ``` - -> [!TIP] -> Result型を使うことで、呼び出し側は `switch` によるパターンマッチングで結果を取り出すことがコンパイラによって強制され、未処理エラーのバグが根絶されます。 diff --git a/public/docs/dart/11-error-handling/4-0-summary.md b/public/docs/dart/11-error-handling/4-0-summary.md index 8af9a7f9..b83a2b40 100644 --- a/public/docs/dart/11-error-handling/4-0-summary.md +++ b/public/docs/dart/11-error-handling/4-0-summary.md @@ -1,5 +1,5 @@ --- -id: dart-error-handling-summary +id: dart-error-summary title: この章のまとめ level: 2 question: [] @@ -7,10 +7,11 @@ question: [] ## この章のまとめ -この章では、[[Dart]]のエラーハンドリングとモダンな堅牢設計について学びました。 +この章では、[[Dart]]のエラーハンドリング手法について学びました。 -* **`try-catch-on-finally`**: `on` による型限定の例外捕捉、スタックトレースの取得、`rethrow` による再スロー。 -* **`assert`**: デバッグ時にのみコードの事前条件を検証し、リリース時にはゼロオーバーヘッドで削除されるバグ検知機構。 -* **Resultパターン**: 例外を投げる代わりに `sealed class Result` で成功・失敗を戻り値型として表現し、コンパイラの網羅性チェックを活用する設計。 +* **`try-catch` / `on` / `finally`**: 型安全な例外の捕捉、再スロー(`rethrow`)、クリーンアップ処理の実行。 +* **カスタム例外**: `Exception` を実装して独自のエラー型を定義。 +* **`assert`**: 開発時・デバッグ時のみ有効な不変条件チェックでバグを早期発見。 +* **Result型パターン**: `sealed class Result` を使って成功と失敗を戻り値型で表現し、呼び出し側でのエラーハンドリング漏れを防止。 -次の [[./12]] では、Dartチュートリアルの締めくくりとして、**並行処理の仕組みと `Isolate`** について学びます。 +次の [[./12]] では、Dartチュートリアルの最終章として、並行・並列処理の仕組みである **Isolate(アイソレート)** について学びます。 diff --git a/public/docs/dart/11-error-handling/4-1-practice1.md b/public/docs/dart/11-error-handling/4-1-practice1.md index 4c64a3e9..10260870 100644 --- a/public/docs/dart/11-error-handling/4-1-practice1.md +++ b/public/docs/dart/11-error-handling/4-1-practice1.md @@ -1,24 +1,24 @@ --- -id: dart-error-handling-practice1 +id: dart-error-practice1 title: '練習問題1: カスタム例外と適切な例外捕捉' level: 3 question: - - 複数の異なるカスタム例外を順番に on 節で捕捉する構文を教えてください。 - - カスタム例外クラスにエラーコードや詳細プロパティを持たせる方法を教えてください。 + - on節とcatch節を組み合わせる書き方を教えてください。 + - rethrowした例外はどこで捕捉されますか? --- ### 練習問題1: カスタム例外と適切な例外捕捉 -ユーザーのパスワード設定バリデーション関数と、それをテストするコードを作成してください。 +パスワード強度チェック関数と、その呼び出し側のエラーハンドリングを実装してください。 -1. `class WeakPasswordException implements Exception` を定義し、`final String reason;` を持たせる。 -2. `void validatePassword(String password)` 関数を定義する。 - * パスワードの長さが8文字未満の場合、`WeakPasswordException('8文字以上である必要があります')` をスローする。 - * 数字(`0`〜`9`)を含まない場合、`WeakPasswordException('少なくとも1つの数字を含む必要があります')` をスローする。 -3. `main()` でいくつかのテスト用パスワードを渡し、`on WeakPasswordException catch (e)` で捕捉して理由を出力する。 +1. `class WeakPasswordException implements Exception` を定義し、`final String reason` を保持する。 +2. `void checkPassword(String password)` 関数を定義する。 + * 文字数が8文字未満の場合、`WeakPasswordException('パスワードは8文字以上必要です')` をスローする。 + * 数字を含まない場合(`!password.contains(RegExp(r'[0-9]'))`)、`WeakPasswordException('数字を1文字以上含める必要があります')` をスローする。 +3. `main()` で `'abc'` を渡して `on WeakPasswordException` で捕捉し、理由を出力する。 ```dart:practice11_1.dart -// ここにコードを書いてください +// ここにクラスと関数を定義してください void main() { // ここで動作確認を行ってください diff --git a/public/docs/dart/11-error-handling/4-2-practice2.md b/public/docs/dart/11-error-handling/4-2-practice2.md index f06c5474..171ff6df 100644 --- a/public/docs/dart/11-error-handling/4-2-practice2.md +++ b/public/docs/dart/11-error-handling/4-2-practice2.md @@ -1,24 +1,25 @@ --- -id: dart-error-handling-practice2 +id: dart-error-practice2 title: '練習問題2: sealedクラスによるResult型の実装' level: 3 question: - - Result型に map や flatMap のような便利メソッドを生やすことはできますか? - - 非同期処理 Future> と組み合わせるパターンの使いどころを教えてください。 + - Result型に map や flatMap などのヘルパーメソッドを生やすことはできますか? + - 実務でResult型を採用するメリットを教えてください。 --- ### 練習問題2: sealedクラスによるResult型の実装 -文字列から整数への変換を安全に行う関数 `safeParseInt` を作成してください。 +`sealed` クラスを用いた `Result` パターンを使い、安全なJSONパース関数を実装してください。 -1. 本章で学習した `sealed class Result`(`Success` と `Failure`)を定義する。 -2. `Result safeParseInt(String input)` を作成する。 - * `int.tryParse(input)` を使い、変換成功時は `Success(value)` を返す。 - * 失敗時は `Failure('"$input" は有効な整数ではありません')` を返す。 -3. `main()` で `'123'` と `'abc'` を変換し、`switch` 式を使って結果を出力する。 +1. `sealed class ParseResult` を定義し、`ParseSuccess` と `ParseFailure` を作成する。 +2. `ParseResult parsePositiveInt(String input)` 関数を定義する。 + * `int.tryParse(input)` でパースを試み、失敗した場合は `ParseFailure('数値を入力してください')` を返す。 + * パースできた値が 0 以下の場合は `ParseFailure('正の整数を入力してください')` を返す。 + * 正常な正の整数の場合は `ParseSuccess(value)` を返す。 +3. `main()` で `'42'`, `'-5'`, `'abc'` を渡してテストし、`switch` 式で結果を出力する。 ```dart:practice11_2.dart -// ここにコードを書いてください +// ここにクラスと関数を定義してください void main() { // ここで動作確認を行ってください diff --git a/public/docs/dart/12-concurrency-isolate/-intro.md b/public/docs/dart/12-concurrency-isolate/-intro.md index 14828989..8d108b2d 100644 --- a/public/docs/dart/12-concurrency-isolate/-intro.md +++ b/public/docs/dart/12-concurrency-isolate/-intro.md @@ -1,7 +1,7 @@ -[[Dart]]のコードはデフォルトでシングルスレッドのイベントループ上で実行されます。 -しかし、巨大なJSONの解析、画像の加工、複雑な暗号化や統計計算など、CPUを長時間占有する処理をそのまま実行するとUIがカクついてしまいます。 +[[Future]]や[[Stream]]による非同期処理は、I/O待ち(通信やファイル読み書き)をノンブロッキングに行うための仕組みであり、実行自体は依然として同一のシングルスレッド(イベントループ)上で行われます。 -Dartでは、マルチコアCPUの性能をフルに活かして真の並列処理を行うために **[[Isolate]](アイソレート)** という仕組みを提供しています。 -各Isolateは完全に独立したメモリヒープを持っており、スレッド間の共有メモリに起因するデータ競合やデッドロックが原理的に発生しません。 +そのため、画像処理、暗号化/複合、数万件に及ぶ巨大なJSONのパースなど、**CPU負荷の高い重い計算** を実行すると、UIのレンダリングがコマ落ち(ジャンク/Jank)してしまいます。 -この最終章では、Dartの並行処理アーキテクチャ、手軽に使える `Isolate.run()`、そしてポート通信を用いた高度な並行処理を学びます。 +Dartはマルチコアプロセッサを活用して真の並列計算を行うために、メモリを共有しない独立した実行単位である **`Isolate`(アイソレート)** を提供しています。 + +この章では、Isolateの仕組み、`Isolate.run()` による簡単な並列化、そしてポートを用いた双方向通信を学びます。 diff --git a/public/docs/dart/12-concurrency-isolate/1-0-single-thread-model.md b/public/docs/dart/12-concurrency-isolate/1-0-single-thread-model.md index d6c80a50..9645c52b 100644 --- a/public/docs/dart/12-concurrency-isolate/1-0-single-thread-model.md +++ b/public/docs/dart/12-concurrency-isolate/1-0-single-thread-model.md @@ -1,42 +1,39 @@ --- -id: dart-concurrency-single-thread +id: dart-isolate-single-thread-model title: Dartのシングルスレッドモデルとメモリ空間 level: 2 question: - - なぜDartは共有メモリスレッドではなくIsolateモデルを採用したのですか? - - メモリが分離されていることによるメリットとデメリットは何ですか? - - FutureとIsolateの使い分けの基準は何ですか? + - なぜDartはスレッド間でのメモリ共有を行わない設計なのですか? + - メモリ共有スレッドとIsolateモデルの安全性の違いは何ですか? term: - - シングルスレッド - - 並行処理 - - メモリ空間 - - データ競合 + - Isolate + - アイソレート + - メモリ分離 + - 並列処理 + - デッドロック + - レースコンディション --- ## Dartのシングルスレッドモデルとメモリ空間 -JavaやC++、C#などの一般的な言語では、複数のスレッドが同一のメモリ空間(ヒープ領域)を共有して動作します。この方式は高速ですが、**データ競合(Data Race)** や **デッドロック(Deadlock)**、複雑なロック(ミューテックス)管理が必要となり、バグの温床になりがちです。 +一般的なマルチスレッドプログラミング(JavaやC++など)では、複数のスレッドが同一のヒープメモリ空間を共有するため、ミューテックスやロックによる排他制御が必要となり、**[[レースコンディション]](競合状態)** や **[[デッドロック]]** の温床になりがちでした。 -一方、[[Dart]]では **[[Isolate]](隔離されたスレッド)** というモデルを採用しています。 +[[Dart]]の **[[Isolate]](アイソレート=隔離されたもの)** は、**独自のヒープメモリと独立したイベントループを持つ完全な隔離環境** です。 ``` -+--------------------------------+ +--------------------------------+ -| Main Isolate | | Worker Isolate | -| | | | -| +--------------------------+ | | +--------------------------+ | -| | 独立したメモリヒープ | | | | 独立したメモリヒープ | | -| +--------------------------+ | | +--------------------------+ | -| +--------------------------+ | | +--------------------------+ | -| | 独立したイベントループ | | | | 独立したイベントループ | | -| +--------------------------+ | | +--------------------------+ | -+---------------+----------------+ +----------------+---------------+ - | ^ - | メッセージパッシング (コピー/転送) | - +-----------------------------------------+ ++-----------------------------+ +-----------------------------+ +| Isolate A | | Isolate B | +| +-----------------------+ | | +-----------------------+ | +| | ヒープメモリ | | | | ヒープメモリ | | +| | (オブジェクト・変数) | | | | (オブジェクト・変数) | | +| +-----------------------+ | | +-----------------------+ | +| +-----------------------+ | | +-----------------------+ | +| | イベントループ | | | | イベントループ | | +| +-----------------------+ | | +-----------------------+ | ++--------------+--------------+ +--------------+--------------+ + ^ | + | メッセージ通信 (コピー) | + +-------------------------------------+ ``` -### Isolateモデルの特徴 - -1. **完全なメモリ分離**: 1つのIsolate内の変数は、他のIsolateから直接読み書きできません。 -2. **ロックフリー**: メモリが共有されないため、ミューテックスやセマフォなどのロック機構が不要です。 -3. **安全なガベージコレクション**: 他のIsolateを停止させることなく、個々のIsolateが独立してガベージコレクション(GC)を実行できます。 +メモリ空間が完全に分離されているため、ロック不要で安全にマルチコアを活用した並列実行が可能です。 diff --git a/public/docs/dart/12-concurrency-isolate/2-0-isolate-intro.md b/public/docs/dart/12-concurrency-isolate/2-0-isolate-intro.md index 7c33d185..1a4a3562 100644 --- a/public/docs/dart/12-concurrency-isolate/2-0-isolate-intro.md +++ b/public/docs/dart/12-concurrency-isolate/2-0-isolate-intro.md @@ -1,54 +1,47 @@ --- -id: dart-concurrency-isolate-run +id: dart-isolate-intro title: Isolate の概念と Isolate.run() による別スレッド処理 level: 2 question: - Isolate.run() はどのような処理に向いていますか? - - Isolate.run() に渡せる関数や引数にどのような制限がありますか? - - compute() 関数(Flutter)と Isolate.run() の関係は何ですか? + - Isolate.run() に渡す関数に関する制約は何ですか? term: - - Isolate - Isolate.run - - アイソレート - - ヘビータスク + - compute + - CPUバウンド --- ## `Isolate` の概念と `Isolate.run()` による別スレッド処理 -Dart 2.19以降、単発の重い処理を別スレッドで実行して結果を受け取るための非常に手軽なAPI **`Isolate.run()`** が導入されました。 +Dart 2.19以降では、単発の重い計算処理([[CPUバウンド]]タスク)を別スレッドで実行して結果を受け取るための **`Isolate.run()`** が導入されました。 -### `Isolate.run()` の使い方 - -`Isolate.run()` に実行したい関数(トップレベル関数、静的メソッド、あるいはクロージャ)を渡すだけで、自動的に新しいIsolateが立ち上がり、処理が完了すると結果を返して自動終了します。 +内部で別Isolateの生成、計算の実行、結果メッセージの返却、そしてIsolateの破棄までを自動で行ってくれます(Flutterの `compute()` 関数と同等です)。 ```dart:isolate_run_demo.dart import 'dart:isolate'; -// 重い計算処理の例(大きな数値の合計) +// 別Isolateで実行される重い計算関数 int heavyCalculation(int count) { - int total = 0; - for (int i = 1; i <= count; i++) { - total += i; + int sum = 0; + for (int i = 0; i < count; i++) { + sum += i; } - return total; + return sum; } void main() async { - print('1. メイン処理開始'); + print('メインスレッド開始'); - // 別スレッド (Isolate) で重い処理をバックグラウンド実行 + // 別スレッドで重い計算を実行 final result = await Isolate.run(() => heavyCalculation(1000000)); + print('計算完了: $result'); - print('2. 計算完了: $result'); - print('3. メイン処理終了'); + print('メインスレッド終了'); } ``` ```dart-exec:isolate_run_demo.dart -1. メイン処理開始 -2. 計算完了: 500000500000 -3. メイン処理終了 +メインスレッド開始 +計算完了: 499999500000 +メインスレッド終了 ``` - -> [!NOTE] -> `Isolate.run()` に渡す引数や戻り値はメッセージとして転送可能なオブジェクト(基本型、コレクション、または特定条件を満たすオブジェクト)である必要があります。 diff --git a/public/docs/dart/12-concurrency-isolate/3-0-isolate-ports.md b/public/docs/dart/12-concurrency-isolate/3-0-isolate-ports.md index 26559205..232a258b 100644 --- a/public/docs/dart/12-concurrency-isolate/3-0-isolate-ports.md +++ b/public/docs/dart/12-concurrency-isolate/3-0-isolate-ports.md @@ -1,74 +1,55 @@ --- -id: dart-concurrency-isolate-ports +id: dart-isolate-ports title: ポート(ReceivePort、SendPort)による双方向メッセージ通信 level: 2 question: - - 長時間稼働するバックグラウンドワーカーを作るにはどうしますか? - ReceivePort と SendPort の役割の違いは何ですか? - - Isolate.spawn() を使ったワーカーの起動方法は? + - バックグラウンドワーカーIsolateとメインIsolateで双方向通信を行う手順は? term: - ReceivePort - SendPort - メッセージパッシング - - Isolate.spawn - - 双方向通信 + - ポート通信 --- -## ポート(`ReceivePort`、`SendPort`)による双方向メッセージ通信 +## ポート(ReceivePort、SendPort)による双方向メッセージ通信 -`Isolate.run()` は単発の処理に適していますが、長時間常駐して継続的にメッセージを送受信するバックグラウンドワーカーを作成する場合は、**`ReceivePort`** と **`SendPort`** による **[[メッセージパッシング]]** を使用します。 - -* **`ReceivePort`**: メッセージの受信口(`Stream` として機能)。 -* **`SendPort`**: メッセージの送信先アドレス。 +長時間常駐するバックグラウンドワーカーを作成し、メインIsolateとの間で継続的な双方向通信を行うには、**`ReceivePort`** と **`SendPort`** によるメッセージパッシングを使用します。 ```dart:isolate_ports_demo.dart import 'dart:isolate'; -// ワーカースレッドのエントリーポイント +// ワーカーIsolateのエントリーポイント void worker(SendPort mainSendPort) { - // ワーカー側の受信ポートを作成 final workerReceivePort = ReceivePort(); - - // ワーカーの送信ポートをメインスレッドに知らせる + // 自身のSendPortをメインIsolateに送り返す mainSendPort.send(workerReceivePort.sendPort); - // メインスレッドからの指示を待機 + // メインからのメッセージを待ち受ける workerReceivePort.listen((message) { - if (message is String) { - // 処理結果をメインスレッドに返信 - mainSendPort.send('処理完了: ${message.toUpperCase()}'); + if (message is int) { + final squared = message * message; + mainSendPort.send('計算結果: $squared'); } }); } void main() async { - // メイン側の受信ポート final mainReceivePort = ReceivePort(); // ワーカーIsolateを起動 - final isolate = await Isolate.spawn(worker, mainReceivePort.sendPort); + await Isolate.spawn(worker, mainReceivePort.sendPort); - SendPort? workerSendPort; + // 最初に応答として送られてくるワーカーのSendPortを取得 + final workerSendPort = await mainReceivePort.first as SendPort; - // メイン側でメッセージを受信 - mainReceivePort.listen((message) { - if (message is SendPort) { - // ワーカーの送信先を受け取ったら指示を送信 - workerSendPort = message; - workerSendPort?.send('hello from main'); - } else { - print('ワーカーからの返信: $message'); - - // クリーンアップ - mainReceivePort.close(); - isolate.kill(); - print('通信完了・Isolate破棄'); - } - }); + // 新たな受信ポートを作成して結果を待機 + final responsePort = ReceivePort(); + workerSendPort.send(7); + + mainReceivePort.close(); } ``` -```dart-exec:isolate_ports_demo.dart -ワーカーからの返信: 処理完了: HELLO FROM MAIN -通信完了・Isolate破棄 -``` +> [!NOTE] +> メッセージとして送信できるデータは、プリミティブ型、コレクション、一部のシステムオブジェクトなどに限られ、Isolateを跨ぐ際にディープコピー(または転送)されます。 diff --git a/public/docs/dart/12-concurrency-isolate/4-0-summary.md b/public/docs/dart/12-concurrency-isolate/4-0-summary.md index 2b12a8f9..5dda838e 100644 --- a/public/docs/dart/12-concurrency-isolate/4-0-summary.md +++ b/public/docs/dart/12-concurrency-isolate/4-0-summary.md @@ -1,5 +1,5 @@ --- -id: dart-concurrency-isolate-summary +id: dart-isolate-summary title: この章のまとめ level: 2 question: [] @@ -7,16 +7,11 @@ question: [] ## この章のまとめ -この章では、[[Dart]]の並行処理とマルチスレッドアーキテクチャについて学びました。 +この章では、[[Dart]]における並列処理の仕組みである **`Isolate`** について学びました。 -* **シングルスレッドとIsolate**: メモリ空間を共有しない独立したスレッドモデルにより、データ競合やロックの複雑さを排除。 -* **`Isolate.run()`**: 単発の重いCPU処理(画像処理、暗号化、データ変換など)を数行で別スレッドにオフロード可能。 -* **ポート通信 (`ReceivePort` / `SendPort`)**: メッセージパッシングによって、常駐型ワーカーIsolateと安全に双方向通信ができる。 +* **シングルスレッド vs Isolate**: 通常の非同期処理は同一スレッドでイベントループにより実行されるが、CPU集約型の処理は `Isolate` で別コアへ逃がす。 +* **メモリ分離(No Shared Memory)**: 各Isolateは完全に独立したヒープを持ち、ロック不要でデッドロックやレースコンディションが発生しない。 +* **`Isolate.run()`**: 単発の重い計算タスクをワンライナーで安全に別スレッド実行。 +* **`ReceivePort` / `SendPort`**: 長時間常駐するワーカーとの双方向メッセージ通信。 ---- - -### Dartチュートリアル完走おめでとうございます! - -本チュートリアルを通じて、Dartの基本構文からSound Null Safety、関数・クロージャ、コレクション操作、レコードとパターンマッチング、クラス設計、クラス修飾子、非同期処理(Future・Stream)、エラーハンドリング、そして並行処理(Isolate)に至るまで、現代のDart開発に必要な知識を網羅しました。 - -これらの知識は、[[Flutter]]によるアプリ開発はもちろん、Dartを用いたWeb・サーバーサイド開発の強固な基盤となります。ぜひ学んだ知識を活かして、素晴らしいアプリケーションを作ってみてください! +Dartチュートリアルの全章を完走したことで、言語基礎・Null Safety・関数型機能・モダンなDart 3パターン・非同期・Isolate並列処理まで、実践で活躍するための知識が身につきました! diff --git a/public/docs/dart/12-concurrency-isolate/4-1-practice1.md b/public/docs/dart/12-concurrency-isolate/4-1-practice1.md index 610e3ffb..bd490868 100644 --- a/public/docs/dart/12-concurrency-isolate/4-1-practice1.md +++ b/public/docs/dart/12-concurrency-isolate/4-1-practice1.md @@ -1,22 +1,24 @@ --- -id: dart-concurrency-isolate-practice1 +id: dart-isolate-practice1 title: '練習問題1: Isolate.run()による重い計算の並行実行' level: 3 question: - - Isolate.run() 内で例外が発生した場合、呼び出し側はどう処理すべきですか? - - 引数としてクロージャを渡す場合の変数キャプチャの注意点は何ですか? + - Isolate.run() で返せる戻り値の型にはどのような制約がありますか? + - クロージャを Isolate.run() に渡す場合の注意点を教えてください。 --- ### 練習問題1: Isolate.run()による重い計算の並行実行 -素数の個数を数える計算を `Isolate.run()` を使って別スレッドで実行してください。 +巨大なリストのソートと集計処理を `Isolate.run()` を使って別スレッドで実行するプログラムを作成してください。 -1. `bool isPrime(int n)` 関数を作成する(2以上の整数に対し素数判定を行う)。 -2. `int countPrimes(int max)` 関数を作成し、`1` から `max` までの素数の総数をカウントする。 -3. `main()` で `Isolate.run(() => countPrimes(50000))` を呼び出して非同期に結果を待機し、計算された素数の個数を出力する。 +1. `List generateAndSortNumbers(int size)` 関数を定義する。 + * 要素数 `size` のランダムな整数リストを生成し、降順ソートして先頭10件を返す。 +2. `main()` で `await Isolate.run(() => generateAndSortNumbers(500000))` を呼び出す。 +3. 取得した上位10件のリストを出力する。 ```dart:practice12_1.dart import 'dart:isolate'; +import 'dart:math'; // ここに関数を定義してください diff --git a/public/docs/dart/12-concurrency-isolate/4-2-practice2.md b/public/docs/dart/12-concurrency-isolate/4-2-practice2.md index 6d3934e4..4ac5d14b 100644 --- a/public/docs/dart/12-concurrency-isolate/4-2-practice2.md +++ b/public/docs/dart/12-concurrency-isolate/4-2-practice2.md @@ -1,20 +1,21 @@ --- -id: dart-concurrency-isolate-practice2 +id: dart-isolate-practice2 title: '練習問題2: ポートを使った双方向ワーカーの作成' level: 3 question: - - Isolateでエラーが発生したときにメインスレッドに通知する onError ポートの設定方法は? - - 複数のワーカーをプールして管理する設計のポイントは何ですか? + - Isolate間のメッセージパッシングで送信できるオブジェクトの制限は何ですか? + - バックグラウンドIsolateで例外が発生した場合のハンドリング方法は? --- ### 練習問題2: ポートを使った双方向ワーカーの作成 -メインスレッドから渡された文字列を逆順にして返信するワーカーIsolateを作成してください。 +文字列を反転して大文字にするワーカースレッドを作成し、メッセージを送受信してください。 -1. `void echoWorker(SendPort mainSendPort)` を作成する。 - * 自身の `ReceivePort` を作成し、その `sendPort` を `mainSendPort` に送信する。 - * 受信したメッセージが `String` であれば、文字列を逆順(`message.split('').reversed.join()`)にして `mainSendPort` に返信する。 -2. `main()` で `Isolate.spawn(echoWorker, mainReceivePort.sendPort)` を起動し、`'Flutter & Dart'` という文字列を送信して逆順になった文字列を受信・出力する。 +1. `void textProcessor(SendPort mainSendPort)` を定義する。 + * 自身の `ReceivePort` を作成し、その `sendPort` を `mainSendPort` に送る。 + * 送られてきた文字列を逆順(`split('').reversed.join()`)かつ大文字(`toUpperCase()`)にして送り返す。 +2. `main()` で `Isolate.spawn(textProcessor, receivePort.sendPort)` を起動する。 +3. `'hello dart'` を送信し、ワーカーから `'TRAD OLLEH'` が返ってくることを確認してポートを閉じる。 ```dart:practice12_2.dart import 'dart:isolate'; diff --git a/public/docs/dart/2-null-safety/1-0-types.md b/public/docs/dart/2-null-safety/1-0-types.md index 1b454a23..ce93c331 100644 --- a/public/docs/dart/2-null-safety/1-0-types.md +++ b/public/docs/dart/2-null-safety/1-0-types.md @@ -5,7 +5,6 @@ level: 2 question: - デフォルトで変数がNull非許容(Non-nullable)であるメリットは何ですか? - Nullableな変数はどのように宣言しますか? - - if文でnullチェックした後は自動的にNon-nullableとして扱われますか? term: - Null Safety - null safety @@ -40,28 +39,3 @@ void main() { nonNullableText: Hello nullableText: null ``` - -### 型プロモーション(スマートキャスト) - -Nullableな変数であっても、`if` 文などで `null` でないことをチェック(フロー解析)すると、そのスコープ内では自動的にNon-nullable型へと昇格(プロモート)します。 - -```dart:flow_analysis.dart -void printLength(String? text) { - if (text != null) { - // このブロック内では text は String? ではなく String として扱われる - print('文字数: ${text.length}'); - } else { - print('テキストは null です'); - } -} - -void main() { - printLength('Dart Flutter'); - printLength(null); -} -``` - -```dart-exec:flow_analysis.dart -文字数: 12 -テキストは null です -``` diff --git a/public/docs/dart/2-null-safety/1-1-flow-analysis.md b/public/docs/dart/2-null-safety/1-1-flow-analysis.md new file mode 100644 index 00000000..819af80c --- /dev/null +++ b/public/docs/dart/2-null-safety/1-1-flow-analysis.md @@ -0,0 +1,37 @@ +--- +id: dart-null-safety-flow-analysis +title: 型プロモーション(スマートキャスト) +level: 3 +question: + - if文でnullチェックした後は自動的にNon-nullableとして扱われますか? + - フロー解析による型プロモーションが効かないケースはありますか? +term: + - 型プロモーション + - フロー解析 + - nullチェック +--- + +### 型プロモーション(スマートキャスト) + +Nullableな変数であっても、`if` 文などで `null` でないことをチェック(フロー解析)すると、そのスコープ内では自動的にNon-nullable型へと昇格(プロモート)します。 + +```dart:flow_analysis.dart +void printLength(String? text) { + if (text != null) { + // このブロック内では text は String? ではなく String として扱われる + print('文字数: ${text.length}'); + } else { + print('テキストは null です'); + } +} + +void main() { + printLength('Dart Flutter'); + printLength(null); +} +``` + +```dart-exec:flow_analysis.dart +文字数: 12 +テキストは null です +``` diff --git a/public/docs/dart/2-null-safety/2-0-null-assertion.md b/public/docs/dart/2-null-safety/2-0-null-assertion.md index 80a5c22c..54d5ef35 100644 --- a/public/docs/dart/2-null-safety/2-0-null-assertion.md +++ b/public/docs/dart/2-null-safety/2-0-null-assertion.md @@ -5,7 +5,6 @@ level: 2 question: - Nullアサーション演算子 (!) はどのような時に使うべきですか? - ! を付けた変数が実際に null だった場合何が起こりますか? - - なぜ可能な限り ! を避けるべきなのですか? term: - Nullアサーション - '!演算子' @@ -20,7 +19,7 @@ term: void main() { String? maybeName = 'Alice'; - // maybeName は String? 型だが、! を付けることで String 型として扱える + // ! を付けることで String 型として強制的に扱う String definiteName = maybeName!; print('名前: $definiteName'); } @@ -30,14 +29,7 @@ void main() { 名前: Alice ``` -### `!` の危険性: 実行時例外の発生 - -もし値が `null` であるにもかかわらず `!` を適用した場合、コンパイルは通過しますが、実行時に `TypeError` / `Null check operator used on a null value` 例外が発生してプログラムが強制終了します。 - -```dart -String? maybeNull; -// String forced = maybeNull!; // 実行時クラッシュ! -``` +もし値が `null` であるにもかかわらず `!` を適用した場合、実行時に `TypeError` 例外が発生してプログラムがクラッシュします。 > [!CAUTION] -> `!` 演算子は、型推論やフロー解析が及ばない特定の場面(外部ライブラリとの連携など)を除き、安易に使うべきではありません。基本的には後述する **[[Null認識演算子]]** や明示的な `null` チェックを優先しましょう。 +> `!` 演算子は安易に使わず、基本的には後述する **[[Null認識演算子]]** や明示的な `null` チェックを優先しましょう。 diff --git a/public/docs/dart/2-null-safety/3-0-null-aware-operators.md b/public/docs/dart/2-null-safety/3-0-null-aware-operators.md index e434cf51..f5b5260c 100644 --- a/public/docs/dart/2-null-safety/3-0-null-aware-operators.md +++ b/public/docs/dart/2-null-safety/3-0-null-aware-operators.md @@ -3,9 +3,8 @@ id: dart-null-safety-null-aware-operators title: 'Null認識演算子(?.、??、??=)' level: 2 question: - - ?. 演算子(オプショナルチェーン)の返り値の型は何になりますか? - - ?? 演算子(Null合流演算子)はどう使いますか? - - ??= 演算子はどのような動作をしますか? + - Null認識演算子を使うとどのようなボイラープレートコードを削減できますか? + - オプショナルチェーンとNull合流演算子の記号は何ですか? term: - Null認識演算子 - null認識演算子 @@ -19,65 +18,4 @@ term: [[Dart]]には、Nullableな値を安全かつ簡潔に処理するための **[[Null認識演算子]](Null-aware operators)** が用意されています。 -### 1. 条件付きアクセス演算子 (`?.`) - -対象が `null` でなければプロパティやメソッドにアクセスし、`null` であれば `null` を返します。 - -```dart:null_aware_access.dart -void main() { - String? text; - // text が null なので、length にアクセスせず null を返す - int? length = text?.length; - print('length: $length'); - - text = 'Hello'; - length = text?.length; - print('length: $length'); -} -``` - -```dart-exec:null_aware_access.dart -length: null -length: 5 -``` - -### 2. Null合流演算子 (`??`) - -左辺が `null` でない場合は左辺の値を、左辺が `null` の場合は右辺のデフォルト値を返します(いわゆるElvis演算子やNullish coalescing演算子)。 - -```dart:null_coalescing.dart -void main() { - String? userName; - String displayName = userName ?? '名無しのユーザー'; - print('表示名: $displayName'); - - userName = 'Alice'; - displayName = userName ?? '名無しのユーザー'; - print('表示名: $displayName'); -} -``` - -```dart-exec:null_coalescing.dart -表示名: 名無しのユーザー -表示名: Alice -``` - -### 3. Null認識代入演算子 (`??=`) - -変数が `null` の場合のみ、右辺の値を代入します。 - -```dart:null_aware_assignment.dart -void main() { - int? count; - count ??= 10; // count は null だったので 10 が代入される - print('count: $count'); - - count ??= 20; // count は既に 10 なので 20 は代入されない - print('count: $count'); -} -``` - -```dart-exec:null_aware_assignment.dart -count: 10 -count: 10 -``` +これらを活用することで、冗長な `if (x != null)` チェックを大幅に減らすことができます。 diff --git a/public/docs/dart/2-null-safety/3-1-conditional-access.md b/public/docs/dart/2-null-safety/3-1-conditional-access.md new file mode 100644 index 00000000..06db28ec --- /dev/null +++ b/public/docs/dart/2-null-safety/3-1-conditional-access.md @@ -0,0 +1,33 @@ +--- +id: dart-null-safety-conditional-access +title: 条件付きアクセス演算子(?.) +level: 3 +question: + - ?. 演算子の返り値の型は何になりますか? + - メソッド呼び出しに ?. を使う例を教えてください。 +term: + - 条件付きアクセス演算子 + - null安全な呼び出し +--- + +### 条件付きアクセス演算子(`?.`) + +対象が `null` でなければプロパティやメソッドにアクセスし、`null` であれば `null` を返します。 + +```dart:null_aware_access.dart +void main() { + String? text; + // text が null なので length にアクセスせず null を返す + int? length = text?.length; + print('length (null時): $length'); + + text = 'Hello'; + length = text?.length; + print('length (値あり時): $length'); +} +``` + +```dart-exec:null_aware_access.dart +length (null時): null +length (値あり時): 5 +``` diff --git a/public/docs/dart/2-null-safety/3-2-null-coalescing.md b/public/docs/dart/2-null-safety/3-2-null-coalescing.md new file mode 100644 index 00000000..0a8087bf --- /dev/null +++ b/public/docs/dart/2-null-safety/3-2-null-coalescing.md @@ -0,0 +1,33 @@ +--- +id: dart-null-safety-null-coalescing +title: Null合流演算子(??) +level: 3 +question: + - ?? 演算子と三項演算子の使い分けはどうなりますか? + - ?? 演算子を連続してチェーンさせることはできますか? +term: + - Null合流演算子 + - '??' + - デフォルト値 +--- + +### Null合流演算子(`??`) + +左辺が `null` でない場合は左辺の値を、`null` の場合は右辺のデフォルト値を返します。 + +```dart:null_coalescing.dart +void main() { + String? userName; + String displayName = userName ?? '名無しのユーザー'; + print('表示名: $displayName'); + + userName = 'Alice'; + displayName = userName ?? '名無しのユーザー'; + print('表示名: $displayName'); +} +``` + +```dart-exec:null_coalescing.dart +表示名: 名無しのユーザー +表示名: Alice +``` diff --git a/public/docs/dart/2-null-safety/3-3-null-assignment.md b/public/docs/dart/2-null-safety/3-3-null-assignment.md new file mode 100644 index 00000000..fe675051 --- /dev/null +++ b/public/docs/dart/2-null-safety/3-3-null-assignment.md @@ -0,0 +1,31 @@ +--- +id: dart-null-safety-null-assignment +title: Null認識代入演算子(??=) +level: 3 +question: + - ??= 演算子は変数が null でない場合はどのような動作をしますか? + - ??= 演算子を使ったキャッシュ初期化パターンの書き方は? +term: + - Null認識代入 + - '??=' +--- + +### Null認識代入演算子(`??=`) + +変数が `null` の場合のみ、右辺の値を代入します。 + +```dart:null_aware_assignment.dart +void main() { + int? count; + count ??= 10; // null なので 10 を代入 + print('count: $count'); + + count ??= 20; // 既に 10 なので 20 は代入されない + print('count: $count'); +} +``` + +```dart-exec:null_aware_assignment.dart +count: 10 +count: 10 +``` diff --git a/public/docs/dart/2-null-safety/4-0-late.md b/public/docs/dart/2-null-safety/4-0-late.md index 1405f50f..fcf51864 100644 --- a/public/docs/dart/2-null-safety/4-0-late.md +++ b/public/docs/dart/2-null-safety/4-0-late.md @@ -5,10 +5,8 @@ level: 2 question: - late修飾子を付けると何が変わりますか? - late変数を初期化前に参照するとどうなりますか? - - lateと遅延初期化(Lazy Initialization)の関係は何ですか? term: - late - - 遅延初期化 - late修飾子 --- @@ -16,64 +14,4 @@ term: **`late` 修飾子** は、Non-nullableな変数の初期化を「宣言時ではなく、後から(あるいは必要になった時に)行う」ことをコンパイラに宣言するキーワードです。 -主な用途は以下の2点です。 - -### 1. 宣言時には値を代入できないNon-nullable変数の初期化 - -クラスの初期化メソッドやFlutterのライフサイクル(`initState`など)で後から値を設定する場合に使われます。 - -```dart:late_variables.dart -class UserProfile { - // 宣言時には値がないが、Non-nullableとして扱いたい - late String description; - - void setup() { - description = 'Dart & Flutter開発者'; - } - - void printProfile() { - print(description); - } -} - -void main() { - final profile = UserProfile(); - profile.setup(); - profile.printProfile(); -} -``` - -```dart-exec:late_variables.dart -Dart & Flutter開発者 -``` - -> [!WARNING] -> `late` で宣言した変数を初期化する前にアクセスすると、実行時に `LateInitializationError` がスローされます。 - -### 2. 遅延初期化(Lazy Initialization)によるパフォーマンス向上 - -`late` 変数に初期化式を記述すると、その変数に**初めてアクセスされた瞬間**にのみ計算が行われます。重い初期化処理をオンデマンドで実行したい場合に有効です。 - -```dart:late_lazy.dart -String heavyComputation() { - print('-> 重い計算を実行中...'); - return '計算結果: 42'; -} - -void main() { - print('プログラム開始'); - late String lazyData = heavyComputation(); // この時点ではまだ実行されない - - print('データが必要になりました:'); - print(lazyData); // ここで初めて heavyComputation が呼ばれる - print('二度目の参照: $lazyData'); // 既に計算済みなので再実行はされない -} -``` - -```dart-exec:late_lazy.dart -プログラム開始 -データが必要になりました: --> 重い計算を実行中... -計算結果: 42 -二度目の参照: 計算結果: 42 -``` +宣言時点では値が決まらないNon-nullable変数の保持や、重い計算の遅延評価に利用されます。 diff --git a/public/docs/dart/2-null-safety/4-1-late-init.md b/public/docs/dart/2-null-safety/4-1-late-init.md new file mode 100644 index 00000000..d43cc9ee --- /dev/null +++ b/public/docs/dart/2-null-safety/4-1-late-init.md @@ -0,0 +1,43 @@ +--- +id: dart-null-safety-late-init +title: late による初期化の遅延とLazy評価 +level: 3 +question: + - late変数に初期化式を書いた場合、いつ実行されますか? + - LateInitializationError を防ぐための注意点は何ですか? +term: + - 遅延初期化 + - Lazy評価 + - LateInitializationError +--- + +### `late` による初期化の遅延とLazy評価 + +`late` 変数に初期化式を記述すると、その変数に**初めてアクセスされた瞬間**にのみ計算が行われます(Lazy評価)。 + +```dart:late_lazy.dart +String heavyComputation() { + print('-> 重い計算を実行中...'); + return '計算結果: 42'; +} + +void main() { + print('プログラム開始'); + late String lazyData = heavyComputation(); // この時点ではまだ実行されない + + print('データが必要になりました:'); + print(lazyData); // ここで初めて heavyComputation が呼ばれる + print('二度目の参照: $lazyData'); // 既に計算済みなので再実行はされない +} +``` + +```dart-exec:late_lazy.dart +プログラム開始 +データが必要になりました: +-> 重い計算を実行中... +計算結果: 42 +二度目の参照: 計算結果: 42 +``` + +> [!WARNING] +> 初期化式のない `late` 変数を値代入前に参照すると、`LateInitializationError` が発生します。 diff --git a/public/docs/dart/2-null-safety/5-1-practice1.md b/public/docs/dart/2-null-safety/5-1-practice1.md index 435247b6..b4653c83 100644 --- a/public/docs/dart/2-null-safety/5-1-practice1.md +++ b/public/docs/dart/2-null-safety/5-1-practice1.md @@ -4,7 +4,7 @@ title: '練習問題1: Null安全なデータ処理' level: 3 question: - nullableなオブジェクトから安全に値を取り出すベストプラクティスは何ですか? - - ?? 演算子を複数チェーンさせることはできますか? + - '?? 演算子を複数チェーンさせることはできますか?' --- ### 練習問題1: Null安全なデータ処理 diff --git a/public/docs/dart/2-null-safety/5-2-practice2.md b/public/docs/dart/2-null-safety/5-2-practice2.md index 0b503f14..3ac8e41a 100644 --- a/public/docs/dart/2-null-safety/5-2-practice2.md +++ b/public/docs/dart/2-null-safety/5-2-practice2.md @@ -4,7 +4,7 @@ title: '練習問題2: lateとNull認識演算子の活用' level: 3 question: - late変数をクラス外のトップレベルで定義した場合も遅延評価されますか? - - ??= 演算子を使ってキャッシュ処理を書く場合の注意点は何ですか? + - '??= 演算子を使ってキャッシュ処理を書く場合の注意点は何ですか?' --- ### 練習問題2: lateとNull認識演算子の活用 diff --git a/public/docs/dart/3-functions/1-0-first-class.md b/public/docs/dart/3-functions/1-0-first-class.md index 09ee80a4..2ab901e8 100644 --- a/public/docs/dart/3-functions/1-0-first-class.md +++ b/public/docs/dart/3-functions/1-0-first-class.md @@ -1,24 +1,21 @@ --- id: dart-functions-first-class -title: 第一級オブジェクトとしての関数とアロー構文 +title: 第一級オブジェクトとしての関数 level: 2 question: - - アロー構文(=>)と通常の関数本体({})の使い分けは何ですか? + - 関数の基本的な定義構文はどう書きますか? - Dartで関数の型(Function型)はどのように表現しますか? - - トップレベル関数とクラスメソッドに関数の扱いの違いはありますか? term: - 第一級オブジェクト - - アロー関数 - - '=>' - 関数型 - Function --- -## 第一級オブジェクトとしての関数とアロー構文 +## 第一級オブジェクトとしての関数 [[Dart]]の関数は **[[第一級オブジェクト]]** であり、`Function` 型の値として変数に代入したり、高階関数の引数として渡すことができます。 -### 1. 関数の基本定義 +まずは標準的な関数定義を見てみましょう。 ```dart:function_basics.dart int add(int a, int b) { @@ -34,45 +31,3 @@ void main() { ```dart-exec:function_basics.dart 3 + 5 = 8 ``` - -### 2. アロー構文(`=>`) - -関数本体が単一の式(Expression)のみで構成される場合、波括弧 `{ return ...; }` の代わりに **`=>`(アロー構文)** を使って簡潔に記述できます。 - -```dart:arrow_syntax.dart -// 通常の関数定義 -int multiplyNormal(int a, int b) { - return a * b; -} - -// アロー構文による定義 -int multiplyArrow(int a, int b) => a * b; - -void main() { - print('multiplyNormal: ${multiplyNormal(4, 5)}'); - print('multiplyArrow: ${multiplyArrow(4, 5)}'); -} -``` - -```dart-exec:arrow_syntax.dart -multiplyNormal: 20 -multiplyArrow: 20 -``` - -### 3. 関数を変数に代入する - -```dart:first_class.dart -void main() { - // 関数を変数に代入 - int Function(int, int) operation = (a, b) => a + b; - print('変数から呼び出し: ${operation(10, 20)}'); - - operation = (a, b) => a * b; - print('乗算に変更: ${operation(10, 20)}'); -} -``` - -```dart-exec:first_class.dart -変数から呼び出し: 30 -乗算に変更: 200 -``` diff --git a/public/docs/dart/3-functions/1-1-arrow-syntax.md b/public/docs/dart/3-functions/1-1-arrow-syntax.md new file mode 100644 index 00000000..b624b1c9 --- /dev/null +++ b/public/docs/dart/3-functions/1-1-arrow-syntax.md @@ -0,0 +1,36 @@ +--- +id: dart-functions-arrow-syntax +title: アロー構文(=>)と関数オブジェクト +level: 3 +question: + - アロー構文(=>)と通常の関数本体({})の使い分けは何ですか? + - 関数を変数に代入して呼び出す方法を教えてください。 +term: + - アロー関数 + - '=>' + - アロー構文 +--- + +### アロー構文(`=>`)と関数オブジェクト + +関数本体が単一の式(Expression)のみで構成される場合、波括弧 `{ return ...; }` の代わりに **`=>`(アロー構文)** を使って簡潔に記述できます。 + +また、関数を変数に格納して利用することもできます。 + +```dart:arrow_syntax.dart +// アロー構文による定義 +int multiply(int a, int b) => a * b; + +void main() { + print('アロー関数: ${multiply(4, 5)}'); + + // 変数に関数を代入 + int Function(int, int) op = multiply; + print('変数経由の呼び出し: ${op(10, 20)}'); +} +``` + +```dart-exec:arrow_syntax.dart +アロー関数: 20 +変数経由の呼び出し: 200 +``` diff --git a/public/docs/dart/3-functions/2-0-parameters.md b/public/docs/dart/3-functions/2-0-parameters.md index 5402b013..687ce9c1 100644 --- a/public/docs/dart/3-functions/2-0-parameters.md +++ b/public/docs/dart/3-functions/2-0-parameters.md @@ -3,14 +3,12 @@ id: dart-functions-parameters title: パラメータ(引数)の種類 level: 2 question: - - 位置パラメータと名前付きパラメータの混在は可能ですか? - - 引数のデフォルト値はどのように指定しますか? - - Dartのパラメータ設計のベストプラクティスは何ですか? + - 必須の位置パラメータとは何ですか? + - オプショナルパラメータの種類には何がありますか? term: - 引数 - パラメータ - 位置パラメータ - - デフォルト引数 --- ## パラメータ(引数)の種類 @@ -20,12 +18,10 @@ term: 1. **必須の位置パラメータ(Required Positional Parameters)**: 通常の引数。渡す順番と型が固定されます。 2. **オプショナルパラメータ(Optional Parameters)**: - 省略可能な引数。さらに以下の2種類に分かれます。 - * **[[名前付き引数]](Named Parameters)**: `{}` で囲む。呼び出し時に `name: value` で指定。 - * **[[位置指定引数]](Optional Positional Parameters)**: `[]` で囲む。順番通りに省略可能。 + 省略可能な引数。**[[名前付き引数]](`{}`)** と **[[位置指定引数]](`[]`)** があります。 ```dart:parameter_types.dart -// 1. 必須の位置引数 +// 必須の位置引数 void greet(String name, String message) { print('$nameさん、$message'); } diff --git a/public/docs/dart/3-functions/2-1-named-positional.md b/public/docs/dart/3-functions/2-1-named-params.md similarity index 54% rename from public/docs/dart/3-functions/2-1-named-positional.md rename to public/docs/dart/3-functions/2-1-named-params.md index e19cc71e..dd1f686d 100644 --- a/public/docs/dart/3-functions/2-1-named-positional.md +++ b/public/docs/dart/3-functions/2-1-named-params.md @@ -1,22 +1,18 @@ --- id: dart-functions-named-positional -title: '名前付き引数({})と位置指定引数([])' +title: '名前付き引数({})と required' level: 3 question: - Flutterで名前付き引数が多用される理由は何ですか? - requiredキーワードを付けるとどうなりますか? - - 名前付き引数にデフォルト値を設定する方法を教えてください。 term: - 名前付き引数 - - 位置指定引数 - optional parameter - required - - デフォルト値 + - デフォルト引数 --- -### 名前付き引数(`{}`)と位置指定引数(`[]`) - -### 1. 名前付き引数(Named Parameters) +### 名前付き引数(`{}`)と `required` 引数を `{}` で囲むと、呼び出し側で引数名を明示して渡すことができるようになります。引数の順番は自由です。 @@ -34,7 +30,6 @@ void createUser({ } void main() { - // 引数名を指定して呼び出す(順番は自由) createUser(username: 'Alice'); createUser(role: 'admin', username: 'Bob', age: 30); } @@ -46,24 +41,4 @@ void main() { ``` > [!TIP] -> [[Flutter]]のWidgetコンストラクタはほぼすべて名前付き引数で設計されています。設定項目が多くなってもコードの可読性が損なわれません。 - -### 2. オプショナルな位置指定引数(Optional Positional Parameters) - -引数を `[]` で囲むと、順番通りのオプショナル引数を定義できます。 - -```dart:optional_positional.dart -String formatMessage(String from, String msg, [String? appName = 'MyChat']) { - return '[$appName] $from: $msg'; -} - -void main() { - print(formatMessage('Alice', 'Hello')); - print(formatMessage('Bob', 'Hi', 'FlutterApp')); -} -``` - -```dart-exec:optional_positional.dart -[MyChat] Alice: Hello -[FlutterApp] Bob: Hi -``` +> [[Flutter]]のWidgetコンストラクタはほぼすべて名前付き引数で設計されています。 diff --git a/public/docs/dart/3-functions/2-2-positional-params.md b/public/docs/dart/3-functions/2-2-positional-params.md new file mode 100644 index 00000000..0c94d8b9 --- /dev/null +++ b/public/docs/dart/3-functions/2-2-positional-params.md @@ -0,0 +1,31 @@ +--- +id: dart-functions-positional-params +title: '位置指定オプショナル引数([])' +level: 3 +question: + - 位置指定オプショナル引数の定義構文はどう書きますか? + - 名前付き引数と位置指定オプショナル引数を同時に使うことはできますか? +term: + - 位置指定引数 + - オプショナル引数 +--- + +### 位置指定オプショナル引数(`[]`) + +引数を `[]` で囲むと、順番通りのオプショナル引数を定義できます。 + +```dart:optional_positional.dart +String formatMessage(String from, String msg, [String? appName = 'MyChat']) { + return '[$appName] $from: $msg'; +} + +void main() { + print(formatMessage('Alice', 'Hello')); + print(formatMessage('Bob', 'Hi', 'FlutterApp')); +} +``` + +```dart-exec:optional_positional.dart +[MyChat] Alice: Hello +[FlutterApp] Bob: Hi +``` diff --git a/public/docs/dart/3-functions/3-0-anonymous-closures.md b/public/docs/dart/3-functions/3-0-anonymous-closures.md index 537ad279..d4868a76 100644 --- a/public/docs/dart/3-functions/3-0-anonymous-closures.md +++ b/public/docs/dart/3-functions/3-0-anonymous-closures.md @@ -3,77 +3,15 @@ id: dart-functions-anonymous-closures title: 無名関数とクロージャ level: 2 question: - - 無名関数(ラムダ式)の書き方はどのようになりますか? - - クロージャ(変数のキャプチャ)とは何ですか? - - コレクションのforEachやmapメソッドに渡す無名関数の実例を見たいです。 + - 無名関数と名前付き関数の使い分けは何ですか? + - クロージャを使うメリットは何ですか? term: - 無名関数 - 匿名関数 - - ラムダ式 - クロージャ - closure - - 変数のキャプチャ --- ## 無名関数とクロージャ -### 1. 無名関数(Anonymous Functions / Lambdas) - -関数名を付けずに定義する関数を **[[無名関数]]** と呼びます。イベントハンドラやコレクションの操作([[./4]]参照)に頻繁に利用されます。 - -```dart:anonymous_functions.dart -void main() { - final fruits = ['apple', 'banana', 'orange']; - - // (item) { ... } という無名関数を forEach に渡す - fruits.forEach((fruit) { - print('フルーツ: $fruit'); - }); - - // アロー構文を用いた無名関数 - fruits.forEach((fruit) => print('UPPER: ${fruit.toUpperCase()}')); -} -``` - -```dart-exec:anonymous_functions.dart -フルーツ: apple -フルーツ: banana -フルーツ: orange -UPPER: APPLE -UPPER: BANANA -UPPER: ORANGE -``` - -### 2. クロージャ(Closures) - -**[[クロージャ]]** とは、関数が定義されたスコープの外側の変数を「キャプチャ(保持)」し、関数が別のスコープで実行されてもその変数にアクセス・変更できる仕組みです。 - -```dart:closures.dart -// カウンター関数を生成する高階関数 -Function makeCounter() { - int count = 0; // クロージャによってキャプチャされるローカル変数 - - return () { - count++; - return count; - }; -} - -void main() { - final counter1 = makeCounter(); - final counter2 = makeCounter(); - - print('counter1: ${counter1()}'); // 1 - print('counter1: ${counter1()}'); // 2 - - print('counter2: ${counter2()}'); // 1 (独立した状態を持つ) - print('counter1: ${counter1()}'); // 3 -} -``` - -```dart-exec:closures.dart -counter1: 1 -counter1: 2 -counter2: 1 -counter1: 3 -``` +[[Dart]]では、名前を持たない **[[無名関数]](ラムダ式)** を定義してコールバックとして渡したり、外側のスコープの変数を保持する **[[クロージャ]]** を作成できます。 diff --git a/public/docs/dart/3-functions/3-1-anonymous.md b/public/docs/dart/3-functions/3-1-anonymous.md new file mode 100644 index 00000000..01931a22 --- /dev/null +++ b/public/docs/dart/3-functions/3-1-anonymous.md @@ -0,0 +1,38 @@ +--- +id: dart-functions-anonymous +title: 無名関数(ラムダ式) +level: 3 +question: + - コレクションのforEachメソッドに無名関数を渡す書き方を教えてください。 + - 無名関数でアロー構文を使うことはできますか? +term: + - ラムダ式 + - コールバック関数 +--- + +### 無名関数(ラムダ式) + +関数名を付けずに定義する関数を **[[無名関数]]** と呼びます。イベントハンドラやコレクションの操作([[./4]]参照)に頻繁に利用されます。 + +```dart:anonymous_functions.dart +void main() { + final fruits = ['apple', 'banana', 'orange']; + + // (item) { ... } という無名関数を forEach に渡す + fruits.forEach((fruit) { + print('フルーツ: $fruit'); + }); + + // アロー構文を用いた無名関数 + fruits.forEach((fruit) => print('UPPER: ${fruit.toUpperCase()}')); +} +``` + +```dart-exec:anonymous_functions.dart +フルーツ: apple +フルーツ: banana +フルーツ: orange +UPPER: APPLE +UPPER: BANANA +UPPER: ORANGE +``` diff --git a/public/docs/dart/3-functions/3-2-closures.md b/public/docs/dart/3-functions/3-2-closures.md new file mode 100644 index 00000000..e9703c5e --- /dev/null +++ b/public/docs/dart/3-functions/3-2-closures.md @@ -0,0 +1,45 @@ +--- +id: dart-functions-closures +title: クロージャと変数のキャプチャ +level: 3 +question: + - クロージャ(変数のキャプチャ)とは何ですか? + - 複数のクロージャインスタンスがそれぞれ独立した状態を保持する仕組みは? +term: + - 変数のキャプチャ + - スコープ +--- + +### クロージャと変数のキャプチャ + +**[[クロージャ]]** とは、関数が定義されたスコープの外側の変数を「キャプチャ(保持)」し、関数が別のスコープで実行されてもその変数にアクセス・変更できる仕組みです。 + +```dart:closures.dart +// カウンター関数を生成する高階関数 +Function makeCounter() { + int count = 0; // クロージャによってキャプチャされるローカル変数 + + return () { + count++; + return count; + }; +} + +void main() { + final counter1 = makeCounter(); + final counter2 = makeCounter(); + + print('counter1: ${counter1()}'); // 1 + print('counter1: ${counter1()}'); // 2 + + print('counter2: ${counter2()}'); // 1 (独立した状態を持つ) + print('counter1: ${counter1()}'); // 3 +} +``` + +```dart-exec:closures.dart +counter1: 1 +counter1: 2 +counter2: 1 +counter1: 3 +``` diff --git a/public/docs/dart/4-collections-control/1-0-collections.md b/public/docs/dart/4-collections-control/1-0-collections.md index 40fa8625..fb187ba8 100644 --- a/public/docs/dart/4-collections-control/1-0-collections.md +++ b/public/docs/dart/4-collections-control/1-0-collections.md @@ -4,7 +4,6 @@ title: List、Set、Map とジェネリクス level: 2 question: - List、Set、Map の使い分けの基準は何ですか? - - コレクションのリテラル記法はどうなっていますか? - 型推論で要素の型が固定される仕組みを教えてください。 term: - List @@ -16,73 +15,6 @@ term: ## `List`、`Set`、`Map` とジェネリクス -[[Dart]]の代表的なコレクション型には、**`List`**、**`Set`**、**`Map`** があります。すべて[[ジェネリクス]](``)に対応しており、要素の型が厳格にチェックされます。 +[[Dart]]の代表的なコレクション型には、**`List`**、**`Set`**、**`Map`** があります。 -### 1. `List`: 順序付きの配列 - -角括弧 `[]` を使ってリテラルを定義します。 - -```dart:list_basics.dart -void main() { - // 型推論により List になる - var fruits = ['apple', 'banana', 'orange']; - - fruits.add('grape'); - print('要素数: ${fruits.length}'); - print('0番目: ${fruits[0]}'); - print('全要素: $fruits'); -} -``` - -```dart-exec:list_basics.dart -要素数: 4 -0番目: apple -全要素: [apple, banana, orange, grape] -``` - -### 2. `Set`: 重複のない集合 - -波括弧 `{}` を使い、要素のみをカンマ区切りで並べます。 - -```dart:set_basics.dart -void main() { - // Set - var numbers = {1, 2, 3, 2, 1}; - numbers.add(4); - numbers.add(3); // 重複は無視される - - print('Setの要素: $numbers'); - print('2を含むか: ${numbers.contains(2)}'); -} -``` - -```dart-exec:set_basics.dart -Setの要素: {1, 2, 3, 4} -2を含むか: true -``` - -### 3. `Map`: キーと値のペア(連想配列) - -波括弧 `{}` を使い、`key: value` 形式で記述します。 - -```dart:map_basics.dart -void main() { - // Map - var scores = { - 'Alice': 95, - 'Bob': 80, - }; - - scores['Charlie'] = 88; // 要素の追加・更新 - print('Aliceの点数: ${scores['Alice']}'); - print('存在しないキー: ${scores['Dave']}'); // null が返る -} -``` - -```dart-exec:map_basics.dart -Aliceの点数: 95 -存在しないキー: null -``` - -> [!NOTE] -> 空の波括弧 `{}` はデフォルトで `Map` とみなされます。空のSetを作りたい場合は `{}` や `Set()` のように型を明示します。 +すべて[[ジェネリクス]](``)に対応しており、要素の型がコンパイル時に厳格にチェックされます。 diff --git a/public/docs/dart/4-collections-control/1-1-list.md b/public/docs/dart/4-collections-control/1-1-list.md new file mode 100644 index 00000000..39e3f3a8 --- /dev/null +++ b/public/docs/dart/4-collections-control/1-1-list.md @@ -0,0 +1,33 @@ +--- +id: dart-collections-list +title: 'List: 順序付き配列' +level: 3 +question: + - Listのリテラル記法はどうなっていますか? + - 要素の追加やインデックスアクセスの方法を教えてください。 +term: + - 配列 + - リスト +--- + +### `List`: 順序付き配列 + +角括弧 `[]` を使ってリテラルを定義します。 + +```dart:list_basics.dart +void main() { + // 型推論により List になる + var fruits = ['apple', 'banana', 'orange']; + + fruits.add('grape'); + print('要素数: ${fruits.length}'); + print('0番目: ${fruits[0]}'); + print('全要素: $fruits'); +} +``` + +```dart-exec:list_basics.dart +要素数: 4 +0番目: apple +全要素: [apple, banana, orange, grape] +``` diff --git a/public/docs/dart/4-collections-control/1-2-set.md b/public/docs/dart/4-collections-control/1-2-set.md new file mode 100644 index 00000000..1a1a70d1 --- /dev/null +++ b/public/docs/dart/4-collections-control/1-2-set.md @@ -0,0 +1,35 @@ +--- +id: dart-collections-set +title: 'Set: 重複のない集合' +level: 3 +question: + - SetとListの違いは何ですか? + - 空のSetをリテラルで定義する際の注意点は? +term: + - 集合 + - 一意 +--- + +### `Set`: 重複のない集合 + +波括弧 `{}` を使い、要素のみをカンマ区切りで並べます。 + +```dart:set_basics.dart +void main() { + // Set + var numbers = {1, 2, 3, 2, 1}; + numbers.add(4); + numbers.add(3); // 重複は無視される + + print('Setの要素: $numbers'); + print('2を含むか: ${numbers.contains(2)}'); +} +``` + +```dart-exec:set_basics.dart +Setの要素: {1, 2, 3, 4} +2を含むか: true +``` + +> [!NOTE] +> 空の波括弧 `{}` はデフォルトで `Map` と判定されます。空のSetを作る場合は `{}` や `Set()` と型を明示します。 diff --git a/public/docs/dart/4-collections-control/1-3-map.md b/public/docs/dart/4-collections-control/1-3-map.md new file mode 100644 index 00000000..bbdeb780 --- /dev/null +++ b/public/docs/dart/4-collections-control/1-3-map.md @@ -0,0 +1,34 @@ +--- +id: dart-collections-map +title: 'Map: キーと値のペア' +level: 3 +question: + - Mapのリテラル記法はどうなっていますか? + - 存在しないキーにアクセスした場合に返る値は何ですか? +term: + - 連想配列 + - ディクショナリ +--- + +### `Map`: キーと値のペア + +波括弧 `{}` を使い、`key: value` 形式で記述します。 + +```dart:map_basics.dart +void main() { + // Map + var scores = { + 'Alice': 95, + 'Bob': 80, + }; + + scores['Charlie'] = 88; // 要素の追加・更新 + print('Aliceの点数: ${scores['Alice']}'); + print('存在しないキー: ${scores['Dave']}'); // null が返る +} +``` + +```dart-exec:map_basics.dart +Aliceの点数: 95 +存在しないキー: null +``` diff --git a/public/docs/dart/4-collections-control/2-0-collection-features.md b/public/docs/dart/4-collections-control/2-0-collection-features.md index 04082aa5..cf711e51 100644 --- a/public/docs/dart/4-collections-control/2-0-collection-features.md +++ b/public/docs/dart/4-collections-control/2-0-collection-features.md @@ -3,85 +3,14 @@ id: dart-collections-features title: コレクション if、for とスプレッド演算子 level: 2 question: - - コレクション if と三項演算子の違いは何ですか? - - スプレッド演算子 (...) と Null認識スプレッド演算子 (...?) の使い分けは何ですか? - - コレクション for を使うとどのようなコードが簡潔になりますか? + - コレクション内で直接条件分岐やループを展開する構文とは何ですか? + - FlutterのWidgetツリー構築でこれらが重宝される理由は何ですか? term: - - コレクションif - - コレクションfor - - スプレッド演算子 - - '...' - - '...?' + - コレクション構文 --- ## コレクション `if`、`for` とスプレッド演算子 [[Dart]]のコレクションリテラル内では、要素の生成ロジックとして `if`、`for`、スプレッド演算子を直接埋め込むことができます。 -### 1. コレクション `if` - -条件が `true` の場合のみ要素をコレクションに含めます。 - -```dart:collection_if.dart -void main() { - bool isAdmin = true; - bool isGuest = false; - - var navItems = [ - 'ホーム', - 'プロフィール', - if (isAdmin) '管理者パネル', - if (isGuest) 'ログイン案内', - ]; - - print(navItems); -} -``` - -```dart-exec:collection_if.dart -[ホーム, プロフィール, 管理者パネル] -``` - -### 2. コレクション `for` - -ループを使って複数の要素を動的に展開してコレクションを構築します。 - -```dart:collection_for.dart -void main() { - var numbers = [1, 2, 3]; - var stringList = [ - '#0', - for (var n in numbers) '#$n', - ]; - - print(stringList); -} -``` - -```dart-exec:collection_for.dart -[#0, #1, #2, #3] -``` - -### 3. スプレッド演算子 (`...` / `...?`) - -既存のコレクションの全要素を別のコレクション内に展開して埋め込みます。対象が `null` になり得る場合は **`...?`(Null認識スプレッド演算子)** を使用します。 - -```dart:spread_operator.dart -void main() { - var baseList = [1, 2]; - List? extraList; // null - - var combined = [ - 0, - ...baseList, - ...?extraList, // null なので展開を安全にスキップ - 3, - ]; - - print(combined); -} -``` - -```dart-exec:spread_operator.dart -[0, 1, 2, 3] -``` +これにより、動的なリスト生成を非常に宣言的かつクリーンに記述できます。 diff --git a/public/docs/dart/4-collections-control/2-1-collection-if.md b/public/docs/dart/4-collections-control/2-1-collection-if.md new file mode 100644 index 00000000..aca1c871 --- /dev/null +++ b/public/docs/dart/4-collections-control/2-1-collection-if.md @@ -0,0 +1,34 @@ +--- +id: dart-collections-if +title: コレクション if +level: 3 +question: + - コレクション if はどのような構文で記述しますか? + - コレクション if の中で else を使ってフォールバック要素を追加できますか? +term: + - コレクションif +--- + +### コレクション `if` + +条件が `true` の場合のみ要素をコレクションに含めます。 + +```dart:collection_if.dart +void main() { + bool isAdmin = true; + bool isGuest = false; + + var navItems = [ + 'ホーム', + 'プロフィール', + if (isAdmin) '管理者パネル', + if (isGuest) 'ログイン案内', + ]; + + print(navItems); +} +``` + +```dart-exec:collection_if.dart +[ホーム, プロフィール, 管理者パネル] +``` diff --git a/public/docs/dart/4-collections-control/2-2-collection-for.md b/public/docs/dart/4-collections-control/2-2-collection-for.md new file mode 100644 index 00000000..aafddefa --- /dev/null +++ b/public/docs/dart/4-collections-control/2-2-collection-for.md @@ -0,0 +1,30 @@ +--- +id: dart-collections-for +title: コレクション for +level: 3 +question: + - コレクション for を使ってリストを展開する例を教えてください。 + - コレクション for の中でコレクション if をネストさせることはできますか? +term: + - コレクションfor +--- + +### コレクション `for` + +ループを使って複数の要素を動的に展開してコレクションを構築します。 + +```dart:collection_for.dart +void main() { + var numbers = [1, 2, 3]; + var stringList = [ + '#0', + for (var n in numbers) '#$n', + ]; + + print(stringList); +} +``` + +```dart-exec:collection_for.dart +[#0, #1, #2, #3] +``` diff --git a/public/docs/dart/4-collections-control/2-3-spread-operator.md b/public/docs/dart/4-collections-control/2-3-spread-operator.md new file mode 100644 index 00000000..44b0208f --- /dev/null +++ b/public/docs/dart/4-collections-control/2-3-spread-operator.md @@ -0,0 +1,36 @@ +--- +id: dart-collections-spread +title: 'スプレッド演算子(... / ...?)' +level: 3 +question: + - スプレッド演算子 (...) と Null認識スプレッド演算子 (...?) の使い分けは何ですか? + - リストの途中に別のリストの要素を展開する方法は? +term: + - スプレッド演算子 + - '...' + - '...?' +--- + +### スプレッド演算子(`...` / `...?`) + +既存のコレクションの全要素を別のコレクション内に展開して埋め込みます。対象が `null` になり得る場合は **`...?`(Null認識スプレッド演算子)** を使用します。 + +```dart:spread_operator.dart +void main() { + var baseList = [1, 2]; + List? extraList; // null + + var combined = [ + 0, + ...baseList, + ...?extraList, // null なので展開を安全にスキップ + 3, + ]; + + print(combined); +} +``` + +```dart-exec:spread_operator.dart +[0, 1, 2, 3] +``` diff --git a/public/docs/dart/4-collections-control/3-0-higher-order.md b/public/docs/dart/4-collections-control/3-0-higher-order.md index 2c745adf..73310583 100644 --- a/public/docs/dart/4-collections-control/3-0-higher-order.md +++ b/public/docs/dart/4-collections-control/3-0-higher-order.md @@ -1,76 +1,15 @@ --- id: dart-collections-higher-order -title: '高階関数によるデータ変換(map、where、reduce、fold)' +title: 高階関数によるデータ変換 level: 2 question: - Iterable と List の関係は何ですか? - - map や where の結果を List に変換するにはどうすればよいですか? - - reduce と fold の違いは何ですか? + - Dartで関数型スタイルのデータ変換を行うメソッドには何がありますか? term: - 高階関数 - - map - - where - - reduce - - fold - Iterable - - toList --- -## 高階関数によるデータ変換(`map`、`where`、`reduce`、`fold`) +## 高階関数によるデータ変換 -[[Dart]]のコレクション(`Iterable`)には、関数型プログラミングスタイルでデータを操作・集計するための便利な高階メソッドが用意されています。 - -### 1. `where` (フィルタリング) と `map` (要素変換) - -* `where`: 条件を満たす(コールバックが `true` を返す)要素のみを抽出します。 -* `map`: 各要素を変換関数で別の値に写像します。 -* `toList()`: 遅延評価される `Iterable` を `List` に確定します。 - -```dart:map_where.dart -void main() { - final numbers = [1, 2, 3, 4, 5, 6]; - - // 偶数だけを抽出し、それぞれを2乗してListに変換 - final result = numbers - .where((n) => n.isEven) - .map((n) => n * n) - .toList(); - - print('元のリスト: $numbers'); - print('変換後: $result'); -} -``` - -```dart-exec:map_where.dart -元のリスト: [1, 2, 3, 4, 5, 6] -変換後: [4, 16, 36] -``` - -### 2. `reduce` と `fold` (畳み込み集計) - -* `reduce`: リストの先頭要素を初期値として要素を1つの値にまとめます(空リストではエラー)。 -* `fold`: 明示的な初期値を指定して集計を開始します(空リストでも安全、型変換も可能)。 - -```dart:reduce_fold.dart -void main() { - final numbers = [10, 20, 30, 40]; - - // reduce による合計 - final sum = numbers.reduce((acc, curr) => acc + curr); - print('合計 (reduce): $sum'); - - // fold による初期値 100 からの加算 - final totalWithBase = numbers.fold(100, (acc, curr) => acc + curr); - print('ベース値付き合計 (fold): $totalWithBase'); - - // fold で文字列結合 - final joined = numbers.fold('値:', (acc, curr) => '$acc $curr'); - print('文字列 (fold): $joined'); -} -``` - -```dart-exec:reduce_fold.dart -合計 (reduce): 100 -ベース値付き合計 (fold): 200 -文字列 (fold): 値: 10 20 30 40 -``` +[[Dart]]のコレクション(`Iterable`)には、関数型プログラミングスタイルでデータを操作・集計するための便利な高階メソッド(`where`, `map`, `reduce`, `fold` など)が用意されています。 diff --git a/public/docs/dart/4-collections-control/3-1-map-where.md b/public/docs/dart/4-collections-control/3-1-map-where.md new file mode 100644 index 00000000..b1285307 --- /dev/null +++ b/public/docs/dart/4-collections-control/3-1-map-where.md @@ -0,0 +1,38 @@ +--- +id: dart-collections-map-where +title: where と map による抽出・変換 +level: 3 +question: + - where メソッドと map メソッドの役割の違いは何ですか? + - map の結果を List に変換するにはどうすればよいですか? +term: + - where + - map + - toList +--- + +### `where` と `map` による抽出・変換 + +* `where`: 条件を満たす(コールバックが `true` を返す)要素のみを抽出します。 +* `map`: 各要素を変換関数で別の値に写像します。 +* `toList()`: 遅延評価される `Iterable` を `List` に確定します。 + +```dart:map_where.dart +void main() { + final numbers = [1, 2, 3, 4, 5, 6]; + + // 偶数だけを抽出し、それぞれを2乗してListに変換 + final result = numbers + .where((n) => n.isEven) + .map((n) => n * n) + .toList(); + + print('元のリスト: $numbers'); + print('変換後: $result'); +} +``` + +```dart-exec:map_where.dart +元のリスト: [1, 2, 3, 4, 5, 6] +変換後: [4, 16, 36] +``` diff --git a/public/docs/dart/4-collections-control/3-2-reduce-fold.md b/public/docs/dart/4-collections-control/3-2-reduce-fold.md new file mode 100644 index 00000000..45ad720a --- /dev/null +++ b/public/docs/dart/4-collections-control/3-2-reduce-fold.md @@ -0,0 +1,41 @@ +--- +id: dart-collections-reduce-fold +title: reduce と fold による集計 +level: 3 +question: + - reduce と fold の決定的な違いは何ですか? + - 空リストに対して reduce を呼ぶとどうなりますか? +term: + - reduce + - fold + - 畳み込み集計 +--- + +### `reduce` と `fold` による集計 + +* `reduce`: リストの先頭要素を初期値として要素を1つの値にまとめます(空リストではエラー)。 +* `fold`: 明示的な初期値を指定して集計を開始します(空リストでも安全、型変換も可能)。 + +```dart:reduce_fold.dart +void main() { + final numbers = [10, 20, 30, 40]; + + // reduce による合計 + final sum = numbers.reduce((acc, curr) => acc + curr); + print('合計 (reduce): $sum'); + + // fold による初期値 100 からの加算 + final totalWithBase = numbers.fold(100, (acc, curr) => acc + curr); + print('ベース値付き合計 (fold): $totalWithBase'); + + // fold で文字列結合 + final joined = numbers.fold('値:', (acc, curr) => '$acc $curr'); + print('文字列 (fold): $joined'); +} +``` + +```dart-exec:reduce_fold.dart +合計 (reduce): 100 +ベース値付き合計 (fold): 200 +文字列 (fold): 値: 10 20 30 40 +``` diff --git a/public/docs/dart/5-records-patterns/1-0-records.md b/public/docs/dart/5-records-patterns/1-0-records.md index f411179d..0f4fccc7 100644 --- a/public/docs/dart/5-records-patterns/1-0-records.md +++ b/public/docs/dart/5-records-patterns/1-0-records.md @@ -4,8 +4,7 @@ title: レコード(Records)による複数戻り値の実現 level: 2 question: - レコードとクラス(Class)の違いは何ですか? - - 位置指定フィールドと名前付きフィールドを持つレコードはどう書きますか? - - レコードのフィールド値を取得するための構文($1, $2, フィールド名)を教えてください。 + - レコードを使うことでどのようなメリットがありますか? term: - レコード - Records @@ -18,52 +17,3 @@ term: **[[レコード]](Records)** は、複数の値を1つにまとめることができる匿名かつ不変(イミュータブル)な集約型(いわゆるタプル)です。 専用のクラスを定義することなく、関数から複数の値を型安全に返すことができます。 - -### 1. 位置指定フィールド(Positional Fields) - -丸括弧 `()` で値を囲むことでレコードを作成します。各フィールドには `$1`, `$2` でアクセスします。 - -```dart:positional_records.dart -// (String, int) 型のレコードを返す関数 -(String, int) getUserInfo() { - return ('Alice', 25); -} - -void main() { - final user = getUserInfo(); - print('名前 (\$1): ${user.$1}'); - print('年齢 (\$2): ${user.$2}'); -} -``` - -```dart-exec:positional_records.dart -名前 ($1): Alice -年齢 ($2): 25 -``` - -### 2. 名前付きフィールド(Named Fields) - -波括弧 `{}` を使うことで、フィールドに名前を付けることができます。 - -```dart:named_records.dart -// 名前付きフィールドを持つレコード型 -({String name, int age, bool isAdmin}) getDetailedUser() { - return (name: 'Bob', age: 30, isAdmin: true); -} - -void main() { - final user = getDetailedUser(); - print('名前: ${user.name}'); - print('年齢: ${user.age}'); - print('管理者: ${user.isAdmin}'); -} -``` - -```dart-exec:named_records.dart -名前: Bob -年齢: 30 -管理者: true -``` - -> [!NOTE] -> レコードは値の同一性(Equality)を持ちます。同じフィールドと値を持つ2つのレコードは `==` で比較した際に `true` になります。 diff --git a/public/docs/dart/5-records-patterns/1-1-positional-records.md b/public/docs/dart/5-records-patterns/1-1-positional-records.md new file mode 100644 index 00000000..bd2f202f --- /dev/null +++ b/public/docs/dart/5-records-patterns/1-1-positional-records.md @@ -0,0 +1,33 @@ +--- +id: dart-records-positional +title: 位置指定フィールド(Positional Fields) +level: 3 +question: + - 位置指定フィールドへのアクセス方法($1, $2)はどう書きますか? + - 位置指定フィールドの型定義の構文を教えてください。 +term: + - 位置指定フィールド + - '$1' + - '$2' +--- + +### 位置指定フィールド(Positional Fields) + +丸括弧 `()` で値を囲むことでレコードを作成します。各フィールドには `$1`, `$2` でアクセスします。 + +```dart:positional_records.dart +(String, int) getUserInfo() { + return ('Alice', 25); +} + +void main() { + final user = getUserInfo(); + print('名前 (\$1): ${user.$1}'); + print('年齢 (\$2): ${user.$2}'); +} +``` + +```dart-exec:positional_records.dart +名前 ($1): Alice +年齢 ($2): 25 +``` diff --git a/public/docs/dart/5-records-patterns/1-2-named-records.md b/public/docs/dart/5-records-patterns/1-2-named-records.md new file mode 100644 index 00000000..76cdbdac --- /dev/null +++ b/public/docs/dart/5-records-patterns/1-2-named-records.md @@ -0,0 +1,33 @@ +--- +id: dart-records-named +title: 名前付きフィールド(Named Fields) +level: 3 +question: + - 名前付きフィールドを持つレコードの定義方法を教えてください。 + - レコード内で位置指定フィールドと名前付きフィールドを混在させることはできますか? +term: + - 名前付きフィールド +--- + +### 名前付きフィールド(Named Fields) + +波括弧 `{}` を使うことで、フィールドに名前を付けることができます。 + +```dart:named_records.dart +({String name, int age, bool isAdmin}) getDetailedUser() { + return (name: 'Bob', age: 30, isAdmin: true); +} + +void main() { + final user = getDetailedUser(); + print('名前: ${user.name}'); + print('年齢: ${user.age}'); + print('管理者: ${user.isAdmin}'); +} +``` + +```dart-exec:named_records.dart +名前: Bob +年齢: 30 +管理者: true +``` diff --git a/public/docs/dart/5-records-patterns/2-0-destructuring.md b/public/docs/dart/5-records-patterns/2-0-destructuring.md index b1e9e729..6c77cf3f 100644 --- a/public/docs/dart/5-records-patterns/2-0-destructuring.md +++ b/public/docs/dart/5-records-patterns/2-0-destructuring.md @@ -3,61 +3,14 @@ id: dart-patterns-destructuring title: 分解(Destructuring)によるデータの抽出 level: 2 question: - - レコードやListから直接変数に分解代入する方法はどう書きますか? - - 分解時に一部の値を無視するにはどうすればよいですか? - - Mapの分解代入はどのように行いますか? + - パターン分解(Destructuring)とは何ですか? + - 分解代入を使うメリットは何ですか? term: - パターン分解 - 分解代入 - Destructuring - - ワイルドカード --- ## 分解(Destructuring)によるデータの抽出 Dart 3のパターン構文を使用すると、レコード、List、Mapなどの複合データ構造を宣言的に分解(**[[分解代入]]**)して個別のローカル変数に抽出できます。 - -### 1. レコードの分解 - -```dart:destructure_records.dart -(String, int) getCoords() => ('Tokyo', 100); -({int x, int y}) getPoint() => (x: 10, y: 20); - -void main() { - // 位置レコードの分解 - final (city, population) = getCoords(); - print('都市: $city, 人口: $population 万人'); - - // 名前付きレコードの分解 (フィールド名と同名の変数にバインド) - final (:x, :y) = getPoint(); - print('座標: x=$x, y=$y'); -} -``` - -```dart-exec:destructure_records.dart -都市: Tokyo, 人口: 100 万人 -座標: x=10, y=20 -``` - -### 2. List と Map の分解 - -リストの要素数や中身に一致するパターンを使って値を抽出できます。不要な要素は `_`(ワイルドカード)で無視できます。 - -```dart:destructure_collections.dart -void main() { - // List の分解 - final numbers = [1, 2, 3, 4]; - final [first, second, _, fourth] = numbers; - print('1番目: $first, 2番目: $second, 4番目: $fourth'); - - // Map の分解 - final json = {'id': 'user_123', 'status': 'active'}; - final {'id': String userId, 'status': String userStatus} = json; - print('ユーザーID: $userId, ステータス: $userStatus'); -} -``` - -```dart-exec:destructure_collections.dart -1番目: 1, 2番目: 2, 4番目: 4 -ユーザーID: user_123, ステータス: active -``` diff --git a/public/docs/dart/5-records-patterns/2-1-destructure-records.md b/public/docs/dart/5-records-patterns/2-1-destructure-records.md new file mode 100644 index 00000000..64e3993a --- /dev/null +++ b/public/docs/dart/5-records-patterns/2-1-destructure-records.md @@ -0,0 +1,32 @@ +--- +id: dart-patterns-destructure-records +title: レコードの分解 +level: 3 +question: + - レコードから直接変数に分解代入する方法はどう書きますか? + - 名前付きレコードの分解時にプロパティ名と同じ変数名へバインドする省略記法((:x, :y))とは? +term: + - レコード分解 +--- + +### レコードの分解 + +```dart:destructure_records.dart +(String, int) getCoords() => ('Tokyo', 100); +({int x, int y}) getPoint() => (x: 10, y: 20); + +void main() { + // 位置レコードの分解 + final (city, population) = getCoords(); + print('都市: $city, 人口: $population 万人'); + + // 名前付きレコードの分解 + final (:x, :y) = getPoint(); + print('座標: x=$x, y=$y'); +} +``` + +```dart-exec:destructure_records.dart +都市: Tokyo, 人口: 100 万人 +座標: x=10, y=20 +``` diff --git a/public/docs/dart/5-records-patterns/2-2-destructure-collections.md b/public/docs/dart/5-records-patterns/2-2-destructure-collections.md new file mode 100644 index 00000000..813d856f --- /dev/null +++ b/public/docs/dart/5-records-patterns/2-2-destructure-collections.md @@ -0,0 +1,35 @@ +--- +id: dart-patterns-destructure-collections +title: List と Map の分解 +level: 3 +question: + - リストの分解時に不要な要素をスキップするワイルドカード(_)の使い方は? + - Map の要素をパターン分解して型注釈付きで変数に抽出する方法を教えてください。 +term: + - リスト分解 + - マップ分解 + - ワイルドカード +--- + +### List と Map の分解 + +リストの要素数や中身に一致するパターンを使って値を抽出できます。不要な要素は `_`(ワイルドカード)で無視できます。 + +```dart:destructure_collections.dart +void main() { + // List の分解 + final numbers = [1, 2, 3, 4]; + final [first, second, _, fourth] = numbers; + print('1番目: $first, 2番目: $second, 4番目: $fourth'); + + // Map の分解 + final json = {'id': 'user_123', 'status': 'active'}; + final {'id': String userId, 'status': String userStatus} = json; + print('ユーザーID: $userId, ステータス: $userStatus'); +} +``` + +```dart-exec:destructure_collections.dart +1番目: 1, 2番目: 2, 4番目: 4 +ユーザーID: user_123, ステータス: active +``` diff --git a/public/docs/dart/5-records-patterns/3-0-switch-expressions.md b/public/docs/dart/5-records-patterns/3-0-switch-expressions.md index ca17b36b..c9ce7cf2 100644 --- a/public/docs/dart/5-records-patterns/3-0-switch-expressions.md +++ b/public/docs/dart/5-records-patterns/3-0-switch-expressions.md @@ -4,23 +4,18 @@ title: パターンマッチングと switch 式 level: 2 question: - switch文とswitch式の違いは何ですか? - - switch式での網羅性チェック(Exhaustiveness checking)とは何ですか? - - switch式の中でパターンマッチングを使って型判定と値の取り出しを同時に行う方法は? + - switch式での網羅性チェックとは何ですか? term: - パターンマッチング - switch式 - 網羅性チェック - - switch文 --- ## パターンマッチングと `switch` 式 -従来の `switch` 文(Statement)に加え、Dart 3では評価結果の値を返す **`switch` 式(Expression)** が導入されました。 +従来の `switch` 文に加え、Dart 3では評価結果の値を返す **`switch` 式(Expression)** が導入されました。 -### 1. `switch` 式の基本構文 - -* `case` や `break` キーワードが不要になり、`パターン => 式` の簡潔な構文になります。 -* すべてのケースが網羅されているかコンパイラが厳密に検証する **[[網羅性チェック]]** が働きます。 +すべてのケースが網羅されているかコンパイラが厳密に検証する **[[網羅性チェック]]** が働きます。 ```dart:switch_expression.dart String describeHttpCode(int statusCode) { @@ -29,7 +24,7 @@ String describeHttpCode(int statusCode) { 400 => '不正なリクエスト (Bad Request)', 404 => '未検出 (Not Found)', 500 => 'サーバーエラー (Internal Server Error)', - _ => '不明なステータスコード ($statusCode)', // デフォルトケース + _ => '不明なステータスコード ($statusCode)', }; } @@ -45,37 +40,3 @@ void main() { 未検出 (Not Found) 不明なステータスコード (418) ``` - -### 2. オブジェクトやコレクションのパターンマッチング - -`switch` 式の中でデータの形状を検証しながら変数を取り出すことができます。 - -```dart:pattern_matching_complex.dart -String formatData(dynamic data) { - return switch (data) { - // 整数で 0 の場合 - 0 => 'ゼロ', - // 正の整数の場合 (関係演算子パターン) - int n && > 0 => '正の整数: $n', - // 2要素のリストの場合 - [var a, var b] => '2要素リスト: ($a, $b)', - // 特定のキーを持つマップの場合 - {'name': String name, 'age': int age} => '名前: $name, 年齢: $age', - _ => 'その他のデータ', - }; -} - -void main() { - print(formatData(10)); - print(formatData(['apple', 'banana'])); - print(formatData({'name': 'Alice', 'age': 20})); - print(formatData(false)); -} -``` - -```dart-exec:pattern_matching_complex.dart -正の整数: 10 -2要素リスト: (apple, banana) -名前: Alice, 年齢: 20 -その他のデータ -``` diff --git a/public/docs/dart/5-records-patterns/3-1-pattern-matching.md b/public/docs/dart/5-records-patterns/3-1-pattern-matching.md new file mode 100644 index 00000000..4b92c46c --- /dev/null +++ b/public/docs/dart/5-records-patterns/3-1-pattern-matching.md @@ -0,0 +1,45 @@ +--- +id: dart-patterns-matching-complex +title: 網羅性チェックと複雑なパターンマッチング +level: 3 +question: + - switch式の中で型判定と変数のバインドを同時に行う方法は? + - 論理演算子パターン(&& や ||)の使い方は? +term: + - 型パターン + - 論理パターン +--- + +### 網羅性チェックと複雑なパターンマッチング + +`switch` 式の中でデータの型や形状を検証しながら変数を取り出すことができます。 + +```dart:pattern_matching_complex.dart +String formatData(dynamic data) { + return switch (data) { + // 整数で 0 の場合 + 0 => 'ゼロ', + // 正の整数の場合 (関係演算子パターン) + int n && > 0 => '正の整数: $n', + // 2要素のリストの場合 + [var a, var b] => '2要素リスト: ($a, $b)', + // 特定のキーを持つマップの場合 + {'name': String name, 'age': int age} => '名前: $name, 年齢: $age', + _ => 'その他のデータ', + }; +} + +void main() { + print(formatData(10)); + print(formatData(['apple', 'banana'])); + print(formatData({'name': 'Alice', 'age': 20})); + print(formatData(false)); +} +``` + +```dart-exec:pattern_matching_complex.dart +正の整数: 10 +2要素リスト: (apple, banana) +名前: Alice, 年齢: 20 +その他のデータ +``` diff --git a/public/docs/dart/5-records-patterns/4-0-guards.md b/public/docs/dart/5-records-patterns/4-0-guards.md index 0dbdda79..da616d2f 100644 --- a/public/docs/dart/5-records-patterns/4-0-guards.md +++ b/public/docs/dart/5-records-patterns/4-0-guards.md @@ -5,7 +5,6 @@ level: 2 question: - when句(ガード節)はパターンマッチのどの位置に記述しますか? - when句の条件が満たされなかった場合、処理はどう流れますか? - - 複雑なビジネスロジックでwhen句を活用する具体例を見たいです。 term: - Guard句 - when diff --git a/public/docs/dart/6-classes/1-0-classes.md b/public/docs/dart/6-classes/1-0-classes.md index bd39ce35..ee4d80fd 100644 --- a/public/docs/dart/6-classes/1-0-classes.md +++ b/public/docs/dart/6-classes/1-0-classes.md @@ -5,7 +5,6 @@ level: 2 question: - なぜDartではnewキーワードを省略できるのですか? - コンストラクタで this.field を使う構文のメリットは何ですか? - - メソッド内で this を明示する必要があるのはどのような場合ですか? term: - クラス - class @@ -18,13 +17,11 @@ term: [[Dart]]でオブジェクトの設計図となる **[[クラス]](`class`)** を定義し、インスタンスを生成する基本的な構文を見てみましょう。 -### 1. クラス定義とコンストラクタの糖衣構文 - -Dartでは、引数をそのままフィールドに代入する場合、`Point(this.x, this.y);` のように宣言するだけで初期化処理が完了します。 +Dartでは、引数をそのままフィールドに代入する場合、`Point(this.x, this.y);` のように宣言するだけで初期化処理が完了します。また、インスタンス生成時の `new` は省略するのが標準です。 ```dart:point_class.dart class Point { - // フィールド(インスタンス変数) + // フィールド final double x; final double y; @@ -38,7 +35,7 @@ class Point { } void main() { - // new キーワードは完全に省略可能 + // new キーワードは省略可能 final p1 = Point(3.0, 4.0); p1.printCoordinates(); } @@ -47,6 +44,3 @@ void main() { ```dart-exec:point_class.dart Point(3.0, 4.0) ``` - -> [!NOTE] -> Dart 2以降、インスタンス生成時の `new` キーワードは省略するのが公式スタイルガイドの標準です(`new Point(...)` と書くことも文法上は可能ですが、通常は書きません)。 diff --git a/public/docs/dart/6-classes/2-0-constructors.md b/public/docs/dart/6-classes/2-0-constructors.md index 242b1a5a..d1e6ccc1 100644 --- a/public/docs/dart/6-classes/2-0-constructors.md +++ b/public/docs/dart/6-classes/2-0-constructors.md @@ -1,68 +1,15 @@ --- id: dart-classes-constructors -title: 様々なコンストラクタ(名前付き、リダイレクト) +title: 様々なコンストラクタ level: 2 question: - - 名前付きコンストラクタはどのような時に便利ですか? - - リダイレクトコンストラクタの構文はどう書きますか? - - constコンストラクタを定義するための要件は何ですか? + - Dartにはどのような種類のコンストラクタがありますか? + - 名前付きコンストラクタのメリットは何ですか? term: - コンストラクタ - - 名前付きコンストラクタ - - リダイレクトコンストラクタ - - constコンストラクタ + - コンストラクタオーバーロード --- -## 様々なコンストラクタ(名前付き、リダイレクト) +## 様々なコンストラクタ Dartでは、1つのクラスに複数の異なる初期化方法を提供するために **[[名前付きコンストラクタ]]** や **[[リダイレクトコンストラクタ]]** を作成できます。 - -### 1. 名前付きコンストラクタ(Named Constructors) - -`ClassName.constructorName(...)` の形式で定義します。JavaやC++のオーバーロードと異なり、コンストラクタの意図が名前によって明確になります。 - -### 2. リダイレクトコンストラクタ(Redirecting Constructors) - -別のコンストラクタに初期化処理を委譲(リダイレクト)するコンストラクタです。コロン `:` に続けて `this(...)` を呼び出します。 - -### 3. `const` コンストラクタ - -すべてのフィールドが `final` で構成されている場合、コンストラクタに `const` を付与してコンパイル時定数インスタンスを生成できます。 - -```dart:constructors_demo.dart -class User { - final String name; - final int age; - - // 基本コンストラクタ (const対応) - const User(this.name, this.age); - - // 1. 名前付きコンストラクタ - User.guest() - : name = 'ゲスト', - age = 0; - - // 2. リダイレクトコンストラクタ (基本コンストラクタを呼び出す) - User.adult(String name) : this(name, 20); - - void display() { - print('ユーザー: $name (年齢: $age)'); - } -} - -void main() { - const u1 = User('Alice', 25); - final u2 = User.guest(); - final u3 = User.adult('Bob'); - - u1.display(); - u2.display(); - u3.display(); -} -``` - -```dart-exec:constructors_demo.dart -ユーザー: Alice (年齢: 25) -ユーザー: ゲスト (年齢: 0) -ユーザー: Bob (年齢: 20) -``` diff --git a/public/docs/dart/6-classes/2-1-named-redirecting.md b/public/docs/dart/6-classes/2-1-named-redirecting.md new file mode 100644 index 00000000..ec1edfc7 --- /dev/null +++ b/public/docs/dart/6-classes/2-1-named-redirecting.md @@ -0,0 +1,56 @@ +--- +id: dart-classes-named-redirecting +title: 名前付き・リダイレクト・const コンストラクタ +level: 3 +question: + - リダイレクトコンストラクタの構文はどう書きますか? + - constコンストラクタを定義するための要件は何ですか? +term: + - 名前付きコンストラクタ + - リダイレクトコンストラクタ + - constコンストラクタ +--- + +### 名前付き・リダイレクト・const コンストラクタ + +* **名前付きコンストラクタ**: `ClassName.identifier(...)` の形式で定義。 +* **リダイレクトコンストラクタ**: `: this(...)` で別コンストラクタに初期化を委譲。 +* **`const` コンストラクタ**: 全フィールドが `final` の場合、コンパイル時定数としてインスタンス化可能。 + +```dart:constructors_demo.dart +class User { + final String name; + final int age; + + // 基本コンストラクタ (const対応) + const User(this.name, this.age); + + // 名前付きコンストラクタ + User.guest() + : name = 'ゲスト', + age = 0; + + // リダイレクトコンストラクタ + User.adult(String name) : this(name, 20); + + void display() { + print('ユーザー: $name (年齢: $age)'); + } +} + +void main() { + const u1 = User('Alice', 25); + final u2 = User.guest(); + final u3 = User.adult('Bob'); + + u1.display(); + u2.display(); + u3.display(); +} +``` + +```dart-exec:constructors_demo.dart +ユーザー: Alice (年齢: 25) +ユーザー: ゲスト (年齢: 0) +ユーザー: Bob (年齢: 20) +``` diff --git a/public/docs/dart/6-classes/3-0-initializer-super.md b/public/docs/dart/6-classes/3-0-initializer-super.md index 56807135..3f1aac8e 100644 --- a/public/docs/dart/6-classes/3-0-initializer-super.md +++ b/public/docs/dart/6-classes/3-0-initializer-super.md @@ -1,76 +1,15 @@ --- id: dart-classes-initializer-super -title: '初期化子リスト(:)と super の呼び出し' +title: 初期化子リスト(:)と super の呼び出し level: 2 question: - - 初期化子リストとコンストラクタ本体の実行順序はどうなっていますか? - - superパラメータ(super.field)を使うとコードがどう短縮されますか? - - 初期化子リスト内でassertを使って引数を検証する方法を教えてください。 + - 初期化子リストを使う目的は何ですか? + - 親クラスのコンストラクタに引数を渡す方法は? term: - 初期化子リスト - super - - superパラメータ - - 継承 --- ## 初期化子リスト(`:`)と `super` の呼び出し -### 1. 初期化子リスト(Initializer List) - -コンストラクタ本体 `{}` が実行される前に、フィールドの計算や `assert` による引数検証を行うには、引数リストの後にコロン `:` を付けて **[[初期化子リスト]]** を記述します。 - -```dart:initializer_list.dart -class Rectangle { - final double width; - final double height; - final double area; - - // 初期化子リストで面積 (area) を事前計算 - Rectangle(this.width, this.height) - : area = width * height, - assert(width > 0, 'width は正の数である必要があります'), - assert(height > 0, 'height は正の数である必要があります'); - - void display() { - print('幅: $width, 高さ: $height, 面積: $area'); - } -} - -void main() { - final rect = Rectangle(5.0, 8.0); - rect.display(); -} -``` - -```dart-exec:initializer_list.dart -幅: 5.0, 高さ: 8.0, 面積: 40.0 -``` - -### 2. 親クラスのコンストラクタ呼び出し (`super` / `super.field`) - -子クラスのコンストラクタから親クラスのコンストラクタに値を渡すには、初期化子リストで `: super(...)` を呼び出すか、Dart 2.17で追加された **`super.field`(Super Parameters)** を使います。 - -```dart:super_params.dart -class Person { - final String name; - Person(this.name); -} - -class Employee extends Person { - final String department; - - // super.name で親クラスのコンストラクタへ引数を直接転送 - Employee(super.name, this.department); - - void info() => print('社員: $name, 部署: $department'); -} - -void main() { - final emp = Employee('Charlie', '開発部'); - emp.info(); -} -``` - -```dart-exec:super_params.dart -社員: Charlie, 部署: 開発部 -``` +コンストラクタ実行前にフィールドの計算やバリデーションを行う **[[初期化子リスト]]** と、親クラスへの引数委譲について学びます。 diff --git a/public/docs/dart/6-classes/3-1-super-params.md b/public/docs/dart/6-classes/3-1-super-params.md new file mode 100644 index 00000000..af370f17 --- /dev/null +++ b/public/docs/dart/6-classes/3-1-super-params.md @@ -0,0 +1,45 @@ +--- +id: dart-classes-super-params +title: superパラメータと初期化子リストでのassert +level: 3 +question: + - 初期化子リスト内でassertを使うメリットは何ですか? + - super.field 構文を使うとどのようにコードが短縮されますか? +term: + - superパラメータ + - assert初期化 +--- + +### superパラメータと初期化子リストでのassert + +初期化子リスト(コロン `:` に続く記述)では、フィールドの事前計算や `assert` による引数検証が可能です。また、親クラスへの引数転送には `super.field` が使えます。 + +```dart:super_params.dart +class Shape { + final String type; + Shape(this.type); +} + +class Rectangle extends Shape { + final double width; + final double height; + final double area; + + // 初期化子リストで面積計算、バリデーション、親クラス呼出 + Rectangle(this.width, this.height) + : area = width * height, + assert(width > 0, 'width は正の数である必要があります'), + super('Rectangle'); + + void info() => print('図形: $type, 幅: $width, 高さ: $height, 面積: $area'); +} + +void main() { + final rect = Rectangle(5.0, 8.0); + rect.info(); +} +``` + +```dart-exec:super_params.dart +図形: Rectangle, 幅: 5.0, 高さ: 8.0, 面積: 40.0 +``` diff --git a/public/docs/dart/6-classes/4-0-encapsulation-getter-setter.md b/public/docs/dart/6-classes/4-0-encapsulation-getter-setter.md index c6b089ac..d3f2a380 100644 --- a/public/docs/dart/6-classes/4-0-encapsulation-getter-setter.md +++ b/public/docs/dart/6-classes/4-0-encapsulation-getter-setter.md @@ -5,67 +5,16 @@ level: 2 question: - Dartには private や public キーワードがないのですか? - アンダースコア _ でプライベート化されるスコープの単位は何ですか? - - ゲッター(get)とセッター(set)の構文はどう書きますか? term: - カプセル化 - プライベート変数 - - ゲッター - - セッター - - getter - - setter - ライブラリスコープ --- ## カプセル化(`_` によるプライベート化と `get` / `set`) -### 1. `_` によるプライベート化 - -[[Dart]]には `public`、`private`、`protected` といったアクセス修飾子キーワードがありません。 +[[Dart]]には `public`、`private` などのアクセス修飾子キーワードがありません。 識別子の先頭に **アンダースコア `_`** を付けることで、その要素は **ライブラリ(同一ファイル)プライベート** になります。 > [!IMPORTANT] -> Dartのプライベート化は「クラス単位」ではなく「ファイル(ライブラリ)単位」です。同一ファイル内であれば別クラスからでも `_` の付いた要素にアクセスできますが、別ファイルから `import` された場合は完全に非公開になります。 - -### 2. ゲッター(`get`)とセッター(`set`) - -プロパティアクセスのように振る舞うメソッドとして、`get` と `set` を定義できます。 - -```dart:bank_account.dart -class BankAccount { - // プライベートフィールド - double _balance = 0.0; - - // ゲッター - double get balance => _balance; - - // セッター - set balance(double value) { - if (value < 0) { - print('エラー: 残高を負の値にすることはできません'); - return; - } - _balance = value; - } - - void deposit(double amount) { - if (amount > 0) _balance += amount; - } -} - -void main() { - final account = BankAccount(); - account.deposit(5000); - print('残高: ${account.balance} 円'); - - account.balance = 8000; // セッターの呼び出し - print('更新後残高: ${account.balance} 円'); - - account.balance = -100; // 不正な値 -} -``` - -```dart-exec:bank_account.dart -残高: 5000.0 円 -更新後残高: 8000.0 円 -エラー: 残高を負の値にすることはできません -``` +> Dartのプライベート化は「ファイル(ライブラリ)単位」です。同一ファイル内であれば別クラスからでも `_` の付いた要素にアクセスできますが、別ファイルから `import` された場合は完全に非公開になります。 diff --git a/public/docs/dart/6-classes/4-1-private-get-set.md b/public/docs/dart/6-classes/4-1-private-get-set.md new file mode 100644 index 00000000..60b27819 --- /dev/null +++ b/public/docs/dart/6-classes/4-1-private-get-set.md @@ -0,0 +1,57 @@ +--- +id: dart-classes-private-get-set +title: ライブラリプライベートとゲッター・セッター +level: 3 +question: + - ゲッター(get)とセッター(set)の構文はどう書きますか? + - セッター内でバリデーションを行う例を教えてください。 +term: + - ゲッター + - セッター + - getter + - setter +--- + +### ライブラリプライベートとゲッター・セッター + +非公開フィールドに対して安全な読み書き手段を提供するために `get` と `set` を定義します。 + +```dart:bank_account.dart +class BankAccount { + // プライベートフィールド + double _balance = 0.0; + + // ゲッター + double get balance => _balance; + + // セッター + set balance(double value) { + if (value < 0) { + print('エラー: 残高を負の値にすることはできません'); + return; + } + _balance = value; + } + + void deposit(double amount) { + if (amount > 0) _balance += amount; + } +} + +void main() { + final account = BankAccount(); + account.deposit(5000); + print('残高: ${account.balance} 円'); + + account.balance = 8000; + print('更新後残高: ${account.balance} 円'); + + account.balance = -100; +} +``` + +```dart-exec:bank_account.dart +残高: 5000.0 円 +更新後残高: 8000.0 円 +エラー: 残高を負の値にすることはできません +``` diff --git a/public/docs/dart/6-classes/5-0-factory-constructors.md b/public/docs/dart/6-classes/5-0-factory-constructors.md index 1d0a1aeb..2f3d282c 100644 --- a/public/docs/dart/6-classes/5-0-factory-constructors.md +++ b/public/docs/dart/6-classes/5-0-factory-constructors.md @@ -4,87 +4,13 @@ title: factory コンストラクタ(シングルトンやJSONパースの実 level: 2 question: - 通常のコンストラクタと factory コンストラクタの決定的な違いは何ですか? - - factory コンストラクタを使ってシングルトンパターンを実装する方法を教えてください。 - - fromJson などのファクトリメソッドが factory として定義される理由は何ですか? + - factory コンストラクタを使う典型的なユースケースは何ですか? term: - factoryコンストラクタ - factory - ファクトリコンストラクタ - - シングルトン - - fromJson --- ## `factory` コンストラクタ(シングルトンやJSONパースの実装) -通常のコンストラクタは常にそのクラスの「新しいインスタンス」を生成しますが、**`factory` コンストラクタ** を使うと、コンストラクタ構文でありながら以下の制御が可能になります。 - -1. 既存のキャッシュ済みインスタンス(**[[シングルトン]]**)を返す。 -2. サブクラスのインスタンスを生成して返す。 -3. 条件に応じたインスタンス生成ロジック(JSONからのデシリアライズなど)をカプセル化する。 - -### 1. シングルトンパターンの実装 - -```dart:singleton_demo.dart -class DatabaseService { - final String dbName; - - // プライベートな静的インスタンス - static DatabaseService? _instance; - - // プライベートな通常コンストラクタ - DatabaseService._internal(this.dbName); - - // factory コンストラクタ: 常に同一のインスタンスを返す - factory DatabaseService({String dbName = 'main.db'}) { - _instance ??= DatabaseService._internal(dbName); - return _instance!; - } -} - -void main() { - final db1 = DatabaseService(); - final db2 = DatabaseService(); - - print('同一インスタンスか: ${identical(db1, db2)}'); -} -``` - -```dart-exec:singleton_demo.dart -同一インスタンスか: true -``` - -### 2. JSONパース用 `fromJson` ファクトリ - -```dart:factory_from_json.dart -class Product { - final String id; - final String title; - final int price; - - const Product({ - required this.id, - required this.title, - required this.price, - }); - - // Map から Product を構築する factory コンストラクタ - factory Product.fromJson(Map json) { - return Product( - id: json['id'] as String, - title: json['title'] as String, - price: json['price'] as int, - ); - } -} - -void main() { - final jsonMap = {'id': 'p_01', 'title': 'メカニカルキーボード', 'price': 14800}; - final product = Product.fromJson(jsonMap); - - print('商品: ${product.title} (¥${product.price})'); -} -``` - -```dart-exec:factory_from_json.dart -商品: メカニカルキーボード (¥14800) -``` +通常のコンストラクタは常に新しいインスタンスを生成しますが、**`factory` コンストラクタ** を使うと、既存のキャッシュ済みインスタンスを返したり、サブクラスのインスタンスを生成して返すなど、生成ロジックを柔軟に制御できます。 diff --git a/public/docs/dart/6-classes/5-1-singleton.md b/public/docs/dart/6-classes/5-1-singleton.md new file mode 100644 index 00000000..b8b5ebbe --- /dev/null +++ b/public/docs/dart/6-classes/5-1-singleton.md @@ -0,0 +1,41 @@ +--- +id: dart-classes-singleton +title: シングルトンパターンの実装 +level: 3 +question: + - factory コンストラクタを使ってシングルトンパターンを実装する方法を教えてください。 + - identical() 関数の役割は何ですか? +term: + - シングルトン + - identical +--- + +### シングルトンパターンの実装 + +`factory` コンストラクタを使って、常に同一のプライベート静的インスタンスを返却することで、シングルトンをエレガントに実装できます。 + +```dart:singleton_demo.dart +class DatabaseService { + final String dbName; + static DatabaseService? _instance; + + DatabaseService._internal(this.dbName); + + // factory コンストラクタ: 常に同一のインスタンスを返す + factory DatabaseService({String dbName = 'main.db'}) { + _instance ??= DatabaseService._internal(dbName); + return _instance!; + } +} + +void main() { + final db1 = DatabaseService(); + final db2 = DatabaseService(); + + print('同一インスタンスか: ${identical(db1, db2)}'); +} +``` + +```dart-exec:singleton_demo.dart +同一インスタンスか: true +``` diff --git a/public/docs/dart/6-classes/5-2-from-json.md b/public/docs/dart/6-classes/5-2-from-json.md new file mode 100644 index 00000000..2934358b --- /dev/null +++ b/public/docs/dart/6-classes/5-2-from-json.md @@ -0,0 +1,48 @@ +--- +id: dart-classes-from-json +title: JSONパース用 fromJson ファクトリ +level: 3 +question: + - fromJson ファクトリコンストラクタの典型的なシグネチャを教えてください。 + - Mapから安全にキャストしてフィールドを初期化する書き方は? +term: + - fromJson + - JSONパース +--- + +### JSONパース用 `fromJson` ファクトリ + +`Map` からオブジェクトを生成する `fromJson` を `factory` コンストラクタとして定義するのがDartのデファクトスタンダードです。 + +```dart:factory_from_json.dart +class Product { + final String id; + final String title; + final int price; + + const Product({ + required this.id, + required this.title, + required this.price, + }); + + factory Product.fromJson(Map json) { + return Product( + id: json['id'] as String, + title: json['title'] as String, + price: json['price'] as int, + ); + } +} + +void main() { + final jsonMap = {'id': 'p_01', 'title': 'メカニカルキーボード', 'price': 14800}; + final product = Product.fromJson(jsonMap); + + print('商品: ${product.title} (¥${product.price})'); +} +``` + +```dart-exec:factory_from_json.dart +商品: メカニカルキーボード (¥14800) +``` diff --git a/public/docs/dart/7-class-extension/1-0-extends-implements.md b/public/docs/dart/7-class-extension/1-0-extends-implements.md index 8bf680c6..fe46cf84 100644 --- a/public/docs/dart/7-class-extension/1-0-extends-implements.md +++ b/public/docs/dart/7-class-extension/1-0-extends-implements.md @@ -4,63 +4,15 @@ title: extends(継承)と implements(インターフェース実装)の level: 2 question: - なぜDartには interface キーワードが別途不要だったのですか? - - implements を使った場合、親クラスの実装コードは引き継がれますか? - - 多重継承が禁止されている一方で implements を複数指定できる理由は何ですか? + - 暗黙的インターフェースとは何ですか? term: - extends - 継承 - implements - 暗黙的インターフェース - インターフェース - - '' --- ## `extends`(継承)と `implements`(インターフェース実装)の違い [[Dart]]では、すべてのクラスが自動的に **[[暗黙的インターフェース]](Implicit Interface)** を定義しています。これにより、任意のクラスを `implements` の対象として利用できます。 - -### 1. `extends` (単一継承) - -親クラスの実装(メソッドの処理やフィールド)をそのまま引き継ぎます。Dartでは多重継承(複数のクラスを `extends` すること)はできません。 - -### 2. `implements` (インターフェース実装) - -親クラスの「型のシグネチャ(メソッド名や引数・戻り値の型)」だけを満たすことを約束します。親クラスの実装コードは**一切引き継がれず、すべてのメソッドを再定義(`@override`)する必要があります**。カンマ区切りで複数のインターフェースを実装可能です。 - -```dart:extends_implements.dart -class Animal { - void speak() { - print('動物が鳴きます'); - } -} - -// 1. extends: 実装を引き継ぎ、必要に応じてオーバーライド -class Dog extends Animal { - @override - void speak() { - print('ワンワン!'); - } -} - -// 2. implements: 型だけを流用し、全メソッドを自前で再実装 -class RobotDog implements Animal { - @override - void speak() { - print('ビープ!ワンワン (電子音)'); - } -} - -void makeNoise(Animal animal) { - animal.speak(); -} - -void main() { - makeNoise(Dog()); - makeNoise(RobotDog()); -} -``` - -```dart-exec:extends_implements.dart -ワンワン! -ビープ!ワンワン (電子音) -``` diff --git a/public/docs/dart/7-class-extension/1-1-extends-implements-details.md b/public/docs/dart/7-class-extension/1-1-extends-implements-details.md new file mode 100644 index 00000000..2bbb80d8 --- /dev/null +++ b/public/docs/dart/7-class-extension/1-1-extends-implements-details.md @@ -0,0 +1,54 @@ +--- +id: dart-classes-extends-implements-details +title: 単一継承と暗黙的インターフェースの使い分け +level: 3 +question: + - implements を使った場合、親クラスの実装コードは引き継がれますか? + - 多重継承が禁止されている一方で implements を複数指定できる理由は何ですか? +term: + - '@override' + - オーバーライド +--- + +### 単一継承と暗黙的インターフェースの使い分け + +* **`extends` (単一継承)**: 親クラスの実装(コード)を引き継ぎます。 +* **`implements` (インターフェース実装)**: 親クラスの型シグネチャのみを引き継ぎ、全メソッドを自前で `@override` 再実装します(複数指定可能)。 + +```dart:extends_implements.dart +class Animal { + void speak() { + print('動物が鳴きます'); + } +} + +// 1. extends: 実装を引き継ぎオーバーライド +class Dog extends Animal { + @override + void speak() { + print('ワンワン!'); + } +} + +// 2. implements: 型のみ流用し全メソッドを自前実装 +class RobotDog implements Animal { + @override + void speak() { + print('ビープ!ワンワン (電子音)'); + } +} + +void makeNoise(Animal animal) { + animal.speak(); +} + +void main() { + makeNoise(Dog()); + makeNoise(RobotDog()); +} +``` + +```dart-exec:extends_implements.dart +ワンワン! +ビープ!ワンワン (電子音) +``` diff --git a/public/docs/dart/7-class-extension/2-0-mixins.md b/public/docs/dart/7-class-extension/2-0-mixins.md index c6470ca8..df2216be 100644 --- a/public/docs/dart/7-class-extension/2-0-mixins.md +++ b/public/docs/dart/7-class-extension/2-0-mixins.md @@ -4,14 +4,12 @@ title: Mixin(mixin、with)による機能の注入 level: 2 question: - Mixinと継承の違いは何ですか? - - onキーワードを使ってMixinを特定の基底クラスに限定する方法は? - - 複数のMixinを with で組み合わせた場合のメソッド解決順序はどうなりますか? + - 複数のMixinを with で組み合わせる方法を教えてください。 term: - Mixin - mixin - with - 機能の注入 - - on --- ## Mixin(`mixin`、`with`)による機能の注入 @@ -57,6 +55,3 @@ void main() { [LOG 5:20] ユーザー Alice がログインしました JSON: {name: Alice} ``` - -> [!TIP] -> Mixin内で特定のクラスの機能を利用したい場合は、`mixin MyMixin on SomeBaseClass` のように `on` 節を指定して制約をかけることができます。 diff --git a/public/docs/dart/7-class-extension/3-0-extension-methods.md b/public/docs/dart/7-class-extension/3-0-extension-methods.md index fd9cc52d..c32f5e7f 100644 --- a/public/docs/dart/7-class-extension/3-0-extension-methods.md +++ b/public/docs/dart/7-class-extension/3-0-extension-methods.md @@ -5,7 +5,6 @@ level: 2 question: - 拡張メソッドを使うと標準ライブラリのクラス(Stringやintなど)にメソッドを追加できますか? - 拡張メソッドの定義構文はどう書きますか? - - ジェネリクスやゲッターをExtensionで定義することはできますか? term: - Extension - 拡張メソッド @@ -32,25 +31,16 @@ extension StringExtensions on String { int? toIntOrNull() => int.tryParse(this); } -// List に特化した拡張 -extension NumberListExtension on List { - int get sum => fold(0, (a, b) => a + b); -} - void main() { String word = 'flutter'; print('capitalize: ${word.capitalize}'); String numStr = '42'; print('toIntOrNull: ${numStr.toIntOrNull()}'); - - List numbers = [10, 20, 30]; - print('sum: ${numbers.sum}'); } ``` ```dart-exec:extension_methods.dart capitalize: Flutter toIntOrNull: 42 -sum: 60 ``` diff --git a/public/docs/dart/7-class-extension/4-0-enhanced-enums.md b/public/docs/dart/7-class-extension/4-0-enhanced-enums.md index c6164730..fa61f556 100644 --- a/public/docs/dart/7-class-extension/4-0-enhanced-enums.md +++ b/public/docs/dart/7-class-extension/4-0-enhanced-enums.md @@ -5,13 +5,11 @@ level: 2 question: - 通常のenumとEnhanced Enumの違いは何ですか? - Enumにフィールドやメソッド、コンストラクタを持たせるにはどう書きますか? - - Enhanced Enumとswitch式を組み合わせるメリットは何ですか? term: - Enum - 列挙型 - Enhanced Enum - enum - - enumコンストラクタ --- ## 高機能な列挙型(Enhanced Enum) diff --git a/public/docs/dart/8-class-modifiers/1-0-sealed-classes.md b/public/docs/dart/8-class-modifiers/1-0-sealed-classes.md index e38695cb..f39561ac 100644 --- a/public/docs/dart/8-class-modifiers/1-0-sealed-classes.md +++ b/public/docs/dart/8-class-modifiers/1-0-sealed-classes.md @@ -65,4 +65,4 @@ void main() { ``` > [!TIP] -> 新しい状態(例: `OfflineState`)を後から追加した際、`render` 関数内でそのケースを書き忘れていると、コンパイラがビルド時に「すべてのケースが網羅されていません」と即座にエラーを教えてくれます。 +> 新しい状態を後から追加した際、`switch` 式でそのケースを書き忘れていると、コンパイラがビルド時に即座に未網羅エラーを通知してくれます。 diff --git a/public/docs/dart/8-class-modifiers/2-0-other-modifiers.md b/public/docs/dart/8-class-modifiers/2-0-other-modifiers.md index 96598b41..cf08f13d 100644 --- a/public/docs/dart/8-class-modifiers/2-0-other-modifiers.md +++ b/public/docs/dart/8-class-modifiers/2-0-other-modifiers.md @@ -3,20 +3,18 @@ id: dart-class-modifiers-base-interface-final title: base、interface、final 修飾子の使い分け level: 2 question: - - base修飾子を付けると外部パッケージでどのような制約が課されますか? - - interface修飾子と通常のclassの違いは何ですか? - - final修飾子をクラスに付けた場合、継承や実装はどう制限されますか? + - クラス修飾子を導入する目的は何ですか? + - 各修飾子の制約の違いの概要を教えてください。 term: - base - interface修飾子 - final修飾子 - クラス修飾子 - - abstract --- ## `base`、`interface`、`final` 修飾子の使い分け -[[Dart]] 3では、ライブラリ境界外(外部パッケージや別ファイル)からのクラス利用方法を制限するために、以下のクラス修飾子が提供されています。 +[[Dart]] 3では、ライブラリ境界外(外部パッケージや別ファイル)からのクラス利用方法を制限するために、各種クラス修飾子が提供されています。 | 修飾子 | 外部でのインスタンス化 | 外部での `extends` (継承) | 外部での `implements` (実装) | 外部での `with` (Mixin) | | :--- | :---: | :---: | :---: | :---: | @@ -25,37 +23,3 @@ term: | **`interface`** | ○ | × | **○** | × | | **`final`** | ○ | × | × | × | | **`sealed`** | × (abstract) | × (同ファイル内のみ) | × (同ファイル内のみ) | × | - -### 1. `base`: 継承のみを許可し、暗黙的インターフェースのimplementsを禁止 - -クラスに新しいメソッドを追加しても、外部のサブクラスが壊れないように設計したい場合に利用します。 - -```dart -// 外部ライブラリ側 -base class Vehicle { - void move() => print('移動中'); -} - -// 利用側 -// class Car implements Vehicle {} // コンパイルエラー: implements 不可 -base class Car extends Vehicle {} // OK (子クラスも base/final/sealed が必要) -``` - -### 2. `interface`: 実装(implements)のみを許可し、継承を禁止 - -APIの型シグネチャのみを提供し、内部実装の継承による依存を防ぎたい場合に利用します。 - -```dart -interface class StorageService { - void save(String key, String value) {} -} -// class LocalStorage extends StorageService {} // エラー: 外部から extends 不可 -class LocalStorage implements StorageService { // OK: implements のみ許可 - @override - void save(String key, String value) => print('Saved $key'); -} -``` - -### 3. `final`: 外部からの継承・実装を完全に禁止 - -クラスの動作を完全に固定し、サブクラス化を一切許さない場合に使用します。 diff --git a/public/docs/dart/8-class-modifiers/2-1-base-modifier.md b/public/docs/dart/8-class-modifiers/2-1-base-modifier.md new file mode 100644 index 00000000..afc52f5f --- /dev/null +++ b/public/docs/dart/8-class-modifiers/2-1-base-modifier.md @@ -0,0 +1,25 @@ +--- +id: dart-class-modifiers-base +title: base 修飾子(継承のみを許可し implements を禁止) +level: 3 +question: + - base修飾子を付けると外部パッケージでどのような制約が課されますか? + - baseクラスを継承するサブクラスにもbaseやfinalの指定が必要ですか? +term: + - base +--- + +### `base` 修飾子(継承のみを許可し implements を禁止) + +クラスに新しいメソッドを追加しても外部のサブクラスが壊れないように設計したい場合に利用します。外部ライブラリからの暗黙的インターフェース実装(`implements`)を禁止します。 + +```dart +// 外部ライブラリ側 +base class Vehicle { + void move() => print('移動中'); +} + +// 利用側 +// class Car implements Vehicle {} // エラー: implements 不可 +base class Car extends Vehicle {} // OK: extends のみ許可 +``` diff --git a/public/docs/dart/8-class-modifiers/2-2-interface-modifier.md b/public/docs/dart/8-class-modifiers/2-2-interface-modifier.md new file mode 100644 index 00000000..35c12be5 --- /dev/null +++ b/public/docs/dart/8-class-modifiers/2-2-interface-modifier.md @@ -0,0 +1,29 @@ +--- +id: dart-class-modifiers-interface +title: interface 修飾子(実装のみを許可し継承を禁止) +level: 3 +question: + - interface修飾子と通常のclassの違いは何ですか? + - 外部で extends を禁止して implements のみを強制するメリットは何ですか? +term: + - interface + - interface修飾子 +--- + +### `interface` 修飾子(実装のみを許可し継承を禁止) + +APIの型シグネチャのみを提供し、内部実装の継承による暗黙の依存を防ぎたい場合に利用します。外部ライブラリからの継承(`extends`)を禁止し、実装(`implements`)のみを許可します。 + +```dart +// 外部ライブラリ側 +interface class StorageService { + void save(String key, String value) {} +} + +// 利用側 +// class LocalStorage extends StorageService {} // エラー: extends 不可 +class LocalStorage implements StorageService { // OK: implements のみ許可 + @override + void save(String key, String value) => print('Saved $key'); +} +``` diff --git a/public/docs/dart/8-class-modifiers/2-3-final-modifier.md b/public/docs/dart/8-class-modifiers/2-3-final-modifier.md new file mode 100644 index 00000000..2a7e7605 --- /dev/null +++ b/public/docs/dart/8-class-modifiers/2-3-final-modifier.md @@ -0,0 +1,26 @@ +--- +id: dart-class-modifiers-final +title: final 修飾子(外部からの継承・実装を完全に禁止) +level: 3 +question: + - final修飾子をクラスに付けると何が禁止されますか? + - クラスの動作を完全に不変に保ちたい場合にfinalを使う理由は何ですか? +term: + - final修飾子 +--- + +### `final` 修飾子(外部からの継承・実装を完全に禁止) + +クラスの動作を完全に固定し、外部ライブラリからのサブクラス化(継承 `extends` および実装 `implements` の両方)を一切許さない場合に使用します。 + +```dart +// 外部ライブラリ側 +final class ImmutableConfig { + final String env; + const ImmutableConfig(this.env); +} + +// 利用側 +// class MyConfig extends ImmutableConfig {} // エラー: extends 不可 +// class MyConfig implements ImmutableConfig {} // エラー: implements 不可 +``` diff --git a/public/docs/dart/8-class-modifiers/3-0-domain-modeling.md b/public/docs/dart/8-class-modifiers/3-0-domain-modeling.md index 5e862562..a07d0838 100644 --- a/public/docs/dart/8-class-modifiers/3-0-domain-modeling.md +++ b/public/docs/dart/8-class-modifiers/3-0-domain-modeling.md @@ -5,7 +5,6 @@ level: 2 question: - アプリケーションの状態管理でsealedクラスを活用するベストプラクティスは何ですか? - 不正な状態の表現を型レベルで不可能にするにはどうすればよいですか? - - イミュータブルなデータモデルを作るコツを教えてください。 term: - ドメインモデル - 状態モデリング @@ -15,11 +14,7 @@ term: ## ドメインモデルの堅牢な設計方法 -クラス修飾子(特に `sealed`)とDart 3のパターンマッチングを組み合わせることで、**「不正な状態を型レベルで表現不可能にする(Make Impossible States Impossible)」** 堅牢な[[ドメインモデル]]が構築できます。 - -### 認証状態のモデリング例 - -フラグ変数(`bool isLoggedIn`, `String? token`, `String? errorMessage`)をバラバラに持つ代わりに、状態そのものを `sealed` クラスの階層として定義します。 +クラス修飾子(特に `sealed`)とDart 3のパターンマッチングを組み合わせることで、**「不正な状態を型レベルで表現不可能にする」** 堅牢な[[ドメインモデル]]が構築できます。 ```dart:auth_state_modeling.dart sealed class AuthState { @@ -68,5 +63,3 @@ void main() { スピナーを表示して待機します ユーザー u_777 のマイページを表示します ``` - -この設計により、「ログイン中なのにトークンが `null` である」といった不整合な状態が存在し得なくなり、UIのバグを大幅に減らすことができます。 diff --git a/public/docs/dart/9-async-future/1-0-event-loop.md b/public/docs/dart/9-async-future/1-0-event-loop.md index a79687e9..90ea1e21 100644 --- a/public/docs/dart/9-async-future/1-0-event-loop.md +++ b/public/docs/dart/9-async-future/1-0-event-loop.md @@ -5,7 +5,6 @@ level: 2 question: - Dartのシングルスレッドモデルで非同期処理が並行して進む仕組みは何ですか? - イベントキューとマイクロタスクキューの違いは何ですか? - - JavaScriptのイベントループとDartのイベントループの違いはありますか? term: - イベントループ - マイクロタスク @@ -16,7 +15,7 @@ term: ## 同期処理と非同期処理の違い(イベントループの概念) -[[Dart]]コードは基本的に単一のスレッド(**[[シングルスレッド]]**)上で動作します。処理が重い計算でスレッドを長時間占有すると、描画やタップ入力がブロックされてしまいます。 +[[Dart]]コードは単一のスレッド(**[[シングルスレッド]]**)上で動作します。処理が重い計算でスレッドを長時間占有すると、描画やタップ入力がブロックされてしまいます。 Dartはこの問題を **[[イベントループ]](Event Loop)** によって解決しています。 @@ -37,8 +36,4 @@ Dartはこの問題を **[[イベントループ]](Event Loop)** によっ +-------------------------------------------------------+ ``` -1. **実行中タスク**: 現在実行しているDartコードが完了するまで走り切ります。 -2. **マイクロタスクキュー**: 最優先で処理される内部タスクのキュー。 -3. **イベントキュー**: 外部からのI/Oイベント、タイマー、描画リクエスト、UI操作などを処理するキュー。 - 非同期処理(`Future`)をスケジューリングすると、現在のコードの実行が終わった後、イベントループが順番にそれを取り出して実行します。 diff --git a/public/docs/dart/9-async-future/2-0-future.md b/public/docs/dart/9-async-future/2-0-future.md index 916662f1..e6c876d6 100644 --- a/public/docs/dart/9-async-future/2-0-future.md +++ b/public/docs/dart/9-async-future/2-0-future.md @@ -4,54 +4,13 @@ title: Future の仕組み level: 2 question: - Futureとは具体的に何を表すオブジェクトですか? - - Futureの状態(Uncompleted, Completed with data, Completed with error)について教えてください。 - - then() と catchError() を使ったコールバック記法はどう書きますか? + - JavaScriptのPromiseとFutureの共通点は何ですか? term: - Future - Future.value - Future.delayed - - then - - コールバック --- ## `Future` の仕組み **`Future`** は、「将来のある時点で値 `T` またはエラーを返す非同期処理の結果」を表すオブジェクトです(JavaScriptの `Promise` や Rustの `Future` に相当します)。 - -### Futureの3つの状態 - -1. **未完了(Uncompleted)**: 非同期処理が実行中で、まだ結果が出ていない状態。 -2. **完了(Completed with data)**: 処理が正常に完了し、値が得られた状態。 -3. **エラー完了(Completed with error)**: 例外が発生して失敗した状態。 - -### Futureの生成と `then()` による購読 - -```dart:future_basics.dart -Future fetchUserGreeting() { - // 100ミリ秒後に完了する Future を生成 - return Future.delayed( - const Duration(milliseconds: 100), - () => 'こんにちは、ユーザーさん!', - ); -} - -void main() { - print('1. 処理開始'); - - fetchUserGreeting().then((greeting) { - print('3. データ受信: $greeting'); - }).catchError((error) { - print('エラー: $error'); - }); - - print('2. メイン関数の同期処理完了'); -} -``` - -```dart-exec:future_basics.dart -1. 処理開始 -2. メイン関数の同期処理完了 -3. データ受信: こんにちは、ユーザーさん! -``` - -`then()` コールバックをチェーンするスタイルも可能ですが、コードがネストしやすいため、通常は次に解説する `async` / `await` を使用します。 diff --git a/public/docs/dart/9-async-future/2-1-future-details.md b/public/docs/dart/9-async-future/2-1-future-details.md new file mode 100644 index 00000000..75609902 --- /dev/null +++ b/public/docs/dart/9-async-future/2-1-future-details.md @@ -0,0 +1,47 @@ +--- +id: dart-async-future-details +title: Futureの3つの状態と then() による購読 +level: 3 +question: + - Futureの3つの状態(Uncompleted, Completed with data, Completed with error)とは? + - then() と catchError() を使ったコールバック記法はどう書きますか? +term: + - then + - catchError + - コールバック +--- + +### Futureの3つの状態と `then()` による購読 + +Futureには以下の3つの状態があります。 + +1. **未完了(Uncompleted)**: 非同期処理が実行中で、まだ結果が出ていない状態。 +2. **値完了(Completed with data)**: 処理が正常に完了し、値が得られた状態。 +3. **エラー完了(Completed with error)**: 例外が発生して失敗した状態。 + +```dart:future_basics.dart +Future fetchUserGreeting() { + return Future.delayed( + const Duration(milliseconds: 100), + () => 'こんにちは、ユーザーさん!', + ); +} + +void main() { + print('1. 処理開始'); + + fetchUserGreeting().then((greeting) { + print('3. データ受信: $greeting'); + }).catchError((error) { + print('エラー: $error'); + }); + + print('2. メイン関数の同期処理完了'); +} +``` + +```dart-exec:future_basics.dart +1. 処理開始 +2. メイン関数の同期処理完了 +3. データ受信: こんにちは、ユーザーさん! +``` diff --git a/public/docs/dart/9-async-future/3-0-async-await.md b/public/docs/dart/9-async-future/3-0-async-await.md index 3c329d80..b890b4a2 100644 --- a/public/docs/dart/9-async-future/3-0-async-await.md +++ b/public/docs/dart/9-async-future/3-0-async-await.md @@ -5,13 +5,10 @@ level: 2 question: - asyncキーワードを付けた関数の戻り値型は何になりますか? - awaitキーワードはどこで使用できますか? - - Future.waitを使って複数の非同期タスクを並行実行する方法を教えてください。 term: - async - await - async/await - - Future.wait - - 並行実行 --- ## `async` / `await` による可読性の高い非同期コード @@ -48,28 +45,3 @@ void main() async { ユーザー情報を取得中... 取得完了: Alice (ID: 42) ``` - -### 複数の非同期処理を並行実行する (`Future.wait`) - -互いに依存しない複数の非同期処理を同時に実行したい場合は、`Future.wait` を使います。 - -```dart:future_wait.dart -Future fetchPostTitle() async => 'Dart 3の紹介'; -Future fetchLikeCount() async => 128; - -void main() async { - // 並行して実行し、両方の完了を待つ - final results = await Future.wait([ - fetchPostTitle(), - fetchLikeCount(), - ]); - - print('タイトル: ${results[0]}'); - print('いいね数: ${results[1]}'); -} -``` - -```dart-exec:future_wait.dart -タイトル: Dart 3の紹介 -いいね数: 128 -``` diff --git a/public/docs/dart/9-async-future/3-1-future-wait.md b/public/docs/dart/9-async-future/3-1-future-wait.md new file mode 100644 index 00000000..b2716ff4 --- /dev/null +++ b/public/docs/dart/9-async-future/3-1-future-wait.md @@ -0,0 +1,36 @@ +--- +id: dart-async-future-wait +title: Future.wait による複数の非同期処理の並行実行 +level: 3 +question: + - Future.waitを使って複数の非同期タスクを並行実行する方法を教えてください。 + - 並行実行と直列実行で所要時間はどう変化しますか? +term: + - Future.wait + - 並行実行 +--- + +### `Future.wait` による複数の非同期処理の並行実行 + +互いに依存しない複数の非同期処理を同時に実行したい場合は、`Future.wait` を使います。 + +```dart:future_wait.dart +Future fetchPostTitle() async => 'Dart 3の紹介'; +Future fetchLikeCount() async => 128; + +void main() async { + // 並行して実行し、両方の完了を待つ + final results = await Future.wait([ + fetchPostTitle(), + fetchLikeCount(), + ]); + + print('タイトル: ${results[0]}'); + print('いいね数: ${results[1]}'); +} +``` + +```dart-exec:future_wait.dart +タイトル: Dart 3の紹介 +いいね数: 128 +``` diff --git a/public/docs/dart/9-async-future/4-0-async-error-handling.md b/public/docs/dart/9-async-future/4-0-async-error-handling.md index a477bd89..b4ca3d48 100644 --- a/public/docs/dart/9-async-future/4-0-async-error-handling.md +++ b/public/docs/dart/9-async-future/4-0-async-error-handling.md @@ -5,10 +5,8 @@ level: 2 question: - async/await でのエラーハンドリングは通常の try-catch と同じですか? - 非同期例外を確実に捕捉するための注意点は何ですか? - - finally ブロックはどのような場面で活用されますか? term: - 非同期エラーハンドリング - - catchError - try-catch-finally - try - catch @@ -17,7 +15,7 @@ term: ## 非同期処理のエラーハンドリング(`try-catch-finally`) -`async` / `await` を使った非同期処理では、同期コードと全く同じ **`try-catch-finally`** 構文でエラーを捕捉できます。 +`async` / `await` を使った非同期処理では、同期コードと全く同じ **`try-catch-finally`** 構文で例外を捕捉できます。 ```dart:async_try_catch.dart Future loadDataFromServer({required bool shouldFail}) async { @@ -57,4 +55,4 @@ void main() async { ``` > [!TIP] -> `await` を付け忘れた `Future` で例外が発生すると、`try-catch` ブロックをすり抜けて未処理の非同期例外(Uncaught asynchronous error)となるため注意してください。 +> `await` を付け忘れた `Future` で例外が発生すると、`try-catch` ブロックをすり抜けて未処理の非同期例外となるため注意してください。 diff --git a/public/docs/dart/9-async-future/5-2-practice2.md b/public/docs/dart/9-async-future/5-2-practice2.md index 3a3d84d7..32e20ff8 100644 --- a/public/docs/dart/9-async-future/5-2-practice2.md +++ b/public/docs/dart/9-async-future/5-2-practice2.md @@ -1,21 +1,20 @@ --- id: dart-async-future-practice2 -title: '練習問題2: 複数の非同期処理の並行実行' +title: '練習問題2: 複数の非同期タスクの並行処理' level: 3 question: - - Future.wait で一部のFutureが失敗した場合の挙動はどうなりますか? - - 並行実行と逐次実行の処理時間の違いを教えてください。 + - Future.wait で複数の異なる戻り値型を持つ処理をまとめる方法は? + - 非同期処理を並行実行する際の例外ハンドリングの注意点は何ですか? --- -### 練習問題2: 複数の非同期処理の並行実行 +### 練習問題2: 複数の非同期タスクの並行処理 -複数のAPIエンドポイントから並行してデータを取得し、まとめるプログラムを作成してください。 +商品の価格情報と在庫情報を並行してフェッチし、合算結果を出力するプログラムを作成してください。 -1. 以下の2つの非同期関数を作成する。 - * `Future fetchConfig()`: 100ミリ秒後に `'AppConfig: v2.0'` を返す。 - * `Future> fetchNotifications()`: 150ミリ秒後に `['メンテ予告', '新着メッセージ']` を返す。 -2. `main()` 内で `Future.wait` を使用して両方を並行して実行・待機する。 -3. 取得した設定と通知一覧を整形して画面に出力する。 +1. `Future fetchPrice(String productId)`: 50ミリ秒後に価格 `3500` を返す。 +2. `Future fetchStock(String productId)`: 50ミリ秒後に在庫数 `12` を返す。 +3. `main()` で `Future.wait` を使って2つの処理を同時に実行し、結果をレコードまたはリストとして受け取る。 +4. `'商品ID: prod_abc, 価格: ¥3500, 在庫数: 12個'` と出力する。 ```dart:practice9_2.dart // ここに関数を定義してください