{"id":16538044,"url":"https://github.com/maxnachlinger/classes-and-functions-chat","last_synced_at":"2025-09-06T03:32:58.137Z","repository":{"id":148670330,"uuid":"78073232","full_name":"maxnachlinger/classes-and-functions-chat","owner":"maxnachlinger","description":"An exploration of some ideas around functions and classes in Javascript.","archived":false,"fork":false,"pushed_at":"2017-10-11T17:19:10.000Z","size":127,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-03T21:15:03.139Z","etag":null,"topics":["composition","curried-functions","functional-js","inheritance","javascript","monad","promises"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/maxnachlinger.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2017-01-05T02:55:48.000Z","updated_at":"2018-01-12T16:19:36.000Z","dependencies_parsed_at":null,"dependency_job_id":"ff512d7e-054d-4cbe-8d85-6cefc5ed518b","html_url":"https://github.com/maxnachlinger/classes-and-functions-chat","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/maxnachlinger/classes-and-functions-chat","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxnachlinger%2Fclasses-and-functions-chat","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxnachlinger%2Fclasses-and-functions-chat/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxnachlinger%2Fclasses-and-functions-chat/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxnachlinger%2Fclasses-and-functions-chat/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/maxnachlinger","download_url":"https://codeload.github.com/maxnachlinger/classes-and-functions-chat/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxnachlinger%2Fclasses-and-functions-chat/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273853328,"owners_count":25179824,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-06T02:00:13.247Z","response_time":2576,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["composition","curried-functions","functional-js","inheritance","javascript","monad","promises"],"created_at":"2024-10-11T18:44:17.977Z","updated_at":"2025-09-06T03:32:58.100Z","avatar_url":"https://github.com/maxnachlinger.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"## classes-and-functions-chat\n\n## Installation\n```shell\ngit clone git@github.com:maxnachlinger/classes-and-functions-chat.git\ncd classes-and-functions-chat\nnpm i\n```\n\n## Run the examples\n```shell\n# you can run the index.js file in each directory\nnode ./0-class/index.js\n```\n\n## Notes\n### for these to make sense, you'll need to look at the code for each section.\n---\n## Requirement: get a set of results from a REST API on the network\n---\n## 0 - Class\n[Relevant code](./0-class)\n\nA `ThingRequest` class.\n\n### Question:\nWhere does `ThingRequest::request()` get its state from?\n\nThe answer is _lots of places_. The class instance, the function's arguments, the things we required above etc.\n\nHow a function gets its state can be complicated. **Minimizing that complexity makes the function easier to think \nabout, work on, and test.**\n\nIn `ThingRequest` we have class instance variables that influence `request()`'s behavior several lines away from \nthat function. We can improve this.\n\n### Consider this example of code calling a function vs a class + method:\n```javascript\n// Function f\nf(a, b, c)\n\n// vs Class C with method f\nconst c = new C(a)\nc.f(b,c)\n```\nThe class approach sure adds a lot of complexity to save having to pass `a` to `f()`.\n\n---\n## 1 - Function\n[Relevant code](./1-fx)\n\nThis is a first pass at simplifying `request()`. Now more of the function's state comes from it's arguments.\n\nOne benefit of this approach is that `request()` is open about its dependencies, which makes it easier to reason\nabout.\n\nSome folks claim a benefit of classes is that you don't have to pass the state given to the constructor along to each\ninstance method. The calling code in `index.js` shows that partial application is a reasonable way around that.\n\nIn case you don't know what partial application is:\n\n### Partial Application:\n\nA fancy phrase for taking a function with, say, 3 params, like this:\n```javascript\nconst getStuff = (url, accessKey, type) =\u003e {\n  // etc\n}\n```\nand making a new function that provides values for some of those params up-front. Like this:\n```javascript\nconst getStuffLocal = (type) =\u003e getStuff('http://www.example.com', 'secret-access-key', type)\n\n// you can also use a helper like lodash\n\nconst _ = require('lodash')\nconst getStuffLocal2 = _.partial(getStuff, 'http://www.example.com', 'secret-access-key')\n```\n\n### Question:\nFrom the point of view of the calling code, which is more maintainable? A class you instantiate with an arg and\nan instance method you call with another arg,\n\n```javascript\nconst instance = new ThingRequest(serviceConfig)\n\n// many lines of code later...\n\ninstance.request({type: 'squirrels', limit: 5})\n  .then((results) =\u003e console.log(results))\n  .catch((err) =\u003e console.error(err.stack || err))\n```\nor a method you call passing 2 args?\n```javascript\nrequest(serviceConfig, {type: 'squirrels', limit: 5})\n  .then((results) =\u003e console.log(results))\n  .catch((err) =\u003e console.error(err.stack || err))\n```\n\n---\n## 2 - Extract and compose pure functions\n[Relevant code](./2-fx-pure)\n\nA few new pure functions are extracted, namely `prepareParams()`, `prepareRequestParams()`, and \n`transformResults()` .\n\nPromises are used to compose those functions along with `requestP()`.\n\n### Benefits of this approach:\n- Each function gets just enough state to do it's job\n- Each function has one responsibility\n- + all the benefits of pure functions.\n\nIn case you've no idea what a Pure function is:\n\n### Pure Function:\n\nA fancy phrase which means a function that:\n* given the same input will always return the same output\n* produces no side effects - a side effect is any application state change that is observable outside the called \nfunction other than its return value.\n* gets all its state from its arguments.\n\nWhy should I care about all this Pure Function nonsense anyway?\n\n* You can memoize / cache them\n* They are super easy to test\n* They (can be) simple to reason about and maintain.\n* You can run N of them at once without issue (less of a concern in JS)\n\nIt's worth noting that `requestP()` is _not_ pure. Its output varies based on state external to its input, namely \nthe network :) We can make `requestP()` pure, and we'll explore what that looks like later on.\n\n---\n## Requirement-change: get a set of results from a REST API on the network and validate those results\n\n---\n## 3 - Class inheritance\n[Relevant code](./3-class-inheritance)\n\nThis example adds result-validation by extending `ThingRequest` with a new child class `ValidatedThingRequest`.\n\nUnfortunately we had to modify `ThingRequest` and export `serviceConfigSchema`, then use the exported \n`serviceConfigSchema` in `ValidatedThingRequest`'s validation.\n\n### Wait a moment\nWasn't the whole point of inheritance the ability to reuse code without modifying it? Modifying a base class when\ninheriting is sadly quite common, and if lots of classes inherit from that base class, you can cause lots of bugs.\n\nThere are other ways of structuring `ThingRequest` and `ValidatedThingRequest` to get around _some_ of these issues \nbut that's not the point here.\n\nThe point is that **your requirements will change**, and when they do you'll not only have to change the functionality\nitself, but you'll also have to contend with a whole set of classes meant to describe those requirements as a world\nconsisting of objects.\n\nWhen using inheritance the issue gets even more complex as child classes gain access to and alter the internal state\nof their parent classes. As systems structured in this way grow, these dependencies are rarely obvious and the\nside-effects to altering state are even less obvious.\n\n---\n## 4 - Class composition through Dependency Injection\n[Relevant code](./4-class-composition-di)\n\nThis attempts to add result-validation by creating a class which adds that validation by having an instance of `ThingRequest` injected into it.\n\n### Dependency Injection\n\nA fancy term for passing a class it's dependencies.\n\nConsider this code:\n```javascript\nclass ValidatedThingRequest {\n  constructor (serviceConfig, responseSchema) {\n    // snip\n    this._thingRequest = new ThingRequest(serviceConfig);\n  }\n  \n  // more methods here etc\n```\n\nNotice that:\n\n1. `ValidatedThingRequest` is now responsible for creating and destroying that instance of `ThingRequest`.\n2. It's also harder to test `ValidatedThingRequest` since we can easily supply a mocked `ThingRequest`.\n3. There are loads of other benefits (like loose coupling and programming to interfaces) but those are less relevant \nfor Javascript IMHO.\n\nConsider this code:\n```javascript\nclass ValidatedThingRequest {\n  constructor (responseSchema, thingRequest) {\n    // snip\n    this._thingRequest = thingRequest\n  }\n  \n  // more methods here etc\n```\nWe're _injecting_ `ValidatedThingRequest`'s `ThingRequest` dependency. \n\nNow we can easily mock thingRequest when testing, and `ValidatedThingRequest` isn't responsible for managing \n`ThingRequest`. It can simply use the instance passed in.\n\nThe takeaway here is that if you're going to use classes to construct your programs, you should learn about OO Design\nPatterns and techniques like dependency injection. There are bookshelves filled with great old tomes on this stuff.\nOne benefit of dependency injection is it makes a class' dependencies explicit, which makes the class easier to \nreason about.\n\n---\n## 5 - More pure function composition\n[Relevant code](./5-fx-pure-composition)\n\n`validate-result.js`  simply adds a validation check to the result. This function is curried because we have the \nresult schema way before we have the result.\n\nIn case you have no idea what currying is:\n\n### Currying\n\nA fancy phrase for taking a function with, say, 3 params, like this:\n```javascript\nconst getStuff = (url, accessKey, type) =\u003e {\n  // etc\n}\n```\nand turning it into a chained series of N functions each taking 1 argument. Like this:\n```javascript\nconst getStuff = (type) =\u003e (timeout) =\u003e (url) =\u003e { /*body here*/ };\n\n// you can also use a helper like lodash\n\nconst _ = require('lodash')\n// (type) =\u003e {}\nconst getStuffLocal2 = _.curry(getStuff)('http://www.example.com')('secret-access-key')\n```\nOf course you can only curry functions with a fixed arity (``arity == number of arguments``) or you'll have to provide \nthe function arity to the curry helper function up front (see lodash's `curry` function). \n\nOne thing I find helpful when creating new functions is to think of the arguments you're going to have values for \nright away, and then add those arguments _first_ in the function. For example, we almost always have a `joi` \nvalidation schema before we have data to validate. Wouldn't this `joi.validate` signature be nice?\n```javascript\njoi.validate(schema, options, value, callback)\n```\nThen we could do cool stuff like:\n```javascript\nconst validate = _.curry(joi.validate, {\n  name: joi.string().required()\n})({allowUnknown: true}); // --\u003e (value) =\u003e (callback) =\u003e {}\n```\n---\n## 6 - Some concepts\nHere's an int: `42`\n\nand here's that int in an array: `[42]`\n\n`[42]` is still an int value, but now it's in a _context_ - an array. So we might say, `42` is an int value in \nthe content of an array `[]`.\n\n### map()\nHere's an awesome `add()` function which can handle an input of 2 numbers:\n```javascript\nconst add = (a, b) =\u003e a + b\n```\nLook at it go!\n```javascript\nadd(42, 1) // 43\n```\nbut `add()` has no idea what to do with an int in an array :(\n```javascript\nadd([42], 1) // '421' - egad! That's from the node repl BTW :)\n```\nTo use `add()` on an int in an array we have to use `.map()`:\n```javascript\n[42].map((i) =\u003e add(i, 1)) // [43]\n```\n\nGreat, so what's `map()` doing here? \n\n`map()` is taking the value `42` out of the array (the context), running `add(42, 1)` \nagainst that value, and placing the result of `add(42, 1)` into a new array (a new context). \n\n`map()` also allows us to compose functions, check this out:\n```javascript\n[1]\n  .map((i) =\u003e add(i, 1)) // [2]\n  .map((i) =\u003e Math.pow(i, 2)) // [4]\n  .map((i) =\u003e add(i, 1)) // [5]\n```\nWe just got our result via composing `add()` and `Math.pow`. Another benefit here is that the functions in each map \nare [pure](#pure-function). \n\n### Functor - a fancy name for a plain concept\nYou've just seen a `functor`. A `functor` is a fancy term for a mappable thing, or a thing with a `map()` function. \nWhen values are wrapped in contexts, we cannot run functions on those values, this is what `map()` helps us to do - \nrun functions on values in contexts.\n\n### Identity - A really simple functor\n[Relevant code](6-some-concepts/identity-functor.js)\n```javascript\n'use strict'\n\nconst util = require('util')\n\nconst identity = (x) =\u003e ({\n  map: (f) =\u003e identity(f(x)),\n  // for debugging\n  inspect: () =\u003e `Identity(${util.inspect(x, {depth: null})})`\n})\n```\n`inspect()` just prints the value out for us for debugging. Let's focus on `map()`. `map()` takes a function `f` and \npasses `f` the Identity functor's value as an argument `f(x)`. `map()` then places the result of `f(x)` into a new \nIdentity functor via `identity(f(x))`.\n\nHere's how to use it:\n```javascript\nconst simpleMap = identity(1)\n  .map((i) =\u003e add(i, 1)) // Identity(2)\n  .map((i) =\u003e Math.pow(i, 2)) // Identity(4)\n  .map((i) =\u003e add(i, 1)) // Identity(5)\n```\n\n### Pointed functors\nA pointed functor is a functor with an `of()` function. Pretty simple, check it out:\n[Relevant code](6-some-concepts/identity-pointed-functor.js)\n```javascript\n'use strict'\n\nconst util = require('util')\n\nconst identity = ({\n  of: (x) =\u003e ({\n    map: (f) =\u003e identity.of(f(x)),\n    // for debugging\n    inspect: () =\u003e `Identity(${util.inspect(x, {depth: null})})`\n  })\n})\n\nconst simpleMap = identity.of(1)\n  .map((i) =\u003e add(i, 1)) // Identity(2)\n  .map((i) =\u003e Math.pow(i, 2)) // Identity(4)\n  .map((i) =\u003e add(i, 1)) // Identity(5)\n```\n\n`of()` probably looks a lot like a constructor, but it isn't. `of()` is a common interface which allows us to create \na value and place it in a default minimal context. This is quite different from a constructor, constructors are by \ndefinition tied to specific classes, `of()` is common. You'll also hear `of()` referred to as `unit`, `pure`, and \n`point`. \n\nIt's worth noting that `Array` is actually a pointed functor:\n```javascript\nArray.of(1, 2, 3) // [1, 2, 3]\nArray.of(23.95, 'Fun', false) // [ 23.95, 'Fun', false ]\n```\n\n### Why is having a common interface like \"of()\" so important?\n\nConsider an array in Javascript. Is there a special syntax for `map()`-ing over an array of strings \nversus an array of numbers? Of course there isn't :) Arrays if any type - or mixed types - share a \ncommon interface (or API) which makes array quite flexible. Imagine how much more complex Javascript \nwould be if we had to learn an API per collection.\n\n### when map() doesn't work\n\nConsider the previous Pointed Functor (you know, a unit of computation with a `map()` and an `of()` function).\n```javascript\n'use strict'\n\nconst util = require('util')\n\nconst identity = ({\n  of: (x) =\u003e ({\n    map: (f) =\u003e identity.of(f(x)),\n    // for debugging\n    inspect: () =\u003e `Identity(${util.inspect(x, {depth: null})})`\n  })\n})\n```\nNow say we wanted to use another pointed functor in our program. Since we're used to composing things in our programs, \nlet's try to compose the new functor with our existing one using `map()`, here's a first pass:\n```javascript\nconst mapAttempt = identity.of(1)\n  .map((x) =\u003e identity.of(`Test ${x}`)) // Identity(Identity('Test 1'))\n```\nEgad! See that `Identity(Identity('Test 1'))` line? \n\nNo, `map()` isn't broken. Like most annoyances in our field - _the code did exactly what we told it to do_ :)\n\nRemember that `map()` takes a value out of it's context (the Identity functor), runs a function using that value, and \nplaces the result of that function into a new context - in this case a new the Identity functor.\n\nIf this is still confusing, consider the same example with an array:\n```javascript\nconst mapAttempt = [1]\n  .map((x) =\u003e [`Test ${x}`]) // [['Test 1']] \u003c-- has the same nesting issue\n```\n\n### enter chain(), a \"flat\" map()\nLet's fix this by adding a simple function called `chain()` to our functor.\n\n```javascript\nconst identity = ({\n  // of() is also known as unit, pure, and point\n  of: (x) =\u003e ({\n    chain: (f) =\u003e f(x), // chain() is also known as flatMap or bind\n    map: (f) =\u003e identity.of(f(x)),\n    // for debugging\n    inspect: () =\u003e `Identity(${util.inspect(x, {depth: null})})`\n  })\n})\n```\n`chain()` above is taking a function `f` and passing it the value from the functor `x` but instead of placing the \nresult of `f(x)` back into a new functor (a new context) like `map()`, it's simply returning the result of `f(x)`. \nNow let's try composing 2 functors via `chain()`:\n```javascript\nconst simpleChain = identity.of(1)\n  .chain((x) =\u003e identity.of(`Test ${x}`)) // Identity('Test 1') much nicer!\n```\nSuccess! \n \nSo in the example above, we start with: `identity.of(1)`, then we have a function ``(x) =\u003e identity.of(`Test ${x}`)``\nwhich returns a new Identity functor of `'Test 1'`. `chain()` then takes the returned `identity.of('Test 1')` and returns\nit. \n\n### The M word - Monads!\nThe code we created above, a pointed functor (with `of()` and `map()`) and a `chain()` function is a _Monad_. Monads are\npointed functors that have a `chain()` (or flatMap or bind) function. Hey, now you know what a Monad is!\n\n[Relevant code](6-some-concepts/identity-monad.js)\n```javascript\nconst identityMonad = ({\n  // of() is also known as unit, pure, and point\n  of: (x) =\u003e ({\n    chain: (f) =\u003e f(x), // chain() is also known as flatMap or bind\n    map: (f) =\u003e identityMonad.of(f(x)),\n    // for debugging\n    inspect: () =\u003e `Identity(${util.inspect(x, {depth: null})})`\n  })\n})\n```\nYou can compose Monads together just like we composed functors together above, `chain()` works for that case too. \n```javascript\nconst chainToTheRescue = identity.of(1)\n  .chain((x) =\u003e identity.of(`Test ${x}`)) // Identity('Test 1')\n```\n\nThere are 3 laws monads must obey to be called monads, but I'm not going to go into them right now. We've had enough \ntheory, let's take some Monads out for a spin!\n\n---\n## 7 - Awesome composition via the `data.task` Monad\n[Relevant code](7-fx-data.task)\n\nThis example introduces the `data.task` Monad from the [Folktale library](https://github.com/origamitower/folktale).\n\nLet's start out with the familiar `Promise` API, we'll then contrast it with `data.task`:\n```javascript\nconst addPromiseYay = (value) =\u003e Promise.resolve(`${value} YAY! :)`)\n\nconst excitedPromise = Promise.resolve('fun') // = resolved Promise, execution starts here\n  .then((value) =\u003e value.toUpperCase()) // = simple value\n  .then((value) =\u003e addPromiseYay(value)) // = resolved Promise\n  .then(console.log) // = simple value\n```\nNote that the `Promise` API does not make a distinction between returning a value, or a resolved Promise, both are \nhandled with `.then()`.\n\nIt's also worth noting that Promises run as soon as they're defined \n([per the ECMAScript spec](https://tc39.github.io/ecma262/#sec-promise-constructor)). This code:\n```javascript\nconsole.log('Before promise is defined')\nconst promise = new Promise((res, rej) =\u003e {\n  console.log('Promise is executing')\n  return res()\n})\nconsole.log('After promise is defined')\n```\nPrints:\n```\nBefore promise is defined\nPromise is executing\nAfter promise is defined\n```\n\nBack to `data.task`, it has our friends `of()`, `map()` and `chain()`. Here's a `map()` over `data.task`:\n\n`map()` is pulling a value out of a `Task`, transforming it, and placing it back inside a new `Task` e.g.:\n```javascript\nTask.of('fun')\n  .map((value) =\u003e value.toUpperCase()) // Task('FUN')\n```\n\nNow if we want to pull a value out of a `Task` and use it in a new `Task` we know `map()` won't help us, e.g.:\n```javascript\nTask.of('fun') // Task('fun')\n  .map((value) =\u003e Task.of(value.toUpperCase())) // Task(Task('FUN'))\n```\nbut `chain()` will:\n```javascript\nTask.of('fun') // Task('fun')\n  .chain((value) =\u003e Task.of(value.toUpperCase())) // Task('FUN')\n```\n\nOK, let's consider the `Task` analog to the above `Promise` example:\n```javascript\nconst addTaskYay = (value) =\u003e Task.of(`${value} YAY! :)`)\n\nconst excitedTask = Task.of('fun') // Task('fun')\n  // value is taken out of the Task, upper-cased, and put back in to the Task\n  .map((value) =\u003e value.toUpperCase()) // Task('FUN')\n  // value is taken out of the Task and placed inside a new Task\n  .chain((value) =\u003e addTaskYay(value)) // Task('FUN YAY! :)')\n  // execution starts, error and result handlers get simple values\n  .fork(console.error, console.log); // error or FUN YAY! :)\n```\n\n### Why use data.Task?\n\nRemember that previously `request()` wasn't pure, its output varied based on state external to its input, namely the \nnetwork. Now `request()` is pure and easily composable with other functions. We've also pushed control for running \n`request()` and handling errors out to the caller, which is where those concerns belong. By letting the caller \ncontrol when the `Task` runs, the caller can take that `Task` and compose it with other computations via \n`.map()` and `.chain()` as per above. Once the caller has composed everything it needs, it can call `fork()` \nto run the composed computations.\n\n---\n## 8 - (fun?) bonus\n[Relevant code](8-fx-data.task-parallel-calls)\n\nThis example shows one way to run `data.task`'s in parallel. It's included as a silly bonus, or something.\n\n---\n## Solutions not considered:\n### Factory function\nIt would have been possible to define `request-things::request` like this (pseudo code):\n```javascript\n(serviceConfig) =\u003e {\n  validateServiceConfig(serviceConfig) // only option is to throw here\n  return (requestOptions) =\u003e {\n    // rest of the functions + request\n  }\n}\n```\nThis design not only encourages devs to keep an instance of the returned function around in memory, but also the only \nbenefit to this design is when making a request, `joi` validates an object that looks like this:\n```javascript\n{\n  type: 'cool', \n  limit: 5\n}\n```\ninstead of one which looks like this:\n```javascript\n{\n  serviceConfig: {\n    url: 'http://localhost:9000',\n    accessKey: '1234567890'\n  },\n  requestOptions: {\n    type: 'cool', \n    limit: 5\n  }\n}\n```\nNot much of a benefit, well, unless `joi` is our bottleneck :)\n\n---\n## Hey watch these videos, they're awesome!\n\n- [Professor Frisby Introduces Composable Functional JavaScript](\nhttps://egghead.io/courses/professor-frisby-introduces-function-composition)\n\n- Classroom Coding with Prof. Frisby\n - [Part 1](https://www.youtube.com/watch?v=h_tkIpwbsxY\u0026t=3s)\n - [Part 2](https://www.youtube.com/watch?v=oZ6C9h49bu8\u0026t=7s)\n - [Part 3](https://www.youtube.com/watch?v=mMCgJA8HScA\u0026t=609s)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaxnachlinger%2Fclasses-and-functions-chat","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmaxnachlinger%2Fclasses-and-functions-chat","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaxnachlinger%2Fclasses-and-functions-chat/lists"}