├── .eslintrc.cjs ├── .gitignore ├── .prettierrc ├── .stylelintrc.cjs ├── LICENSE.txt ├── README.en.md ├── README.md ├── apps ├── LICENSE_MIT_code.txt ├── example │ ├── package.json │ ├── public │ │ └── favicon.png │ ├── rune.config.cjs │ └── src │ │ ├── app │ │ ├── client │ │ │ └── index.ts │ │ ├── route │ │ │ └── index.ts │ │ └── server │ │ │ └── index.ts │ │ ├── chapters │ │ ├── ChapterPage.ts │ │ ├── HomePage.ts │ │ ├── chapter-1 │ │ │ ├── 1-1 │ │ │ │ ├── Chapter1Section1.ts │ │ │ │ └── main.ts │ │ │ ├── 1-2 │ │ │ │ ├── Chapter1Section2.ts │ │ │ │ └── main.ts │ │ │ ├── 1-3 │ │ │ │ ├── Chapter1Section3.ts │ │ │ │ └── main.ts │ │ │ ├── 1-4 │ │ │ │ ├── Chapter1Section4.ts │ │ │ │ └── main.ts │ │ │ └── 1-5 │ │ │ │ ├── Chapter1Section5.ts │ │ │ │ └── main.ts │ │ ├── chapter-2 │ │ │ ├── 2-1 │ │ │ │ ├── Chapter2Section1.ts │ │ │ │ └── main.ts │ │ │ ├── 2-2 │ │ │ │ ├── Chapter2Section2.ts │ │ │ │ └── main.ts │ │ │ └── 2-3 │ │ │ │ ├── Chapter2Section3.ts │ │ │ │ └── main.ts │ │ ├── chapter-3 │ │ │ ├── 3-1 │ │ │ │ ├── Chapter3Section1.ts │ │ │ │ └── main.ts │ │ │ ├── 3-3 │ │ │ │ ├── Chapter3Section3.ts │ │ │ │ └── main.ts │ │ │ └── 3-4 │ │ │ │ ├── Chapter3Section4.ts │ │ │ │ └── main.ts │ │ ├── chapter-4 │ │ │ ├── 4-1 │ │ │ │ ├── Chapter4Section1.ts │ │ │ │ └── main.ts │ │ │ ├── 4-2 │ │ │ │ ├── Chapter4Section2.ts │ │ │ │ └── main.ts │ │ │ ├── 4-3 │ │ │ │ ├── Chapter4Section3.ts │ │ │ │ └── main.ts │ │ │ └── 4-4 │ │ │ │ ├── Chapter4Section4.ts │ │ │ │ └── main.ts │ │ ├── chapter-5 │ │ │ ├── 5-1 │ │ │ │ ├── Chapter5Section1.ts │ │ │ │ └── main.ts │ │ │ ├── 5-2 │ │ │ │ ├── Chapter5Section2.ts │ │ │ │ └── main.ts │ │ │ ├── 5-3 │ │ │ │ ├── Chapter5Section3.ts │ │ │ │ └── main.ts │ │ │ └── 5-4 │ │ │ │ ├── Chapter5Section4.ts │ │ │ │ └── main.ts │ │ ├── chapter-6 │ │ │ ├── 6-1 │ │ │ │ ├── Chapter6Section1.ts │ │ │ │ └── main.ts │ │ │ └── 6-2 │ │ │ │ ├── Chapter6Section2.ts │ │ │ │ └── main.ts │ │ └── chapter-7 │ │ │ ├── 7-1 │ │ │ ├── Chapter7Section1.ts │ │ │ └── main.ts │ │ │ ├── 7-2_3_4 │ │ │ ├── Chapter7Section234.ts │ │ │ ├── lib │ │ │ │ ├── CheckView.ts │ │ │ │ ├── ListView.ts │ │ │ │ ├── SegmentControlView.ts │ │ │ │ ├── SwitchView.ts │ │ │ │ ├── TextSubmitView.ts │ │ │ │ ├── ToggleListController.ts │ │ │ │ ├── TogglePageController.ts │ │ │ │ ├── ToggleView.ts │ │ │ │ └── decorators.ts │ │ │ ├── setting.ts │ │ │ └── todo.ts │ │ │ └── 7-5 │ │ │ ├── Chapter7Section5.ts │ │ │ ├── Chapter7Section52.ts │ │ │ ├── alert.ts │ │ │ └── chat.ts │ │ └── lib │ │ ├── fx2.ts │ │ └── fx4.ts └── lecture │ ├── package.json │ ├── public │ └── favicon.png │ ├── rune.config.cjs │ └── src │ ├── HomePage.ts │ ├── SectionPage.ts │ ├── app │ ├── client │ │ └── index.ts │ ├── route │ │ └── index.ts │ └── server │ │ └── index.ts │ ├── lib │ ├── fx1.ts │ ├── fx2.ts │ └── fx3.ts │ └── part-1 │ ├── section-10 │ ├── Section10.ts │ ├── lesson-1.ts │ ├── lesson-2.ts │ └── lesson-3.ts │ ├── section-11 │ ├── Section11.ts │ ├── lesson-1-2.ts │ ├── lesson-1-3.ts │ ├── lesson-1-4.ts │ ├── lesson-1.ts │ └── lesson-2.ts │ ├── section-2 │ ├── Section2.ts │ ├── lesson-1.ts │ ├── lesson-2-1.ts │ ├── lesson-2-2.ts │ ├── lesson-2-3.ts │ ├── lesson-2-4.ts │ ├── lesson-2-5.ts │ ├── lesson-2-6.ts │ ├── lesson-2-7.ts │ ├── lesson-3-1.ts │ ├── lesson-4-1.ts │ ├── lesson-4-2.ts │ ├── lesson-5-1.ts │ ├── lesson-5-2.ts │ ├── lesson-5-3.ts │ ├── lesson-5-4.ts │ ├── lesson-5-5.ts │ └── lesson-6-1.ts │ ├── section-3 │ ├── Section3.ts │ ├── lesson-1-1.ts │ ├── lesson-1-2.ts │ ├── lesson-2.ts │ └── lesson-3.ts │ ├── section-4 │ ├── Section4.ts │ ├── lesson-1-1.ts │ ├── lesson-1-2.ts │ ├── lesson-1-3.ts │ ├── lesson-2.ts │ ├── lesson-3.ts │ ├── lesson-4-1.ts │ ├── lesson-4-2.ts │ └── lesson-4-3.ts │ ├── section-5 │ ├── Section5.ts │ ├── lesson-1-1.ts │ ├── lesson-1-2.ts │ ├── lesson-2-1.ts │ ├── lesson-2-2.ts │ ├── lesson-2-3.ts │ ├── lesson-3-1.ts │ ├── lesson-3-2.ts │ ├── lesson-3-3.ts │ ├── lesson-4.ts │ └── lib.ts │ ├── section-6 │ ├── Section6.ts │ ├── lesson-2-1.ts │ ├── lesson-2-2.ts │ ├── lesson-3-1.ts │ ├── lesson-3-2.ts │ ├── lesson-3-3.ts │ ├── lesson-4.ts │ └── lesson-5.ts │ ├── section-7 │ ├── Section7.ts │ ├── lesson-1.md │ ├── lesson-2.ts │ └── lesson-3.ts │ ├── section-8 │ ├── Section8.ts │ ├── lesson-1.ts │ ├── lesson-3.ts │ └── lesson-4.ts │ └── section-9 │ ├── Section9.ts │ ├── lesson-1.ts │ ├── lesson-4.ts │ ├── lesson-5.ts │ ├── lesson-6.ts │ ├── lesson-7.ts │ ├── lesson-8.ts │ └── lesson-9.ts ├── book ├── en │ ├── 0.1-From-the-Author.md │ ├── 0.2-Endorsements.md │ ├── 0.3-Environment-Setup-and-Example-Code.md │ ├── 1.0.-How-Multiparadigm-Is-Expanding-Modern-Languages.md │ ├── 1.1-The-Iterator-Pattern-in-OOP-and-First-Class-Functions.md │ ├── 1.2-Generators:-Building-Iterators-with-Imperative-Programming.md │ ├── 1.3-The-Iterator-Pattern-in-TypeScript:-The-Iteration-Protocol.md │ ├── 1.4-Functional-Programming-with-Iterables.md │ ├── 1.5-Why-the-Iteration-Protocol-Is-Designed-as-an-Interface-Rather-Than-Inheritance.md │ ├── 1.6-Summary.md │ ├── 2.0-Functional-Programming,-Type-Systems,-and-Lisp.md │ ├── 2.1-Type-Inference,-Function-Types,-and-Generics.md │ ├── 2.2-Functional-Type-Systems-in-a-Multi-Paradigm-Language.md │ ├── 2.3-Multiparadigm-Languages-and-Metaprogramming-–-From-LISP.md │ ├── 2.4-Summary.md │ ├── 3.0-Code:Object:Function-=-Generator:Iterator:LISP-=-IP:OOP:FP.md │ ├── 3.1-Code-Is-Data-–-A-List-Containing-Logic.md │ ├── 3.2-Learning-from-Haskell.md │ ├── 3.3-Taking-a-Closer-Look-at-Lazy-Evaluation.md │ ├── 3.4-Generator:Iterator:LISP-–-Lazy-Evaluation-and-Safe-Composition.md │ ├── 3.5-Summary.md │ └── README.md └── ko │ ├── 0.1-지은이의-글.md │ ├── 0.2-추천의-글.md │ ├── 0.3-환경-설치-및-예제-코드.md │ ├── 1.0-멀티패러다임이-현대-언어를-확장하는-방법.md │ ├── 1.1-객체-지향-디자인-패턴의-반복자-패턴과-일급-함수.md │ ├── 1.2-명령형-프로그래밍으로-이터레이터를-만드는-제너레이터-함수.md │ ├── 1.3-타입스크립트에서의-반복자-패턴-사례---이터레이션-프로토콜.md │ ├── 1.4-이터러블을-다루는-함수형-프로그래밍.md │ ├── 1.5-이터러블-프로토콜이-상속이-아닌-인터페이스로-설계된-이유.md │ ├── 1.6-중간-정리.md │ ├── 2.0-함수형-프로그래밍과-타입-시스템,-그리고-LISP.md │ ├── 2.1-타입-추론과-함수-타입,-그리고-제네릭.md │ ├── 2.2-멀티패러다임-언어에서의-함수형-타입-시스템.md │ ├── 2.3-멀티패러다임-언어와-메타프로그래밍---LISP으로부터.md │ ├── 2.4-중간-정리.md │ ├── 3.0-코드:객체:함수-=-Generator:Iterator:LISP-=-IP:OOP:FP.md │ ├── 3.1-코드가-곧-데이터---로직이-담긴-리스트.md │ ├── 3.2-하스켈로부터-배우기.md │ ├── 3.3-지연-평가-자세히-보기.md │ ├── 3.4-Generator:Iterator:LISP---지연-평가와-합성.md │ ├── 3.5-중간-정리.md │ └── README.md ├── img ├── MCO.png ├── book.jpg ├── book_en.png ├── hanbit_plus.png ├── inflearn.png └── mduniv.png ├── package.json ├── packages ├── styles │ ├── base.scss │ ├── global.scss │ ├── object-fe.scss │ └── sub │ │ ├── color.scss │ │ ├── function.scss │ │ ├── mixin.scss │ │ ├── typography_variable.scss │ │ └── variables.scss └── types │ ├── declare.d.ts │ └── renderHandlerType.ts ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── tsconfig.eslint.json └── tsconfig.json /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | 'eslint:recommended', 4 | 'plugin:@typescript-eslint/recommended-type-checked', 5 | 'plugin:@typescript-eslint/stylistic-type-checked', 6 | ], 7 | parser: '@typescript-eslint/parser', 8 | parserOptions: { 9 | project: ['./tsconfig.eslint.json'], 10 | tsconfigRootDir: __dirname, 11 | }, 12 | plugins: ['@typescript-eslint'], 13 | env: { 14 | es6: true, 15 | node: true, 16 | }, 17 | root: true, 18 | rules: { 19 | 'no-var': 'warn', // var 금지 20 | eqeqeq: 'warn', // 일치 연산자 사용 필수 21 | 'no-multiple-empty-lines': 'warn', // 여러 줄 공백 금지 22 | '@typescript-eslint/restrict-template-expressions': ['off'], 23 | '@typescript-eslint/no-unused-vars': ['warn'], 24 | '@typescript-eslint/no-explicit-any': ['warn'], 25 | '@typescript-eslint/no-unsafe-assignment': ['off'], 26 | '@typescript-eslint/no-unsafe-member-access': ['off'], 27 | '@typescript-eslint/no-unsafe-call': ['off'], 28 | '@typescript-eslint/no-unsafe-argument': ['off'], 29 | '@typescript-eslint/no-useless-template-literals': 'error', 30 | '@typescript-eslint/no-unsafe-return': ['off'], 31 | '@typescript-eslint/await-thenable': ['warn'], 32 | '@typescript-eslint/no-misused-promises': ['warn'], 33 | '@typescript-eslint/no-var-requires': ['off'], 34 | '@typescript-eslint/no-empty-function': ['off'], 35 | '@typescript-eslint/prefer-nullish-coalescing': ['warn'], 36 | '@typescript-eslint/prefer-for-of': ['warn'], 37 | '@typescript-eslint/unbound-method': ['off'], 38 | '@typescript-eslint/restrict-plus-operands': ['off'], 39 | '@typescript-eslint/no-base-to-string': ['off'], 40 | '@typescript-eslint/no-redundant-type-constituents': ['off'], 41 | '@typescript-eslint/no-floating-promises': ['error'], 42 | semi: 'error', 43 | }, 44 | }; 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # node-waf configuration 27 | .lock-wscript 28 | 29 | # Compiled binary addons (https://nodejs.org/api/addons.html) 30 | build/Release 31 | 32 | # Dependency directories 33 | node_modules/ 34 | jspm_packages/ 35 | 36 | # Snowpack dependency directory (https://snowpack.dev/) 37 | web_modules/ 38 | 39 | # TypeScript cache 40 | *.tsbuildinfo 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional stylelint cache 49 | .stylelintcache 50 | 51 | # Microbundle cache 52 | .rpt2_cache/ 53 | .rts2_cache_cjs/ 54 | .rts2_cache_es/ 55 | .rts2_cache_umd/ 56 | 57 | # Optional REPL history 58 | .node_repl_history 59 | 60 | # Output of 'npm pack' 61 | *.tgz 62 | 63 | # Yarn Integrity file 64 | .yarn-integrity 65 | 66 | # dotenv environment variable files 67 | .env 68 | .env.* 69 | 70 | # parcel-bundler cache (https://parceljs.org/) 71 | .cache 72 | .parcel-cache 73 | 74 | # Next.js build output 75 | .next 76 | out 77 | 78 | # Nuxt.js build / generate output 79 | .nuxt 80 | dist 81 | 82 | # Gatsby files 83 | .cache/ 84 | # Comment in the public line in if your project uses Gatsby and not Next.js 85 | # https://nextjs.org/blog/next-9-1#public-directory-support 86 | # public 87 | 88 | # vuepress build output 89 | .vuepress/dist 90 | 91 | # vuepress v2.x temp and cache directory 92 | .temp 93 | .cache 94 | 95 | # Docusaurus cache and generated files 96 | .docusaurus 97 | 98 | # Serverless directories 99 | .serverless/ 100 | 101 | # FuseBox cache 102 | .fusebox/ 103 | 104 | # DynamoDB Local files 105 | .dynamodb/ 106 | 107 | # TernJS port file 108 | .tern-port 109 | 110 | # Stores VSCode versions used for testing VSCode extensions 111 | .vscode-test 112 | 113 | # yarn v2 114 | .yarn/cache 115 | .yarn/unplugged 116 | .yarn/build-state.yml 117 | .yarn/install-state.gz 118 | .pnp.* 119 | 120 | # build output 121 | **/dist 122 | **/.rune 123 | **/.run 124 | 125 | **/.DS_Store 126 | .settings 127 | **/.vs 128 | **/.vscode/* 129 | !**/.vscode/tasks.json 130 | !**/.vscode/settings.template.json 131 | !**/.vscode/launch.template.json 132 | !**/.vscode/extensions.json 133 | !tests/cases/projects/projectOption/**/node_modules 134 | !tests/cases/projects/NodeModulesSearch/**/* 135 | !tests/baselines/reference/project/nodeModules*/**/* 136 | .npmrc 137 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "all", 4 | "singleQuote": true, 5 | "printWidth": 100, 6 | "tabWidth": 2 7 | } 8 | -------------------------------------------------------------------------------- /.stylelintrc.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import('stylelint').Config} */ 2 | module.exports = { 3 | extends: [ 4 | 'stylelint-config-standard', 5 | 'stylelint-config-recommended-scss', 6 | 'stylelint-config-property-sort-order-smacss', 7 | 'stylelint-config-prettier-scss', 8 | ], 9 | plugins: ['stylelint-scss', 'stylelint-prettier'], 10 | rules: { 11 | 'prettier/prettier': true, 12 | 'block-no-empty': true, 13 | 'at-rule-no-unknown': null, 14 | 'selector-class-pattern': null, 15 | 'max-nesting-depth': 4, // 최대 nesting은 4depth 까지 16 | 'no-descending-specificity': null, 17 | 'scss/at-rule-conditional-no-parentheses': null, 18 | // 조건부 @ 규칙(if, elsif, while)(자동 수정 가능)에서 괄호를 허용합니다. 19 | 'import-notation': null, 20 | 'no-empty-source': null, 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /README.en.md: -------------------------------------------------------------------------------- 1 | [English](#multi-paradigm-programming) | [한국어](https://github.com/marpple/multi-paradigm-programming) 2 | 3 | 4 | 5 | --- 6 | 7 | # Multi-Paradigm Programming 8 | 9 | Combining Object-Oriented, Functional, and Imperative Approaches for Software Development and Design ([Get Started](book/en/README.md) 🧐) 10 | 11 | ## 📖 Table of Contents (Book) 12 | 13 | [View Detailed Table of Contents](book/en/README.md) 14 | [From the Author](book/en/0.1-From-the-Author.md) 15 | [Endorsements](book/en/0.2-Endorsements.md) 16 | 17 | 1. [How Multiparadigm Is Expanding Modern Languages](book/en/1.0.-How-Multiparadigm-Is-Expanding-Modern-Languages.md) 18 | 2. [Functional Programming, Type Systems, and Lisp](book/en/2.0-Functional-Programming,-Type-Systems,-and-Lisp.md) 19 | 3. [Code:Object:Function = Generator:Iterator:LISP = IP:OOP:FP](book/en/3.0-Code%3AObject%3AFunction-=-Generator%3AIterator%3ALISP-=-IP%3AOOP%3AFP.md) 20 | 4. Asynchronous Programming 21 | 5. Practical Functional Programming 22 | 6. Multi-Paradigm Programming 23 | 7. Object-Oriented Front-End Development and Multi-Paradigm Approaches in Practice 24 | 25 | ## 🚀 Quick Menu 26 | 27 | - [Project Setup](#%EF%B8%8F-project-setup) 28 | - [Video ](#-video-course) 29 | - [License & Copyright](#-license--copyright) 30 | 31 | ## 🛠️ Project Setup 32 | 33 | ### Node.js with Volta 34 | 35 | [Volta Guide](https://docs.volta.sh/guide/getting-started) 36 | 37 | ### Unix Installation 38 | 39 | ```shell 40 | curl https://get.volta.sh | bash 41 | volta install node@22 42 | ``` 43 | 44 | ### Windows Installation 45 | 46 | ```shell 47 | winget install Volta.Volta 48 | volta install node@22 49 | ``` 50 | 51 | ### pnpm 52 | 53 | [pnpm Guide](https://docs.volta.sh/guide/getting-started) 54 | 55 | ```bash 56 | npm install -g pnpm@10 57 | ``` 58 | 59 | ### Installing and Running the Book Examples 60 | 61 | ``` 62 | pnpm install 63 | pnpm -F example dev 64 | ``` 65 | 66 | 1. Open the terminal, run the code above, and then navigate to http://localhost:2118/. 67 | 2. The book’s example code can be found in [/apps/example/src/chapters](./apps/example/src/chapters). 68 | 69 | ### Installing and Running the Video Course Examples 70 | 71 | ``` 72 | pnpm install 73 | pnpm -F example dev 74 | ``` 75 | 76 | 1. Open the terminal, run the code above, and then navigate to http://localhost:7000/. 77 | 2. The video-lecture example code is located in [/apps/lecture/src/part-*](./apps/lecture/src). 78 | 79 | ## 🎥 Video Course 80 | 81 | You can take various video courses related to this book on Inflearn. These courses are conducted in a live coding format, allowing you to see the code evolve in real-time for a more detailed and intuitive learning experience. 82 | 83 | #### Go to the Multi-Paradigm Programming Video Course 🖥️ 84 | 85 | - [English](https://www.inflearn.com/en/course/multi-paradigm-programming) 86 | - [日本語](https://www.inflearn.com/ja/course/multi-paradigm-programming) 87 | - [Tiếng Việt](https://www.inflearn.com/vi/course/multi-paradigm-programming) 88 | 89 | > Inflearn sponsors the creation of learning content related to "Multi-Paradigm Programming." 90 | > 91 | > 92 | 93 | ## 📝 Publication 94 | 95 | The Korean version of **『Multi-Paradigm Programming』** is published by [Hanbit Media](https://www.hanbit.co.kr/). 96 | 97 | ## 📜 License & Copyright 98 | 99 | All example codes in this repository are licensed under the MIT License. However, the original book text and any text partially disclosed in this repository are licensed under [CC BY-NC-SA](https://creativecommons.org/licenses/by-nc-sa/4.0/). 100 | 101 | > The materials herein are all © 2025 [Marpple Corporation](https://www.marpplecorp.com/). 102 | > 103 | > 104 | 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [한국어](#멀티패러다임-프로그래밍) | [English](./README.en.md) 2 | 3 | 4 | 5 | --- 6 | 7 | # Multi-Paradigm Programming 8 | 9 | 객체 지향, 함수형, 명령형의 통합적 사고로 구현하는 소프트웨어 설계와 구현 ([보러가기](book/ko/README.md) 🧐) 10 | 11 | ## 📖 책의 목차 12 | 13 | [상세 목차](book/ko/README.md) 14 | [지은이의 글](book/ko/0.1-지은이의-글.md) 15 | [추천의 글](book/ko/0.2-추천의-글.md) 16 | 17 | 1. [멀티패러다임이 현대 언어를 확장하는 방법](book/ko/1.0-멀티패러다임이-현대-언어를-확장하는-방법.md) 18 | 2. [함수형 프로그래밍과 타입 시스템, 그리고 LISP](book/ko/2.0-함수형-프로그래밍과-타입-시스템,-그리고-LISP.md) 19 | 3. [코드:객체:함수 = Generator:Iterator:LISP = IP:OOP:FP](book/ko/3.0-코드%3A객체%3A함수-=-Generator%3AIterator%3ALISP-=-IP%3AOOP%3AFP.md) 20 | 4. 비동기 프로그래밍 21 | 5. 실전 함수형 프로그래밍 22 | 6. 멀티패러다임 프로그래밍 23 | 7. 객체 지향 프론트엔드 개발, 그리고 멀티패러다임적 접근과 응용 24 | 25 | ## 📖 부록: 코틀린 멀티패러다임 이터레이션 (무료 공개) 26 | 27 | 『코틀린 멀티패러다임 이터레이션』은 본서의 부록으로, 『멀티패러다임 프로그래밍』의 일부 내용을 코틀린 언어에 특화하여 풀어낸 자료입니다. 28 | 29 | - [코틀린 멀티패러다임 이터레이션 보러가기](https://github.com/marpple/kotlin-multi-paradigm-iteration) 30 | 31 | ## 🚀 퀵 메뉴 32 | 33 | - [예제 환경 설치](#%EF%B8%8F-예제-환경-설치) 34 | - [인프런 영상 강의](#-인프런-영상-강의) 35 | - [온라인 커뮤니티 및 유튜브 채널](#-온라인-커뮤니티-및-유튜브-채널) 36 | - [라이센스 및 저작권](#-라이센스-및-저작권) 37 | 38 | ## 🛠️ 예제 환경 설치 39 | 40 | ### Node.js with Volta 41 | 42 | [Volta Guide](https://docs.volta.sh/guide/getting-started) 43 | 44 | ### Unix 환경 45 | 46 | ```shell 47 | curl https://get.volta.sh | bash 48 | volta install node@22 49 | ``` 50 | 51 | ### Windows 환경 52 | 53 | ```shell 54 | winget install Volta.Volta 55 | volta install node@22 56 | ``` 57 | 58 | ### pnpm 59 | 60 | [pnpm Guide](https://pnpm.io/installation) 61 | 62 | ```bash 63 | npm install -g pnpm@10 64 | ``` 65 | 66 | ### 책 예제 설치하고 실행하기 67 | 68 | ``` 69 | pnpm install 70 | pnpm -F example dev 71 | ``` 72 | 73 | 1. 터미널을 열어 위 코드를 실행한 후 http://localhost:2118/ 로 접속하세요. 74 | 2. 책의 예제 코드 위치는 [/apps/example/src/chapters](./apps/example/src/chapters) 입니다. 75 | 76 | ### 영상 강의 예제 설치하고 실행하기 77 | 78 | ``` 79 | pnpm install 80 | pnpm -F lecture dev 81 | ``` 82 | 83 | 1. 터미널을 열어 위 코드를 실행한 후 http://localhost:7000/ 로 접속하세요. 84 | 2. 영상 강의의 예제 코드 위치는 [/apps/lecture/src/part-*](./apps/lecture/src) 입니다. 85 | 86 | ## 🎥 인프런 영상 강의 87 | 88 | 인프런에서 본 책과 관련된 여러 영상 강의를 수강할 수 있습니다. 특히 라이브 코딩 형식으로 진행되어, 코드가 발전해 나가는 과정을 좀 더 상세하고 직관적으로 볼 수 있습니다. 89 | 90 | - [멀티패러다임 프로그래밍 영상 강의 바로가기](https://inf.run/cWvDs) 🖥 91 | 92 | > 인프런에서 "멀티패러다임 프로그래밍" 관련 학습 컨텐츠 제작을 후원합니다. 93 | > 94 | > 95 | 96 | ## 🌐 온라인 커뮤니티 및 유튜브 채널 97 | 98 | 온라인 커뮤니티에서 멀티패러다임 프로그래밍에 관한 더욱 폭넓은 학습을 진행할 수 있습니다. 추가로, 이 책과 관련된 퀴즈와 정답 풀이, 다양한 교육 자료가 공유되고 있어, 책의 내용을 더 깊이 이해하고 응용력을 키우는 데 큰 도움이 됩니다. 필요하신 분들은 꼭 방문해 참고해 보세요. 99 | 100 | - [온라인 커뮤니티 바로가기 📢](https://ciety.xyz/@mduniv) 101 | - [유튜브 채널 바로가기 📺](https://www.youtube.com/@mduniv) 102 | 103 | > 마플개발대학(MDU)은 소프트웨어 대학을 콘셉트로 활동하는 커뮤니티로, 개발자들과 함께 성장하며 프로그래밍의 즐거움을 나누고, 보다 깊이 있는 소프트웨어 공학 문화를 만들어가기 위해 설립되었습니다. 104 | > 105 | > 106 | 107 | ## 📝 출판 108 | 109 | 본 도서 『멀티패러다임 프로그래밍』은 [한빛미디어](https://www.hanbit.co.kr/)에서 출판되었습니다. 본 저장소에 공개된 책 내용은 집필 원고 원본입니다. 더욱 매끄럽게 편집된 글은 종이책 혹은 전자책으로 만나보실 수 있습니다. 110 | 111 | - [교보문고](https://product.kyobobook.co.kr/detail/S000216318962) 112 | - [yes24](https://www.yes24.com/product/goods/145367977) 113 | - [알라딘](https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=362548794) 114 | 115 | > 116 | 117 | ## 📜 라이센스 및 저작권 118 | 119 | 이 저장소의 예제 코드는 모두 MIT 라이선스로 배포됩니다. 다만, 책의 원문과 이 저장소에 공개된 모든 텍스트는 [CC BY-NC-SA](https://creativecommons.org/licenses/by-nc-sa/4.0/) 라이선스를 적용받습니다. 120 | 121 | > 본 문서의 모든 자료는 © 2025 [마플코퍼레이션](https://www.marpplecorp.com/)의 저작권이 적용됩니다. 122 | > 123 | > -------------------------------------------------------------------------------- /apps/LICENSE_MIT_code.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 MARPPLE 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /apps/example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "1.0.0", 4 | "description": "", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "pnpm rune dev", 8 | "build": "pnpm rune build", 9 | "start": "pnpm rune start" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "dependencies": { 15 | "@fxts/core": "^1.9.0", 16 | "@rune-ts/server": "^1.0.24", 17 | "rune-ts": "^0.8.12", 18 | "ua-parser-js": "^1.0.37" 19 | }, 20 | "devDependencies": { 21 | "@types/animejs": "^3.1.12", 22 | "@types/express": "^4.17.21", 23 | "@types/express-serve-static-core": "^4.17.43", 24 | "@types/ua-parser-js": "^0.7.39" 25 | }, 26 | "volta": { 27 | "node": "22.13.0", 28 | "pnpm": "10.0.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /apps/example/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marpple/multi-paradigm-programming/70c00e248c93f901ffa06936439cfb38c10cf10c/apps/example/public/favicon.png -------------------------------------------------------------------------------- /apps/example/rune.config.cjs: -------------------------------------------------------------------------------- 1 | /** 2 | * @type {import('@rune-ts/server').RuneConfigType} 3 | */ 4 | module.exports = { 5 | port: 2118, 6 | hostname: 'localhost', 7 | mode: 'render', 8 | clientEntry: './src/app/client/index.ts', 9 | serverEntry: './src/app/server/index.ts', 10 | watchToReloadPaths: ['../../packages'], 11 | watchToIgnorePaths: ['**/.env.*', '*.json'], 12 | }; 13 | -------------------------------------------------------------------------------- /apps/example/src/app/client/index.ts: -------------------------------------------------------------------------------- 1 | import '../../../../../packages/styles/global.scss'; 2 | import { hydrate } from '@rune-ts/server'; 3 | import { ClientRouter } from '../route'; 4 | 5 | hydrate(ClientRouter); 6 | -------------------------------------------------------------------------------- /apps/example/src/app/route/index.ts: -------------------------------------------------------------------------------- 1 | import { createRouter } from '@rune-ts/server'; 2 | import { Chapter1Section1 } from '../../chapters/chapter-1/1-1/Chapter1Section1'; 3 | import { Chapter1Section2 } from '../../chapters/chapter-1/1-2/Chapter1Section2'; 4 | import { Chapter1Section3 } from '../../chapters/chapter-1/1-3/Chapter1Section3'; 5 | import { Chapter1Section4 } from '../../chapters/chapter-1/1-4/Chapter1Section4'; 6 | import { Chapter1Section5 } from '../../chapters/chapter-1/1-5/Chapter1Section5'; 7 | import { Chapter2Section1 } from '../../chapters/chapter-2/2-1/Chapter2Section1'; 8 | import { Chapter2Section2 } from '../../chapters/chapter-2/2-2/Chapter2Section2'; 9 | import { Chapter2Section3 } from '../../chapters/chapter-2/2-3/Chapter2Section3'; 10 | import { Chapter3Section1 } from '../../chapters/chapter-3/3-1/Chapter3Section1'; 11 | import { Chapter3Section3 } from '../../chapters/chapter-3/3-3/Chapter3Section3'; 12 | import { Chapter3Section4 } from '../../chapters/chapter-3/3-4/Chapter3Section4'; 13 | import { Chapter4Section1 } from '../../chapters/chapter-4/4-1/Chapter4Section1'; 14 | import { Chapter4Section2 } from '../../chapters/chapter-4/4-2/Chapter4Section2'; 15 | import { Chapter4Section3 } from '../../chapters/chapter-4/4-3/Chapter4Section3'; 16 | import { Chapter4Section4 } from '../../chapters/chapter-4/4-4/Chapter4Section4'; 17 | import { Chapter5Section1 } from '../../chapters/chapter-5/5-1/Chapter5Section1'; 18 | import { Chapter5Section2 } from '../../chapters/chapter-5/5-2/Chapter5Section2'; 19 | import { Chapter5Section3 } from '../../chapters/chapter-5/5-3/Chapter5Section3'; 20 | import { Chapter5Section4 } from '../../chapters/chapter-5/5-4/Chapter5Section4'; 21 | import { Chapter6Section1 } from '../../chapters/chapter-6/6-1/Chapter6Section1'; 22 | import { Chapter6Section2 } from '../../chapters/chapter-6/6-2/Chapter6Section2'; 23 | import { Chapter7Section1 } from '../../chapters/chapter-7/7-1/Chapter7Section1'; 24 | import { Chapter7Section234 } from '../../chapters/chapter-7/7-2_3_4/Chapter7Section234'; 25 | import { Chapter7Section5 } from '../../chapters/chapter-7/7-5/Chapter7Section5'; 26 | import { Chapter7Section52 } from '../../chapters/chapter-7/7-5/Chapter7Section52'; 27 | import { HomePage } from '../../chapters/HomePage'; 28 | 29 | export const ClientRouter = createRouter({ 30 | '/': HomePage, 31 | '/1-1': Chapter1Section1, 32 | '/1-2': Chapter1Section2, 33 | '/1-3': Chapter1Section3, 34 | '/1-4': Chapter1Section4, 35 | '/1-5': Chapter1Section5, 36 | '/2-1': Chapter2Section1, 37 | '/2-2': Chapter2Section2, 38 | '/2-3': Chapter2Section3, 39 | '/3-1': Chapter3Section1, 40 | '/3-3': Chapter3Section3, 41 | '/3-4': Chapter3Section4, 42 | '/4-1': Chapter4Section1, 43 | '/4-2': Chapter4Section2, 44 | '/4-3': Chapter4Section3, 45 | '/4-4': Chapter4Section4, 46 | '/5-1': Chapter5Section1, 47 | '/5-2': Chapter5Section2, 48 | '/5-3': Chapter5Section3, 49 | '/5-4': Chapter5Section4, 50 | '/6-1': Chapter6Section1, 51 | '/6-2': Chapter6Section2, 52 | '/7-1': Chapter7Section1, 53 | '/7-234': Chapter7Section234, 54 | '/7-5': Chapter7Section5, 55 | '/7-5-2': Chapter7Section52, 56 | }); 57 | -------------------------------------------------------------------------------- /apps/example/src/app/server/index.ts: -------------------------------------------------------------------------------- 1 | import { app, MetaView, type LayoutData } from '@rune-ts/server'; 2 | import type { Page } from 'rune-ts'; 3 | import UAParser from 'ua-parser-js'; 4 | import favicon from '../../../public/favicon.png'; 5 | import { ClientRouter } from '../route'; 6 | 7 | const server = app(); 8 | server.use((req, res, next) => { 9 | const ua_string = req.headers['user-agent']; 10 | const parser = new UAParser(ua_string); 11 | res.locals.is_mobile = !!parser.getDevice().type; 12 | 13 | res.locals.layoutData = { 14 | html: { 15 | is_mobile: res.locals.is_mobile, 16 | }, 17 | head: { 18 | title: '', 19 | description: '', 20 | link_tags: [ 21 | { 22 | rel: 'icon', 23 | href: favicon, 24 | type: 'image/png', 25 | }, 26 | ], 27 | }, 28 | } as LayoutData; 29 | 30 | return next(); 31 | }); 32 | 33 | const addRenderHandler = (createPage: (data: object) => Page) => { 34 | server.get(createPage.toString(), (req, res, next) => { 35 | res.send(new MetaView(createPage({}), res.locals.layoutData).toHtml()); 36 | }); 37 | }; 38 | 39 | addRenderHandler(ClientRouter['/']); 40 | addRenderHandler(ClientRouter['/1-1']); 41 | addRenderHandler(ClientRouter['/1-2']); 42 | addRenderHandler(ClientRouter['/1-3']); 43 | addRenderHandler(ClientRouter['/1-4']); 44 | addRenderHandler(ClientRouter['/1-5']); 45 | addRenderHandler(ClientRouter['/2-1']); 46 | addRenderHandler(ClientRouter['/2-2']); 47 | addRenderHandler(ClientRouter['/2-3']); 48 | addRenderHandler(ClientRouter['/3-1']); 49 | addRenderHandler(ClientRouter['/3-3']); 50 | addRenderHandler(ClientRouter['/3-4']); 51 | addRenderHandler(ClientRouter['/4-1']); 52 | addRenderHandler(ClientRouter['/4-2']); 53 | addRenderHandler(ClientRouter['/4-3']); 54 | addRenderHandler(ClientRouter['/4-4']); 55 | addRenderHandler(ClientRouter['/5-1']); 56 | addRenderHandler(ClientRouter['/5-2']); 57 | addRenderHandler(ClientRouter['/5-3']); 58 | addRenderHandler(ClientRouter['/5-4']); 59 | addRenderHandler(ClientRouter['/6-1']); 60 | addRenderHandler(ClientRouter['/6-2']); 61 | addRenderHandler(ClientRouter['/7-1']); 62 | addRenderHandler(ClientRouter['/7-234']); 63 | addRenderHandler(ClientRouter['/7-5']); 64 | addRenderHandler(ClientRouter['/7-5-2']); 65 | -------------------------------------------------------------------------------- /apps/example/src/chapters/ChapterPage.ts: -------------------------------------------------------------------------------- 1 | import { html, Page } from 'rune-ts'; 2 | 3 | export class ChapterPage extends Page { 4 | override template() { 5 | return html` 6 | 7 | Home 8 | 9 | `; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /apps/example/src/chapters/HomePage.ts: -------------------------------------------------------------------------------- 1 | import { Page, html } from "rune-ts"; 2 | 3 | 4 | export class HomePage extends Page { 5 | override template() { 6 | return html` 7 | 8 | Home 9 | 10 | 1-1 11 | 1-2 12 | 1-3 13 | 1-4 14 | 1-5 15 | 2-1 16 | 2-2 17 | 2-3 18 | 3-1 19 | 3-3 20 | 3-4 21 | 4-1 22 | 4-2 23 | 4-3 24 | 4-4 25 | 5-1 26 | 5-2 27 | 5-3 28 | 5-4 29 | 6-1 30 | 6-2 31 | 7-1 32 | 7-234 33 | 7-5 34 | 7-5-2 35 | 36 | 37 | `; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /apps/example/src/chapters/chapter-1/1-1/Chapter1Section1.ts: -------------------------------------------------------------------------------- 1 | import { ChapterPage } from '../../ChapterPage'; 2 | import { main } from './main'; 3 | 4 | export class Chapter1Section1 extends ChapterPage { 5 | override onRender() { 6 | main(); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /apps/example/src/chapters/chapter-1/1-1/main.ts: -------------------------------------------------------------------------------- 1 | // [1-2] 2 | class ArrayLikeIterator implements Iterator { 3 | private index = 0; 4 | constructor(private arrayLike: ArrayLike) {} 5 | 6 | next(): IteratorResult { 7 | if (this.index < this.arrayLike.length) { 8 | return { 9 | value: this.arrayLike[this.index++], 10 | done: false, 11 | }; 12 | } else { 13 | return { 14 | value: undefined, 15 | done: true, 16 | }; 17 | } 18 | } 19 | } 20 | 21 | function code_1_2() { 22 | const arrayLike: ArrayLike = { 23 | 0: 10, 24 | 1: 20, 25 | 2: 30, 26 | length: 3, 27 | }; 28 | 29 | const iterator: Iterator = new ArrayLikeIterator(arrayLike); 30 | 31 | console.log(iterator.next()); // { value: 10, done: false } 32 | console.log(iterator.next()); // { value: 20, done: false } 33 | console.log(iterator.next()); // { value: 30, done: false } 34 | console.log(iterator.next()); // { value: undefined, done: true } 35 | } 36 | 37 | function code_1_3() { 38 | const array: Array = ['a', 'b', 'c']; 39 | const iterator2: Iterator = new ArrayLikeIterator(array); 40 | 41 | console.log(iterator2.next()); // { value: 'a', done: false } 42 | console.log(iterator2.next()); // { value: 'b', done: false } 43 | console.log(iterator2.next()); // { value: 'c', done: false } 44 | console.log(iterator2.next()); // { value: undefined, done: true } 45 | } 46 | 47 | function code_1_4() { 48 | const array = ['A', 'B']; 49 | array.reverse(); 50 | console.log(array[0], array[1]); // 'B', 'A' 51 | } 52 | 53 | // 1-5 54 | function reverse(arrayLike: ArrayLike): Iterator { 55 | let idx = arrayLike.length; 56 | return { 57 | next() { 58 | if (idx-- >= 0) { 59 | return { value: arrayLike[idx], done: false }; 60 | } else { 61 | return { value: undefined, done: true }; 62 | } 63 | }, 64 | }; 65 | } 66 | 67 | function code_1_5() { 68 | const array = ['A', 'B']; 69 | const reversed = reverse(array); 70 | console.log(array); // ['A', 'B'] 71 | 72 | console.log(reversed.next().value, reversed.next().value); 73 | // 'B', 'A' 74 | } 75 | 76 | function code_1_6() { 77 | const array = ['A', 'B', 'C', 'D', 'E', 'F']; 78 | array.reverse(); 79 | console.log(array); // ['F', 'E', 'D', 'C', 'B', 'A'] 80 | console.log(array[0], array[1]); // 'F', 'E' 81 | 82 | const array2 = ['A', 'B', 'C', 'D', 'E', 'F']; 83 | const reversed = reverse(array2); 84 | console.log(array2); // ['A', 'B', 'C', 'D', 'E', 'F'] 85 | console.log(reversed.next().value, reversed.next().value); // 'F', 'E' 86 | } 87 | 88 | function code_1_7() { 89 | const array = ['A', 'B', 'C', 'D', 'E', 'F']; 90 | const reversed = [...array].reverse(); 91 | console.log(reversed[0], reversed[1], array[0], array[1]); 92 | // 'F', 'E', 'A', 'B' 93 | 94 | const array2 = ['A', 'B', 'C', 'D', 'E', 'F']; 95 | const reversed2 = reverse(array2); 96 | console.log(reversed2.next().value, reversed2.next().value, array2[0], array2[1]); 97 | // 'F', 'E', 'A', 'B' 98 | } 99 | 100 | // [1-8] 101 | function map(transform: (value: A) => B, iterator: Iterator): Iterator { 102 | return { 103 | next(): IteratorResult { 104 | const { value, done } = iterator.next(); 105 | return done ? { value, done } : { value: transform(value), done }; 106 | }, 107 | }; 108 | } 109 | 110 | function code_1_8() { 111 | const array = ['A', 'B', 'C', 'D', 'E', 'F']; 112 | const iterator = map((str) => str.toLowerCase(), reverse(array)); 113 | console.log(iterator.next().value, iterator.next().value); // f e 114 | } 115 | 116 | export function main() { 117 | code_1_2(); 118 | code_1_3(); 119 | code_1_4(); 120 | code_1_5(); 121 | code_1_6(); 122 | code_1_7(); 123 | code_1_8(); 124 | } 125 | -------------------------------------------------------------------------------- /apps/example/src/chapters/chapter-1/1-2/Chapter1Section2.ts: -------------------------------------------------------------------------------- 1 | import { ChapterPage } from '../../ChapterPage'; 2 | import { main } from './main'; 3 | 4 | export class Chapter1Section2 extends ChapterPage { 5 | override onRender() { 6 | main(); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /apps/example/src/chapters/chapter-1/1-2/main.ts: -------------------------------------------------------------------------------- 1 | function code_1_10() { 2 | function* generator() { 3 | yield 1; 4 | yield 2; 5 | yield 3; 6 | } 7 | 8 | const iter = generator(); 9 | 10 | console.log(iter.next()); // { value: 1, done: false } 11 | console.log(iter.next()); // { value: 2, done: false } 12 | console.log(iter.next()); // { value: 3, done: false } 13 | console.log(iter.next()); // { value: undefined, done: true } 14 | } 15 | 16 | function code_1_10a() { 17 | function* generator() { 18 | yield 1; 19 | console.log('hi'); 20 | yield 2; 21 | yield 3; 22 | } 23 | 24 | const iter = generator(); 25 | 26 | console.log(iter.next()); 27 | // { value: 1, done: false } 28 | console.log(iter.next()); // hi 29 | // { value: 2, done: false } 30 | console.log(iter.next()); 31 | // { value: 3, done: false } 32 | console.log(iter.next()); 33 | // { value: undefined, done: true } 34 | } 35 | 36 | function code_1_11_11a() { 37 | function* generator(condition: boolean) { 38 | yield 1; 39 | if (condition) { 40 | yield 2; 41 | } 42 | yield 3; 43 | } 44 | 45 | const iter1 = generator(false); 46 | 47 | console.log(iter1.next()); // { value: 1, done: false } 48 | console.log(iter1.next()); // { value: 3, done: false } 49 | console.log(iter1.next()); // { value: undefined, done: true } 50 | 51 | const iter2 = generator(true); 52 | 53 | console.log(iter2.next()); // { value: 1, done: false } 54 | console.log(iter2.next()); // { value: 2, done: false } 55 | console.log(iter2.next()); // { value: 3, done: false } 56 | console.log(iter2.next()); // { value: undefined, done: true } 57 | } 58 | 59 | function code_1_12() { 60 | function* generator() { 61 | yield 1; 62 | yield* [2, 3]; 63 | yield 4; 64 | } 65 | 66 | const iter = generator(); 67 | 68 | console.log(iter.next()); // { value: 1, done: false } 69 | console.log(iter.next()); // { value: 2, done: false } 70 | console.log(iter.next()); // { value: 3, done: false } 71 | console.log(iter.next()); // { value: 4, done: false } 72 | console.log(iter.next()); // { value: undefined, done: true } 73 | } 74 | 75 | function code_1_13() { 76 | function* naturals() { 77 | let n = 1; 78 | while (true) { 79 | yield n++; 80 | } 81 | } 82 | 83 | const iter = naturals(); 84 | 85 | console.log(iter.next()); // { value: 1, done: false } 86 | console.log(iter.next()); // { value: 2, done: false } 87 | console.log(iter.next()); // { value: 3, done: false } 88 | // 계속해서 호출할 수 있습니다. 89 | } 90 | 91 | function code_1_14() { 92 | function* reverse(arrayLike: ArrayLike): IterableIterator { 93 | let idx = arrayLike.length; 94 | while (idx--) { 95 | yield arrayLike[idx]; 96 | } 97 | } 98 | 99 | const array = ['A', 'B', 'C', 'D', 'E', 'F']; 100 | const reversed = reverse(array); 101 | 102 | console.log(reversed.next().value); // F 103 | console.log(reversed.next().value); // E 104 | console.log(reversed.next().value); // D 105 | } 106 | 107 | export function main() { 108 | code_1_10(); 109 | code_1_10a(); 110 | code_1_11_11a(); 111 | code_1_12(); 112 | code_1_13(); 113 | code_1_14(); 114 | } 115 | -------------------------------------------------------------------------------- /apps/example/src/chapters/chapter-1/1-3/Chapter1Section3.ts: -------------------------------------------------------------------------------- 1 | import { ChapterPage } from '../../ChapterPage'; 2 | import { main } from './main'; 3 | 4 | export class Chapter1Section3 extends ChapterPage { 5 | override onRender() { 6 | main(); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /apps/example/src/chapters/chapter-1/1-4/Chapter1Section4.ts: -------------------------------------------------------------------------------- 1 | import { ChapterPage } from '../../ChapterPage'; 2 | import { main } from './main'; 3 | 4 | export class Chapter1Section4 extends ChapterPage { 5 | override onRender() { 6 | main(); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /apps/example/src/chapters/chapter-1/1-5/Chapter1Section5.ts: -------------------------------------------------------------------------------- 1 | import { code_1_45, main } from './main'; 2 | import { html, Page } from 'rune-ts'; 3 | 4 | export class Chapter1Section5 extends Page { 5 | override template() { 6 | return html` 7 | 8 | Home 9 | ${code_1_45()} 10 | 11 | `; 12 | } 13 | 14 | override onRender() { 15 | main(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /apps/example/src/chapters/chapter-1/1-5/main.ts: -------------------------------------------------------------------------------- 1 | import { html } from 'rune-ts'; 2 | 3 | export function code_1_45() { 4 | return html` 5 | 6 | 7 | 1 8 | 2 9 | 3 10 | 4 11 | 5 12 | 13 | 25 | 26 | ` 27 | } 28 | 29 | function code_1_46() { 30 | forEach(console.log, 31 | filter(x => x % 2 === 1, 32 | map(node => parseInt(node.textContent), 33 | document.querySelectorAll('li')))); 34 | // 1 35 | // 3 36 | // 5 37 | 38 | forEach(element => element.remove(), 39 | filter(node => parseInt(node.textContent) % 2 === 0, 40 | document.querySelectorAll('li'))); 41 | // removed: 2 42 | // removed: 4 43 | } 44 | 45 | function code_1_47() { 46 | const nodes: NodeList = document.querySelectorAll('li'); 47 | 48 | console.log(nodes[0], nodes[1], nodes.length); 49 | // 1 3 3 50 | 51 | // nodes.map(node => node.textContent); 52 | // Uncaught TypeError: nodes.map is not a function 53 | } 54 | 55 | export function main() { 56 | code_1_46(); 57 | code_1_47() 58 | } 59 | 60 | function forEach(f, iterable) { 61 | const iterator = iterable[Symbol.iterator](); 62 | let result = iterator.next(); 63 | while (!result.done) { 64 | f(result.value); 65 | result = iterator.next(); 66 | } 67 | } 68 | 69 | function* map(f, iterable) { 70 | for (const value of iterable) { 71 | yield f(value); 72 | } 73 | } 74 | 75 | function* filter(f, iterable) { 76 | for (const value of iterable) { 77 | if (f(value)) { 78 | yield value; 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /apps/example/src/chapters/chapter-2/2-1/Chapter2Section1.ts: -------------------------------------------------------------------------------- 1 | import { ChapterPage } from '../../ChapterPage'; 2 | import { main } from './main'; 3 | 4 | export class Chapter2Section1 extends ChapterPage { 5 | override onRender() { 6 | main(); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /apps/example/src/chapters/chapter-2/2-1/main.ts: -------------------------------------------------------------------------------- 1 | function code_2_1_2_3() { 2 | let a = 10; 3 | 4 | let message = "Hello, TypeScript!"; 5 | 6 | const selected = true; 7 | // [const selected: true] 8 | 9 | let checked = true; 10 | // [let checked: boolean] 11 | } 12 | 13 | function code_2_4() { 14 | function add(a: number, b: number) { 15 | return a + b; 16 | } 17 | } 18 | 19 | function code_2_5() { 20 | function add(a: string, b: string) { 21 | return a + b; 22 | } 23 | } 24 | 25 | function code_2_6() { 26 | function add(a: string, b: string) { 27 | return parseInt(a) + parseInt(b); 28 | } 29 | } 30 | 31 | function code_2_7() { 32 | function add(a: string, b: string): number { 33 | return parseInt(a) + parseInt(b); 34 | } 35 | } 36 | 37 | function code_2_8() { 38 | let user = { 39 | name: "Marty", 40 | age: 30 41 | }; 42 | } 43 | 44 | function code_2_9() { 45 | let strs = ['a', 'b', 'c']; 46 | strs.forEach(str => console.log(str.toUpperCase())); // [str: string] 47 | } 48 | 49 | function code_2_10() { 50 | function identity(arg: T): T { 51 | return arg; 52 | } 53 | 54 | const a = identity("hi"); // [const a: "hi"] 55 | 56 | const b = identity(1); // [const b: 1] 57 | 58 | const c = identity("a"); // [const c: string] 59 | 60 | const d = identity(1); // [const d: number] 61 | 62 | class User {} 63 | const e = identity(new User()); // [const e: User] 64 | 65 | const f = identity((n: number) => n % 2 === 1); // [const f: (n: number) => boolean] 66 | } 67 | 68 | function code_2_11_12_13() { 69 | function add(a: number, b: number): number { 70 | return a + b; 71 | } 72 | 73 | const result: number = add(2, 3); // 5 74 | 75 | function double(a: number): number; 76 | function double(a: string): string; 77 | function double(a: number | string): number | string { 78 | if (typeof a === 'number') { 79 | return a * 2; 80 | } else { 81 | return a + a; 82 | } 83 | } 84 | 85 | const numResult: number = double(10); // 20 86 | const strResult: string = double('Hi'); // 'HiHi' 87 | 88 | const multiply = (a: number, b: number): number => a * b; 89 | 90 | const num: number = multiply(4, 5); // 20 91 | } 92 | 93 | function code_2_14_15() { 94 | const multiply = (a: number, b: number) => a * b; 95 | 96 | const num: number = multiply(4, 5); // 20 97 | 98 | type Add = (a: number, b: number) => number; 99 | 100 | const add: Add = (a, b) => a + b; 101 | } 102 | 103 | function code_2_16() { 104 | function constant(a: T): () => T { 105 | return () => a; 106 | } 107 | 108 | const getFive = constant(5); 109 | const ten: number = getFive() + getFive(); 110 | console.log(ten); // 10 111 | 112 | const getHi = constant("Hi"); 113 | const hi2: string = getHi() + getHi(); 114 | console.log(hi2); // HiHi 115 | } 116 | 117 | export function main() { 118 | code_2_1_2_3() 119 | code_2_4(); 120 | code_2_5(); 121 | code_2_6(); 122 | code_2_7(); 123 | code_2_8(); 124 | code_2_9(); 125 | code_2_10(); 126 | code_2_11_12_13(); 127 | code_2_14_15(); 128 | code_2_16(); 129 | } 130 | -------------------------------------------------------------------------------- /apps/example/src/chapters/chapter-2/2-2/Chapter2Section2.ts: -------------------------------------------------------------------------------- 1 | import { ChapterPage } from '../../ChapterPage'; 2 | import { main } from './main'; 3 | 4 | export class Chapter2Section2 extends ChapterPage { 5 | override onRender() { 6 | main(); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /apps/example/src/chapters/chapter-2/2-3/Chapter2Section3.ts: -------------------------------------------------------------------------------- 1 | import { ChapterPage } from '../../ChapterPage'; 2 | import { main } from './main'; 3 | 4 | export class Chapter2Section3 extends ChapterPage { 5 | override onRender() { 6 | main(); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /apps/example/src/chapters/chapter-3/3-1/Chapter3Section1.ts: -------------------------------------------------------------------------------- 1 | import { ChapterPage } from '../../ChapterPage'; 2 | import { main } from './main'; 3 | 4 | export class Chapter3Section1 extends ChapterPage { 5 | override onRender() { 6 | main(); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /apps/example/src/chapters/chapter-3/3-1/main.ts: -------------------------------------------------------------------------------- 1 | import { filter, fx, map, reduce, take } from '../../../lib/fx2'; 2 | 3 | function code_3_1() { 4 | function sumOfSquaresOfOddNumbers(limit: number, list: number[]): number { 5 | let acc = 0; 6 | for (const a of list) { 7 | if (a % 2 === 1) { 8 | const b = a * a; 9 | acc += b; 10 | if (--limit === 0) break; 11 | } 12 | } 13 | return acc; 14 | } 15 | 16 | console.log( 17 | sumOfSquaresOfOddNumbers(3, [1, 2, 3, 4, 5, 6, 7, 8, 9]) 18 | ); 19 | // 35 20 | } 21 | 22 | function code_3_2() { 23 | function sumOfSquaresOfOddNumbers(limit: number, list: number[]): number { 24 | let acc = 0; 25 | for (const a of filter(a => a % 2 === 1, list)) { 26 | const b = a * a; 27 | acc += b; 28 | if (--limit === 0) break; 29 | } 30 | return acc; 31 | } 32 | 33 | console.log( 34 | sumOfSquaresOfOddNumbers(3, [1, 2, 3, 4, 5, 6, 7, 8, 9]) 35 | ); 36 | // 35 37 | } 38 | 39 | function code_3_3() { 40 | function sumOfSquaresOfOddNumbers(limit: number, list: number[]): number { 41 | let acc = 0; 42 | for (const a of map(a => a * a, filter(a => a % 2 === 1, list))) { 43 | acc += a; 44 | if (--limit === 0) break; 45 | } 46 | return acc; 47 | } 48 | 49 | console.log( 50 | sumOfSquaresOfOddNumbers(3, [1, 2, 3, 4, 5, 6, 7, 8, 9]) 51 | ); 52 | // 35 53 | } 54 | 55 | function code_3_5() { 56 | function* take(limit: number, iterable: Iterable): IterableIterator { 57 | const iterator = iterable[Symbol.iterator](); 58 | while (true) { 59 | const { value, done } = iterator.next(); 60 | if (done) break; 61 | yield value; 62 | if (--limit === 0) break; 63 | } 64 | } 65 | 66 | function sumOfSquaresOfOddNumbers(limit: number, list: number[]): number { 67 | let acc = 0; 68 | for (const a of take(limit, map(a => a * a, filter(a => a % 2 === 1, list)))) { 69 | acc += a; 70 | } 71 | return acc; 72 | } 73 | 74 | console.log( 75 | sumOfSquaresOfOddNumbers(3, [1, 2, 3, 4, 5, 6, 7, 8, 9]) 76 | ); 77 | // 35 78 | } 79 | 80 | function code_3_6() { 81 | const sumOfSquaresOfOddNumbers = (limit: number, list: number[]): number => 82 | reduce((a, b) => a + b, 0, 83 | take(limit, 84 | map(a => a * a, 85 | filter(a => a % 2 === 1, list)))); 86 | 87 | console.log( 88 | sumOfSquaresOfOddNumbers(3, [1, 2, 3, 4, 5, 6, 7, 8, 9]) 89 | ); 90 | // 35 91 | } 92 | 93 | function code_3_7() { 94 | // 함수형 코드: 95 | const sumOfSquaresOfOddNumbers = (limit: number, list: number[]): number => 96 | fx(list) 97 | .filter(a => a % 2 === 1) 98 | .map(a => a * a) 99 | .take(limit) 100 | .reduce((a, b) => a + b, 0); 101 | 102 | console.log( 103 | sumOfSquaresOfOddNumbers(3, [1, 2, 3, 4, 5, 6, 7, 8, 9]) 104 | ); 105 | // 35 106 | } 107 | 108 | export function main() { 109 | code_3_1(); 110 | code_3_2(); 111 | code_3_3(); 112 | code_3_5(); 113 | code_3_6(); 114 | code_3_7(); 115 | } 116 | -------------------------------------------------------------------------------- /apps/example/src/chapters/chapter-3/3-3/Chapter3Section3.ts: -------------------------------------------------------------------------------- 1 | import { ChapterPage } from '../../ChapterPage'; 2 | import { main } from './main'; 3 | 4 | export class Chapter3Section3 extends ChapterPage { 5 | override onRender() { 6 | main(); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /apps/example/src/chapters/chapter-3/3-4/Chapter3Section4.ts: -------------------------------------------------------------------------------- 1 | import { ChapterPage } from '../../ChapterPage'; 2 | import { main } from './main'; 3 | 4 | export class Chapter3Section4 extends ChapterPage { 5 | override onRender() { 6 | main(); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /apps/example/src/chapters/chapter-4/4-1/Chapter4Section1.ts: -------------------------------------------------------------------------------- 1 | import { ChapterPage } from '../../ChapterPage'; 2 | import { main } from './main'; 3 | 4 | export class Chapter4Section1 extends ChapterPage { 5 | override async onRender() { 6 | await main(); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /apps/example/src/chapters/chapter-4/4-2/Chapter4Section2.ts: -------------------------------------------------------------------------------- 1 | import { ChapterPage } from '../../ChapterPage'; 2 | import { main } from './main'; 3 | 4 | export class Chapter4Section2 extends ChapterPage { 5 | override async onRender() { 6 | await main(); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /apps/example/src/chapters/chapter-4/4-3/Chapter4Section3.ts: -------------------------------------------------------------------------------- 1 | import { ChapterPage } from '../../ChapterPage'; 2 | import { main } from './main'; 3 | 4 | export class Chapter4Section3 extends ChapterPage { 5 | override async onRender() { 6 | await main(); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /apps/example/src/chapters/chapter-4/4-4/Chapter4Section4.ts: -------------------------------------------------------------------------------- 1 | import { ChapterPage } from '../../ChapterPage'; 2 | import { main } from './main'; 3 | 4 | export class Chapter4Section4 extends ChapterPage { 5 | override async onRender() { 6 | await main(); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /apps/example/src/chapters/chapter-4/4-4/main.ts: -------------------------------------------------------------------------------- 1 | import { fx } from '../../../lib/fx4'; 2 | 3 | // [4-41] 4 | function loadImage(url: string): Promise { 5 | return new Promise((resolve, reject) => { 6 | const image = new Image(); 7 | image.src = url; 8 | image.onload = function() { 9 | resolve(image); 10 | } 11 | image.onerror = function() { 12 | reject(new Error(`load error : ${url}`)); 13 | } 14 | }); 15 | } 16 | 17 | const urls = [ 18 | "https://s3.marpple.co/files/m2/t3/colored_images/45_1115570_1162087_150x0.png", 19 | "https://s3.marpple.co/f1/2018/1/1054966_1516076919028_64501_150x0.png", 20 | "https://s3.marpple.co/f1/2019/1/1235206_1548918825999_78819_150x0.png", 21 | "https://s3.marpple.co/f1/2019/1/1235206_1548918758054_55883_150x0.png", 22 | ]; 23 | 24 | const urls2 = [ 25 | "https://s3.marpple.co/files/m2/t3/colored_images/45_1115570_1162087_150x0.png", 26 | "https://s3.marpple.co/f1/2018/1/1054966_1516076919028_64501_150x0.jpg", 27 | "https://s3.marpple.co/f1/2019/1/1235206_1548918825999_78819_150x0.png", 28 | "https://s3.marpple.co/f1/2019/1/1235206_1548918758054_55883_150x0.png", 29 | ]; 30 | 31 | async function code_4_42() { 32 | async function calcTotalHeight(urls: string[]) { 33 | try { 34 | const totalHeight = await urls 35 | .map(async (url) => { 36 | const img = await loadImage(url); 37 | return img.height; 38 | }) 39 | .reduce( 40 | async (a, b) => await a + await b, 41 | Promise.resolve(0) 42 | ); 43 | return totalHeight; 44 | } catch (e) { 45 | console.error('error: ', e); 46 | } 47 | } 48 | 49 | console.log(await calcTotalHeight(urls)); 50 | // 585 51 | console.log(await calcTotalHeight(urls2)); // Error: load error.. 52 | // undefined 53 | } 54 | 55 | async function code_4_43() { 56 | async function calcTotalHeight2(urls: string[]) { 57 | try { 58 | const totalHeight = await fx(urls) 59 | .toAsync() 60 | .map(loadImage) 61 | .map(img => img.height) 62 | .reduce((a, b) => a + b, 0); 63 | return totalHeight; 64 | } catch (e) { 65 | console.error('error: ', e); 66 | } 67 | } 68 | 69 | console.log(await calcTotalHeight2(urls)); 70 | // 585 71 | console.log(await calcTotalHeight2(urls2)); // Error: load error.. 72 | // undefined 73 | } 74 | 75 | async function code_4_44_45() { 76 | const getTotalHeight = (urls: string[]) => 77 | fx(urls) 78 | .toAsync() 79 | .map(loadImage) 80 | .map(img => img.height) 81 | .reduce((a, b) => a + b, 0); 82 | 83 | try { 84 | const height = await getTotalHeight(urls); 85 | // ... 86 | } catch (e) { 87 | console.error(e); 88 | } 89 | 90 | // or 91 | async function myFunction(urls: string[]) { 92 | try { 93 | return await getTotalHeight(urls); 94 | } catch { 95 | return 0; 96 | } 97 | } 98 | 99 | console.log(await myFunction(urls)); 100 | console.log(await myFunction(urls2)); 101 | } 102 | 103 | export async function main() { 104 | await code_4_42(); 105 | await code_4_43(); 106 | await code_4_44_45(); 107 | } 108 | -------------------------------------------------------------------------------- /apps/example/src/chapters/chapter-5/5-1/Chapter5Section1.ts: -------------------------------------------------------------------------------- 1 | import { ChapterPage } from '../../ChapterPage'; 2 | import { main } from './main'; 3 | 4 | export class Chapter5Section1 extends ChapterPage { 5 | override onRender() { 6 | main(); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /apps/example/src/chapters/chapter-5/5-2/Chapter5Section2.ts: -------------------------------------------------------------------------------- 1 | import { ChapterPage } from '../../ChapterPage'; 2 | import { main } from './main'; 3 | 4 | export class Chapter5Section2 extends ChapterPage { 5 | override async onRender() { 6 | await main(); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /apps/example/src/chapters/chapter-5/5-3/Chapter5Section3.ts: -------------------------------------------------------------------------------- 1 | import { ChapterPage } from '../../ChapterPage'; 2 | import { main } from './main'; 3 | 4 | export class Chapter5Section3 extends ChapterPage { 5 | override async onRender() { 6 | await main(); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /apps/example/src/chapters/chapter-5/5-4/Chapter5Section4.ts: -------------------------------------------------------------------------------- 1 | import { ChapterPage } from '../../ChapterPage'; 2 | import { main } from './main'; 3 | 4 | export class Chapter5Section4 extends ChapterPage { 5 | override async onRender() { 6 | await main(); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /apps/example/src/chapters/chapter-6/6-1/Chapter6Section1.ts: -------------------------------------------------------------------------------- 1 | import { ChapterPage } from '../../ChapterPage'; 2 | import { main } from './main'; 3 | 4 | export class Chapter6Section1 extends ChapterPage { 5 | override onRender() { 6 | main(); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /apps/example/src/chapters/chapter-6/6-2/Chapter6Section2.ts: -------------------------------------------------------------------------------- 1 | import { ChapterPage } from '../../ChapterPage'; 2 | import { main } from './main'; 3 | 4 | export class Chapter6Section2 extends ChapterPage { 5 | override async onRender() { 6 | await main(); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /apps/example/src/chapters/chapter-7/7-1/Chapter7Section1.ts: -------------------------------------------------------------------------------- 1 | import { ChapterPage } from '../../ChapterPage'; 2 | import { main } from './main'; 3 | 4 | export class Chapter7Section1 extends ChapterPage { 5 | override onRender() { 6 | main(); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /apps/example/src/chapters/chapter-7/7-1/main.ts: -------------------------------------------------------------------------------- 1 | import { html, View, CustomEventWithDetail } from "rune-ts"; 2 | 3 | type Toggle = { on: boolean; }; 4 | 5 | class Toggled extends CustomEventWithDetail {} 6 | 7 | class SwitchView extends View { 8 | override template() { 9 | return html` 10 | 11 | 12 | 13 | `; 14 | } 15 | 16 | protected override onRender() { 17 | this.addEventListener('click', () => this.toggle()); 18 | } 19 | 20 | private toggle() { 21 | this.setOn(!this.data.on); 22 | this.dispatchEvent(Toggled, {bubbles: true, detail: this.data}); 23 | } 24 | 25 | setOn(bool: boolean) { 26 | this.data.on = bool; 27 | this.element().classList.toggle('on', bool); 28 | } 29 | } 30 | 31 | type Setting = { 32 | title: string; 33 | on: boolean; 34 | }; 35 | 36 | class SettingItemView extends View { 37 | switchView = new SwitchView(this.data); 38 | 39 | override template() { 40 | return html` 41 | 42 | ${this.data.title} 43 | ${this.switchView} 44 | 45 | `; 46 | } 47 | } 48 | 49 | class SettingListView extends View { 50 | itemViews = this.data.map(setting => new SettingItemView(setting)); 51 | 52 | override template() { 53 | return html` 54 | 55 | ${this.itemViews} 56 | 57 | `; 58 | } 59 | } 60 | 61 | class SettingPage extends View { 62 | private listView = new SettingListView(this.data); 63 | private toggleAllView = new SwitchView({on: this.isAllOn()}); 64 | 65 | override template() { 66 | return html` 67 | 68 | 69 | Setting 70 | ${this.toggleAllView} 71 | 72 | 73 | ${this.listView} 74 | 75 | 76 | `; 77 | } 78 | 79 | protected override onRender() { 80 | this.toggleAllView.addEventListener(Toggled, e => this.toggleAll(e.detail.on)); 81 | this.listView.addEventListener(Toggled, () => this.syncToggleAllView()); 82 | } 83 | 84 | toggleAll(on: boolean) { 85 | this.listView.itemViews 86 | .filter((itemView) => itemView.data.on !== on) 87 | .forEach(itemView => itemView.switchView.setOn(on)); 88 | } 89 | 90 | syncToggleAllView() { 91 | this.toggleAllView.setOn(this.isAllOn()); 92 | } 93 | 94 | isAllOn() { 95 | return this.listView.itemViews.every(itemView => itemView.data.on); 96 | } 97 | } 98 | 99 | export function main() { 100 | const settings: Setting[] = [ 101 | { title: 'Wi-Fi', on: false }, 102 | { title: 'Bluetooth', on: true }, 103 | { title: 'Sound', on: false }, 104 | ]; 105 | 106 | document.querySelector('#body')!.append( 107 | new SettingPage(settings).render() 108 | ); 109 | } 110 | -------------------------------------------------------------------------------- /apps/example/src/chapters/chapter-7/7-2_3_4/Chapter7Section234.ts: -------------------------------------------------------------------------------- 1 | import { ChapterPage } from '../../ChapterPage'; 2 | import { main as todo } from './todo'; 3 | import { main as setting } from './setting'; 4 | 5 | export class Chapter7Section234 extends ChapterPage { 6 | override onRender() { 7 | todo(); 8 | setting(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /apps/example/src/chapters/chapter-7/7-2_3_4/lib/CheckView.ts: -------------------------------------------------------------------------------- 1 | import { ToggleView } from './ToggleView'; 2 | import { html } from 'rune-ts'; 3 | 4 | export class CheckView extends ToggleView { 5 | override template() { 6 | return html` 7 | 8 | `; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /apps/example/src/chapters/chapter-7/7-2_3_4/lib/ListView.ts: -------------------------------------------------------------------------------- 1 | import { html, View } from 'rune-ts'; 2 | 3 | export abstract class ListView> extends View { 4 | abstract ItemView: new (item: IV['data']) => IV; 5 | 6 | private _itemViews: IV[] | null = null; 7 | 8 | get itemViews() { 9 | if (this._itemViews === null) { 10 | this._itemViews = this.data.map(item => new this.ItemView(item)); 11 | } 12 | return this._itemViews; 13 | } 14 | 15 | override template() { 16 | return html` 17 | 18 | ${this.itemViews} 19 | 20 | `; 21 | } 22 | 23 | append(item: IV['data']): this { 24 | const itemView = new this.ItemView(item); 25 | this.data.push(itemView.data); 26 | this.itemViews.push(itemView); 27 | this.element().append(itemView.render()); 28 | console.log(this.data.length); 29 | return this; 30 | } 31 | 32 | set(items: IV['data'][]): this { 33 | let i = 0, j = 0; 34 | 35 | const oldItemsMap = new Map( 36 | this.data.map(item => [item, true]) 37 | ); 38 | 39 | while (i < this.data.length && j < items.length) { 40 | const oldItem = this.data[i]; 41 | const newItem = items[j]; 42 | 43 | if (oldItem === newItem) { 44 | i++; 45 | j++; 46 | continue; 47 | } 48 | 49 | if (oldItemsMap.has(newItem)) { 50 | this.itemViews[i].element().remove(); 51 | this.itemViews.splice(i, 1); 52 | this.data.splice(i, 1); 53 | } else { 54 | const oldItemView = this.itemViews[i]; 55 | const newItemView = new this.ItemView(newItem); 56 | 57 | oldItemView.element().before(newItemView.render()); 58 | 59 | this.itemViews.splice(i, 0, newItemView); 60 | this.data.splice(i, 0, newItem); 61 | 62 | i++; 63 | j++; 64 | } 65 | } 66 | 67 | while (i < this.data.length) { 68 | const oldItemView = this.itemViews[i]; 69 | oldItemView.element().remove(); 70 | this.itemViews.splice(i, 1); 71 | this.data.splice(i, 1); 72 | } 73 | 74 | while (j < items.length) { 75 | const newItem = items[j]; 76 | const newItemView = new this.ItemView(newItem); 77 | this.itemViews.push(newItemView); 78 | this.element().append(newItemView.render()); 79 | this.data.push(newItem); 80 | j++; 81 | } 82 | 83 | return this; 84 | } 85 | } -------------------------------------------------------------------------------- /apps/example/src/chapters/chapter-7/7-2_3_4/lib/SegmentControlView.ts: -------------------------------------------------------------------------------- 1 | import { CustomEventWithDetail, html, View } from 'rune-ts'; 2 | import { ListView } from './ListView'; 3 | 4 | interface Segment { 5 | title: string; 6 | value?: string; 7 | selected?: boolean; 8 | } 9 | 10 | export class SegmentSelected extends CustomEventWithDetail {} 11 | 12 | class SegmentItemView extends View { 13 | override template({ selected, title }: Segment) { 14 | return html` 15 | ${title} 16 | `; 17 | } 18 | } 19 | 20 | export class SegmentControlView extends ListView> { 21 | ItemView = SegmentItemView; 22 | 23 | selectedIndex: number; 24 | 25 | constructor(data: T[], selectedIndex?: number) { 26 | super(data); 27 | this.selectedIndex = selectedIndex ?? Math.max(0, this.data.findIndex((segment) => segment.selected)); 28 | this.data[this.selectedIndex].selected = true; 29 | } 30 | 31 | override onRender() { 32 | this.delegate('click', SegmentItemView, (e, itemView) => { 33 | if (itemView.data === this.selectedSegment()) return; 34 | itemView.element().classList.add('selected'); 35 | itemView.data.selected = true; 36 | this.selectedSegmentView().element().classList.remove('selected'); 37 | this.selectedSegmentView().data.selected = false; 38 | this.selectedIndex = this.itemViews.indexOf(itemView); 39 | this.dispatchEvent(SegmentSelected, { detail: this.selectedSegment(), bubbles: true }); 40 | }); 41 | } 42 | 43 | private selectedSegmentView() { 44 | return this.itemViews[this.selectedIndex]; 45 | } 46 | 47 | selectedSegment() { 48 | return this.data[this.selectedIndex]; 49 | } 50 | } -------------------------------------------------------------------------------- /apps/example/src/chapters/chapter-7/7-2_3_4/lib/SwitchView.ts: -------------------------------------------------------------------------------- 1 | import { ToggleView } from './ToggleView'; 2 | import { html } from 'rune-ts'; 3 | 4 | export class SwitchView extends ToggleView { 5 | override template() { 6 | return html` 7 | 8 | 9 | 10 | `; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /apps/example/src/chapters/chapter-7/7-2_3_4/lib/TextSubmitView.ts: -------------------------------------------------------------------------------- 1 | import { CustomEventWithDetail, html, on, View } from 'rune-ts'; 2 | 3 | export class TextSubmitted extends CustomEventWithDetail {} 4 | 5 | export class TextSubmitView extends View<{ value?: string }> { 6 | override template() { 7 | return html``; 8 | } 9 | @on('keypress') 10 | private keypress(e: KeyboardEvent) { 11 | if (e.code === 'Enter') { 12 | const input = e.target as HTMLInputElement; 13 | const detail = input.value.trim(); 14 | if (detail) { 15 | this.dispatchEvent(TextSubmitted, { detail, bubbles: true }); 16 | input.value = ''; 17 | } 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /apps/example/src/chapters/chapter-7/7-2_3_4/lib/ToggleListController.ts: -------------------------------------------------------------------------------- 1 | import type { ToggleView } from './ToggleView'; 2 | import type { ListView } from './ListView'; 3 | import { Toggled } from './ToggleView'; 4 | 5 | type ExtractItemView = T extends ListView ? IV : never; 6 | 7 | export class ToggleListController< 8 | TV extends ToggleView, 9 | LV extends ListView> 10 | > { 11 | constructor( 12 | public toggleAllView: TV, 13 | public listView: LV, 14 | private getItemViewOn: (itemView: ExtractItemView) => boolean, 15 | private setItemViewOn: (itemView: ExtractItemView, bool: boolean) => void, 16 | ) { 17 | this.toggleAllView.data.on = this.isAllOn(); 18 | this.toggleAllView.addEventListener(Toggled, (e) => this.toggleAll(e.detail.on)); 19 | this.listView.addEventListener(Toggled, () => this.syncToggleAllView()); 20 | } 21 | 22 | toggleAll(bool: boolean) { 23 | this.listView.itemViews 24 | .filter((itemView) => this.getItemViewOn(itemView) !== bool) 25 | .forEach((itemView) => this.setItemViewOn(itemView, bool)); 26 | } 27 | 28 | syncToggleAllView() { 29 | this.toggleAllView.setOn(this.isAllOn()); 30 | } 31 | 32 | isAllOn() { 33 | return this.listView.itemViews.every(this.getItemViewOn); 34 | } 35 | } -------------------------------------------------------------------------------- /apps/example/src/chapters/chapter-7/7-2_3_4/lib/TogglePageController.ts: -------------------------------------------------------------------------------- 1 | import { Toggled, ToggleView } from './ToggleView'; 2 | import type { ListView } from './ListView'; 3 | 4 | type ExtractItemView = T extends ListView ? IV : never; 5 | 6 | export interface TogglePage>> { 7 | toggleAllView: ToggleView; 8 | listView: LV; 9 | getItemViewOn(itemView: ExtractItemView): boolean; 10 | setItemViewOn(itemView: ExtractItemView, bool: boolean): void; 11 | } 12 | 13 | export class TogglePageController>> { 14 | constructor(private togglePage: TogglePage) { 15 | const toggleView: ToggleView = this.togglePage.toggleAllView; 16 | const listView: LV = this.togglePage.listView; 17 | toggleView.data.on = this.isAllOn(); 18 | toggleView.addEventListener(Toggled, e => this.toggleAll(e.detail.on)); 19 | listView.addEventListener(Toggled, () => this.syncToggleAllView()); 20 | } 21 | 22 | private toggleAll(on: boolean) { 23 | const { listView, getItemViewOn, setItemViewOn } = this.togglePage; 24 | listView.itemViews 25 | .filter((itemView) => getItemViewOn(itemView) !== on) 26 | .forEach((itemView) => setItemViewOn(itemView, on)); 27 | } 28 | 29 | private syncToggleAllView() { 30 | this.togglePage.toggleAllView.setOn(this.isAllOn()); 31 | } 32 | 33 | private isAllOn() { 34 | return this.togglePage.listView.itemViews.every( 35 | this.togglePage.getItemViewOn 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /apps/example/src/chapters/chapter-7/7-2_3_4/lib/ToggleView.ts: -------------------------------------------------------------------------------- 1 | import { View, CustomEventWithDetail } from "rune-ts"; 2 | 3 | export type Toggle = { on: boolean; }; 4 | 5 | export class Toggled extends CustomEventWithDetail {} 6 | 7 | export abstract class ToggleView extends View { 8 | constructor(data?: Toggle) { 9 | super(data ?? { on: false }); 10 | } 11 | 12 | protected override onRender() { 13 | this.addEventListener('click', () => this.toggle()); 14 | } 15 | 16 | private toggle() { 17 | this.setOn(!this.data.on); 18 | this.dispatchEvent(Toggled, { bubbles: true, detail: this.data }); 19 | } 20 | 21 | setOn(bool: boolean) { 22 | this.data.on = bool; 23 | this.element().classList.toggle('on', bool); 24 | } 25 | } -------------------------------------------------------------------------------- /apps/example/src/chapters/chapter-7/7-2_3_4/lib/decorators.ts: -------------------------------------------------------------------------------- 1 | import type {View} from "rune-ts"; 2 | 3 | // @on('click') 용 시그니처 4 | function on( 5 | eventType: K 6 | ): void>( 7 | target: View, 8 | propertyKey: string, 9 | descriptor: TypedPropertyDescriptor 10 | ) => void; 11 | 12 | // @on(Toggled) 용 시그니처 13 | function on Event>( 14 | EventClass: E 15 | ): ) => void>( // InstanceType로 E의 인스턴스 타입 추출 16 | view: View, 17 | propertyKey: string, 18 | descriptor: TypedPropertyDescriptor 19 | ) => void; 20 | 21 | function on(eventType: any) { 22 | return function void>( 23 | viewPrototype: any, 24 | propertyKey: string, 25 | descriptor: TypedPropertyDescriptor // T는 데코레이터를 붙인 메서드의 타입 26 | ) { 27 | const method: T = descriptor.value!; 28 | const onRender: () => void = viewPrototype.onRender; 29 | viewPrototype.onRender = function() { 30 | this.addEventListener(eventType, method); 31 | onRender.call(this); 32 | } 33 | }; 34 | } 35 | 36 | export { on }; -------------------------------------------------------------------------------- /apps/example/src/chapters/chapter-7/7-2_3_4/setting.ts: -------------------------------------------------------------------------------- 1 | import { html, View } from 'rune-ts'; 2 | import { ToggleListController } from './lib/ToggleListController'; 3 | import {SwitchView} from "./lib/SwitchView"; 4 | import {ListView} from "./lib/ListView"; 5 | 6 | interface Setting { 7 | title: string; 8 | on: boolean; 9 | } 10 | 11 | class SettingItemView extends View { 12 | switchView = new SwitchView(this.data); 13 | 14 | override template() { 15 | return html` 16 | 17 | ${this.data.title} 18 | ${this.switchView} 19 | 20 | `; 21 | } 22 | } 23 | 24 | class SettingListView extends ListView { 25 | ItemView = SettingItemView; 26 | } 27 | 28 | class SettingPage extends View { 29 | private toggleAllView = new SwitchView(); 30 | private listView = new SettingListView(this.data); 31 | 32 | private toggleListController = new ToggleListController( 33 | this.toggleAllView, 34 | this.listView, 35 | (itemView) => itemView.data.on, // getItemViewOn 36 | (itemView, bool) => itemView.switchView.setOn(bool) // setItemViewOn 37 | ); 38 | 39 | override template() { 40 | return html` 41 | 42 | 43 | Setting 44 | ${this.toggleListController.toggleAllView} 45 | 46 | 47 | ${this.toggleListController.listView} 48 | 49 | 50 | `; 51 | } 52 | } 53 | 54 | export function main() { 55 | const settings: Setting[] = [ 56 | {title: 'Wifi', on: true}, 57 | {title: 'Bluetooth', on: false}, 58 | {title: 'AirDrop', on: true}, 59 | ]; 60 | 61 | document.querySelector('#body')!.append( 62 | new SettingPage(settings).render() 63 | ); 64 | } -------------------------------------------------------------------------------- /apps/example/src/chapters/chapter-7/7-2_3_4/todo.ts: -------------------------------------------------------------------------------- 1 | import { html, View } from 'rune-ts'; 2 | import { CheckView } from './lib/CheckView'; 3 | import { Toggled } from './lib/ToggleView'; 4 | import { ToggleListController } from './lib/ToggleListController'; 5 | import { on } from './lib/decorators'; 6 | import { SegmentControlView, SegmentSelected } from './lib/SegmentControlView'; 7 | import { TextSubmitted, TextSubmitView } from './lib/TextSubmitView'; 8 | import {ListView} from "./lib/ListView"; 9 | 10 | type Todo = { 11 | title: string; 12 | completed: boolean; 13 | }; 14 | 15 | class TodoItemView extends View { 16 | private checkView = new CheckView({ on: this.data.completed }); 17 | 18 | override template() { 19 | return html` 20 | 21 | ${this.checkView} 22 | ${this.data.title} 23 | 24 | `; 25 | } 26 | 27 | protected override onRender() { 28 | this.addEventListener(Toggled, () => this.syncCompleted()); 29 | } 30 | 31 | private syncCompleted() { 32 | this.data.completed = this.checkView.data.on; 33 | } 34 | 35 | setCompleted(bool: boolean) { 36 | this.checkView.setOn(bool); 37 | this.syncCompleted(); 38 | } 39 | } 40 | 41 | export class TodoListView extends ListView { 42 | ItemView = TodoItemView; 43 | } 44 | 45 | interface FilterState { 46 | title: string; 47 | predicate: (todo: Todo) => boolean; 48 | } 49 | 50 | class TodoPage extends View { 51 | private listView = new TodoListView([...this.data]); 52 | 53 | private toggleListController = new ToggleListController( 54 | new CheckView(), 55 | this.listView, 56 | (itemView) => itemView.data.completed, 57 | (itemView, bool) => itemView.setCompleted(bool) 58 | ); 59 | 60 | private filterView = new SegmentControlView( 61 | [ 62 | { title: 'All', predicate: () => true }, 63 | { title: 'Active', predicate: todo => !todo.completed }, 64 | { title: 'Completed', predicate: todo => todo.completed } 65 | ] as FilterState[] 66 | ); 67 | 68 | addFilterState(filterState: FilterState) { 69 | this.filterView.append(filterState); 70 | } 71 | 72 | private get filterState() { 73 | return this.filterView.selectedSegment(); 74 | } 75 | 76 | override template() { 77 | return html` 78 | 79 | 80 | ${this.toggleListController.toggleAllView} 81 | ${new TextSubmitView({})} 82 | 83 | 84 | ${this.listView} 85 | ${this.filterView} 86 | 87 | 88 | `; 89 | } 90 | 91 | @on(TextSubmitted) 92 | private append({ detail: title }: TextSubmitted) { 93 | const todo: Todo = { title, completed: false }; 94 | this.data.push(todo); 95 | if (this.filterState.predicate(todo)) { 96 | this.listView.append(todo); 97 | this.toggleListController.syncToggleAllView(); 98 | } 99 | } 100 | 101 | @on(Toggled) 102 | @on(SegmentSelected) 103 | private refresh() { 104 | const todos = this.data.filter(this.filterState.predicate); 105 | this.listView.set(todos); 106 | this.toggleListController.syncToggleAllView(); 107 | } 108 | } 109 | 110 | export function main() { 111 | const todos: Todo[] = [ 112 | { title: 'Coding', completed: false }, 113 | { title: 'Dinner', completed: true }, 114 | { title: 'Test', completed: false }, 115 | ]; 116 | 117 | const todoPage = new TodoPage(todos); 118 | // window.todoPage = new TodoPage(todos); 119 | 120 | document.querySelector('#body')!.append( 121 | todoPage.render() 122 | ); 123 | } -------------------------------------------------------------------------------- /apps/example/src/chapters/chapter-7/7-5/Chapter7Section5.ts: -------------------------------------------------------------------------------- 1 | import { ChapterPage } from '../../ChapterPage'; 2 | import { main } from './alert'; 3 | 4 | export class Chapter7Section5 extends ChapterPage { 5 | override async onRender() { 6 | await main(); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /apps/example/src/chapters/chapter-7/7-5/Chapter7Section52.ts: -------------------------------------------------------------------------------- 1 | import { ChapterPage } from '../../ChapterPage'; 2 | import { main } from './chat' 3 | 4 | export class Chapter7Section52 extends ChapterPage { 5 | override onRender() { 6 | main(); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /apps/example/src/chapters/chapter-7/7-5/alert.ts: -------------------------------------------------------------------------------- 1 | import { html, View, on } from 'rune-ts'; 2 | 3 | export class AlertView extends View<{ message: string }> { 4 | private resolve!: () => void; // (1) 5 | readonly promise = new Promise(res => this.resolve = res); // (2) 6 | 7 | override template() { 8 | return html` 9 | 10 | ${this.data.message} 11 | OK 12 | 13 | `; 14 | } 15 | 16 | @on('click', 'button') // (3) 17 | private close() { 18 | this.element().remove(); 19 | this.resolve(); 20 | } 21 | 22 | static open(message: string) { // (4) 23 | const view = new AlertView({ message }); 24 | document.body.append(view.render()); 25 | return view.promise; 26 | } 27 | } 28 | 29 | export class ConfirmView extends View<{ message: string }> { 30 | private resolve!: (bool: boolean) => void; // (1) 31 | readonly promise = new Promise(res => this.resolve = res); // (2) 32 | 33 | override template() { 34 | return html` 35 | 36 | ${this.data.message} 37 | Cancel 38 | Confirm 39 | 40 | `; 41 | } 42 | 43 | @on('click', 'button') 44 | private close(e: MouseEvent) { 45 | const button = e.currentTarget as HTMLButtonElement; 46 | this.element().remove(); 47 | this.resolve(button.matches('.confirm')); 48 | } 49 | 50 | static open(message: string) { 51 | const view = new ConfirmView({ message }); 52 | document.body.append(view.render()); 53 | return view.promise; 54 | } 55 | } 56 | 57 | export async function main() { 58 | alert('Completed.'); 59 | console.log('alert'); 60 | 61 | await AlertView.open('Completed.'); 62 | console.log('AlertView'); 63 | 64 | if (confirm('Do you want to delete it?')) { 65 | console.log('Delete'); 66 | } else { 67 | console.log('Cancel'); 68 | } 69 | 70 | if (await ConfirmView.open('Completed.')) { 71 | console.log('Delete'); 72 | } else { 73 | console.log('Cancel'); 74 | } 75 | } -------------------------------------------------------------------------------- /apps/example/src/chapters/chapter-7/7-5/chat.ts: -------------------------------------------------------------------------------- 1 | import { html, on, View } from 'rune-ts'; 2 | import { CheckView } from '../7-2_3_4/lib/CheckView'; 3 | import { ListView } from '../7-2_3_4/lib/ListView'; 4 | import { ToggleListController } from '../7-2_3_4/lib/ToggleListController'; 5 | import { AlertView, ConfirmView } from './alert'; 6 | 7 | type User = { 8 | id: number; 9 | name: string; 10 | } 11 | 12 | type Chat = { 13 | users: User[] 14 | }; 15 | 16 | class UserItemView extends View { 17 | override template() { 18 | return html` 19 | ${this.data.name} 20 | `; 21 | } 22 | } 23 | 24 | class UserListView extends ListView { 25 | ItemView = UserItemView; 26 | } 27 | 28 | class CheckUserItemView extends View { 29 | checkView = new CheckView(); 30 | 31 | override template() { 32 | return html` 33 | 34 | ${this.checkView} 35 | ${new UserItemView(this.data)} 36 | 37 | `; 38 | } 39 | } 40 | 41 | class CheckUserListView extends ListView { 42 | ItemView = CheckUserItemView; 43 | } 44 | 45 | class UserPickerView extends View { 46 | private resolve!: (users: User[]) => void; 47 | readonly promise = new Promise(res => this.resolve = res); 48 | 49 | private toggleListController = new ToggleListController( 50 | new CheckView(), 51 | new CheckUserListView(this.data), 52 | (itemView) => itemView.checkView.data.on, 53 | (itemView, bool) => itemView.checkView.setOn(bool) 54 | ); 55 | 56 | override template() { 57 | return html` 58 | 59 | 60 | ${this.toggleListController.toggleAllView} 61 | Select friends 62 | Confirm 63 | 64 | 65 | ${this.toggleListController.listView} 66 | 67 | 68 | `; 69 | } 70 | 71 | @on('click', 'button.done') 72 | private done() { 73 | this.element().remove(); 74 | this.resolve( 75 | this.toggleListController 76 | .listView 77 | .itemViews 78 | .filter(({checkView}) => checkView.data.on) 79 | .map(({data}) => data) 80 | ); 81 | } 82 | 83 | static open() { 84 | const users: User[] = [ 85 | {id: 1, name: 'Luka'}, 86 | {id: 2, name: 'Stephen'}, 87 | {id: 3, name: 'Nikola'}, 88 | {id: 4, name: 'Kevin'}, 89 | ]; 90 | const view = new UserPickerView(users); 91 | document.body.append(view.render()); 92 | return view.promise; 93 | } 94 | } 95 | 96 | class ChatCreationView extends View { 97 | private userListView = new UserListView(this.data.users); 98 | 99 | override template() { 100 | return html` 101 | 102 | Select a friend to chat 103 | ${this.userListView} 104 | Please select your friends. 105 | 106 | `; 107 | } 108 | 109 | @on('click', 'button.pick') 110 | private async pickUsers() { 111 | const users = await UserPickerView.open(); 112 | this.userListView.set(users); 113 | } 114 | 115 | @on('click', 'button.create') 116 | private async create() { 117 | if (this.isEmpty()) { 118 | await AlertView.open('Please select your friends.'); 119 | } else { 120 | if (await ConfirmView.open(this.startMessage)) { 121 | alert('Enjoy your chat!'); 122 | // new ChatView(this.data)... 123 | } else { 124 | this.userListView.set([]); 125 | } 126 | } 127 | } 128 | 129 | isEmpty() { 130 | return this.data.users.length === 0; 131 | } 132 | 133 | get startMessage() { 134 | const names = this.data.users.map(({name}) => name); 135 | return `Do you want to start a chat with ${names.join(', ')}?`; 136 | } 137 | } 138 | 139 | export function main() { 140 | document.body.append( 141 | new ChatCreationView({users: []}).render() 142 | ); 143 | } -------------------------------------------------------------------------------- /apps/lecture/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lecture", 3 | "version": "1.0.0", 4 | "description": "", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "pnpm rune dev", 8 | "build": "pnpm rune build", 9 | "start": "pnpm rune start" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "dependencies": { 15 | "@fxts/core": "^1.9.0", 16 | "@rune-ts/server": "^1.0.24", 17 | "rune-ts": "^0.8.12", 18 | "ua-parser-js": "^1.0.37" 19 | }, 20 | "devDependencies": { 21 | "@types/animejs": "^3.1.12", 22 | "@types/express": "^4.17.21", 23 | "@types/express-serve-static-core": "^4.17.43", 24 | "@types/ua-parser-js": "^0.7.39" 25 | }, 26 | "volta": { 27 | "node": "22.13.0", 28 | "pnpm": "10.0.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /apps/lecture/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marpple/multi-paradigm-programming/70c00e248c93f901ffa06936439cfb38c10cf10c/apps/lecture/public/favicon.png -------------------------------------------------------------------------------- /apps/lecture/rune.config.cjs: -------------------------------------------------------------------------------- 1 | /** 2 | * @type {import('@rune-ts/server').RuneConfigType} 3 | */ 4 | module.exports = { 5 | port: 7000, 6 | hostname: 'localhost', 7 | mode: 'render', 8 | clientEntry: './src/app/client/index.ts', 9 | serverEntry: './src/app/server/index.ts', 10 | watchToReloadPaths: ['../../packages'], 11 | watchToIgnorePaths: ['**/.env.*', '*.json'], 12 | }; 13 | -------------------------------------------------------------------------------- /apps/lecture/src/HomePage.ts: -------------------------------------------------------------------------------- 1 | import { Page, html } from "rune-ts"; 2 | 3 | export class HomePage extends Page { 4 | override template() { 5 | return html` 6 | 7 | Home 8 | Multi-Paradigm Programming 9 | Part 1. Iterator 10 | 11 | Section 2. The Iterator Pattern in OOP and First-Class Functions 12 | Section 3. Generator Functions for Building Iterators in Imperative Programming 13 | Section 4. Iteration Protocol 14 | Section 5. Functional Programming with Iterables 15 | Section 6. Functional Type System in a Multi-Paradigm Language 16 | Section 7. Combining Classes, Higher-Order Functions, Iterables, and the Type System 17 | Section 8. Classes as Lists: Learning from LISP 18 | Section 9. List Processing – Code as Data, Logic in a List 19 | Section 10. Working with Real-World Data 20 | Section 11. Applying to More Problems 21 | 22 | 23 | `; 24 | } 25 | 26 | protected override onRender() { 27 | const lis = document.querySelectorAll('li'); 28 | const list = [...lis]; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /apps/lecture/src/SectionPage.ts: -------------------------------------------------------------------------------- 1 | import { html, Page } from 'rune-ts'; 2 | 3 | export class SectionPage extends Page { 4 | title = ''; 5 | link = ''; 6 | lessons: (() => void)[] = []; 7 | 8 | get lessonsMap() { 9 | return new Map(this.lessons.map((f) => [f.name, f])); 10 | } 11 | 12 | override template() { 13 | return html` 14 | 15 | Home 16 | ${this.title} 17 | 18 | ${[...this.lessonsMap.keys()].map( 19 | (name) => html` 20 | 21 | ${name} 22 | 23 | `, 24 | )} 25 | 26 | 27 | `; 28 | } 29 | 30 | override onRender() { 31 | setTimeout(() => { 32 | const name = location.search.replace('?f=', ''); 33 | if (name) { 34 | this.lessonsMap.get(name)!(); 35 | this.element().querySelector(`.${name}`)!.style.background = 'yellow'; 36 | } 37 | }, 100); 38 | } 39 | } 40 | 41 | -------------------------------------------------------------------------------- /apps/lecture/src/app/client/index.ts: -------------------------------------------------------------------------------- 1 | import '../../../../../packages/styles/global.scss'; 2 | import { hydrate } from '@rune-ts/server'; 3 | import { ClientRouter } from '../route'; 4 | 5 | hydrate(ClientRouter); 6 | -------------------------------------------------------------------------------- /apps/lecture/src/app/route/index.ts: -------------------------------------------------------------------------------- 1 | import { createRouter } from '@rune-ts/server'; 2 | import { HomePage } from '../../HomePage'; 3 | import { Section2 as Part1Section2 } from '../../part-1/section-2/Section2'; 4 | import { Section3 as Part1Section3 } from '../../part-1/section-3/Section3'; 5 | import { Section4 as Part1Section4 } from '../../part-1/section-4/Section4'; 6 | import { Section5 as Part1Section5 } from '../../part-1/section-5/Section5'; 7 | import { Section6 as Part1Section6 } from '../../part-1/section-6/Section6'; 8 | import { Section7 as Part1Section7 } from '../../part-1/section-7/Section7'; 9 | import { Section8 as Part1Section8 } from '../../part-1/section-8/Section8'; 10 | import { Section9 as Part1Section9 } from '../../part-1/section-9/Section9'; 11 | import { Section10 as Part1Section10 } from '../../part-1/section-10/Section10'; 12 | import { Section11 as Part1Section11 } from '../../part-1/section-11/Section11'; 13 | 14 | export const ClientRouter = createRouter({ 15 | '/': HomePage, 16 | '/part-1/section-2': Part1Section2, 17 | '/part-1/section-3': Part1Section3, 18 | '/part-1/section-4': Part1Section4, 19 | '/part-1/section-5': Part1Section5, 20 | '/part-1/section-6': Part1Section6, 21 | '/part-1/section-7': Part1Section7, 22 | '/part-1/section-8': Part1Section8, 23 | '/part-1/section-9': Part1Section9, 24 | '/part-1/section-10': Part1Section10, 25 | '/part-1/section-11': Part1Section11, 26 | }); 27 | -------------------------------------------------------------------------------- /apps/lecture/src/app/server/index.ts: -------------------------------------------------------------------------------- 1 | import { app, MetaView, type LayoutData } from '@rune-ts/server'; 2 | import type { Page } from 'rune-ts'; 3 | import UAParser from 'ua-parser-js'; 4 | import favicon from '../../../public/favicon.png'; 5 | import { ClientRouter } from '../route'; 6 | 7 | const server = app(); 8 | server.use((req, res, next) => { 9 | const ua_string = req.headers['user-agent']; 10 | const parser = new UAParser(ua_string); 11 | res.locals.is_mobile = !!parser.getDevice().type; 12 | 13 | res.locals.layoutData = { 14 | html: { 15 | is_mobile: res.locals.is_mobile, 16 | }, 17 | head: { 18 | title: '', 19 | description: '', 20 | link_tags: [ 21 | { 22 | rel: 'icon', 23 | href: favicon, 24 | type: 'image/png', 25 | }, 26 | ], 27 | }, 28 | } as LayoutData; 29 | 30 | return next(); 31 | }); 32 | 33 | const addRenderHandler = (createPage: (data: object) => Page) => { 34 | server.get(createPage.toString(), (req, res, next) => { 35 | res.send(new MetaView(createPage({}), res.locals.layoutData).toHtml()); 36 | }); 37 | }; 38 | 39 | addRenderHandler(ClientRouter['/']); 40 | addRenderHandler(ClientRouter['/part-1/section-2']); 41 | addRenderHandler(ClientRouter['/part-1/section-3']); 42 | addRenderHandler(ClientRouter['/part-1/section-4']); 43 | addRenderHandler(ClientRouter['/part-1/section-5']); 44 | addRenderHandler(ClientRouter['/part-1/section-6']); 45 | addRenderHandler(ClientRouter['/part-1/section-7']); 46 | addRenderHandler(ClientRouter['/part-1/section-8']); 47 | addRenderHandler(ClientRouter['/part-1/section-9']); 48 | addRenderHandler(ClientRouter['/part-1/section-10']); 49 | addRenderHandler(ClientRouter['/part-1/section-11']); -------------------------------------------------------------------------------- /apps/lecture/src/lib/fx3.ts: -------------------------------------------------------------------------------- 1 | /* 2 | import { filter, forEach, map, range, reduce, take } from '@fxts/core'; 3 | 4 | function isIterable(a: Iterable | unknown): a is Iterable { 5 | return typeof a?.[Symbol.iterator] === "function"; 6 | } 7 | 8 | function isIterator(a: any): a is Iterator { 9 | return a != null && typeof a.next === 'function'; 10 | } 11 | 12 | function* genIterator(seed: A, f: (seed: A) => A): IterableIterator { 13 | while (true) { 14 | yield seed; 15 | seed = f(seed); 16 | } 17 | } 18 | 19 | function fx(iterator: Iterator): FxIterator; 20 | function fx(seed: A, f: (seed: A) => A): FxIterable; 21 | function fx(start: number): FxIterable; 22 | function fx(f: () => IterableIterator): FxIterable; 23 | function fx(iterable: Iterable): FxIterable; 24 | function fx( 25 | iterable: number | Iterable | (() => IterableIterator) | Iterator, 26 | f?: (seed: A) => IterableIterator, 27 | ): FxIterable | FxIterable | FxIterator { 28 | if (isIterator(iterable)) { 29 | return new FxIterator(iterable); 30 | } else if (f !== undefined) { 31 | return fx(() => genIterator(iterable as A, f as (seed: A) => A)); 32 | } if (typeof iterable === 'number') { 33 | return fx(() => range(iterable, Infinity)); 34 | } else if (typeof iterable === 'function') { 35 | return new FxIterable({ [Symbol.iterator]: iterable }); 36 | } else { 37 | return new FxIterable(iterable); 38 | } 39 | } 40 | 41 | abstract class FxBase { 42 | constructor(protected iterable: Iterable) {} 43 | 44 | [Symbol.iterator]() { 45 | return this.iterable[Symbol.iterator](); 46 | } 47 | 48 | map(f: (a: A) => B) { 49 | return fx(() => map(f, this)); 50 | } 51 | 52 | filter(f: (a: A) => boolean) { 53 | return fx(() => filter(f, this)); 54 | } 55 | 56 | take(limit: number) { 57 | return fx(() => take(limit, this)); 58 | } 59 | 60 | forEach(f: (a: A) => void): void { 61 | return forEach(f, this); 62 | } 63 | 64 | reduce(f: (acc: Acc, a: A) => Acc, acc: Acc): Acc; 65 | reduce(f: (a: A, b: A) => Acc): Acc; 66 | reduce(f: (a: Acc | A, b: A) => Acc, acc?: Acc): Acc { 67 | return acc === undefined 68 | ? reduce(f, this) 69 | : reduce(f, acc, this); 70 | } 71 | 72 | toArray(): A[] { 73 | return [...this]; 74 | } 75 | } 76 | 77 | class FxIterable extends FxBase {} 78 | 79 | class FxIterator extends FxBase { 80 | constructor(protected iterator: Iterator) { 81 | super({ 82 | [Symbol.iterator]() { 83 | return iterator; 84 | } 85 | }); 86 | } 87 | 88 | next() { 89 | return this.iterator.next(); 90 | } 91 | } 92 | 93 | 94 | // 1. Iterable Examples 95 | const iterable = fx(1); 96 | console.log(iterable.take(3).toArray()); // [1, 2, 3] 97 | console.log([...iterable.take(3)]); // [1, 2, 3] 98 | 99 | const iterable2 = iterable.map(a => a * 10); 100 | console.log(iterable2.take(2).toArray()); // [10, 20] 101 | 102 | const iterable3 = iterable2.map(a => a / 10); 103 | console.log([...iterable3.take(3)]); // [1, 2, 3] 104 | 105 | const iterable4 = iterable3.filter(a => a % 2 === 1); 106 | console.log(iterable4.take(3).toArray()); // [1, 3, 5] 107 | 108 | console.log(iterable4.take(2).reduce((a, b) => a + b)); 109 | // 4 110 | 111 | iterable4.take(2).forEach(console.log); 112 | // 1 113 | // 3 114 | 115 | const iterable5 = fx(10, n => n - 1).take(5); 116 | console.log(iterable5.toArray()); // [10, 9, 8, 7, 6] 117 | console.log(iterable5.take(3).toArray()); // [10, 9, 8] 118 | 119 | console.log([...fx([1, 2, 3])]); // [1, 2, 3] 120 | 121 | function* numbers() { 122 | yield 1; 123 | yield 2; 124 | yield 3; 125 | } 126 | 127 | const iterable6 = fx(numbers); 128 | console.log(iterable6.take(2).toArray()); // [1, 2] 129 | console.log(iterable6.take(2).toArray()); // [1, 2] 130 | 131 | // 2. Iterator Examples 132 | const iterable7 = fx(numbers()); 133 | console.log(iterable7.take(2).toArray()); // [1, 2] 134 | console.log(iterable7.take(2).toArray()); // [3] 135 | 136 | console.log(iterable7 instanceof FxIterable, iterable7 instanceof FxIterator); 137 | // false, true 138 | 139 | export { fx }; 140 | 141 | */ 142 | -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-10/Section10.ts: -------------------------------------------------------------------------------- 1 | import { SectionPage } from '../../SectionPage'; 2 | import { html } from 'rune-ts'; 3 | import { lesson1 } from './lesson-1'; 4 | import { lesson2 } from './lesson-2'; 5 | import { lesson3 } from './lesson-3'; 6 | 7 | const fs = [lesson1, lesson2, lesson3]; 8 | 9 | export class Section10 extends SectionPage { 10 | override title = 'Section10'; 11 | override link = '/part-1/section-10'; 12 | override lessons = fs; 13 | } 14 | -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-10/lesson-1.ts: -------------------------------------------------------------------------------- 1 | export function lesson1() { 2 | // Section 10. Working with Real-World Data 3 | // 10.1 Handling numeric data in 2D arrays 4 | 5 | const numbers = [ 6 | [1, 2], 7 | [3, 4, 5], 8 | [6, 7, 8], 9 | [9, 10] 10 | ]; 11 | 12 | const oddSquareSum = numbers 13 | .flat() 14 | .filter(a => a % 2 === 1) 15 | .map(a => a * a) 16 | .reduce((a, b) => a + b, 0); 17 | 18 | console.log(oddSquareSum); // 165 19 | } 20 | -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-10/lesson-2.ts: -------------------------------------------------------------------------------- 1 | export function lesson2() { 2 | // 10.2 Basketball league statistics 3 | 4 | type Player = { 5 | name: string; 6 | pointsPerGame: number; 7 | starter: boolean; 8 | } 9 | 10 | type Team = { 11 | name: string; 12 | players: Player[]; 13 | } 14 | 15 | const teams: Team[] = [ 16 | { 17 | name: "Bears", 18 | players: [ 19 | { name: "Luka", pointsPerGame: 32, starter: true }, 20 | { name: "Anthony", pointsPerGame: 28, starter: true }, 21 | { name: "Kevin", pointsPerGame: 15, starter: false }, 22 | { name: "Jaylen", pointsPerGame: 14, starter: false }, 23 | { name: "Chris", pointsPerGame: 22, starter: true }, 24 | { name: "Derrick", pointsPerGame: 10, starter: false }, 25 | { name: "Dillon", pointsPerGame: 31, starter: true }, 26 | { name: "John", pointsPerGame: 18, starter: true }, 27 | ], 28 | }, 29 | { 30 | name: "Lions", 31 | players: [ 32 | { name: "Stephen", pointsPerGame: 37, starter: true }, 33 | { name: "Zach", pointsPerGame: 20, starter: true }, 34 | { name: "Nikola", pointsPerGame: 19, starter: false }, 35 | { name: "Austin", pointsPerGame: 22, starter: true }, 36 | { name: "Bruce", pointsPerGame: 13, starter: false }, 37 | { name: "Damian", pointsPerGame: 33, starter: true }, 38 | { name: "Tyrese", pointsPerGame: 29, starter: true }, 39 | { name: "Jamal", pointsPerGame: 11, starter: false }, 40 | ], 41 | }, 42 | { 43 | name: "Wolves", 44 | players: [ 45 | { name: "Jayson", pointsPerGame: 32, starter: true }, 46 | { name: "Klay", pointsPerGame: 37, starter: true }, 47 | { name: "Andrew", pointsPerGame: 15, starter: false }, 48 | { name: "Patrick", pointsPerGame: 14, starter: false }, 49 | { name: "Malik", pointsPerGame: 24, starter: true }, 50 | { name: "Buddy", pointsPerGame: 10, starter: false }, 51 | { name: "Jordan", pointsPerGame: 27, starter: true }, 52 | { name: "Kyle", pointsPerGame: 18, starter: true }, 53 | ], 54 | }, 55 | { 56 | name: "Tigers", 57 | players: [ 58 | { name: "DeMar", pointsPerGame: 37, starter: true }, 59 | { name: "Marcus", pointsPerGame: 21, starter: true }, 60 | { name: "Al", pointsPerGame: 19, starter: false }, 61 | { name: "Dennis", pointsPerGame: 22, starter: true }, 62 | { name: "Miles", pointsPerGame: 14, starter: false }, 63 | { name: "Paul", pointsPerGame: 29, starter: true }, 64 | { name: "Fred", pointsPerGame: 13, starter: false }, 65 | { name: "Terry", pointsPerGame: 25, starter: true }, 66 | ], 67 | }, 68 | ]; 69 | 70 | const totalStartersPPG = teams 71 | .flatMap(team => team.players) 72 | .filter(player => player.starter) 73 | .map(player => player.pointsPerGame) 74 | .reduce((a, b) => a + b, 0); 75 | 76 | console.log(totalStartersPPG) // 544 77 | } 78 | -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-10/lesson-3.ts: -------------------------------------------------------------------------------- 1 | export function lesson3() { 2 | // 10.3 Working with commerce data 3 | 4 | type Product = { 5 | name: string; 6 | price: number; 7 | quantity: number; 8 | selected: boolean; 9 | }; 10 | 11 | const products: Product[] = [ 12 | { 13 | name: 'T-shirt', 14 | price: 10000, 15 | quantity: 1, 16 | selected: true, 17 | }, 18 | { 19 | name: 'Shirt', 20 | price: 30000, 21 | quantity: 2, 22 | selected: false, 23 | }, 24 | { 25 | name: 'Pants', 26 | price: 15000, 27 | quantity: 2, 28 | selected: true, 29 | } 30 | ]; 31 | 32 | function sumSelectedQuantities(products: Product[]) { 33 | return products 34 | .filter(prd => prd.selected) 35 | .map(prd => prd.quantity) 36 | .reduce((a, b) => a + b, 0); 37 | } 38 | 39 | function calcSelectedPrices(products: Product[]) { 40 | return products 41 | .filter(prd => prd.selected) 42 | .map(prd => prd.price * prd.quantity) 43 | .reduce((a, b) => a + b, 0); 44 | } 45 | 46 | console.log(sumSelectedQuantities(products)); 47 | console.log(calcSelectedPrices(products)); 48 | } 49 | -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-11/Section11.ts: -------------------------------------------------------------------------------- 1 | import { SectionPage } from '../../SectionPage'; 2 | import { lesson1 } from './lesson-1'; 3 | import { lesson2 } from './lesson-2'; 4 | import { lesson1_2 } from './lesson-1-2'; 5 | import { lesson1_3 } from './lesson-1-3'; 6 | import { lesson1_4 } from './lesson-1-4'; 7 | 8 | export class Section11 extends SectionPage { 9 | override title = 'Section11'; 10 | override link = '/part-1/section-11'; 11 | override lessons = [lesson1, lesson1_2, lesson1_3, lesson1_4, lesson2]; 12 | } 13 | -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-11/lesson-1-2.ts: -------------------------------------------------------------------------------- 1 | export function lesson1_2() { 2 | // 11.1 `zip` and Index Increment 3 | 4 | const entries = [ 5 | ['name', 'Marty'], 6 | ['job', 'Programmer'], 7 | ['location', 'New York'] 8 | ]; 9 | 10 | const obj = {}; 11 | for (const [key, value] of entries) { 12 | obj[key] = value; 13 | } 14 | console.log(obj); 15 | // { name: 'Marty', job: 'Programmer', location: 'New York' } 16 | 17 | const obj2 = Object.fromEntries(entries); 18 | console.log(obj2); 19 | // { name: 'Marty', job: 'Programmer', location: 'New York' } 20 | } 21 | -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-11/lesson-1-3.ts: -------------------------------------------------------------------------------- 1 | import { take, zip } from '@fxts/core'; 2 | 3 | export function lesson1_3() { 4 | // 11.1 `zip` and Index Increment 5 | 6 | const keys = ['name', 'job', 'location']; 7 | const values = ['Marty', 'Programmer', 'New York']; 8 | const entries = [...zip(keys, values)]; 9 | console.log(entries); 10 | 11 | const iterator = zip(keys, values); 12 | console.log(iterator.next().value); // ['name', 'Marty'] 13 | console.log(iterator.next().value); // ['job', 'Programmer'] 14 | console.log(iterator.next().value); // ['location', 'New York'] 15 | console.log(iterator.next().value); // undefined 16 | 17 | const obj = Object.fromEntries( 18 | take(3, zip(keys, values)) 19 | ); 20 | console.log(obj); 21 | // { name: 'Marty', job: 'Programmer', location: 'New York' } 22 | } 23 | -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-11/lesson-1-4.ts: -------------------------------------------------------------------------------- 1 | import { range, zip } from '@fxts/core'; 2 | 3 | export function lesson1_4() { 4 | // 11.1 `zip` and Index Increment 5 | 6 | const strings = ['a', 'b', 'c', 'd']; 7 | const iter = zip(range(Infinity), strings); 8 | console.log(iter.next().value); // [0, 'a'] 9 | console.log(iter.next().value); // [1, 'b'] 10 | console.log(iter.next().value); // [2, 'c'] 11 | console.log(iter.next().value); // [3, 'd'] 12 | console.log(iter.next().value); // undefined 13 | } -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-11/lesson-1.ts: -------------------------------------------------------------------------------- 1 | export function lesson1() { 2 | // 11.1 `zip` and Index Increment 3 | 4 | const keys = ['name', 'job', 'location']; 5 | const values = ['Marty', 'Programmer', 'New York']; 6 | 7 | const obj = {}; 8 | for (let i = 0; i < keys.length; i++) { 9 | obj[keys[i]] = values[i]; 10 | } 11 | 12 | console.log(obj); 13 | // { name: 'Marty', job: 'Programmer', location: 'New York' } 14 | } -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-11/lesson-2.ts: -------------------------------------------------------------------------------- 1 | import { fx, range } from '@fxts/core'; 2 | 3 | export function lesson2() { 4 | // 11.2 Collatz Conjecture: Counting Until It Becomes 1 5 | function* repeatApply(f: (acc: A) => A, acc: A) { 6 | while (true) { 7 | acc = f(acc); 8 | yield acc; 9 | } 10 | } 11 | 12 | function count(start = 1) { 13 | return range(start, Infinity); 14 | } 15 | 16 | function withCount(iterable: Iterable) { 17 | return fx(iterable) 18 | .zip(count(1)) 19 | .map(([count, value]) => ({ count, value })) 20 | } 21 | 22 | function nextCollatz(num: number) { 23 | return num % 2 === 0 24 | ? num / 2 25 | : num * 3 + 1 26 | } 27 | 28 | const collatzCount = (num: number) => 29 | fx(repeatApply(nextCollatz, num)) 30 | .chain(withCount) 31 | .find(({ value }) => value === 1)! 32 | .count; 33 | 34 | console.log(collatzCount(1)); // 3 35 | console.log(collatzCount(4)); // 2 36 | console.log(collatzCount(5)); // 5 37 | } 38 | -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-2/Section2.ts: -------------------------------------------------------------------------------- 1 | import { SectionPage } from '../../SectionPage'; 2 | import { lesson1 } from './lesson-1'; 3 | import { lesson2_1 } from './lesson-2-1'; 4 | import { lesson2_2 } from './lesson-2-2'; 5 | import { lesson2_3 } from './lesson-2-3'; 6 | import { lesson2_4 } from './lesson-2-4'; 7 | import { lesson2_5 } from './lesson-2-5'; 8 | import { lesson2_6 } from './lesson-2-6'; 9 | import { lesson2_7 } from './lesson-2-7'; 10 | import { lesson3_1 } from './lesson-3-1'; 11 | import { lesson4_2 } from './lesson-4-2'; 12 | import { lesson4_1 } from './lesson-4-1'; 13 | import { lesson5_1 } from './lesson-5-1'; 14 | import { lesson5_2 } from './lesson-5-2'; 15 | import { lesson5_3 } from './lesson-5-3'; 16 | import { lesson5_4 } from './lesson-5-4'; 17 | import { lesson5_5 } from './lesson-5-5'; 18 | import { lesson6_1 } from './lesson-6-1'; 19 | 20 | export class Section2 extends SectionPage { 21 | override title = 'Section2'; 22 | override link = '/part-1/section-2'; 23 | override lessons = [ 24 | lesson1, 25 | lesson2_1, 26 | lesson2_2, 27 | lesson2_3, 28 | lesson2_4, 29 | lesson2_5, 30 | lesson2_6, 31 | lesson2_7, 32 | lesson3_1, 33 | lesson4_1, 34 | lesson4_2, 35 | lesson5_1, 36 | lesson5_2, 37 | lesson5_3, 38 | lesson5_4, 39 | lesson5_5, 40 | lesson6_1, 41 | ]; 42 | } 43 | -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-2/lesson-1.ts: -------------------------------------------------------------------------------- 1 | export function lesson1() { 2 | // Section 2. The Iterator Pattern in OOP and First-Class Functions 3 | // 2.1 GoF’s Iterator Pattern 4 | interface IteratorYieldResult { 5 | done?: false; 6 | value: T; 7 | } 8 | 9 | interface IteratorReturnResult { 10 | done: true; 11 | value: undefined; 12 | } 13 | 14 | type IteratorResult = IteratorYieldResult | IteratorReturnResult; 15 | 16 | interface Iterator { 17 | next(): IteratorResult; 18 | } 19 | } -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-2/lesson-2-1.ts: -------------------------------------------------------------------------------- 1 | export function lesson2_1() { 2 | // 2.2 Creating an Iterator from an Array 3 | class NumbersIterator { 4 | private array: number[]; 5 | 6 | constructor(array: number[]) { 7 | this.array = array; 8 | } 9 | } 10 | 11 | new NumbersIterator([10, 20, 30]); 12 | } 13 | -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-2/lesson-2-2.ts: -------------------------------------------------------------------------------- 1 | export function lesson2_2() { 2 | // 2.2 Creating an Iterator from an Array 3 | class NumbersIterator implements Iterator { 4 | constructor(private array: number[]) {} 5 | 6 | next(): IteratorResult { 7 | return { done: false, value: this.array[0] }; 8 | } 9 | } 10 | 11 | new NumbersIterator([10, 20, 30]); 12 | } -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-2/lesson-2-3.ts: -------------------------------------------------------------------------------- 1 | export function lesson2_3() { 2 | // 2.2 Creating an Iterator from an Array 3 | class NumbersIterator implements Iterator { 4 | constructor(private array: number[]) {} 5 | 6 | next(): IteratorResult { 7 | return { done: false, value: this.array[0] }; 8 | } 9 | } 10 | 11 | const iterator = new NumbersIterator([10, 20, 30]); 12 | console.log(iterator.next()); 13 | } -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-2/lesson-2-4.ts: -------------------------------------------------------------------------------- 1 | export function lesson2_4() { 2 | // 2.2 Creating an Iterator from an Array 3 | class NumbersIterator implements Iterator { 4 | private index = 0; 5 | 6 | constructor(private array: number[]) {} 7 | 8 | next(): IteratorResult { 9 | return { done: false, value: this.array[this.index++] }; 10 | } 11 | } 12 | 13 | const iterator = new NumbersIterator([10, 20, 30]); 14 | console.log(iterator.next()); 15 | console.log(iterator.next()); 16 | console.log(iterator.next()); 17 | console.log(iterator.next()); 18 | } -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-2/lesson-2-5.ts: -------------------------------------------------------------------------------- 1 | export function lesson2_5() { 2 | // 2.2 Creating an Iterator from an Array 3 | class NumbersIterator implements Iterator { 4 | private index = 0; 5 | 6 | constructor(private array: number[]) {} 7 | 8 | next(): IteratorResult { 9 | return this.index === this.array.length 10 | ? { done: true, value: undefined } 11 | : { done: false, value: this.array[this.index++] }; 12 | } 13 | } 14 | 15 | const iterator = new NumbersIterator([10, 20, 30]); 16 | const result = iterator.next(); 17 | if (result.done) { 18 | const value = result.value; 19 | } else { 20 | const value = result.value; 21 | } 22 | console.log(result); 23 | console.log(iterator.next()); 24 | console.log(iterator.next()); 25 | console.log(iterator.next()); 26 | } -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-2/lesson-2-6.ts: -------------------------------------------------------------------------------- 1 | export function lesson2_6() { 2 | // 2.2 Creating an Iterator from an Array 3 | class NumbersIterator implements Iterator { 4 | private index = 0; 5 | 6 | constructor(private array: number[]) {} 7 | 8 | next(): IteratorResult { 9 | return this.index === this.array.length 10 | ? { done: true, value: undefined } 11 | : { done: false, value: this.array[this.index++] }; 12 | } 13 | } 14 | 15 | class StringsIterator implements Iterator { 16 | private index = 0; 17 | 18 | constructor(private array: string[]) {} 19 | 20 | next(): IteratorResult { 21 | return this.index === this.array.length 22 | ? { done: true, value: undefined } 23 | : { done: false, value: this.array[this.index++] }; 24 | } 25 | } 26 | 27 | const iterator = new NumbersIterator([10, 20, 30]); 28 | const result = iterator.next(); 29 | if (result.done) { 30 | const value = result.value; 31 | } else { 32 | const value = result.value; 33 | } 34 | console.log(result); 35 | console.log(iterator.next()); 36 | console.log(iterator.next()); 37 | console.log(iterator.next()); 38 | 39 | const iterator2 = new StringsIterator(['A', 'B', 'C']); 40 | const result2 = iterator2.next(); 41 | if (result2.done) { 42 | const value = result2.value; 43 | } else { 44 | const value = result2.value; 45 | } 46 | console.log(result2); 47 | console.log(iterator2.next()); 48 | console.log(iterator2.next()); 49 | console.log(iterator2.next()); 50 | } -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-2/lesson-2-7.ts: -------------------------------------------------------------------------------- 1 | export function lesson2_7() { 2 | // 2.2 Creating an Iterator from an Array 3 | class ArrayIterator implements Iterator { 4 | private index = 0; 5 | 6 | constructor(private array: T[]) {} 7 | 8 | next(): IteratorResult { 9 | return this.index === this.array.length 10 | ? { done: true, value: undefined } 11 | : { done: false, value: this.array[this.index++] }; 12 | } 13 | } 14 | 15 | const iterator = new ArrayIterator([10, 20, 30]); 16 | const result = iterator.next(); 17 | if (result.done) { 18 | const value = result.value; 19 | } else { 20 | const value = result.value; 21 | } 22 | console.log(result); 23 | console.log(iterator.next()); 24 | console.log(iterator.next()); 25 | console.log(iterator.next()); 26 | 27 | const iterator2 = new ArrayIterator(['A', 'B', 'C']); 28 | const result2 = iterator2.next(); 29 | if (result2.done) { 30 | const value = result2.value; 31 | } else { 32 | const value = result2.value; 33 | } 34 | console.log(result2); 35 | console.log(iterator2.next()); 36 | console.log(iterator2.next()); 37 | console.log(iterator2.next()); 38 | } -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-2/lesson-3-1.ts: -------------------------------------------------------------------------------- 1 | export function lesson3_1() { 2 | // 2.3 An Iterator That Generates Infinite Sequences 3 | class ArrayIterator implements Iterator { 4 | private index = 0; 5 | 6 | constructor(private array: T[]) {} 7 | 8 | next(): IteratorResult { 9 | return this.index === this.array.length 10 | ? { done: true, value: undefined } 11 | : { done: false, value: this.array[this.index++] }; 12 | } 13 | } 14 | 15 | const iter = new ArrayIterator([10, 20]); 16 | console.log(iter.next()); 17 | console.log(iter.next()); 18 | 19 | function naturals(): Iterator { 20 | let i = 1; 21 | return { 22 | next() { 23 | return { done: false, value: i++ }; 24 | } 25 | } 26 | } 27 | 28 | const iter2 = naturals(); 29 | console.log(iter2.next()); 30 | console.log(iter2.next()); 31 | } -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-2/lesson-4-1.ts: -------------------------------------------------------------------------------- 1 | export function lesson4_1() { 2 | // 2.4 `reverse` Function That Creates a Reversed Iterator 3 | 4 | const arr = ['a', 'b', 'c', 'd']; 5 | arr.reverse(); 6 | console.log(arr[0]); 7 | console.log(arr[1]); 8 | console.log(arr[2]); 9 | console.log(arr[3]); 10 | console.log(arr); 11 | } -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-2/lesson-4-2.ts: -------------------------------------------------------------------------------- 1 | export function lesson4_2() { 2 | // 2.4 `reverse` Function That Creates a Reversed Iterator 3 | function reverse(array: T[]): Iterator { 4 | let index = array.length; 5 | return { 6 | next() { 7 | return index === 0 8 | ? { done: true, value: undefined } 9 | : { done: false, value: array[--index] } 10 | } 11 | } 12 | } 13 | 14 | const arr = ['a', 'b', 'c', 'd']; 15 | arr.reverse(); 16 | console.log(arr[0]); 17 | console.log(arr[1]); 18 | console.log(arr[2]); 19 | console.log(arr[3]); 20 | 21 | const arr2 = ['a', 'b', 'c', 'd']; 22 | const iter2 = reverse(arr2); 23 | console.log(iter2.next()); 24 | console.log(iter2.next()); 25 | console.log(iter2.next()); 26 | console.log(iter2.next()); 27 | console.log(iter2.next()); 28 | } -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-2/lesson-5-1.ts: -------------------------------------------------------------------------------- 1 | export function lesson5_1() { 2 | // 2.5 Lazy Evaluation – The Efficiency of the `reverse` Function 3 | function reverse(array: T[]): Iterator { 4 | let index = array.length; 5 | return { 6 | next() { 7 | return index === 0 8 | ? { done: true, value: undefined } 9 | : { done: false, value: array[--index] } 10 | } 11 | } 12 | } 13 | 14 | const arr = ['A', 'B', 'C', 'D', 'E', 'F']; 15 | const reversed = [...arr].reverse(); 16 | console.log( 17 | reversed[0], 18 | reversed[1], 19 | arr[0], 20 | arr[1] 21 | ); 22 | // F E A B 23 | 24 | const arr2 = ['A', 'B', 'C', 'D', 'E', 'F']; 25 | const reversed2 = reverse(arr2); 26 | console.log( 27 | reversed2.next().value, 28 | reversed2.next().value, 29 | arr2[0], 30 | arr2[1] 31 | ); 32 | // F E A B 33 | } -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-2/lesson-5-2.ts: -------------------------------------------------------------------------------- 1 | export function lesson5_2() { 2 | // 2.5 Lazy Evaluation – The Efficiency of the `reverse` Function 3 | function reverse(array: T[]): Iterator { 4 | let index = array.length; 5 | return { 6 | next() { 7 | return index === 0 8 | ? { done: true, value: undefined } 9 | : { done: false, value: array[--index] } 10 | } 11 | } 12 | } 13 | 14 | const arr = ['A', 'B', 'C', 'D', 'E', 'F']; 15 | const reversed = arr.reverse(); 16 | console.log( 17 | reversed[0], 18 | reversed[1], 19 | arr[0], 20 | arr[1] 21 | ); 22 | // F E F E 23 | 24 | const arr2 = ['A', 'B', 'C', 'D', 'E', 'F']; 25 | const reversed2 = reverse(arr2); 26 | console.log( 27 | reversed2.next().value, 28 | reversed2.next().value, 29 | arr2[0], 30 | arr2[1] 31 | ); 32 | // F E A B 33 | } -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-2/lesson-5-3.ts: -------------------------------------------------------------------------------- 1 | export function lesson5_3() { 2 | // 2.5 Lazy Evaluation – The Efficiency of the `reverse` Function 3 | function reverse(array: T[]): Iterator { 4 | let index = array.length; 5 | return { 6 | next() { 7 | return index === 0 8 | ? { done: true, value: undefined } 9 | : { done: false, value: array[--index] } 10 | } 11 | } 12 | } 13 | 14 | const arr = ['A', 'B', 'C', 'D', 'E', 'F']; 15 | const reversed = [...arr].reverse(); 16 | console.log( 17 | reversed[0], 18 | reversed[1], 19 | arr[0], 20 | arr[1] 21 | ); 22 | // F E A B 23 | 24 | const arr2 = ['A', 'B', 'C', 'D', 'E', 'F']; 25 | const reversed2 = reverse(arr2); 26 | console.log( 27 | reversed2.next().value, 28 | reversed2.next().value, 29 | arr2[0], 30 | arr2[1] 31 | ); 32 | // F E A B 33 | } -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-2/lesson-5-4.ts: -------------------------------------------------------------------------------- 1 | export function lesson5_4() { 2 | // 2.5 Lazy Evaluation – The Efficiency of the `reverse` Function 3 | function reverse(array: T[]): Iterator { 4 | let index = array.length; 5 | return { 6 | next() { 7 | return index === 0 8 | ? { done: true, value: undefined } 9 | : { done: false, value: array[--index] } 10 | } 11 | } 12 | } 13 | 14 | const arr = ['A', 'B', 'C', 'D', 'E', 'F']; 15 | const reversed = [...arr].reverse(); 16 | console.log(reversed); 17 | console.log( 18 | reversed[0], 19 | reversed[1], 20 | arr[0], 21 | arr[1] 22 | ); 23 | // F E A B 24 | 25 | const arr2 = ['A', 'B', 'C', 'D', 'E', 'F']; 26 | const reversed2 = reverse(arr2); 27 | console.log(reversed2); 28 | console.log( 29 | reversed2.next().value, 30 | reversed2.next().value, 31 | arr2[0], 32 | arr2[1] 33 | ); 34 | // F E A B 35 | } -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-2/lesson-5-5.ts: -------------------------------------------------------------------------------- 1 | export function lesson5_5() { 2 | // 2.5 Lazy Evaluation – The Efficiency of the `reverse` Function 3 | function reverse(array: T[]): Iterator { 4 | let index = array.length; 5 | return { 6 | next() { 7 | console.log('reverse next', index); 8 | return index === 0 9 | ? { done: true, value: undefined } 10 | : { done: false, value: array[--index] } 11 | } 12 | } 13 | } 14 | 15 | const arr = ['A', 'B', 'C', 'D', 'E', 'F']; 16 | const reversed = [...arr].reverse(); 17 | console.log(reversed); 18 | console.log( 19 | reversed[0], 20 | reversed[1], 21 | arr[0], 22 | arr[1] 23 | ); 24 | // F E A B 25 | 26 | const arr2 = ['A', 'B', 'C', 'D', 'E', 'F']; 27 | const reversed2 = reverse(arr2); 28 | console.log(reversed2); 29 | // console.log( 30 | // reversed2.next().value, 31 | // reversed2.next().value, 32 | // arr2[0], 33 | // arr2[1] 34 | // ); 35 | // F E A B 36 | } -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-2/lesson-6-1.ts: -------------------------------------------------------------------------------- 1 | export function lesson6_1() { 2 | // 2.6 Lazy Evaluation – A `map` Function That Returns an Iterator 3 | function reverse(array: T[]): Iterator { 4 | let index = array.length; 5 | return { 6 | next() { 7 | console.log('reverse'); 8 | return index === 0 9 | ? { done: true, value: undefined } 10 | : { done: false, value: array[--index] } 11 | } 12 | } 13 | } 14 | 15 | function map(f: (a: A) => B, iterator: Iterator): Iterator { 16 | return { 17 | next() { 18 | console.log('map'); 19 | const { done, value } = iterator.next(); 20 | return done 21 | ? { done, value } 22 | : { done, value: f(value) }; 23 | } 24 | } 25 | } 26 | 27 | const reversed = reverse([1, 2, 3, 4, 5, 6, 7, 8]); 28 | // console.log(reversed.next().value, reversed.next().value); 29 | 30 | const mapped = map(a => a * 10, reversed); 31 | console.log(mapped.next().value, mapped.next().value); 32 | // 80 70 33 | } -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-3/Section3.ts: -------------------------------------------------------------------------------- 1 | import { SectionPage } from '../../SectionPage'; 2 | import { lesson1_1 } from './lesson-1-1'; 3 | import { lesson1_2 } from './lesson-1-2'; 4 | import { lesson2 } from './lesson-2'; 5 | import { lesson3 } from './lesson-3'; 6 | 7 | export class Section3 extends SectionPage { 8 | override title = 'Section3'; 9 | override link = '/part-1/section-3'; 10 | override lessons = [lesson1_1, lesson1_2, lesson2, lesson3]; 11 | } 12 | -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-3/lesson-1-1.ts: -------------------------------------------------------------------------------- 1 | export function lesson1_1() { 2 | // Section 3. Generator Functions for Building Iterators in Imperative Programming 3 | // 3.1 Basic Generator Syntax 4 | function* generator() { 5 | yield 1; 6 | yield 2; 7 | yield 3; 8 | } 9 | 10 | const iterator = generator(); 11 | console.log(iterator.next()); 12 | console.log(iterator.next()); 13 | console.log(iterator.next()); 14 | console.log(iterator.next()); 15 | } -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-3/lesson-1-2.ts: -------------------------------------------------------------------------------- 1 | export function lesson1_2() { 2 | // Section 3. Generator Functions for Building Iterators in Imperative Programming 3 | // 3.1 Basic Generator Syntax 4 | function* generator(bool: boolean) { 5 | yield 1; 6 | if (bool) { 7 | yield 2; 8 | } 9 | yield 3; 10 | } 11 | 12 | const iterator = generator(true); 13 | console.log(iterator.next()); 14 | console.log(iterator.next()); 15 | console.log(iterator.next()); 16 | console.log(iterator.next()); 17 | 18 | const iterator2 = generator(false); 19 | console.log(iterator2.next()); 20 | console.log(iterator2.next()); 21 | console.log(iterator2.next()); 22 | } -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-3/lesson-2.ts: -------------------------------------------------------------------------------- 1 | export function lesson2() { 2 | // 3.2 The `naturals` Generator Function for Creating Infinite Sequences 3 | function* naturals() { 4 | let n = 1; 5 | while (true) { 6 | yield n++; 7 | } 8 | } 9 | 10 | const iterator = naturals(); 11 | console.log(iterator.next()); 12 | console.log(iterator.next()); 13 | console.log(iterator.next()); 14 | console.log(iterator.next()); 15 | console.log(iterator.next()); 16 | } -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-3/lesson-3.ts: -------------------------------------------------------------------------------- 1 | export function lesson3() { 2 | // 3.3 Reimplementing `reverse` with a Generator 3 | // function reverse(array: T[]): Iterator { 4 | // let index = array.length; 5 | // return { 6 | // next() { 7 | // return index === 0 8 | // ? { done: true, value: undefined } 9 | // : { done: false, value: array[--index] }; 10 | // }, 11 | // }; 12 | // } 13 | 14 | function* reverse(array: T[]) { 15 | let i = array.length; 16 | while (i) { 17 | yield array[--i]; 18 | } 19 | } 20 | 21 | const iter = reverse([1, 2, 3, 4]); 22 | console.log(iter.next()); 23 | console.log(iter.next()); 24 | console.log(iter.next()); 25 | console.log(iter.next()); 26 | console.log(iter.next()); 27 | } -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-4/Section4.ts: -------------------------------------------------------------------------------- 1 | import { SectionPage } from '../../SectionPage'; 2 | import { lesson1_1 } from './lesson-1-1'; 3 | import { lesson1_2 } from './lesson-1-2'; 4 | import { lesson1_3 } from './lesson-1-3'; 5 | import { lesson2 } from './lesson-2'; 6 | import { lesson3 } from './lesson-3'; 7 | import { lesson4_1 } from './lesson-4-1'; 8 | import { lesson4_2 } from './lesson-4-2'; 9 | import { lesson4_3 } from './lesson-4-3'; 10 | 11 | const fs = [lesson1_1, lesson1_2, lesson1_3, lesson2, lesson3, lesson4_1, lesson4_2, lesson4_3]; 12 | 13 | export class Section4 extends SectionPage { 14 | override title = 'Section4'; 15 | override link = '/part-1/section-4'; 16 | override lessons = fs; 17 | } 18 | -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-4/lesson-1-1.ts: -------------------------------------------------------------------------------- 1 | export function lesson1_1() { 2 | // Section 4. Iteration Protocol 3 | // 4.1 Iterators and Iterables 4 | function naturals(end = Infinity): IterableIterator { 5 | let n = 1; 6 | return { 7 | next() { 8 | return n <= end 9 | ? { value: n++, done: false } 10 | : { value: undefined, done: true }; 11 | }, 12 | [Symbol.iterator]() { 13 | return this; 14 | } 15 | }; 16 | } 17 | 18 | const iter1 = naturals(3); 19 | for (const a of iter1) { 20 | console.log(a); 21 | } 22 | const iterable = naturals(2); 23 | const iter2 = iterable[Symbol.iterator](); 24 | console.log(iter2.next()); 25 | console.log(iter2.next()); 26 | console.log(iter2.next()); 27 | 28 | const array = [1, 2]; 29 | for (const a of array) { 30 | console.log(a); 31 | } 32 | const iter3 = array[Symbol.iterator](); 33 | console.log(iter3.next()); 34 | console.log(iter3.next()); 35 | console.log(iter3.next()); 36 | } 37 | 38 | function interfaceIterator() { 39 | 40 | interface Iterator { 41 | next(): IteratorResult; 42 | } 43 | 44 | interface Iterable { 45 | [Symbol.iterator](): Iterator; 46 | } 47 | 48 | interface IterableIterator extends Iterator { 49 | [Symbol.iterator](): IterableIterator; 50 | } 51 | 52 | } -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-4/lesson-1-2.ts: -------------------------------------------------------------------------------- 1 | export function lesson1_2() { 2 | // Section 4. Iteration Protocol 3 | // 4.1 Iterators and Iterables 4 | function naturals(end = Infinity): IterableIterator { 5 | let n = 1; 6 | return { 7 | next() { 8 | return n <= end 9 | ? { value: n++, done: false } 10 | : { value: undefined, done: true }; 11 | }, 12 | [Symbol.iterator]() { 13 | return this; 14 | } 15 | }; 16 | } 17 | 18 | const iter1 = naturals(3); 19 | for (const a of iter1) { 20 | console.log(a); 21 | } 22 | const iterable = naturals(2); 23 | const iter2 = iterable[Symbol.iterator](); 24 | console.log(iter2.next()); 25 | console.log(iter2.next()); 26 | console.log(iter2.next()); 27 | 28 | const array = [1, 2]; 29 | for (const a of array) { 30 | console.log(a); 31 | } 32 | const iter3 = array[Symbol.iterator](); 33 | console.log(iter3.next()); 34 | console.log(iter3.next()); 35 | console.log(iter3.next()); 36 | } -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-4/lesson-1-3.ts: -------------------------------------------------------------------------------- 1 | export function lesson1_3() { 2 | // Section 4. Iteration Protocol 3 | // 4.1 Iterators and Iterables 4 | // function naturals(end = Infinity): IterableIterator { 5 | // let n = 1; 6 | // return { 7 | // next() { 8 | // return n <= end 9 | // ? { value: n++, done: false } 10 | // : { value: undefined, done: true }; 11 | // }, 12 | // [Symbol.iterator]() { 13 | // return this; 14 | // } 15 | // }; 16 | // } 17 | 18 | function* naturals(end = Infinity): IterableIterator { 19 | let n = 1; 20 | while (n <= end) { 21 | yield n++; 22 | } 23 | } 24 | 25 | const iter1 = naturals(3); 26 | for (const a of iter1) { 27 | console.log(a); 28 | } 29 | const iterable = naturals(2); 30 | const iter2 = iterable[Symbol.iterator](); 31 | console.log(iter2.next()); 32 | console.log(iter2.next()); 33 | console.log(iter2.next()); 34 | 35 | const array = [1, 2]; 36 | for (const a of array) { 37 | console.log(a); 38 | } 39 | const iter3 = array[Symbol.iterator](); 40 | console.log(iter3.next()); 41 | console.log(iter3.next()); 42 | console.log(iter3.next()); 43 | 44 | console.log([...naturals(5)]); 45 | } -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-4/lesson-2.ts: -------------------------------------------------------------------------------- 1 | export function lesson2() { 2 | // 4.2 Built-in Iterables — Array, Set, Map 3 | const array = [1, 2, 3]; 4 | const arrayIterator = array[Symbol.iterator](); 5 | 6 | console.log(arrayIterator.next()); // { value: 1, done: false } 7 | console.log(arrayIterator.next()); // { value: 2, done: false } 8 | console.log(arrayIterator.next()); // { value: 3, done: false } 9 | console.log(arrayIterator.next()); // { value: undefined, done: true } 10 | 11 | for (const value of array) { 12 | console.log(value); 13 | } 14 | // 1 15 | // 2 16 | // 3 17 | 18 | const set = new Set([1, 2, 3]); 19 | const setIterator = set[Symbol.iterator](); 20 | 21 | console.log(setIterator.next()); // { value: 1, done: false } 22 | console.log(setIterator.next()); // { value: 2, done: false } 23 | console.log(setIterator.next()); // { value: 3, done: false } 24 | console.log(setIterator.next()); // { value: undefined, done: true } 25 | 26 | for (const value of set) { 27 | console.log(value); 28 | } 29 | // 1 30 | // 2 31 | // 3 32 | 33 | const map = new Map([['a', 1], ['b', 2], ['c', 3]]); 34 | const mapIterator = map[Symbol.iterator](); 35 | 36 | console.log(mapIterator.next()); // { value: ['a', 1], done: false } 37 | console.log(mapIterator.next()); // { value: ['b', 2], done: false } 38 | console.log(mapIterator.next()); // { value: ['c', 3], done: false } 39 | console.log(mapIterator.next()); // { value: undefined, done: true } 40 | 41 | for (const [key, value] of map) { 42 | console.log(`${key}: ${value}`); 43 | } 44 | // a: 1 45 | // b: 2 46 | // c: 3 47 | 48 | const mapValues = map.values(); 49 | 50 | console.log(mapValues.next()); // { value: 1, done: false } 51 | 52 | for (const value of mapValues) { 53 | console.log(value); 54 | } 55 | // 2 56 | // 3 57 | 58 | const mapKeys = map.keys(); 59 | console.log(mapKeys.next()); // { value: 'a', done: false } 60 | 61 | for (const key of mapKeys) { 62 | console.log(key); 63 | } 64 | // b 65 | // c 66 | } -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-4/lesson-3.ts: -------------------------------------------------------------------------------- 1 | export function lesson3() { 2 | // 4.3 Interactions Between the Language and Iterables — Spread Operator, Destructuring 3 | 4 | // Spread operator and iterables 5 | const array = [1, 2, 3]; 6 | const array2 = [...array, 4, 5, 6]; 7 | console.log(array2); // [1, 2, 3, 4, 5, 6] 8 | 9 | // Array from Set 10 | const set = new Set([1, 2, 3]); 11 | const array3 = [...set]; 12 | console.log(array3); // [1, 2, 3] 13 | console.log(Array.from(set)); // [1, 2, 3] 14 | 15 | // Passing arguments by expanding with the spread operator 16 | function sum(...nums: number[]): number { 17 | return nums.reduce((a, b) => a + b, 0); 18 | } 19 | 20 | const numbers = [1, 2, 3]; 21 | console.log(sum(...numbers)); // 6 22 | 23 | // Destructuring assignment and iterables 24 | const array4 = [1, 2, 3, 4]; 25 | const [first, second] = array4; 26 | console.log(first); // 1 27 | console.log(second); // 2 28 | 29 | const [head, ...tail] = array4; 30 | console.log(head); // 1 31 | console.log(tail); // [2, 3, 4] 32 | 33 | // Custom iterables and the spread operator 34 | function* naturals(end = Infinity) { 35 | let n = 1; 36 | while (n <= end) { 37 | yield n++; 38 | } 39 | } 40 | 41 | const array5 = [0, ...naturals(3)]; 42 | console.log(array5); // [0, 1, 2, 3] 43 | } -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-4/lesson-4-1.ts: -------------------------------------------------------------------------------- 1 | export function lesson4_1() { 2 | // 4.4 Iterator, Generator, Iterable 3 | function naturals(end = Infinity): IterableIterator { 4 | let n = 1; 5 | return { 6 | next() { 7 | return n <= end 8 | ? { value: n++, done: false } : { value: undefined, done: true }; 9 | }, 10 | [Symbol.iterator]() { 11 | return this; 12 | } 13 | } 14 | } 15 | 16 | function map(f: (a: A) => B, iterable: Iterable): IterableIterator { 17 | const iterator = iterable[Symbol.iterator](); 18 | return { 19 | next() { 20 | const { done, value } = iterator.next(); 21 | return done ? { done, value } : { done, value: f(value) }; 22 | }, 23 | [Symbol.iterator]() { 24 | return this; 25 | } 26 | } 27 | } 28 | 29 | const mapped = map((x) => x * 2, naturals(4)); 30 | 31 | let acc = 0; 32 | for (const num of mapped) { 33 | acc += num; 34 | } 35 | console.log(acc); // 20 36 | 37 | console.log( 38 | [...map(a => a * 10, [1, 2, 3, 4])] 39 | ); // [10, 20, 30, 40] 40 | } 41 | 42 | -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-4/lesson-4-2.ts: -------------------------------------------------------------------------------- 1 | export function lesson4_2() { 2 | // 4.4 Iterator, Generator, Iterable 3 | function* naturals(end = Infinity) { 4 | let n = 1; 5 | while (n <= end) { 6 | yield n++; 7 | } 8 | } 9 | 10 | function* map(f: (value: A) => B, iterable: Iterable) { 11 | for (const value of iterable) { 12 | yield f(value); 13 | } 14 | } 15 | 16 | const mapped = map((x) => x * 2, naturals(4)); 17 | 18 | let acc = 0; 19 | for (const num of mapped) { 20 | acc += num; 21 | } 22 | console.log(acc); // 20 23 | 24 | console.log( 25 | [...map(a => a * 10, [1, 2, 3, 4])] 26 | ); // [10, 20, 30, 40] 27 | } -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-4/lesson-4-3.ts: -------------------------------------------------------------------------------- 1 | export function lesson4_3() { 2 | // 4.4 Iterator, Generator, Iterable 3 | function naturals(end = Infinity): IterableIterator { 4 | let n = 1; 5 | return { 6 | next() { 7 | return n <= end 8 | ? { value: n++, done: false } : { value: undefined, done: true }; 9 | }, 10 | [Symbol.iterator]() { 11 | return this; 12 | } 13 | } 14 | } 15 | 16 | function* map(f: (value: A) => B, iterable: Iterable) { 17 | for (const value of iterable) { 18 | yield f(value); 19 | } 20 | } 21 | 22 | const mapped = map((x) => x * 2, naturals(4)); 23 | 24 | let acc = 0; 25 | for (const num of mapped) { 26 | acc += num; 27 | } 28 | console.log(acc); // 20 29 | 30 | console.log( 31 | [...map(a => a * 10, [1, 2, 3, 4])] 32 | ); // [10, 20, 30, 40] 33 | } -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-5/Section5.ts: -------------------------------------------------------------------------------- 1 | import { SectionPage } from '../../SectionPage'; 2 | import { html } from 'rune-ts'; 3 | import { lesson1_1 } from './lesson-1-1'; 4 | import { lesson1_2 } from './lesson-1-2'; 5 | import { lesson2_1 } from './lesson-2-1'; 6 | import { lesson2_2 } from './lesson-2-2'; 7 | import { lesson2_3 } from './lesson-2-3'; 8 | import { lesson3_1 } from './lesson-3-1'; 9 | import { lesson3_2 } from './lesson-3-2'; 10 | import { lesson3_3 } from './lesson-3-3'; 11 | import { lesson4 } from './lesson-4'; 12 | 13 | const fs = [ 14 | lesson1_1, 15 | lesson1_2, 16 | lesson2_1, 17 | lesson2_2, 18 | lesson2_3, 19 | lesson3_1, 20 | lesson3_2, 21 | lesson3_3, 22 | lesson4, 23 | ]; 24 | 25 | export class Section5 extends SectionPage { 26 | override title = 'Section5'; 27 | override link = '/part-1/section-5'; 28 | override lessons = fs; 29 | } 30 | -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-5/lesson-1-1.ts: -------------------------------------------------------------------------------- 1 | 2 | export function lesson1_1() { 3 | // Section 5. Functional Programming with Iterables 4 | // 5.1 forEach Function (for) 5 | 6 | function forEach(f, iterable) { 7 | for (const a of iterable) { 8 | f(a); 9 | } 10 | } 11 | 12 | const array = [1, 2, 3]; 13 | forEach(console.log, array); 14 | // 1 15 | // 2 16 | // 3 17 | 18 | const set = new Set([10, 30, 30]); 19 | forEach(console.log, set); 20 | } 21 | -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-5/lesson-1-2.ts: -------------------------------------------------------------------------------- 1 | 2 | export function lesson1_2() { 3 | // Section 5. Functional Programming with Iterables 4 | // 5.2 forEach Function (while) 5 | 6 | function forEach(f, iterable) { 7 | const iterator = iterable[Symbol.iterator](); 8 | while (true) { 9 | const { done, value } = iterator.next(); 10 | if (done) break; 11 | f(value); 12 | } 13 | } 14 | 15 | const array = [1, 2, 3]; 16 | forEach(console.log, array); 17 | // 1 18 | // 2 19 | // 3 20 | 21 | const set = new Set([10, 30, 30]); 22 | forEach(console.log, set); 23 | } 24 | -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-5/lesson-2-1.ts: -------------------------------------------------------------------------------- 1 | import { forEach } from './lib'; 2 | 3 | export function lesson2_1() { 4 | // 5.2 map Function (for) 5 | 6 | function* naturals(end = Infinity) { 7 | let n = 1; 8 | while (n <= end) { 9 | yield n++; 10 | } 11 | } 12 | 13 | function* map(f, iterable) { 14 | for (const a of iterable) { 15 | yield f(a); 16 | } 17 | } 18 | 19 | const array = [1, 2, 3]; 20 | const mapped = map(x => x * 2, array); 21 | console.log([...mapped]); // [2, 4, 6] 22 | 23 | const mapped2 = map(x => x * 3, naturals(3)); 24 | forEach(console.log, mapped2); 25 | // 3 26 | // 6 27 | // 9 28 | } 29 | -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-5/lesson-2-2.ts: -------------------------------------------------------------------------------- 1 | import { forEach } from './lib'; 2 | 3 | export function lesson2_2() { 4 | // 5.2 map Function (while) 5 | 6 | function* naturals(end = Infinity) { 7 | let n = 1; 8 | while (n <= end) { 9 | yield n++; 10 | } 11 | } 12 | 13 | function* map(f, iterable) { 14 | const iterator = iterable[Symbol.iterator](); 15 | while (true) { 16 | const { done, value } = iterator.next(); 17 | if (done) break; 18 | yield f(value); 19 | } 20 | } 21 | 22 | const array = [1, 2, 3]; 23 | const mapped = map(x => x * 2, array); 24 | console.log([...mapped]); // [2, 4, 6] 25 | 26 | const mapped2 = map(x => x * 3, naturals(3)); 27 | forEach(console.log, mapped2); 28 | // 3 29 | // 6 30 | // 9 31 | } 32 | -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-5/lesson-2-3.ts: -------------------------------------------------------------------------------- 1 | import { forEach } from './lib'; 2 | 3 | export function lesson2_3() { 4 | // 5.2 map Function (iterator) 5 | 6 | function* naturals(end = Infinity) { 7 | let n = 1; 8 | while (n <= end) { 9 | yield n++; 10 | } 11 | } 12 | 13 | function map(f, iterable): any { 14 | const iterator = iterable[Symbol.iterator](); 15 | return { 16 | next() { 17 | const { done, value } = iterator.next(); 18 | return done 19 | ? { done, value } 20 | : { done, value: f(value) }; 21 | }, 22 | [Symbol.iterator]() { 23 | return this; 24 | } 25 | }; 26 | } 27 | 28 | const array = [1, 2, 3]; 29 | const mapped = map(x => x * 2, array); 30 | console.log([...mapped]); // [2, 4, 6] 31 | 32 | const mapped2 = map(x => x * 3, naturals(3)); 33 | forEach(console.log, mapped2); 34 | // 3 35 | // 6 36 | // 9 37 | } 38 | -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-5/lesson-3-1.ts: -------------------------------------------------------------------------------- 1 | export function lesson3_1() { 2 | // 5.3 filter Function (for) 3 | 4 | function* filter(f, iterable) { 5 | for (const a of iterable) { 6 | if (f(a)) { 7 | yield a; 8 | } 9 | } 10 | } 11 | 12 | const array = [1, 2, 3, 4, 5]; 13 | const filtered = filter(x => x % 2 === 0, array); 14 | console.log([...filtered]); // [2, 4] 15 | 16 | const filtered2 = filter(x => x % 2 === 1, array); 17 | console.log([...filtered2]); // [1, 3, 5] 18 | } 19 | -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-5/lesson-3-2.ts: -------------------------------------------------------------------------------- 1 | export function lesson3_2() { 2 | // 5.3 filter Function (while) 3 | 4 | function* filter(f, iterable) { 5 | const iterator = iterable[Symbol.iterator](); 6 | while (true) { 7 | const { done, value } = iterator.next(); 8 | if (done) break; 9 | if (f(value)) { 10 | yield value; 11 | } 12 | } 13 | } 14 | 15 | const array = [1, 2, 3, 4, 5]; 16 | const filtered = filter(x => x % 2 === 0, array); 17 | console.log([...filtered]); // [2, 4] 18 | 19 | const filtered2 = filter(x => x % 2 === 1, array); 20 | console.log([...filtered2]); // [1, 3, 5] 21 | } 22 | -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-5/lesson-3-3.ts: -------------------------------------------------------------------------------- 1 | export function lesson3_3() { 2 | // 5.3 filter Function (iterator) 3 | 4 | function filter(f, iterable): any { 5 | const iterator = iterable[Symbol.iterator](); 6 | return { 7 | next() { 8 | while (true) { 9 | const { done, value } = iterator.next(); 10 | if (done) return { done, value }; 11 | if (f(value)) return { done, value }; 12 | } 13 | }, 14 | [Symbol.iterator]() { 15 | return this; 16 | } 17 | } 18 | } 19 | 20 | const array = [1, 2, 3, 4, 5]; 21 | const filtered = filter(x => x % 2 === 0, array); 22 | console.log([...filtered]); // [2, 4] 23 | // const iterator = filtered[Symbol.iterator](); 24 | // console.log(iterator.next()); 25 | // console.log(iterator.next()); 26 | // console.log(iterator.next()); 27 | 28 | const filtered2 = filter(x => x % 2 === 1, array); 29 | console.log([...filtered2]); // [1, 3, 5] 30 | } 31 | -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-5/lesson-4.ts: -------------------------------------------------------------------------------- 1 | export function lesson4() { 2 | // 5.4 Composing Higher-Order Functions ((())) 3 | 4 | function forEach(f, iterable) { 5 | for (const value of iterable) { 6 | f(value); 7 | } 8 | } 9 | 10 | function map(f, iterable): any { 11 | const iterator = iterable[Symbol.iterator](); 12 | return { 13 | next() { 14 | const { done, value } = iterator.next(); 15 | return done 16 | ? { done, value } 17 | : { done, value: f(value) }; 18 | }, 19 | [Symbol.iterator]() { 20 | return this; 21 | } 22 | }; 23 | } 24 | 25 | function* filter(f, iterable) { 26 | const iterator = iterable[Symbol.iterator](); 27 | while (true) { 28 | const { value, done } = iterator.next(); 29 | if (done) break; 30 | if (f(value)) { 31 | yield value; 32 | } 33 | } 34 | } 35 | 36 | function* naturals(end = Infinity) { 37 | let n = 1; 38 | while (n <= end) { 39 | yield n++; 40 | } 41 | } 42 | 43 | forEach(console.log, 44 | map(x => x * 10, 45 | filter(x => x % 2 === 1, 46 | naturals(5)))); 47 | // 10 48 | // 30 49 | // 50 50 | } 51 | 52 | -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-5/lib.ts: -------------------------------------------------------------------------------- 1 | export function forEach(f, iterable) { 2 | for (const value of iterable) { 3 | f(value); 4 | } 5 | } 6 | 7 | export function* map(f, iterable) { 8 | for (const value of iterable) { 9 | yield f(value); 10 | } 11 | } 12 | 13 | export function* filter(f, iterable) { 14 | for (const value of iterable) { 15 | if (f(value)) { 16 | yield value; 17 | } 18 | } 19 | } 20 | 21 | export function* naturals(end = Infinity) { 22 | let n = 1; 23 | while (n <= end) { 24 | yield n++; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-6/Section6.ts: -------------------------------------------------------------------------------- 1 | import { SectionPage } from '../../SectionPage'; 2 | import { html } from 'rune-ts'; 3 | import { lesson2_1 } from './lesson-2-1'; 4 | import { lesson2_2 } from './lesson-2-2'; 5 | import { lesson3_1 } from './lesson-3-1'; 6 | import { lesson3_2 } from './lesson-3-2'; 7 | import { lesson3_3 } from './lesson-3-3'; 8 | import { lesson4 } from './lesson-4'; 9 | import { lesson5 } from './lesson-5'; 10 | 11 | const fs = [lesson2_1, lesson2_2, lesson3_1, lesson3_2, lesson3_3, lesson4, lesson5]; 12 | 13 | export class Section6 extends SectionPage { 14 | override title = 'Section6'; 15 | override link = '/part-1/section-6'; 16 | override lessons = fs; 17 | } 18 | -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-6/lesson-2-1.ts: -------------------------------------------------------------------------------- 1 | export function lesson2_1() { 2 | // 6.2 Type Inference, Function Types, and Generics 3 | 4 | let strings = ['a', 'b', 'c']; 5 | strings.forEach(str => console.log(str.toUpperCase())); 6 | 7 | let numbers = [1, 2, 3]; 8 | numbers 9 | .map(n => n / 10) 10 | .map(n => n.toFixed(2)) 11 | .forEach(str => console.log(str.replace("0.", ""))); 12 | 13 | function double(a: number): number; 14 | function double(a: string): string; 15 | function double(a: number | string): number | string { 16 | if (typeof a === 'number') { 17 | return a * 2; 18 | } else { 19 | return a + a; 20 | } 21 | } 22 | 23 | const num = double(10); 24 | console.log(num); // 20 25 | const str = double('Hi'); 26 | console.log(str); // 'HiHi' 27 | } 28 | -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-6/lesson-2-2.ts: -------------------------------------------------------------------------------- 1 | 2 | export function lesson2_2() { 3 | // identity 4 | function identity(arg: T): T { 5 | return arg; 6 | } 7 | 8 | const a = identity("hi"); 9 | const b = identity(1); 10 | class User {} 11 | const c = identity(new User()); 12 | const d = identity((n: number) => n % 2 === 1); 13 | 14 | // constant 15 | function constant(a: T): () => T { 16 | return () => a; 17 | } 18 | 19 | const getFive = constant(5); 20 | const ten = getFive() + getFive(); 21 | console.log(ten); // 10 22 | 23 | const getHi = constant("Hi"); 24 | const hi2 = getHi() + getHi(); 25 | console.log(hi2); // HiHi 26 | } 27 | -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-6/lesson-3-1.ts: -------------------------------------------------------------------------------- 1 | export function lesson3_1() { 2 | // 6.3 Applying Types to map, filter, and forEach 3 | 4 | function forEach(f: (a: A) => void, iterable: Iterable) { 5 | for (const a of iterable) { 6 | f(a); 7 | } 8 | } 9 | 10 | const array = [1, 2, 3]; 11 | forEach(n => console.log(n), array); 12 | 13 | const set = new Set([10, 30, 30]); 14 | forEach(n => console.log(n.toFixed(2)), set); 15 | 16 | forEach(n => console.log(n), ['a', 'b']); 17 | } 18 | -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-6/lesson-3-2.ts: -------------------------------------------------------------------------------- 1 | export function lesson3_2() { 2 | function* map(f: (a: A) => B, iterable: Iterable): IterableIterator { 3 | for (const a of iterable) { 4 | yield f(a); 5 | } 6 | } 7 | 8 | const array = ['1', '2', '3']; 9 | const mapped = map(a => parseInt(a), array); 10 | const array2 = [...mapped]; 11 | console.log(array2); 12 | 13 | const [head] = map(a => a.toFixed(2), [1, 2, 3]); 14 | console.log(head); 15 | } 16 | -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-6/lesson-3-3.ts: -------------------------------------------------------------------------------- 1 | export function lesson3_3() { 2 | function* filter(f: (a: A) => boolean, iterable: Iterable): IterableIterator { 3 | for (const a of iterable) { 4 | if (f(a)) { 5 | yield a; 6 | } 7 | } 8 | } 9 | 10 | const array = [1, 2, 3, 4]; 11 | const filtered = filter(a => a % 2 === 0, array); 12 | 13 | const array2: number[] = [...filtered]; 14 | console.log(array2); 15 | } 16 | -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-6/lesson-4.ts: -------------------------------------------------------------------------------- 1 | 2 | export function lesson4() { 3 | // 6.4 The `reduce` Function and Function Overloading 4 | 5 | function baseReduce( 6 | f: (acc: Acc, a: A) => Acc, 7 | acc: Acc, 8 | iterator: Iterator 9 | ): Acc { 10 | while (true) { 11 | const { done, value: a } = iterator.next(); 12 | if (done) break; 13 | acc = f(acc, a); 14 | } 15 | return acc; 16 | } 17 | 18 | function reduce( 19 | f: (a: A, b: A) => Acc, 20 | iterable: Iterable 21 | ): Acc; 22 | function reduce( 23 | f: (acc: Acc, a: A) => Acc, 24 | acc: Acc, 25 | iterable: Iterable 26 | ): Acc; 27 | function reduce( 28 | f: (acc: A | Acc, a: A) => Acc, 29 | accOrIterable: Acc | Iterable, 30 | iterable?: Iterable 31 | ): Acc { 32 | if (iterable === undefined) { 33 | iterable = accOrIterable as Iterable; 34 | const iterator = iterable[Symbol.iterator](); 35 | const { done, value: acc } = iterator.next(); 36 | if (done) throw new TypeError("'reduce' of empty iterable with no initial value"); 37 | return baseReduce(f, acc, iterator) as Acc; 38 | } else { 39 | return baseReduce(f, accOrIterable as Acc, iterable[Symbol.iterator]()); 40 | } 41 | } 42 | // 43 | // const result = reduce((acc, a) => acc + a, 10, [1, 2, 3]); 44 | // console.log(result); 45 | // 46 | // const result2 = reduce((total, img) => total + img.height, 0, [ 47 | // { height: 50 }, 48 | // { height: 100 }, 49 | // { height: 200 }, 50 | // ]); 51 | // console.log(result2); 52 | // 53 | // const result3 = reduce((a, b) => a + b, [1, 2, 3]); 54 | // console.log(result3); 55 | // 56 | // const result4 = reduce((a, b) => `${a},${b}`, [1, 2, 3]); 57 | // console.log(result4); 58 | } 59 | -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-6/lesson-5.ts: -------------------------------------------------------------------------------- 1 | import {filter, forEach, map} from '../../lib/fx1'; 2 | 3 | export function lesson5() { 4 | // 6.5 Extensibility of the Iteration Protocol Designed with Interfaces 5 | document.body.innerHTML = ` 6 | 7 | 1 8 | 2 9 | 3 10 | 4 11 | 5 12 | 13 | `; 14 | 15 | const nodeList = document.querySelectorAll('li'); 16 | 17 | // Use a for...of loop to iterate over the NodeList 18 | for (const node of nodeList) { 19 | console.log(node.textContent); 20 | // 1 21 | // 2 22 | // 3 23 | // 4 24 | // 5 25 | } 26 | 27 | forEach(console.log, 28 | filter(x => x % 2 === 1, 29 | map(node => parseInt(node.textContent!), 30 | document.querySelectorAll('li')))); 31 | 32 | forEach(element => element.remove(), 33 | filter(node => parseInt(node.textContent!) % 2 === 0, 34 | document.querySelectorAll('li'))); 35 | 36 | console.log(document.querySelectorAll('li')); 37 | 38 | // const nodes = document.querySelectorAll('li'); 39 | // console.log(nodes[0], nodes[1], nodes.length); 40 | // // 1 3 3 41 | // 42 | // nodes.forEach(node => console.log(node)); 43 | // // 1 44 | // // 3 45 | // // 5 46 | // 47 | // nodes.map(node => node.textContent); 48 | // // Uncaught TypeError: nodes.map is not a function 49 | } 50 | -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-7/Section7.ts: -------------------------------------------------------------------------------- 1 | import { SectionPage } from '../../SectionPage'; 2 | import { html } from 'rune-ts'; 3 | import {lesson2} from "./lesson-2"; 4 | import {lesson3} from "./lesson-3"; 5 | 6 | const fs = [ 7 | lesson2, 8 | lesson3, 9 | ]; 10 | 11 | export class Section7 extends SectionPage { 12 | override title = 'Section7'; 13 | override link = '/part-1/section-7'; 14 | override lessons = fs; 15 | } 16 | -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-7/lesson-1.md: -------------------------------------------------------------------------------- 1 | ## 7.1 Waiting for the Pipe Operator 2 | 3 | ### ((())) 4 | 5 | forEach(printNumber, 6 | map(n => n * 10, 7 | filter(n => n % 2 === 1, 8 | naturals(5)))); 9 | // 10 10 | // 30 11 | // 50 12 | 13 | ### Pipe Operator (Stage: 2) 14 | 15 | naturals(5) 16 | |> filter(n => n % 2 === 1, %) 17 | |> map(n => n * 10, %) 18 | |> forEach(printNumber, %) 19 | // 10 20 | // 30 21 | // 50 22 | -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-7/lesson-2.ts: -------------------------------------------------------------------------------- 1 | function* naturals(end = Infinity) { 2 | let n = 1; 3 | while (n <= end) { 4 | yield n++; 5 | } 6 | } 7 | 8 | function* map(f: (a: A) => B, iterable: Iterable): IterableIterator { 9 | for (const a of iterable) { 10 | yield f(a); 11 | } 12 | } 13 | 14 | function* filter(f: (a: A) => boolean, iterable: Iterable): IterableIterator { 15 | for (const a of iterable) { 16 | if (f(a)) { 17 | yield a; 18 | } 19 | } 20 | } 21 | 22 | function forEach(f: (a: A) => void, iterable: Iterable): void { 23 | for (const a of iterable) { 24 | f(a); 25 | } 26 | } 27 | 28 | export function lesson2() { 29 | // 7.2 Extending Iterables with Generic Classes 30 | 31 | class FxIterable { 32 | constructor(private iterable: Iterable) {} 33 | 34 | map(f: (a: A) => B) { 35 | return fx(map(f, this.iterable)); 36 | } 37 | 38 | filter(f: (a: A) => boolean) { 39 | return fx(filter(f, this.iterable)); 40 | } 41 | 42 | forEach(f: (a: A) => void) { 43 | return forEach(f, this.iterable); 44 | } 45 | } 46 | 47 | function fx(iterable: Iterable): FxIterable { 48 | return new FxIterable(iterable); 49 | } 50 | 51 | const mapped = new FxIterable(['1', '2']) 52 | .map(a => parseInt(a)) 53 | .map(b => b + b) 54 | .map(c => c.toFixed(2)); 55 | 56 | // ((())) 57 | // forEach(printNumber, 58 | // map(n => n * 10, 59 | // filter(n => n % 2 === 1, 60 | // naturals(5)))); 61 | 62 | // Pipe Operator 63 | // naturals(5) 64 | // |> filter(n => n % 2 === 1, %) 65 | // |> map(n => n * 10, %) 66 | // |> forEach(printNumber, %) 67 | 68 | // Chaining 69 | fx(naturals(5)) 70 | .filter(n => n % 2 === 1) 71 | .map(n => n * 10) 72 | .forEach(printNumber); 73 | 74 | function printNumber(n: number) { 75 | console.log(n); 76 | } 77 | } 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-7/lesson-3.ts: -------------------------------------------------------------------------------- 1 | import {red} from "@rune-ts/server/dist/lib/logHelper/picocolors"; 2 | 3 | function* naturals(end = Infinity) { 4 | let n = 1; 5 | while (n <= end) { 6 | yield n++; 7 | } 8 | } 9 | 10 | function* map(f: (a: A) => B, iterable: Iterable): IterableIterator { 11 | for (const a of iterable) { 12 | yield f(a); 13 | } 14 | } 15 | 16 | function* filter(f: (a: A) => boolean, iterable: Iterable): IterableIterator { 17 | for (const a of iterable) { 18 | if (f(a)) { 19 | yield a; 20 | } 21 | } 22 | } 23 | 24 | function forEach(f: (a: A) => void, iterable: Iterable): void { 25 | for (const a of iterable) { 26 | f(a); 27 | } 28 | } 29 | 30 | function baseReduce( 31 | f: (acc: Acc, a: A) => Acc, 32 | acc: Acc, 33 | iterator: Iterator 34 | ): Acc { 35 | while (true) { 36 | const { done, value: a } = iterator.next(); 37 | if (done) break; 38 | acc = f(acc, a); 39 | } 40 | return acc; 41 | } 42 | 43 | function reduce( 44 | f: (a: A, b: A) => Acc, 45 | iterable: Iterable 46 | ): Acc; 47 | function reduce( 48 | f: (acc: Acc, a: A) => Acc, 49 | acc: Acc, 50 | iterable: Iterable 51 | ): Acc; 52 | function reduce( 53 | f: (acc: A | Acc, a: A) => Acc, 54 | accOrIterable: Acc | Iterable, 55 | iterable?: Iterable 56 | ): Acc { 57 | if (iterable === undefined) { 58 | iterable = accOrIterable as Iterable; 59 | const iterator = iterable[Symbol.iterator](); 60 | const { done, value: acc } = iterator.next(); 61 | if (done) throw new TypeError("'reduce' of empty iterable with no initial value"); 62 | return baseReduce(f, acc, iterator) as Acc; 63 | } else { 64 | return baseReduce(f, accOrIterable as Acc, iterable[Symbol.iterator]()); 65 | } 66 | } 67 | 68 | export function lesson3() { 69 | // 7.3 Method Overloading 70 | 71 | class FxIterable { 72 | constructor(private iterable: Iterable) {} 73 | 74 | map(f: (a: A) => B) { 75 | return fx(map(f, this.iterable)); 76 | } 77 | 78 | filter(f: (a: A) => boolean) { 79 | return fx(filter(f, this.iterable)); 80 | } 81 | 82 | forEach(f: (a: A) => void) { 83 | return forEach(f, this.iterable); 84 | } 85 | 86 | reduce(f: (a: A, b: A) => Acc): Acc; 87 | reduce(f: (acc: Acc, a: A) => Acc, acc: Acc): Acc; 88 | reduce(f: (acc: A | Acc, a: A) => Acc, acc?: Acc): Acc { 89 | return acc === undefined 90 | ? reduce(f, this.iterable) 91 | : reduce(f, acc, this.iterable); 92 | } 93 | } 94 | 95 | function fx(iterable: Iterable) { 96 | return new FxIterable(iterable); 97 | } 98 | 99 | const num = fx(naturals(5)) 100 | .filter(n => n % 2 === 1) 101 | .map(n => n * 10) 102 | .reduce((a, b) => a + b); 103 | 104 | console.log(num); 105 | // 90 106 | 107 | const num2 = fx(naturals(5)) 108 | .filter(n => n % 2 === 1) 109 | .map(n => n * 10) 110 | .reduce((a, b) => a + b, 10); 111 | 112 | console.log(num2); 113 | // 100 114 | } 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-8/Section8.ts: -------------------------------------------------------------------------------- 1 | import { SectionPage } from '../../SectionPage'; 2 | import { html } from 'rune-ts'; 3 | import { lesson1 } from './lesson-1'; 4 | import { lesson3 } from './lesson-3'; 5 | import { lesson4 } from './lesson-4'; 6 | 7 | const fs = [lesson1, lesson3, lesson4]; 8 | 9 | export class Section8 extends SectionPage { 10 | override title = 'Section8'; 11 | override link = '/part-1/section-8'; 12 | override lessons = fs; 13 | } 14 | -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-8/lesson-1.ts: -------------------------------------------------------------------------------- 1 | function* naturals(end = Infinity) { 2 | let n = 1; 3 | while (n <= end) { 4 | yield n++; 5 | } 6 | } 7 | 8 | function* map(f: (a: A) => B, iterable: Iterable): IterableIterator { 9 | for (const a of iterable) { 10 | yield f(a); 11 | } 12 | } 13 | 14 | function* filter(f: (a: A) => boolean, iterable: Iterable): IterableIterator { 15 | for (const a of iterable) { 16 | if (f(a)) { 17 | yield a; 18 | } 19 | } 20 | } 21 | 22 | function forEach(f: (a: A) => void, iterable: Iterable): void { 23 | for (const a of iterable) { 24 | f(a); 25 | } 26 | } 27 | 28 | function baseReduce( 29 | f: (acc: Acc, a: A) => Acc, 30 | acc: Acc, 31 | iterator: Iterator 32 | ): Acc { 33 | while (true) { 34 | const { done, value: a } = iterator.next(); 35 | if (done) break; 36 | acc = f(acc, a); 37 | } 38 | return acc; 39 | } 40 | 41 | function reduce( 42 | f: (a: A, b: A) => Acc, 43 | iterable: Iterable 44 | ): Acc; 45 | function reduce( 46 | f: (acc: Acc, a: A) => Acc, 47 | acc: Acc, 48 | iterable: Iterable 49 | ): Acc; 50 | function reduce( 51 | f: (acc: A | Acc, a: A) => Acc, 52 | accOrIterable: Acc | Iterable, 53 | iterable?: Iterable 54 | ): Acc { 55 | if (iterable === undefined) { 56 | iterable = accOrIterable as Iterable; 57 | const iterator = iterable[Symbol.iterator](); 58 | const { done, value: acc } = iterator.next(); 59 | if (done) throw new TypeError("'reduce' of empty iterable with no initial value"); 60 | return baseReduce(f, acc, iterator) as Acc; 61 | } else { 62 | return baseReduce(f, accOrIterable as Acc, iterable[Symbol.iterator]()); 63 | } 64 | } 65 | 66 | export function lesson1() { 67 | // Section 8. Classes as Lists: Learning from LISP 68 | // 8.1 Lazy Intermediate Operations and Terminal Operations 69 | 70 | class FxIterable { 71 | constructor(private iterable: Iterable) {} 72 | 73 | map(f: (a: A) => B) { 74 | return fx(map(f, this.iterable)); 75 | } 76 | 77 | filter(f: (a: A) => boolean) { 78 | return fx(filter(f, this.iterable)); 79 | } 80 | 81 | forEach(f: (a: A) => void) { 82 | return forEach(f, this.iterable); 83 | } 84 | 85 | reduce(f: (a: A, b: A) => Acc): Acc; 86 | reduce(f: (acc: Acc, a: A) => Acc, acc: Acc): Acc; 87 | reduce(f: (acc: A | Acc, a: A) => Acc, acc?: Acc): Acc { 88 | return acc === undefined 89 | ? reduce(f, this.iterable) 90 | : reduce(f, acc, this.iterable); 91 | } 92 | } 93 | 94 | function fx(iterable: Iterable) { 95 | return new FxIterable(iterable); 96 | } 97 | 98 | const mapped = fx([1, 2, 3, 4]).map(a => { 99 | console.log('map:', a); 100 | return a + 10; 101 | }); 102 | console.log(mapped); 103 | 104 | console.log( 105 | mapped.reduce((a, b) => a + b) 106 | ); 107 | 108 | const filtered = fx([1, 2, 3, 4]).filter(a => { 109 | console.log('filter:', a); 110 | return a % 2 === 0; 111 | }); 112 | console.log(filtered); 113 | 114 | console.log( 115 | filtered.reduce((a, b) => a + b) 116 | ); 117 | } 118 | -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-8/lesson-3.ts: -------------------------------------------------------------------------------- 1 | function* naturals(end = Infinity) { 2 | let n = 1; 3 | while (n <= end) { 4 | yield n++; 5 | } 6 | } 7 | 8 | function* map(f: (a: A) => B, iterable: Iterable): IterableIterator { 9 | for (const a of iterable) { 10 | yield f(a); 11 | } 12 | } 13 | 14 | function* filter(f: (a: A) => boolean, iterable: Iterable): IterableIterator { 15 | for (const a of iterable) { 16 | if (f(a)) { 17 | yield a; 18 | } 19 | } 20 | } 21 | 22 | function forEach(f: (a: A) => void, iterable: Iterable): void { 23 | for (const a of iterable) { 24 | f(a); 25 | } 26 | } 27 | 28 | function baseReduce( 29 | f: (acc: Acc, a: A) => Acc, 30 | acc: Acc, 31 | iterator: Iterator 32 | ): Acc { 33 | while (true) { 34 | const { done, value: a } = iterator.next(); 35 | if (done) break; 36 | acc = f(acc, a); 37 | } 38 | return acc; 39 | } 40 | 41 | function reduce( 42 | f: (a: A, b: A) => Acc, 43 | iterable: Iterable 44 | ): Acc; 45 | function reduce( 46 | f: (acc: Acc, a: A) => Acc, 47 | acc: Acc, 48 | iterable: Iterable 49 | ): Acc; 50 | function reduce( 51 | f: (acc: A | Acc, a: A) => Acc, 52 | accOrIterable: Acc | Iterable, 53 | iterable?: Iterable 54 | ): Acc { 55 | if (iterable === undefined) { 56 | iterable = accOrIterable as Iterable; 57 | const iterator = iterable[Symbol.iterator](); 58 | const { done, value: acc } = iterator.next(); 59 | if (done) throw new TypeError("'reduce' of empty iterable with no initial value"); 60 | return baseReduce(f, acc, iterator) as Acc; 61 | } else { 62 | return baseReduce(f, accOrIterable as Acc, iterable[Symbol.iterator]()); 63 | } 64 | } 65 | 66 | export function lesson3() { 67 | // 8.3 Using Destructuring - Turning Classes into Lists 68 | 69 | function fx(iterable: Iterable) { 70 | return new FxIterable(iterable); 71 | } 72 | 73 | class FxIterable { 74 | constructor(private iterable: Iterable) {} 75 | 76 | [Symbol.iterator]() { 77 | return this.iterable[Symbol.iterator](); 78 | } 79 | 80 | map(f: (a: A) => B) { 81 | return fx(map(f, this)); 82 | } 83 | 84 | filter(f: (a: A) => boolean) { 85 | return fx(filter(f, this)); 86 | } 87 | 88 | forEach(f: (a: A) => void) { 89 | return forEach(f, this); 90 | } 91 | 92 | reduce(f: (a: A, b: A) => Acc): Acc; 93 | reduce(f: (acc: Acc, a: A) => Acc, acc: Acc): Acc; 94 | reduce(f: (a: A | Acc, b: A) => Acc, acc?: Acc): Acc { 95 | return acc === undefined 96 | ? reduce(f, this) 97 | : reduce(f, acc, this); 98 | } 99 | 100 | toArray() { 101 | return [...this]; 102 | } 103 | } 104 | 105 | // # LISP (Clojure) 106 | // (let [[first second] (map #(+ % 10) [1 2 3 4])] 107 | // (println first second)) 108 | 109 | // # TS 110 | const [first, second] = fx([1, 2, 3, 4]).map(a => a + 10).map(a => a + 10); 111 | console.log(first, second); // 11 12 112 | } -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-9/Section9.ts: -------------------------------------------------------------------------------- 1 | import { SectionPage } from '../../SectionPage'; 2 | import { html } from 'rune-ts'; 3 | import { lesson1 } from './lesson-1'; 4 | import { lesson4 } from './lesson-4'; 5 | import { lesson5 } from './lesson-5'; 6 | import { lesson6 } from './lesson-6'; 7 | import { lesson7 } from './lesson-7'; 8 | import { lesson8 } from './lesson-8'; 9 | import { lesson9 } from './lesson-9'; 10 | 11 | const fs = [lesson1, lesson4, lesson5, lesson6, lesson7, lesson8, lesson9]; 12 | 13 | export class Section9 extends SectionPage { 14 | override title = 'Section9'; 15 | override link = '/part-1/section-9'; 16 | override lessons = fs; 17 | } 18 | -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-9/lesson-1.ts: -------------------------------------------------------------------------------- 1 | function* naturals(end = Infinity) { 2 | let n = 1; 3 | while (n <= end) { 4 | yield n++; 5 | } 6 | } 7 | 8 | function* map(f: (a: A) => B, iterable: Iterable): IterableIterator { 9 | for (const a of iterable) { 10 | yield f(a); 11 | } 12 | } 13 | 14 | function* filter(f: (a: A) => boolean, iterable: Iterable): IterableIterator { 15 | for (const a of iterable) { 16 | if (f(a)) { 17 | yield a; 18 | } 19 | } 20 | } 21 | 22 | function forEach(f: (a: A) => void, iterable: Iterable): void { 23 | for (const a of iterable) { 24 | f(a); 25 | } 26 | } 27 | 28 | function baseReduce( 29 | f: (acc: Acc, a: A) => Acc, 30 | acc: Acc, 31 | iterator: Iterator 32 | ): Acc { 33 | while (true) { 34 | const { done, value: a } = iterator.next(); 35 | if (done) break; 36 | acc = f(acc, a); 37 | } 38 | return acc; 39 | } 40 | 41 | function reduce( 42 | f: (a: A, b: A) => Acc, 43 | iterable: Iterable 44 | ): Acc; 45 | function reduce( 46 | f: (acc: Acc, a: A) => Acc, 47 | acc: Acc, 48 | iterable: Iterable 49 | ): Acc; 50 | function reduce( 51 | f: (acc: A | Acc, a: A) => Acc, 52 | accOrIterable: Acc | Iterable, 53 | iterable?: Iterable 54 | ): Acc { 55 | if (iterable === undefined) { 56 | iterable = accOrIterable as Iterable; 57 | const iterator = iterable[Symbol.iterator](); 58 | const { done, value: acc } = iterator.next(); 59 | if (done) throw new TypeError("'reduce' of empty iterable with no initial value"); 60 | return baseReduce(f, acc, iterator) as Acc; 61 | } else { 62 | return baseReduce(f, accOrIterable as Acc, iterable[Symbol.iterator]()); 63 | } 64 | } 65 | 66 | function fx(iterable: Iterable) { 67 | return new FxIterable(iterable); 68 | } 69 | 70 | class FxIterable { 71 | constructor(private iterable: Iterable) {} 72 | 73 | [Symbol.iterator]() { 74 | return this.iterable[Symbol.iterator](); 75 | } 76 | 77 | map(f: (a: A) => B) { 78 | return fx(map(f, this)); 79 | } 80 | 81 | filter(f: (a: A) => boolean) { 82 | return fx(filter(f, this)); 83 | } 84 | 85 | forEach(f: (a: A) => void) { 86 | return forEach(f, this); 87 | } 88 | 89 | reduce(f: (a: A, b: A) => Acc): Acc; 90 | reduce(f: (acc: Acc, a: A) => Acc, acc: Acc): Acc; 91 | reduce(f: (a: A | Acc, b: A) => Acc, acc?: Acc): Acc { 92 | return acc === undefined 93 | ? reduce(f, this) 94 | : reduce(f, acc, this); 95 | } 96 | 97 | toArray() { 98 | return [...this]; 99 | } 100 | 101 | to(converter: (iterable: this) => R) { 102 | return converter(this); 103 | } 104 | 105 | chain(f: (iterable: this) => Iterable) { 106 | return fx(f(this)); 107 | } 108 | 109 | take(limit: number) { 110 | return fx(take(limit, this)); 111 | } 112 | } 113 | 114 | function* take(limit: number, iterable: Iterable): IterableIterator { 115 | for (const a of iterable) { 116 | yield a; 117 | if (--limit === 0) break; 118 | } 119 | } 120 | 121 | export function lesson1() { 122 | // Section 9. List Processing – Code as Data, Logic in a List 123 | // 9.1 (for, i++, if, break) – Thinking of Code as a List 124 | 125 | const sumOfSquaresOfOddNumbers = (limit: number, list: number[]) => 126 | fx(list) 127 | .filter(a => a % 2 === 1) 128 | .map(a => a * a) 129 | .take(limit) 130 | .reduce((a, b) => a + b, 0); 131 | 132 | // function sumOfSquaresOfOddNumbers(limit: number, list: number[]): number { 133 | // let acc = 0; 134 | // for (const a of list) { 135 | // if (a % 2 === 1) { 136 | // const b = a * a; 137 | // acc += b; 138 | // if (--limit === 0) break; 139 | // } 140 | // } 141 | // return acc; 142 | // } 143 | 144 | console.log( 145 | sumOfSquaresOfOddNumbers(3, [1, 2, 3, 4, 5, 6, 7, 8, 9]) 146 | ); 147 | // 35 148 | } 149 | 150 | 151 | 152 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-9/lesson-4.ts: -------------------------------------------------------------------------------- 1 | function fx(iterable: Iterable): FxIterable { 2 | return new FxIterable(iterable); 3 | } 4 | 5 | class FxIterable { 6 | constructor(private iterable: Iterable) {} 7 | 8 | [Symbol.iterator]() { 9 | return this.iterable[Symbol.iterator](); 10 | } 11 | 12 | map(f: (a: A) => B): FxIterable { 13 | return fx(map(f, this)); 14 | } 15 | 16 | filter(f: (a: A) => boolean): FxIterable { 17 | return fx(filter(f, this)); 18 | } 19 | 20 | take(limit: number): FxIterable { 21 | return fx(take(limit, this)); 22 | } 23 | } 24 | 25 | function* filter(f: (a: A) => boolean, iterable: Iterable): IterableIterator { 26 | const iterator = iterable[Symbol.iterator](); 27 | while (true) { 28 | console.log('filter'); 29 | const { value, done } = iterator.next(); 30 | if (done) break; 31 | if (f(value)) yield value; 32 | } 33 | } 34 | 35 | function* map(f: (a: A) => B, iterable: Iterable): IterableIterator { 36 | const iterator = iterable[Symbol.iterator](); 37 | while (true) { 38 | console.log('map'); 39 | const { value, done } = iterator.next(); 40 | if (done) break; 41 | yield f(value); 42 | } 43 | } 44 | 45 | function* take(limit: number, iterable: Iterable): IterableIterator { 46 | const iterator = iterable[Symbol.iterator](); 47 | while (true) { 48 | console.log('take limit:', limit); 49 | const { value, done } = iterator.next(); 50 | if (done) break; 51 | yield value; 52 | if (--limit === 0) break; 53 | } 54 | } 55 | 56 | export function lesson4() { 57 | // 9.4 Execution Order in Lazy Evaluation 58 | 59 | const iterable = fx([1, 2, 3, 4, 5]) 60 | .filter(a => a % 2 === 1) 61 | .map(a => a * a) 62 | .take(2); 63 | 64 | for (const a of iterable) { 65 | console.log('result:', a); 66 | } 67 | 68 | /* 69 | (1) 70 | filter 71 | filter 72 | filter 73 | map 74 | map 75 | take limit: 2 76 | take limit: 1 77 | result: 1 78 | result: 9 79 | 80 | (2) 81 | filter 82 | map 83 | take limit: 2 84 | result: 1 85 | filter 86 | filter 87 | map 88 | take limit: 1 89 | result: 9 90 | * */ 91 | } 92 | -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-9/lesson-5.ts: -------------------------------------------------------------------------------- 1 | export function lesson5() { 2 | // 9.5 The `find` Function and Safe Composition 3 | 4 | function* filter(f: (a: A) => boolean, iterable: Iterable): IterableIterator { 5 | for (const a of iterable) { 6 | if (f(a)) yield a; 7 | } 8 | } 9 | 10 | function head(iterable: Iterable): A | undefined { 11 | const [first] = iterable; 12 | return first; 13 | } 14 | 15 | function find(f: (a: A) => boolean, iterable: Iterable): A | undefined { 16 | return head(filter(f, iterable)); 17 | } 18 | 19 | const result = find(a => a > 2, [1, 2, 3, 4]); 20 | console.log(result); 21 | // 3 22 | 23 | const result2 = find(a => a % 2 === 1, [2, 4, 6]); 24 | console.log(result2); 25 | // undefined 26 | 27 | type Menu = { 28 | name: string, 29 | price: number, 30 | } 31 | 32 | const desserts: Menu[] = [ 33 | { name: 'Chocolate', price: 5000 }, 34 | { name: 'Latte', price: 3500 }, 35 | { name: 'Coffee', price: 3000 } 36 | ]; 37 | 38 | const dessert = find(({ price }) => price < 2000, desserts); 39 | console.log(dessert?.name ?? 'T^T'); 40 | // T^T 41 | 42 | const dessert2 = find(({ price }) => price < Infinity , desserts)!; 43 | console.log(dessert2.name); 44 | // Chocolate 45 | } 46 | -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-9/lesson-6.ts: -------------------------------------------------------------------------------- 1 | import { fx } from '../../lib/fx1'; 2 | 3 | export function lesson6() { 4 | // 9.6 Implementing `some` and `every` via List Processing 5 | 6 | const isOdd = (a: number) => a % 2 === 1; 7 | 8 | // function some(f: (a: A) => boolean, iterable: Iterable): boolean { 9 | // return fx(iterable) 10 | // .filter(f) 11 | // .take(1) 12 | // .toArray() 13 | // .length > 0; 14 | // } 15 | 16 | function some(f: (a: A) => boolean, iterable: Iterable): boolean { 17 | return fx(iterable) 18 | .map(f) 19 | .filter(a => a) 20 | .take(1) 21 | .reduce((a, b) => a || b, false); 22 | } 23 | 24 | console.log(some(isOdd, [2, 5, 6])); 25 | // true 26 | console.log(some(isOdd, [2, 4, 6])); 27 | // false 28 | 29 | function every(f: (a: A) => boolean, iterable: Iterable): boolean { 30 | return fx(iterable) 31 | .filter(a => !f(a)) 32 | .take(1) 33 | .toArray() 34 | .length === 0; 35 | } 36 | 37 | // console.log(every(isOdd, [1, 3, 5])); 38 | // true 39 | // console.log(every(isOdd, [1, 2, 5, 4, 6, 7, 8])); 40 | // false 41 | } 42 | 43 | -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-9/lesson-7.ts: -------------------------------------------------------------------------------- 1 | import { fx } from '../../lib/fx1'; 2 | 3 | export function lesson7() { 4 | // 9.7 Iterator Helpers 5 | 6 | function* naturals(end = Infinity) { 7 | let n = 1; 8 | while (n <= end) { 9 | yield n++; 10 | } 11 | } 12 | 13 | Iterator.from(naturals(Infinity)) 14 | .map(a => a + 10) 15 | .filter(a => a % 2 === 1) 16 | .take(5) 17 | .forEach(a => console.log(a)); 18 | 19 | const iterator = Iterator.from(naturals(Infinity)); 20 | console.log(iterator.next()); 21 | console.log(iterator.next()); 22 | console.log(iterator.next()); 23 | console.log(iterator.next()); 24 | 25 | const iter2 = Iterator.from([1, 2, 3, 4, 5]); 26 | 27 | iter2 28 | .map(a => a + 10) 29 | .filter(a => a % 2 === 1) 30 | .take(5) 31 | .forEach(a => console.log(a)); 32 | 33 | } 34 | -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-9/lesson-8.ts: -------------------------------------------------------------------------------- 1 | function* concat(...iterables: Iterable[]): IterableIterator { 2 | for (const iterable of iterables) yield* iterable; 3 | } 4 | 5 | export function lesson8() { 6 | const arr = [1, 2, 3, 4]; 7 | const iter = concat(arr, [5, 6], [7, 8]); 8 | console.log([...iter]); 9 | code1(); 10 | code2(); 11 | code3(); 12 | code4(); 13 | } 14 | 15 | function code1() { 16 | const arr = [1, 2, 3, 4, 5]; 17 | 18 | arr.push(6, 7); 19 | let acc1 = 0; 20 | for (const a of arr) { 21 | acc1 += a; 22 | } 23 | console.log(acc1); // 28 24 | console.log(arr); // [1, 2, 3, 4, 5, 6, 7] 25 | 26 | arr.pop(); 27 | arr.pop(); 28 | 29 | arr.push(8, 9); 30 | let acc2 = 0; 31 | for (const a of arr) { 32 | acc2 += a; 33 | } 34 | console.log(acc2); // 32 35 | console.log(arr); // [1, 2, 3, 4, 5, 8, 9] 36 | 37 | arr.pop(); 38 | arr.pop(); 39 | } 40 | 41 | function code2() { 42 | const arr = [1, 2, 3, 4, 5]; 43 | 44 | const iter1 = concat(arr, [6, 7]); 45 | let acc3 = 0; 46 | for (const a of iter1) { 47 | acc3 += a; 48 | } 49 | console.log(acc3); // 28 50 | console.log(arr); // [1, 2, 3, 4, 5]; 51 | 52 | const iter2 = concat(arr, [8, 9]); 53 | let acc4 = 0; 54 | for (const a of iter2) { 55 | acc4 += a; 56 | } 57 | console.log(acc4); // 32 58 | console.log(arr); // [1, 2, 3, 4, 5] 59 | } 60 | 61 | function code3() { 62 | const arr = ['2', '3', '4', '5']; 63 | arr.unshift('1'); 64 | console.log(arr); // ['1', '2', '3', '4', '5'] 65 | let result1 = ''; 66 | for (const str of arr) { 67 | result1 += str; 68 | } 69 | console.log(result1); // '12345' 70 | } 71 | 72 | function code4() { 73 | const arr = ['2', '3', '4', '5']; 74 | const iter = concat(['1'], arr); 75 | console.log(arr); // ['2', '3', '4', '5'] 76 | let result2 = ''; 77 | for (const str of iter) { 78 | result2 += str; 79 | } 80 | console.log(result2); // '12345' 81 | } -------------------------------------------------------------------------------- /apps/lecture/src/part-1/section-9/lesson-9.ts: -------------------------------------------------------------------------------- 1 | function* concat(...iterables: Iterable[]): IterableIterator { 2 | for (const iterable of iterables) { 3 | for (const a of iterable) { 4 | yield a; 5 | } 6 | } 7 | } 8 | 9 | export function lesson9() { 10 | // 9.9 Using `concat` Together with `take` and `some` 11 | code1(); 12 | code2(); 13 | } 14 | 15 | function code1() { 16 | const iter = limitedConcat( 17 | 7, 18 | [1, 2, 3, 4, 5], 19 | [6, 7, 8, 9, 10] 20 | ); 21 | console.log([...iter]); 22 | 23 | const iter2 = limitedConcat( 24 | 3, 25 | [1, 2, 3, 4, 5], 26 | [6, 7, 8, 9, 10] 27 | ); 28 | console.log([...iter2]); 29 | } 30 | 31 | function limitedConcat(limit: number, arr1: number[], arr2: number[]) { 32 | return take(limit, concat(arr1, arr2)); 33 | } 34 | 35 | function* take(limit: number, iterable: Iterable): IterableIterator { 36 | for (const a of iterable) { 37 | yield a; 38 | if (--limit === 0) break; 39 | } 40 | } 41 | 42 | function code2() { 43 | const arr = [3, 4, 5]; 44 | console.log(some(n => n < 3, arr)); 45 | // false 46 | 47 | const iter = concat([1, 2], arr); 48 | console.log(some(n => n < 3, iter)); 49 | // true 50 | } 51 | 52 | function some(f: (a: A) => boolean, iterable: Iterable): boolean { 53 | return Iterator.from(iterable).some(f); 54 | } -------------------------------------------------------------------------------- /book/en/0.1-From-the-Author.md: -------------------------------------------------------------------------------- 1 | ## From the Author 2 | 3 | Programming languages used to be divided into functional, object-oriented, and procedural categories. However, this trend is shifting. With the emergence of hybrid programming languages that incorporate both functional and object-oriented features, most major programming languages today—such as Java, C#, Scala, TypeScript, JavaScript, Kotlin, and Swift—have become multiparadigm languages. This shift has become a shared characteristic of many languages widely used in the industry. 4 | 5 | Multiparadigm languages provide not only classic object-oriented features like classes, inheritance, and interfaces, but also a wide range of functional programming capabilities, such as first-class functions, list processing, lazy evaluation, and asynchronous/concurrent processing. They also offer flexible type systems, type inference, generics, and utility types. In some cases, they even mix static and dynamic typing. Why have most languages evolved into these multiparadigm forms? There must be clear advantages driving this change. 6 | 7 | Mike Loukides, Vice President at O’Reilly Media and author of numerous technical books, recently stated in his article, “We need to learn how to use multiparadigm languages that provide functional, object-oriented, and imperative paradigms more effectively,” adding that “many tools have improved, and now it’s our turn to learn how to use them well. It’s time to blend paradigms that were once distinct—and even in conflict—for a long time.” 8 | 9 | In this current environment, using only a single paradigm could be a missed opportunity. Some problems are best solved with a functional approach, while others are better suited for object-oriented programming. Programmers no longer need to argue over whether functional or object-oriented programming is superior, or whether static or dynamic typing is better. Organizations and committees that develop languages and define standards already support multiple paradigms within a single language and platform, enabling us to combine these paradigms within a single project—even within a single function. 10 | 11 | We might be missing out on the full range of language-level features and paradigms if we rely only on specific libraries or frameworks. In fact, many technologies rooted in functional and object-oriented programming boast a long and established history. They have already been verified through solving numerous problems over the years. Programmers who master a variety of language-level skills can solve more problems quickly, safely, and flexibly. They can also write code in a more fundamental way, free from overreliance on any particular library. 12 | 13 | This book primarily uses TypeScript, but the techniques and concepts covered are also applicable to Java, C#, Scala, Kotlin, Swift, and other languages. It aims to help you apply functional programming in practical settings, while continuing to use your existing environment and language. By providing solutions that combine object-oriented and functional programming, this book shows how to effectively leverage the power of multiparadigm languages, thus expanding your problem-solving capabilities and improving your technical proficiency. 14 | 15 | For frontend developers, this book focuses on the language features and the latest Web APIs, demonstrating examples of high-performance, smooth UI implementations. For backend developers, it explains how to work effectively with asynchronous programming and how to combine data efficiently and safely. It also discusses patterns and ideas for writing easily understandable, readable code when working with team members—ultimately increasing productivity and reducing errors. I hope this book helps you experience the wide-ranging possibilities of multiparadigm languages and deepens your enjoyment of programming. 16 | 17 | --- 18 | 19 | ## Navigation 20 | 21 | - [Table of Contents](README.md) 22 | - [Prev](README.md) 23 | - [Next](0.2-Endorsements.md) -------------------------------------------------------------------------------- /book/en/0.3-Environment-Setup-and-Example-Code.md: -------------------------------------------------------------------------------- 1 | ## Environment Setup and Example Code 2 | 3 | ### Official GitHub Page 4 | 5 | All the TypeScript examples from this book can be found in the following GitHub repository: 6 | 7 | - GitHub: https://github.com/marpple/multi-paradigm-programming 8 | 9 | The repository includes everything needed to run the examples, such as installation instructions, `tsconfig.json` settings, and required packages. By simply following the setup process, you can quickly run the examples provided in the book. 10 | 11 | ### Tips for Learning with Example Code 12 | 13 | While the example code is fully prepared, we encourage you to create new files in the same environment and write the code yourself. By writing and running the code while referring to the book, you can achieve a deeper and more effective learning experience. 14 | 15 | ### Additional Resources and Information 16 | 17 | The official GitHub page also provides updates on various resources, such as video lectures that align with the book's content, links to online communities for collaborative learning, and a YouTube channel. If you’re interested, feel free to visit the GitHub repository for more information. 18 | 19 | --- 20 | 21 | ## Navigation 22 | 23 | - [Table of Contents](README.md) 24 | - [Prev](0.2-Endorsements.md) 25 | - [Next](1.0.-How-Multiparadigm-Is-Expanding-Modern-Languages.md) -------------------------------------------------------------------------------- /book/en/1.0.-How-Multiparadigm-Is-Expanding-Modern-Languages.md: -------------------------------------------------------------------------------- 1 | ## 1. How Multiparadigm Is Expanding Modern Languages 2 | 3 | In the past, if you wanted to use the functional paradigm, you had to choose a functional language; if you wanted to use object-oriented programming, you had to choose an object-oriented language. Today, however, most programming languages have evolved into multiparadigm languages. This shift began in earnest around 2010, and especially after 2020, nearly all languages commonly used in the industry now support imperative, object-oriented, and functional paradigms simultaneously. 4 | 5 | Many languages started with object-oriented foundations but have grown in various ways—introducing first-class functions, iteration helper methods that leverage lazy evaluation, built-in features for handling asynchrony, and more. As a result, developers can now leverage multiple paradigms at a mature level within a single language. 6 | 7 | I myself have only recently come to fully embrace this change. Even though today’s languages make it possible to combine multiple paradigms in a single function, I sometimes found myself leaning toward just one paradigm—perhaps the one I preferred or had recently learned. In the past, I was a big fan of object-oriented programming, yet I assumed these paradigms could not be mixed. It seems I wasn’t alone; many developers, as well as open-source library and framework designers, often structure an entire project around a single paradigm or concept. Ironically, this happens even though the very languages we use—the core of programming—have already become multiparadigm. 8 | 9 | Every programming paradigm is a tool for building successful software. We often tend to design and integrate our projects and code so that only a single paradigm or concept is in use, but doing so can actually limit our opportunities for better programming. Sometimes we might even do this knowingly. Some problems are best solved using an imperative style, others using a functional approach, and still others using object-oriented techniques. Yet we may have forced our software or code into an ill-fitting paradigm—potentially sacrificing performance or user experience along the way. 10 | 11 | Today, we live in an era when we can combine multiple paradigms effectively to write better code. In this book, we’ll explore the beautiful code and problem-solving methods that emerge when different paradigms come together. This first chapter tells the story of how these paradigms began to meet in harmony, setting the stage for the journey that lies ahead. 12 | 13 | --- 14 | 15 | ## Navigation 16 | 17 | - [Table of Contents](README.md) 18 | - [Prev](0.3-Environment-Setup-and-Example-Code.md) 19 | - [Next](1.1-The-Iterator-Pattern-in-OOP-and-First-Class-Functions.md) -------------------------------------------------------------------------------- /book/en/2.0-Functional-Programming,-Type-Systems,-and-Lisp.md: -------------------------------------------------------------------------------- 1 | ## 2. Functional Programming, Type Systems, and Lisp 2 | 3 | Programming paradigms and languages are constantly evolving. Today, we live in an era of multiparadigm languages, where object-oriented, functional, and imperative paradigms coexist. Among these, the functional paradigm helps write predictable and readable code through concepts like immutability, pure functions, higher-order functions, and lazy evaluation. These functional techniques are being adopted by more and more languages, including JavaScript and TypeScript. 4 | 5 | In this chapter, we will delve into the core principles of functional programming and explore how TypeScript’s type system supports them. We’ll learn how to leverage type inference, higher-order functions and function types, generics, and other type features to write safer, more maintainable code. We’ll also look at how to combine object-oriented classes and functional functions effectively. 6 | 7 | Additionally, we’ll examine some core ideas from **Lisp**, often considered the root of functional programming. Lisp’s unique approach of treating code itself as list-structured data enables flexible and powerful metaprogramming. Many of Lisp’s ideas have profoundly influenced modern multiparadigm languages, and JavaScript and TypeScript likewise incorporate key concepts that can be traced back to Lisp. 8 | 9 | By the end of this chapter, you’ll understand how functional programming interacts with a type system, as well as the charm and elegant philosophy behind Lisp. Building on this foundation, we’ll explore how to write safer, more efficient, and more readable code. 10 | 11 | --- 12 | 13 | ## Navigation 14 | 15 | - [Table of Contents](README.md) 16 | - [Prev](1.6-Summary.md) 17 | - [Next](2.1-Type-Inference,-Function-Types,-and-Generics.md) -------------------------------------------------------------------------------- /book/en/2.4-Summary.md: -------------------------------------------------------------------------------- 1 | ## 2.4 Mid-Chapter Summary 2 | 3 | #### Type Inference and Function Types in TypeScript 4 | 5 | TypeScript adds a type system to JavaScript, enhancing code reliability and readability. Its type inference feature allows you to write safe code without explicit type declarations, automatically inferring types for variables and function return values. For example, in `let a = 10;`, `a` is inferred to be `number`, and in a function like `(a: number, b: number) => a + b;`, the return type is automatically determined to be `number`. 6 | 7 | #### Type Inference for Variables and Constants 8 | 9 | When you initialize variables and constants, TypeScript infers their types from the assigned values. Declaring `const selected = true;` infers that `selected` has the literal type `true`, whereas declaring `let checked = true;` infers that `checked` is of type `boolean`. 10 | 11 | #### Type Inference via Generics 12 | 13 | Generic functions let you implement polymorphic functions that can handle diverse types. For example, a generic function like `function identity(arg: T): T;` flexibly determines its return type depending on the type of its argument. 14 | 15 | #### Function Types and Generics 16 | 17 | To support functional programming, TypeScript provides features such as higher-order functions, function types, and generics. These enable you to clearly define both the input and output types of functions and to implement versatile functions that accommodate various types with ease. 18 | 19 | #### Iterable Helper Functions 20 | 21 | Iterable helper functions follow a pattern where higher-order functions revolve around iterables. In TypeScript, you can supply precise type information for these functions, further improving code safety and readability. By leveraging generics (e.g., `function* map(f: (a: A) => B, iterable: Iterable): IterableIterator`), you can create iteration functions with clearly defined input and output types. 22 | 23 | #### LISP and Metaprogramming 24 | 25 | LISP-family languages treat code and data identically, making metaprogramming easy to implement. LISP’s S-expressions have a list-based syntax that can treat code itself as data, offering powerful capabilities to dynamically transform and generate code. 26 | 27 | #### Class + Higher-Order Functions + Iterable 28 | 29 | As seen in the `FxIterable` class example, combining classes, higher-order functions, and iterables can produce highly expressive abstractions. For instance, `const [first, second] = fx([1, 2, 3, 4]).map(a => a * 10);` shows how a user-defined object can be flexibly integrated with language features (in this case, destructuring) and how you can use a chaining pattern to write highly readable code. 30 | 31 | #### High-Level Abstractions Achievable in Modern Programming Languages 32 | 33 | The introduction of first-class functions and the spread of iteration protocols have gone beyond the traditional class-based iterator pattern, achieving a higher level of abstraction that naturally combines various paradigms in modern programming languages. Major languages—such as Java 8 (2014), Swift (2014), JavaScript/TypeScript (ES6, 2015), Kotlin (2016), and C# (LINQ, 2007)—provide a variety of features, including first-class functions, iterables, generics, higher-order functions, currying, and rich type systems. This makes it possible to organically combine object-oriented components (including classes), core concepts of functional programming, and standardized iteration methods without modifying language specs or compilers. 34 | 35 | As a result, developers are no longer restricted to a single paradigm. They can implement whatever level of abstraction they need to handle diverse requirements, all built upon a solid foundation that can be widely applied across various platforms and domains. This robust foundation enables practical language extensions and patterns that can be adapted to address a wide range of problem domains. 36 | 37 | --- 38 | 39 | ## Navigation 40 | 41 | - [Table of Contents](README.md) 42 | - [Prev](2.3-Multiparadigm-Languages-and-Metaprogramming-–-From-LISP.md) 43 | - [Next](3.0-Code%3AObject%3AFunction-=-Generator%3AIterator%3ALISP-=-IP%3AOOP%3AFP.md) -------------------------------------------------------------------------------- /book/en/3.0-Code:Object:Function-=-Generator:Iterator:LISP-=-IP:OOP:FP.md: -------------------------------------------------------------------------------- 1 | ## 3. Code:Object:Function = Generator:Iterator:LISP = IP:OOP:FP 2 | 3 | Generators written in **imperative** code, Iterators that implement the **object-oriented** Iterator pattern, and list processing (the core of **LISP**) share deep interconnections. They act as both creators and consumers of each other, enabling highly abstract programming while offering a prime example of how IP (Imperative Programming), OOP (Object-Oriented Programming), and FP (Functional Programming) can converge. 4 | 5 | Below is a summary of the concepts covered in Chapters 1 and 2, viewed through the lens of **Generator:Iterator:LISP = IP:OOP:FP**. 6 | 7 | 1. **Iterator** is the concrete implementation of the Iterator pattern. 8 | - An iterator is an object that traverses collection-like data using a generalized pattern. 9 | - Owing to its **laziness**, which evaluates values only when needed, it can handle not just finite collections but also infinite sequences. 10 | 11 | 2. **Generator** creates iterators using imperative code. 12 | - A generator function is a mechanism for building an iterator in an imperative code style. 13 | - Because execution can pause and resume at `yield`, the generator’s code is effectively evaluated lazily in list-sized chunks. This aligns with the “code as list, list as code” perspective of LISP. 14 | 15 | 3. **Iterable Iterators** can be handled imperatively, object-orientedly, or functionally. 16 | - **Imperatively**, you can call `next()` directly inside a `while` loop or use `for...of` and spread (`...`) to iterate. 17 | - **Object-oriented** approaches can encapsulate iterable iterators in a class or chain multiple iterators internally. 18 | - **Functionally**, you can use higher-order functions (HOFs) to handle iterators by passing functions that process each element, implementing iteration logic through function composition while maximizing lazy evaluation and list processing. 19 | 20 | 4. **Multiple Ways to Create Iterators** 21 | - You can build an iterator yourself by directly implementing the Iterator interface, or can produce one imperatively via a generator function. 22 | - You can also generate one in a functional style by combining list-processing based functions (to be examined in Chapter 3). 23 | - Ultimately, iterators can be created in these three ways, each substitutable on a 1:1:1 basis: 24 | 1. **Imperative (IP)** – Generating iterators with a generator 25 | 2. **Object-Oriented (OOP)** – Directly implementing an iterator object 26 | 3. **Functional (FP)** – Combining list-processing functions to produce an iterator 27 | 28 | All this demonstrates how the iteration protocol serves as a linking bridge across various paradigms. In Chapter 3, we’ll focus specifically on handling iterators from a functional perspective and on **lazy evaluation**—expanding the Lisp-inspired idea of “code as lists” and examining how these multi-pronged approaches form the robust foundation of modern programming. 29 | 30 | --- 31 | 32 | ## Navigation 33 | 34 | - [Table of Contents](README.md) 35 | - [Prev](2.4-Summary.md) 36 | - [Next](3.1-Code-Is-Data-–-A-List-Containing-Logic.md) -------------------------------------------------------------------------------- /book/en/3.5-Summary.md: -------------------------------------------------------------------------------- 1 | ## 3.5 Summary 2 | 3 | ### Code:Object:Function = Generator:Iterator:LISP = IP:OOP:FP 4 | 5 | “Code, object, function” can each be viewed through the lens of “generator, iterator, LISP,” respectively. An **iterator** is the implementation of the Iterator pattern, an object that traverses collection values in a generalized manner. Because an iterator is **lazy**, it executes only as far as it needs and can pause execution. This makes it possible to create iterators that could, in principle, be consumed indefinitely. Consequently, combining imperative, object-oriented, and functional paradigms yields a powerful programming model. 6 | 7 | ### Code as Data: A Logic-Bearing List 8 | 9 | The philosophy of Lisp reflects a programming language paradigm that blurs the boundary between code and data. In Lisp’s S-expressions, logic is represented as a list, enabling code to be more flexible and extensible. The LISP concept can be applied to modern programming languages, by treating code and data as one and the same. LISP's metaprogramming capability makes it possible to treat code and data identically, allowing them to be dynamically generated and manipulated. Through this, programmers can write more flexible and powerful programs. 10 | 11 | ### Learning from Haskell 12 | 13 | Haskell is a purely functional programming language that provides pure functions, function composition, currying, lazy evaluation, and a robust type system. Studying Haskell offers insight into functional programming concepts and how to apply them in modern languages. Its function signatures and pattern matching strengthen both code safety and readability. In particular, Haskell’s powerful type system can infer types without explicit annotations, helping developers write safe yet concise code. 14 | 15 | ### A Closer Look at Lazy Evaluation 16 | 17 | **Lazy evaluation** is a strategy in which computations are deferred until absolutely necessary, helping optimize performance. Through practical implementations and application examples of lazy evaluation, we can write more efficient code. It is an important concept for performance optimization and resource conservation, enabling us to create better programs. Lazy evaluation is particularly useful in processing large-scale data and complex calculations, as it performs computations only when needed, reducing unnecessary resource waste. 18 | 19 | ### Generator:Iterator:LISP – Lazy Evaluation and Safe Composition 20 | 21 | We’ve seen that we can implement higher-order functions like `find`, `every`, and `some` purely by combining list-processing functions. In TypeScript, operators such as the optional chaining operator (`?.`) and the non-null assertion operator (`!`) let us handle potentially missing values in a safe and expressive manner. Meanwhile, list-processing functions remain efficient through lazy evaluation, and functional design makes it easy to abstract out common logic to eliminate duplication. 22 | 23 | --- 24 | 25 | ## Navigation 26 | 27 | - [Table of Contents](README.md) 28 | - [Prev](3.4-Generator%3AIterator%3ALISP-–-Lazy-Evaluation-and-Safe-Composition.md) 29 | - [Next](README.md) -------------------------------------------------------------------------------- /book/ko/0.1-지은이의-글.md: -------------------------------------------------------------------------------- 1 | ## 지은이의 글 2 | 3 | 프로그래밍 언어는 과거에 주로 함수형, 객체 지향, 절차적 언어로 나뉘어 왔습니다. 그러나 오늘날에는 이러한 경향이 바뀌고 있습니다. 함수형과 객체 지향 기능을 모두 가진 하이브리드 프로그래밍 언어가 등장하기 시작하면서 이제는 Java, C#, Scala, TypeScript, JavaScript, Kotlin, Swift 등 대부분의 주요 프로그래밍 언어들이 멀티패러다임 언어가 되었습니다. 이러한 변화는 현업에서 널리 사용되는 언어들의 공통된 특징이 되었습니다. 4 | 5 | 멀티패러다임 언어들은 클래스, 상속, 인터페이스 같은 객체 지향 기능은 물론, 일급 함수, 리스트 프로세싱, 지연 평가, 비동기/동시성 처리 등 함수형 프로그래밍 기능도 폭넓게 지원합니다. 또한 유연한 타입 시스템, 타입 추론, 제네릭, 유틸리티 타입 등을 제공하며, 심지어는 정적 타입과 동적 타입을 혼합하는 것도 가능합니다. 그렇다면 왜 대부분의 언어들이 이렇게 여러 프로그래밍 패러다임을 지원하는 멀티패러다임 언어가 되었을까요? 이러한 변화가 일어난 데에는 분명한 이점들이 존재하기 때문이 아닐까요? 6 | 7 | 오라일리 미디어의 부사장이자 여러 기술 서적의 저자인 마이크 루키데스는 최근 그의 글에서 "우리는 함수형, 객체 지향, 명령형 패러다임을 제공하는 멀티패러다임 언어를 효율적으로 사용하는 법에 대해 배워야 한다"면서 "많은 툴들이 개선되었고 이제 우리는 어떻게 잘 사용할지를 배울 차례다. 그리고 이제는 오랫동안 각각 특별히 고유했고 심지어 갈등 속에 있던 패러다임들을 섞어야 할 때다"라고 했습니다. 8 | 9 | 이러한 환경이 갖춰진 지금, 어느 한쪽의 패러다임만 사용하는 것은 어쩌면 크게 놓친 기회일 수 있습니다. 우리가 해결해야 하는 문제 중에는 함수형으로 해결하는 것이 우세한 경우가 있고, 객체 지향 프로그래밍으로 해결하는 것이 더 우세한 경우들도 있습니다. 프로그래머들은 이제 더 이상 함수형이 좋은지, 객체 지향이 좋은지, 정적 타입이 좋은지, 동적 타입이 좋은지 논쟁할 필요가 없습니다. 언어를 만들고 표준을 정의하는 단체와 기업들이 이미 하나의 언어와 플랫폼에서 여러 패러다임을 지원하도록 만들어주었고, 우리는 하나의 프로젝트, 심지어는 하나의 함수 안에서도 여러 패러다임을 조합하여 프로그래밍할 수 있게 되었습니다. 10 | 11 | 현재 우리는 어쩌면, 특정 라이브러리나 프레임워크에 의존하며, 그에 맞는 한정적인 패러다임을 따르다가 언어 레벨의 다양한 기능과 패러다임을 충분히 활용하지 못하고 있을지도 모릅니다. 사실 함수형 프로그래밍 언어나 객체 지향 프로그래밍 언어에 적용되어온 많은 기술들은 매우 깊은 역사와 정통성을 가지고 있으며 이미 다양한 문제를 해결하며 검증되어왔습니다. 언어 레벨의 다양한 스킬을 가진 프로그래머는 더 많은 문제를 빠르고 안전하게, 그리고 유연하게 해결할 수 있습니다. 또한 특정 라이브러리에 얽매이지 않고 보다 근본적인 방식으로 코드를 작성할 수 있습니다. 12 | 13 | 이 책은 TypeScript를 주요 언어로 사용하며, Java, C#, Scala, Kotlin, Swift 등 다른 언어에서도 적용할 수 있는 기술과 개념을 다룹니다. 기존 환경과 언어를 유지하면서도 함수형 프로그래밍을 실무에 적용할 수 있도록 돕습니다. 또한 객체 지향 프로그래밍과 함수형 프로그래밍을 혼합해 문제를 해결하는 코드를 제시함으로써, 멀티패러다임 언어를 효과적으로 다루는 기술력과 응용력을 높여 문제 해결 능력을 확장할 수 있도록 돕습니다. 14 | 15 | 프론트엔드 개발자에게는 언어와 최신 Web API를 중심으로, 성능이 좋고 매끄러운 UI를 구현하는 사례를 제시합니다. 백엔드 개발자에게는 비동기 프로그래밍을 잘 다루는 법과, 효율적이고 안전하게 데이터를 합성하는 방법을 소개합니다. 또한 팀원들과 함께 쉽게 이해하고, 읽기 좋은 코드를 작성하는 법을 비롯해 생산성을 높이고 에러를 줄이는 여러 코딩 패턴과 아이디어도 다룹니다. 이 책이 멀티패러다임 언어의 다양한 가능성을 실감하고, 프로그래밍의 즐거움을 한층 깊이 누릴 수 있는 계기가 되었으면 좋겠습니다. 16 | 17 | --- 18 | 19 | ## 네비게이션 20 | 21 | - [목차](README.md) 22 | - [이전](README.md) 23 | - [다음](0.2-추천의-글.md) -------------------------------------------------------------------------------- /book/ko/0.3-환경-설치-및-예제-코드.md: -------------------------------------------------------------------------------- 1 | ## 환경 설치 및 예제 코드 2 | 3 | ### 공식 GitHub 페이지 4 | 5 | 이 책의 모든 타입스크립트 예제는 아래 깃허브 저장소에서 확인하실 수 있습니다. 6 | 7 | - GitHub: https://github.com/marpple/multi-paradigm-programming 8 | 9 | 해당 저장소에는 설치 방법, tsconfig.json 설정, 필요 패키지 등 예제 코드에 필요한 구성이 준비되어있으므로, 설치만으로 바로 예제를 실행해 볼 수 있습니다. 10 | 11 | ### 예제 코드 학습 팁 12 | 13 | 예제 코드가 준비되어 있지만, 동일한 환경에서 새로운 파일을 만들어 직접 코드를 작성해 보시는 것을 권장드립니다. 책을 보며 코드를 새롭게 작성하고 실행해 보는 과정에서, 더 깊이 있는 학습 효과를 기대할 수 있습니다. 14 | 15 | ### 추가 학습 자료 및 정보 16 | 17 | 더욱 폭넓은 학습을 원하는 분들을 위해, 공식 깃허브 페이지에 이 책과 동일한 내용으로 제작된 영상 강의, 함께 공부할 수 있는 온라인 커뮤니티 링크, 유튜브 채널 등 여러 자료가 지속적으로 업데이트되고 있습니다. 또한, 온라인 커뮤니티에서는 이 책과 관련된 퀴즈와 정답 풀이, 다양한 교육 자료가 공유되고 있어, 책의 내용을 더 깊이 이해하고 응용력을 키우는 데 큰 도움이 됩니다. 필요하신 분들은 꼭 방문해 참고해 보세요. 18 | 19 | --- 20 | 21 | ## 네비게이션 22 | 23 | - [목차](README.md) 24 | - [이전](0.2-추천의-글.md) 25 | - [다음](1.0-멀티패러다임이-현대-언어를-확장하는-방법.md) -------------------------------------------------------------------------------- /book/ko/1.0-멀티패러다임이-현대-언어를-확장하는-방법.md: -------------------------------------------------------------------------------- 1 | # 1. 멀티패러다임이 현대 언어를 확장하는 방법 2 | 3 | 과거에는 함수형 패러다임을 사용하려면 함수형 언어를, 객체 지향 패러다임을 사용하려면 객체 지향 언어를 선택해야 했습니다. 그러나 오늘날 대부분의 프로그래밍 언어는 멀티패러다임 언어로 발전했습니다. 2010년 즈음부터 이러한 변화가 본격적으로 시작되었으며, 특히 2020년 이후에는 현업에서 주로 사용되는 거의 모든 언어가 명령형, 객체 지향, 함수형 패러다임을 동시에 지원하게 되었습니다. 4 | 5 | 많은 수의 언어들은 객체 지향 패러다임을 기반으로 시작했지만, 일급 함수를 도입하고, 지연성을 적용한 이터레이션 헬퍼 함수나 비동기를 다루는 내장 객체 등을 포함하며 다각도로 발전했습니다. 그 결과, 이제 하나의 언어에서 다양한 패러다임을 성숙한 수준으로 활용할 수 있게 되었습니다. 6 | 7 | 저 역시 이러한 변화를 최근에서야 온전히 받아들이기 시작했습니다. 현재의 언어들에서는 심지어 하나의 함수 안에서도 여러 패러다임을 혼합할 수 있게 되었음에도 불구하고, 내가 좀 더 좋아하거나 최근에 받아들인 어느 한쪽의 패러다임만을 사용하려고 했던 것은 아닌가 생각하게 되었습니다. 과거에는 객체 지향 프로그래밍을 매우 좋아했음에도, 이러한 패러다임들이 서로 섞일 수 없다고 생각했던 것 같습니다. 그리고 저뿐만 아니라 많은 개발자분들 그리고 오픈소스 라이브러리나 프레임워크 설계에서도 하나의 패러다임이나 컨셉만으로 프로젝트를 구성하려는 경향으로 나타나곤 했다고 생각합니다. 아이러니하게도 프로그래밍에서 가장 중심이 되는 언어 그 자체는 이미 멀티패러다임 언어가 되었음에도 말입니다. 8 | 9 | 모든 프로그래밍 패러다임은 성공적인 소프트웨어 개발을 위한 도구입니다. 우리는 그동안 어느 하나의 방식이나 컨셉만이 사용되도록 프로젝트와 코드를 구성하려고 하고 통합하려 했던 경향이 있지만, 이는 오히려 더 나은 프로그래밍으로 나아갈 기회를 제약할 수 있습니다. 그리고 어쩌면 알면서도 부정하는 경향도 있다고 생각합니다. 어떤 문제는 명령형으로 푸는 것이 적합하고, 어떤 문제는 함수형, 또 어떤 문제는 객체 지향이 더 효과적일 수 있습니다. 그럼에도 어쩌면 우리는 성능이나 사용자 경험을 희생하면서까지 우리의 소프트웨어와 코드에게 맞지 않는 옷을 억지로 입히려고 했던 것은 아닐까요? 10 | 11 | 오늘날은 다양한 패러다임을 효과적으로 조합해 더 나은 코드를 작성할 수 있는 시대입니다. 이 책에서는 서로 다른 패러다임이 어우러져 탄생하는 아름다운 코드와 문제 해결 방식을 함께 탐구합니다. 첫 장에서는 이러한 패러다임들이 조화롭게 만나기 시작한 이야기로, 앞으로 이어질 긴 이야기를 열어보고자 합니다. 12 | 13 | --- 14 | 15 | ## 네비게이션 16 | 17 | - [목차](README.md) 18 | - [이전](0.3-환경-설치-및-예제-코드.md) 19 | - [다음](1.1-객체-지향-디자인-패턴의-반복자-패턴과-일급-함수.md) -------------------------------------------------------------------------------- /book/ko/1.6-중간-정리.md: -------------------------------------------------------------------------------- 1 | ## 1.6 중간 정리 2 | 3 | #### 멀티패러다임의 시대 4 | 5 | 오늘날 대부분의 프로그래밍 언어는 멀티패러다임 언어로 발전했습니다. 과거에는 함수형 프로그래밍을 위해 함수형 언어를, 객체 지향 프로그래밍을 위해 객체 지향 언어를 선택해야 했지만, 이제는 하나의 언어에서 명령형, 객체 지향, 함수형 패러다임을 모두 활용할 수 있습니다. 이러한 변화는 2010년 전후로 시작되어, 2020년 이후에는 거의 모든 주요 언어들이 이러한 특징을 갖추게 되었습니다. 6 | 7 | #### 멀티패러다임의 협력 8 | 9 | 마이크 루키데스는 최근 글에서 "함수형, 객체 지향, 명령형 패러다임을 모두 제공하는 멀티패러다임 언어를 효율적으로 사용하는 법을 배워야 하며, 오랫동안 각각 특별히 고유했고 심지어 갈등 속에 있던 패러다임들을 섞어야 할 때다"고 언급했습니다. 각 패러다임은 그 자체로 유용하지만, 특정 문제에 가장 적합한 패러다임을 선택하거나 필요하다면 조합할 수 있는 유연성이야말로 멀티패러다임 언어의 장점입니다. 예를 들어, 객체 지향 프로그래밍은 상태와 행동을 한데 묶는 데 유리하고, 함수형 프로그래밍은 순수 함수와 불변성을 통해 코드의 예측 가능성을 높입니다. 10 | 11 | #### 반복자 패턴과 일급 함수 12 | 13 | 객체 지향 디자인 패턴 중 하나인 반복자(Iterator) 패턴은 컬렉션 요소를 순차적으로 접근하는 규약을 제시합니다. 이를 통해 다양한 형태의 컬렉션 데이터를 일관된 방식으로 순회할 수 있습니다. 한편, 함수형 프로그래밍의 핵심 요소인 일급 함수(First-class Function)는 함수를 값처럼 취급해 다른 함수의 인자로 전달하거나 반환값으로 사용할 수 있게 합니다. 이 두 개념이 결합되면, 멀티패러다임 언어에서 함수형 프로그래밍의 핵심 개념인 지연 평가(Lazy Evaluation)와 리스트 프로세싱(List Processing)을 구현할 수 있게 됩니다. 14 | 15 | #### 지연 평가와 고차 함수 16 | 17 | 지연 평가는 필요한 시점까지 계산을 미루어 메모리 사용량과 연산 비용을 줄여 성능을 향상하는 기법입니다. 고차 함수(Higher-order Function)는 함수를 인자로 받거나 함수를 반환하는 함수를 의미하는데, `map`, `filter`, `reduce`와 같은 함수형 함수(혹은 리스트 프로세싱 함수)들은 지연 평가와 고차 함수 기법을 활용해 더 나은 성능과 가독성을 제공합니다. 18 | 19 | #### 제너레이터 함수 20 | 21 | 제너레이터(Generator) 함수는 이터레이터를 생성하는 문법적 장치로서 명령형 스타일로도 이터레이션 로직을 구현할 수 있게 합니다. `function*` 키워드와 `yield` 키워드를 사용해 값을 하나씩 반환함으로써, 지연 평가가 필요한 로직을 간결하고 명확하게 표현할 수 있습니다. 22 | 23 | #### 타입스크립트와 이터레이션 프로토콜 24 | 25 | 이터러블 객체는 이터레이터를 통해 순회 가능한 구조를 갖추고 있으며, 이를 `for...of` 문, 전개 연산자, 구조 분해 할당 등과 함께 활용하면 더욱 유연한 코드 작성이 가능합니다. 또한, 타입스크립트는 이러한 이터레이션 프로토콜에 대해 정교한 타입 시스템을 제공하여, 각 요소의 타입을 명확히 정의하고 코드 안전성을 한층 강화합니다. 26 | 27 | #### 사용자 정의 이터러블 28 | 29 | 개발자는 직접 만든 객체에도 이터레이션 프로토콜을 구현하여 사용자 정의 이터러블을 만들 수 있습니다. 이를 통해 언어의 기본 기능과 긴밀히 협업하고, 다양한 문제 해결 방식을 시도하거나 언어 자체를 확장하는 것이 가능해집니다. 30 | 31 | #### 상속 대신 인터페이스 32 | 33 | 상속은 객체 지향 프로그래밍에서 중요한 개념이지만, 다양한 자료 구조를 일관성 있게 처리하기 위해서는 인터페이스와 이터레이션 프로토콜을 활용하는 편이 더 효율적일 수 있습니다. 예를 들어 `NodeList`, `Map`, `Set` 등은 서로 다른 특성과 동작 방식을 지니지만, 상속 대신 인터페이스를 통해 공통 로직을 공유하고, 반복자 패턴에 기반한 이터레이션 프로토콜로 다양한 자료 구조를 유연하게 다룰 수 있게 됩니다. 34 | 35 | #### 멀티패러다임의 교차점: 반복자 패턴과 일급 함수 36 | 37 | 반복자 패턴과 일급 함수는 멀티패러다임 언어에서 함수형 프로그래밍 구현의 중요한 시발점이자 교차점입니다. 이를 통해 고도화된 리스트 프로세싱을 구현하고, 더 나은 성능과 가독성을 얻을 수 있으며, 멀티패러다임 언어의 강력한 기능을 최대한 활용할 수 있습니다. 이 책에서는 이러한 개념들을 심도 있게 살펴보고, 다양한 패러다임을 조합하여 아름답고 효율적인 코드를 작성하는 방법을 제시할 것입니다. 38 | 39 | --- 40 | 41 | ## 네비게이션 42 | 43 | - [목차](README.md) 44 | - [이전](1.5-이터러블-프로토콜이-상속이-아닌-인터페이스로-설계된-이유.md) 45 | - [다음](2.0-함수형-프로그래밍과-타입-시스템,-그리고-LISP.md) -------------------------------------------------------------------------------- /book/ko/2.0-함수형-프로그래밍과-타입-시스템,-그리고-LISP.md: -------------------------------------------------------------------------------- 1 | # 2. 함수형 프로그래밍과 타입 시스템, 그리고 LISP 2 | 3 | 프로그래밍 패러다임과 언어는 끊임없이 진화하며, 오늘날 우리는 객체 지향, 함수형, 명령형 등 다양한 패러다임이 공존하는 멀티패러다임 언어의 시대를 살고 있습니다. 이 중 함수형 패러다임은 불변성, 순수 함수, 고차 함수, 지연 평가와 같은 개념을 통해 예측 가능하고 읽기 쉬운 코드를 작성하는 데 도움을 줍니다. 이러한 함수형 기법들은 점차 많은 언어에서 채택되고 있으며, 자바스크립트와 타입스크립트 역시 이러한 흐름을 따르고 있습니다. 4 | 5 | 이번 장에서는 함수형 프로그래밍의 핵심 개념과 이를 뒷받침하는 타입스크립트의 타입 시스템을 다루고자 합니다. 타입 추론, 고차 함수와 함수 타입, 제네릭을 비롯한 다양한 타입 기능을 활용하여 더 안전하고 유지보수가 쉬운 코드를 작성하는 방법을 알아보고, 객체 지향 클래스와 함수형 함수를 결합하는 방법도 살펴볼 것입니다. 6 | 7 | 또한, 함수형 프로그래밍의 뿌리라 할 수 있는 LISP의 핵심 개념도 함께 다룹니다. LISP은 코드 자체를 리스트 형태의 값으로 취급하는 독특한 접근을 통해 유연하고 강력한 메타프로그래밍을 가능하게 합니다. 이와 같은 LISP의 사상은 현대 멀티패러다임 언어에도 많은 영향을 주고 있으며, 자바스크립트와 타입스크립트 역시 LISP에서 비롯된 중요한 개념들을 적극적으로 받아들이고 있습니다. 8 | 9 | 이 장을 통해 함수형 프로그래밍을 다루는 타입 시스템의 특징, 그리고 LISP가 지닌 매력과 아름다운 설계 철학을 이해하게 될 것입니다. 나아가 이를 기반으로, 더 안전하고 효율적이며 가독성 높은 코드를 작성하는 방법을 탐구해봅시다. 10 | 11 | --- 12 | 13 | ## 네비게이션 14 | 15 | - [목차](README.md) 16 | - [이전](1.6-중간-정리.md) 17 | - [다음](2.1-타입-추론과-함수-타입,-그리고-제네릭.md) -------------------------------------------------------------------------------- /book/ko/2.4-중간-정리.md: -------------------------------------------------------------------------------- 1 | ## 2.4 중간 정리 2 | 3 | #### 타입스크립트의 타입 추론과 함수 타입 4 | 5 | 타입스크립트는 자바스크립트에 타입 시스템을 추가함으로써 코드의 안정성과 가독성을 높여줍니다. 타입 추론 기능을 통해 명시적인 타입 선언 없이도 안전한 코드를 작성할 수 있으며, 변수와 함수의 반환 타입을 알아서 추론해줍니다. 예를 들어, `let a = 10;`에서는 `a`가 `number`로 추론되고, `(a: number, b: number) => a + b;` 같은 함수에서는 반환 타입이 자동으로 `number`로 결정됩니다. 6 | 7 | #### 변수와 상수의 타입 추론 8 | 9 | 변수와 상수를 초기화할 때, 타입스크립트는 그 값으로부터 타입을 추론합니다. `const selected = true;`를 선언하면 `selected`는 `true`라는 리터럴 타입으로, `let checked = true;`를 선언하면 `checked`는 `boolean` 타입으로 추론됩니다. 10 | 11 | #### 제네릭을 통한 타입 추론 12 | 13 | 제네릭 함수는 다양한 타입에 대응할 수 있는 다형적인 함수를 구현할 수 있게 합니다. 예를 들어, `function identity(arg: T): T;`와 같은 제네릭 함수는 인자의 타입에 따라 반환 타입을 유연하게 결정합니다. 14 | 15 | #### 함수 타입과 제네릭 16 | 17 | 타입스크립트는 함수형 프로그래밍을 지원하기 위해 고차 함수, 함수 타입, 제네릭과 같은 기능을 제공합니다. 이를 통해 함수의 입력과 출력 타입을 명확히 하고, 다양한 타입을 수용하는 범용적인 함수를 손쉽게 구현할 수 있습니다. 18 | 19 | #### 이터러블 헬퍼 함수 20 | 21 | 이터러블 헬퍼 함수는 이터러블을 중심으로 고차 함수를 구성하는 패턴입니다. 타입스크립트를 사용하면 이러한 함수에 대해 정확한 타입 정보를 제공할 수 있어, 코드의 안정성과 가독성을 더욱 높일 수 있습니다. 예를 들어, `function* map(f: (a: A) => B, iterable: Iterable): IterableIterator`와 같이 제네릭을 활용하면, 입력과 출력 타입을 명확히 한 이터레이션 함수를 만들 수 있습니다. 22 | 23 | #### LISP과 메타프로그래밍 24 | 25 | LISP 계열 언어는 코드와 데이터를 동일하게 취급하여 메타프로그래밍을 쉽게 구현할 수 있습니다. LISP의 S-표현식은 리스트 형태의 구문으로, 코드 자체를 데이터로 다뤄 동적으로 변형하고 생성할 수 있는 강력한 기능을 제공합니다. 26 | 27 | #### 클래스 + 고차 함수 + 이터러블 28 | 29 | `FxIterable` 클래스 예시에서 볼 수 있었듯이, 클래스와 고차 함수, 그리고 이터러블을 결합하면 높은 표현력을 가진 추상화를 만들 수 있습니다. 예를 들어, `const [first, second] = fx([1, 2, 3, 4]).map(a => a * 10);`와 같이 사용자 정의 객체를 언어의 기능(여기서는 구조분해)과 유연하게 결합하고, 체이닝 패턴을 통해 가독성 높은 코드를 구현할 수 있습니다. 30 | 31 | #### 현대 프로그래밍 언어에서 구현 가능한 높은 수준의 추상화 32 | 33 | 일급 함수의 도입과 이터레이션 프로토콜의 확산은 과거의 클래스 기반 반복자 패턴을 넘어, 현대 프로그래밍 언어에서 다양한 패러다임을 자연스럽게 결합하는 높은 수준의 추상화를 실현하고 있습니다. Java 8(2014년), Swift(2014년), JavaScript/TypeScript(ES6, 2015년), Kotlin(2016년), C#(LINQ, 2007년) 등 주요 언어들은 일급 함수, 이터러블, 제네릭, 고차 함수, 커링, 풍부한 타입 시스템 등 다채로운 기능을 제공함으로써, 언어 스펙이나 컴파일러 변경 없이도 클래스를 비롯한 객체 지향 요소, 함수형 패러다임의 핵심 개념, 표준화된 이터레이션 방식 등을 유기적으로 결합할 수 있게 되었습니다. 이를 통해 개발자는 특정 패러다임에 국한되지 않고, 필요에 따라 원하는 추상화 수준을 구현하며 다양한 상황에 대응할 수 있으며, 이런 탄탄한 기반은 다양한 플랫폼과 문제 영역에서 폭넓게 응용할 수 있는 실용적인 언어 확장과 패턴화를 가능케 합니다. 34 | 35 | --- 36 | 37 | ## 네비게이션 38 | 39 | - [목차](README.md) 40 | - [이전](2.3-멀티패러다임-언어와-메타프로그래밍---LISP으로부터.md) 41 | - [다음](3.0-코드%3A객체%3A함수-=-Generator%3AIterator%3ALISP-=-IP%3AOOP%3AFP.md) -------------------------------------------------------------------------------- /book/ko/3.0-코드:객체:함수-=-Generator:Iterator:LISP-=-IP:OOP:FP.md: -------------------------------------------------------------------------------- 1 | # 3. 코드:객체:함수 = Generator:Iterator:LISP = IP:OOP:FP 2 | 3 | 명령형 코드로 작성하는 제너레이터(Generator), 객체 지향적 반복자 패턴의 구현체인 이터레이터(Iterator), 그리고 리스트 프로세싱(LISP의 핵심 개념)은 서로 많은 관계성을 가지고 있습니다. 이들은 상호 생성자이자 사용자로서 협력하며, 고도의 추상화를 가능하게 하고, IP(Imperative Programming), OOP(Object-Oriented Programming), FP(Functional Programming)이 조화를 이루는 사례를 제공합니다. 4 | 5 | 다음은 1장과 2장에서 다루었던 개념들을 **Generator:Iterator:LISP = IP:OOP:FP**의 관점에서 정리한 내용입니다. 6 | 7 | 1. 이터레이터(Iterator)는 반복자 패턴의 구현체입니다. 8 | - 이터레이터는 컬렉션 형태의 데이터를 일반화된 패턴으로 순회하는 객체입니다. 9 | - 필요할 때마다 값을 평가하는 지연성(laziness)으로, 유한한 컬렉션뿐 아니라 무한 시퀀스도 처리할 수 있습니다. 10 | 2. 제너레이터(Generator)는 명령형 코드로 이터레이터 생성합니다. 11 | - 제너레이터 함수는 명령형 코드로 이터레이터를 만들 수 있는 수단입니다. 12 | - `yield` 지점에서 함수 실행을 일시 정지하고 재개할 수 있어, 제너레이터의 코드를 리스트 단위로 지연 평가하는 효과를 냅니다. 이는 "코드가 리스트이고 리스트가 코드"라는 LISP적 사고와 맞닿아 있습니다. 13 | 3. 이터러블 이터레이터(Iterable Iterator)는 명령형, 객체 지향적, 함수형으로 다룰 수 있습니다. 14 | - 명령형으로는 `next()` 메서드를 실행하며 `while`로 순회하거나, `for...of`나 전개 구문(`...`)으로 다룰 수 있습니다. 15 | - 객체 지향적으로는 이터러블 이터레이터를 다루는 클래스를 만들거나, 이터레이터 내부에서 다른 이터레이터와 통신하는 이터레이터를 만들 수 있습니다. 16 | - 함수형으로는 고차 함수(HOF)를 통해 이터레이터와 각 요소를 처리할 함수를 전달하는 방식으로 다루며, 이터레이션 로직을 함수 조합 형태로 구현하고 지연 평가와 리스트 프로세싱을 극대화합니다. 17 | 4. 이터레이터 생성 방식의 다양화 18 | - 이터레이터를 만드는 방법으로는 직접 이터레이터 객체를 구현하거나, 제너레이터를 통해 명령형 스타일로 이터레이터를 생성할 수 있습니다. 19 | - 또한 리스트 프로세싱 기반 함수들의 조합을 통해 함수형 스타일로도 이터레이터를 생성할 수 있습니다. (3장에서 살펴 볼 것) 20 | - 궁극적으로, 이터레이터는 다음 세 가지 방식으로 만들 수 있으며, 이들은 서로를 1:1:1로 대체할 수 있습니다. 21 | 1. 명령형 방식(IP) - 제너레이터를 통한 이터레이터 생성 22 | 2. 객체 지향적 방식(OOP) - 이터레이터 객체 직접 구현 23 | 3. 함수형 방식(FP) - 리스트 프로세싱 함수 조합으로 이터레이터 생성 24 | 25 | 지금까지의 내용은 이터레이션 프로토콜이 다양한 패러다임 간에 연결 고리로 작용함을 보여줍니다. 3장에서는 특히 함수형적으로 이터레이터를 다루는 방법과 지연 평가(Lazy Evaluation)에 초점을 맞추어 LISP적 사고("코드가 리스트")의 적용 범위를 넓혀보며, 이와 같은 다각적인 접근이 현대 프로그래밍의 견고한 기반을 어떻게 형성하는지 살펴볼 것입니다. 26 | 27 | --- 28 | 29 | ## 네비게이션 30 | 31 | - [목차](README.md) 32 | - [이전](2.4-중간-정리.md) 33 | - [다음](3.1-코드가-곧-데이터---로직이-담긴-리스트.md) -------------------------------------------------------------------------------- /book/ko/3.5-중간-정리.md: -------------------------------------------------------------------------------- 1 | ## 3.5 중간 정리 2 | 3 | ### 코드:객체:함수 = Generator:Iterator:LISP = IP:OOP:FP 4 | 5 | 코드, 객체, 함수는 각각 제너레이터, 이터레이터, LISP와 유사한 관계를 가집니다. 이터레이터는 반복자 패턴의 구현체로, 컬렉션의 값을 일반화된 패턴으로 순회하는 객체입니다. 이터레이터는 지연성을 가지며, 평가한 만큼만 실행하고 일시 정지하는 특성을 가지고 있어 무한히 소비할 수 있는 이터레이터를 만들 수 있습니다. 이를 통해 명령형, 객체 지향적, 함수형 접근 방식 모두를 통합하는 강력한 프로그래밍 모델을 만들 수 있습니다. 6 | 7 | ### 코드가 곧 데이터: 로직이 담긴 리스트 8 | 9 | LISP의 철학은 코드와 데이터의 경계가 없는 프로그래밍 언어의 특징을 반영합니다. LISP의 S-표현식을 통해 로직이 리스트 형태로 표현되며, 이를 통해 코드의 유연성과 확장성을 높일 수 있습니다. 코드가 데이터이고 데이터가 코드인 LISP의 개념은 현대 프로그래밍 언어에 적용할 수 있습니다. LISP의 메타프로그래밍 능력은 코드와 데이터를 동일하게 취급하여 동적으로 생성하고 조작하는 것을 가능하게 합니다. 이를 통해 프로그래머는 더 유연하고 강력한 프로그램을 작성할 수 있습니다. 10 | 11 | ### 하스켈로부터 배우기 12 | 13 | 하스켈은 순수 함수형 프로그래밍 언어로, 순수 함수, 함수 합성, 커링, 지연 평가, 강력한 타입 시스템 등의 개념을 제공합니다. 함수형 프로그래밍의 이점을 이해하고 현대 프로그래밍 언어에 적용할 수 있는 방법을 배울 수 있습니다. 하스켈의 함수 시그니처와 패턴 매칭을 통한 강력한 타입 안전성은 코드의 안정성과 가독성을 높이는 데 도움을 줍니다. 하스켈의 타입 시스템은 매우 강력하며, 타입 추론을 통해 명시적인 타입 선언 없이도 안전한 코드를 작성할 수 있습니다. 14 | 15 | ### 지연 평가 자세히 보기 16 | 17 | 지연 평가는 필요한 시점까지 계산을 미루는 전략으로, 프로그램의 성능을 최적화할 수 있습니다. 지연 평가의 실제 구현과 응용 사례를 통해 효율적인 코드 작성을 할 수 있습니다. 성능 최적화와 자원 절약을 위한 중요한 개념으로, 이를 통해 더 나은 프로그램을 작성할 수 있습니다. 지연 평가는 특히 대규모 데이터 처리와 복잡한 계산에서 유용하며, 필요할 때만 계산을 수행하여 불필요한 자원 낭비를 줄일 수 있습니다. 18 | 19 | ### Generator:Iterator:LISP - 지연 평가와 안전한 합성 20 | 21 | 리스트 프로세싱 함수의 조합만으로도 `find`, `every`, `some`과 같은 고차 함수를 구현할 수 있습니다. 타입스크립트에서는 옵셔널 체이닝 연산자(`?.`)와 Non-null 단언 연산자(`!`)를 사용하여 안전하게 값을 합성하고, 이에 대해 언어와 소통할 수 있습니다. 또한, 리스트 프로세싱으로 구현한 함수는 지연 평가를 통해 효율적으로 동작할 수 있으며, 쉽게 공통 로직을 추상화하여 중복을 제거할 수 있습니다. 22 | 23 | --- 24 | 25 | ## 네비게이션 26 | 27 | - [목차](README.md) 28 | - [이전](3.4-Generator%3AIterator%3ALISP---지연-평가와-합성.md) 29 | - [다음](README.md) 30 | -------------------------------------------------------------------------------- /img/MCO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marpple/multi-paradigm-programming/70c00e248c93f901ffa06936439cfb38c10cf10c/img/MCO.png -------------------------------------------------------------------------------- /img/book.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marpple/multi-paradigm-programming/70c00e248c93f901ffa06936439cfb38c10cf10c/img/book.jpg -------------------------------------------------------------------------------- /img/book_en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marpple/multi-paradigm-programming/70c00e248c93f901ffa06936439cfb38c10cf10c/img/book_en.png -------------------------------------------------------------------------------- /img/hanbit_plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marpple/multi-paradigm-programming/70c00e248c93f901ffa06936439cfb38c10cf10c/img/hanbit_plus.png -------------------------------------------------------------------------------- /img/inflearn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marpple/multi-paradigm-programming/70c00e248c93f901ffa06936439cfb38c10cf10c/img/inflearn.png -------------------------------------------------------------------------------- /img/mduniv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marpple/multi-paradigm-programming/70c00e248c93f901ffa06936439cfb38c10cf10c/img/mduniv.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Mpp", 3 | "version": "1.0.0", 4 | "description": "", 5 | "type": "module", 6 | "scripts": { 7 | "ci": "pnpm install --frozen-lockfile", 8 | "fi": "pnpm install --force", 9 | "prepare": "husky" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "dependencies": { 15 | "express": "^4.19.2", 16 | "pretendard": "^1.3.9", 17 | "qs": "^6.12.1" 18 | }, 19 | "devDependencies": { 20 | "@rune-ts/server": "1.0.24", 21 | "@types/express-serve-static-core": "^4.19.0", 22 | "@types/node": "^20.11.20", 23 | "@types/qs": "^6.9.15", 24 | "@typescript-eslint/eslint-plugin": "^7.0.2", 25 | "@typescript-eslint/parser": "^7.0.2", 26 | "eslint": "^8.56.0", 27 | "eslint-config-prettier": "^9.1.0", 28 | "eslint-plugin-boundaries": "^4.2.0", 29 | "eslint-plugin-import": "^2.29.1", 30 | "eslint-plugin-prettier": "^5.1.3", 31 | "husky": "^9.0.11", 32 | "lint-staged": "^15.2.2", 33 | "prettier": "^3.2.5", 34 | "stylelint": "^16.2.1", 35 | "stylelint-config-prettier-scss": "^1.0.0", 36 | "stylelint-config-property-sort-order-smacss": "^10.0.0", 37 | "stylelint-config-recommended-scss": "^14.0.0", 38 | "stylelint-config-standard": "^36.0.0", 39 | "stylelint-prettier": "^5.0.0", 40 | "stylelint-scss": "^6.2.1", 41 | "typescript": "5.8.2" 42 | }, 43 | "lint-staged": { 44 | "*.scss": [ 45 | "stylelint --fix '**/*.scss'" 46 | ], 47 | "*.ts": [ 48 | "eslint --fix", 49 | "prettier --write" 50 | ] 51 | }, 52 | "volta": { 53 | "node": "22.13.0", 54 | "pnpm": "10.0.0" 55 | }, 56 | "engines": { 57 | "node": ">=22", 58 | "pnpm": ">=10" 59 | }, 60 | "pnpm": { 61 | "override": { 62 | "rune-ts": "^0.8.12" 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /packages/styles/base.scss: -------------------------------------------------------------------------------- 1 | @forward 'sub/color'; 2 | @forward 'sub/typography_variable'; 3 | @forward 'sub/function'; 4 | @forward 'sub/mixin'; 5 | @forward 'sub/variables'; 6 | -------------------------------------------------------------------------------- /packages/styles/global.scss: -------------------------------------------------------------------------------- 1 | @use 'object-fe'; 2 | 3 | @import 'pretendard/dist/web/variable/pretendardvariable-dynamic-subset.css'; 4 | -------------------------------------------------------------------------------- /packages/styles/sub/color.scss: -------------------------------------------------------------------------------- 1 | $black: #141414; 2 | $blue: #1c75ff; 3 | $white: #fff; 4 | $bright-5: rgb(255 255 255 / 5%); 5 | $bright-20: rgb(255 255 255 / 20%); 6 | $bright-60: rgb(255 255 255 / 60%); 7 | $bright-95: rgb(255 255 255 / 95%); 8 | $brown: #704c00; 9 | $burgundy: #900; 10 | $cream: #fff9e2; 11 | $dim-5: rgb(20 20 20 / 5%); 12 | $dim-10: rgb(20 20 20 / 10%); 13 | $dim-30: rgb(20 20 20 / 30%); 14 | $dim-60: rgb(20 20 20 / 60%); 15 | $gray-5: #f5f5f5; 16 | $gray-10: #e7e7e7; 17 | $gray-20: #d9d9d9; 18 | $gray-50: #a2a2a2; 19 | $gray-80: #6b6b6b; 20 | $green: #009444; 21 | $light-blue: #ebf3fe; 22 | $light-green: #ebfef3; 23 | $light-purple: #eeebff; 24 | $light-red: #fceeec; 25 | $pink: #ffe4ff; 26 | $purple: #7a44a0; 27 | $red: #d92a0f; 28 | $pink: #ffe4ff; 29 | $sky: #99e0ff; 30 | -------------------------------------------------------------------------------- /packages/styles/sub/function.scss: -------------------------------------------------------------------------------- 1 | @use 'sass:math'; 2 | 3 | @function ptr($target-size) { 4 | @return math.div($target-size, $font-size-default) * 1rem; // rem 단위로 변환 5 | } 6 | -------------------------------------------------------------------------------- /packages/styles/sub/mixin.scss: -------------------------------------------------------------------------------- 1 | @use 'sass:map'; 2 | 3 | @mixin innerBorder($border_color, $border_size: 1px) { 4 | box-shadow: 0 0 0 $border_size $border_color inset; 5 | } 6 | 7 | // flex 설정 8 | // @include flex(space-between, center); // flex row 9 | // @include flex(space-between, center, column); // flex column 10 | @mixin flex($jc, $ai, $di: row) { 11 | display: flex; 12 | align-items: $ai; 13 | justify-content: $jc; 14 | @if $di != row { 15 | flex-direction: $di; 16 | } 17 | } 18 | 19 | // 스크롤바 숨기고 싶을때 20 | // @include hiddenScroll; 21 | @mixin hiddenScroll { 22 | -ms-overflow-style: none; 23 | scrollbar-width: none; 24 | 25 | &::-webkit-scrollbar { 26 | display: none; 27 | } 28 | } 29 | 30 | // 가로로 스냅 스크롤 주고 싶을때 31 | // @include snapScroll(ptr(40)); 32 | @mixin snapScroll($offset) { 33 | scroll-behavior: smooth; 34 | scroll-snap-type: x mandatory; 35 | padding: 0 $offset; 36 | scroll-margin-left: $offset; 37 | scroll-margin-right: $offset; 38 | scroll-padding-left: $offset; 39 | scroll-padding-right: $offset; 40 | touch-action: cross-slide-x; 41 | 42 | & > * { 43 | scroll-snap-align: start; 44 | } 45 | } 46 | 47 | // object-fit cover 48 | // @include objectCover; 49 | @mixin objectCover { 50 | width: 100%; 51 | height: 100%; 52 | object-fit: cover; 53 | } 54 | 55 | // 말줄임 처리 56 | // @include lineLimit(1); 57 | @mixin lineLimit($line: 2) { 58 | display: -webkit-box; 59 | overflow: hidden; 60 | text-overflow: ellipsis; 61 | -webkit-line-clamp: $line; 62 | -webkit-box-orient: vertical; 63 | } 64 | 65 | // 백드롭 블러처리 66 | // @include blur10; 67 | @mixin blur10 { 68 | backdrop-filter: blur(5px); 69 | } 70 | @mixin blur30 { 71 | backdrop-filter: blur(15px); 72 | } 73 | @mixin blur40 { 74 | backdrop-filter: blur(20px); 75 | } 76 | @mixin blur60 { 77 | backdrop-filter: blur(30px); 78 | } 79 | -------------------------------------------------------------------------------- /packages/styles/sub/typography_variable.scss: -------------------------------------------------------------------------------- 1 | $font-family-kr: 2 | 'Pretendard Variable', 3 | 'Pretendard', 4 | -apple-system, 5 | 'BlinkMacSystemFont', 6 | 'system-ui', 7 | 'Roboto', 8 | 'Helvetica Neue', 9 | 'Segoe UI', 10 | 'Apple SD Gothic Neo', 11 | 'Noto Sans KR', 12 | 'Malgun Gothic', 13 | 'Apple Color Emoji', 14 | 'Segoe UI Emoji', 15 | 'Segoe UI Symbol', 16 | sans-serif; 17 | 18 | $font-size-default: 14; 19 | $font-size-default-px: ($font-size-default) * 1px; 20 | $font-size-base: 1rem; 21 | $font-weight-bold: 700; 22 | $font-weight-normal: 500; 23 | $font-weight-regular: 400; 24 | $font-letter-spacing-base: -0.2; 25 | $font-letter-spacing-base-px: ($font-letter-spacing-base) * 1px; 26 | -------------------------------------------------------------------------------- /packages/styles/sub/variables.scss: -------------------------------------------------------------------------------- 1 | $marppleshop-layout-padding: 32px; 2 | $pc-max-width: 1960px; 3 | $pc-layout-body-max-width: 1440px; 4 | $pc-layout-body-min-width: 1024px; 5 | $default-header_height: 4rem; 6 | -------------------------------------------------------------------------------- /packages/types/declare.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.png'; 2 | declare module '*.jpg'; 3 | declare module '*.jpeg'; 4 | declare module '*.gif'; 5 | declare module '*.webp'; 6 | declare module '*.svg'; 7 | declare module '*.ico'; 8 | 9 | declare module '*.module.css'; 10 | declare module '*.module.scss'; 11 | declare module '*.scss'; 12 | -------------------------------------------------------------------------------- /packages/types/renderHandlerType.ts: -------------------------------------------------------------------------------- 1 | import type { ParamsDictionary, RequestHandler } from 'express-serve-static-core'; 2 | import type { ParsedQs } from 'qs'; 3 | import type { FactoryFunction, LayoutData } from '@rune-ts/server'; 4 | 5 | export interface LocalsType { 6 | layoutData: LayoutData; 7 | is_mobile: boolean; 8 | } 9 | 10 | export type RenderHandlerType< 11 | C, 12 | LocalsObj extends Record & LocalsType = Record & LocalsType, 13 | P = ParamsDictionary, 14 | ReqBody = any, 15 | ReqQuery = ParsedQs, 16 | > = (view: FactoryFunction) => RequestHandler; 17 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'apps/*' 3 | - 'packages/*' 4 | -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | // extend your base config to share compilerOptions, etc 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | // ensure that nobody can accidentally use this config for a build 6 | "noEmit": true 7 | }, 8 | "include": [ 9 | // whatever paths you intend to lint 10 | "apps", 11 | "packages", 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "rootDir": "./", 4 | "target": "ESNext", 5 | "module": "ESNext", 6 | "moduleResolution": "node", 7 | "lib": ["ESNext", "dom", "dom.iterable"], 8 | "esModuleInterop": true, 9 | "allowSyntheticDefaultImports": true, 10 | "allowJs": true, 11 | "strict": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "typeRoots": ["node_modules/@types"], 16 | "noImplicitOverride": true, 17 | "noImplicitAny": false, 18 | "verbatimModuleSyntax": true, 19 | "experimentalDecorators": true, 20 | "noEmit": true 21 | } 22 | } 23 | --------------------------------------------------------------------------------
; 17 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'apps/*' 3 | - 'packages/*' 4 | -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | // extend your base config to share compilerOptions, etc 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | // ensure that nobody can accidentally use this config for a build 6 | "noEmit": true 7 | }, 8 | "include": [ 9 | // whatever paths you intend to lint 10 | "apps", 11 | "packages", 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "rootDir": "./", 4 | "target": "ESNext", 5 | "module": "ESNext", 6 | "moduleResolution": "node", 7 | "lib": ["ESNext", "dom", "dom.iterable"], 8 | "esModuleInterop": true, 9 | "allowSyntheticDefaultImports": true, 10 | "allowJs": true, 11 | "strict": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "typeRoots": ["node_modules/@types"], 16 | "noImplicitOverride": true, 17 | "noImplicitAny": false, 18 | "verbatimModuleSyntax": true, 19 | "experimentalDecorators": true, 20 | "noEmit": true 21 | } 22 | } 23 | --------------------------------------------------------------------------------