{"id":"djuntu/memoize","name":"memoize","scope":"djuntu","platform":"roblox","description":"Luau integration of memoization which mimics TypeScript package.","version":"0.1.0","latest":"0.1.0","versions":["0.1.0"],"license":"MIT","licenseRating":"safe","licenseCaveats":[],"licenseVerified":true,"dependencies":{},"integrity":"986afcf4a6fe5345ed5f971d150191583d8702ecacc1bceed59c6074131f5a9c","likes":0,"downloads":0,"install":"forest install djuntu/memoize","url":"https://forest.dev/p/roblox/djuntu/memoize","files":"https://api.forest.dev/ai/package/roblox/djuntu/memoize/files","readme":"<div align=\"center\">\r\n    <img src=\"assets/logo.png\" alt=\"Panther\" height=\"150\" />\r\n    <br />\r\n</div>\r\n\r\n<h2>Memoize</h2>\r\nMemoize provides a wrapping tool for developers to cache and output computed results for expensive\r\nfunctions with exposed options and choices for developers.\r\n\r\nMemoize is inspired off the TypeScript NPM package Memoize.\r\n\r\n<h2>What are expensive functions?</h2>\r\nAn expensive function is one which demands high processing resources such as CPU usage, and is usually found in\r\nlarge mathematical calculations, or deep recursion. It is vital that developers are aware of the 'cost' of their\r\nfunctions when utilising it in code.\r\n\r\n**An example of a cheap, non-expensive function**\r\n\r\n```lua\r\nlocal function fiveTimesTwo(): number\r\n    return 5 * 2\r\nend\r\n\r\n-- O(1) notation\r\n```\r\n\r\nThis is considered a **cheap** function as we have:\r\n\r\n- Fixed inputs\\*.\r\n- Working with small, practical numbers far under any straining values.\r\n- Performing a simple, arithmetic mathematical function.\r\n\r\n_\\*fixed inputs itself does not confirm a cheap function, however in our case we can confirm the function is cheap because our fixed inputs are small integers._\r\n\r\n**An example of an expensive function**\r\n\r\n```lua\r\nlocal function ackermann(m, n)\r\n    if m == 0 then\r\n        return n + 1\r\n    elseif n == 0 then\r\n        return ackermann(m - 1, 1)\r\n    else\r\n        return ackermann(m - 1, ackermann(m, n - 1))\r\n    end\r\nend\r\n\r\n-- Θ(A(m, n)) - Big-Theta notation which describes the asymptotic tight bound of function A(m, n)\r\n```\r\n\r\nThis is considered an **expensive** function as:\r\n\r\n- There is an explosive recursion which is notorious with the Ackermann function.\r\n\r\n_Important note_\r\n\r\n- This is an **extremely expensive function, and is a hyperbole of what an expensive function is**.\r\n\r\n<h2>When should I memoize?</h2>\r\nMemoization might sound like a bandaid solution to a lot of functions you have in your project, and can be quite tempting to use for things that might not exactly need memoization. It's important to memoize with care, as you could actually incur more performance-related issues using it, than without.\r\n\r\n**Tips**\r\nIf your function takes the same inputs then its generally good to memoize, like calculating the start position and end position of a moving door which root position doesn't change, and is not influenced by physics- then you would likely consider memoizing.\r\n\r\nAn example of deciding whether to Memoize or not is shown in the difference between the **Leibniz determinant formula** and the **recursive Laplace expansion**, both used for calculating the determinant of a matrix.\r\n\r\n**Leibniz**\r\n\r\n<div align=\"left\">\r\n    <img src=\"assets/naive-detmatrix.png\" alt=\"Leibniz\" height=\"150\" />\r\n</div>\r\nThe Leibniz formula is *not ideal* for memoization, as a naive method we are working with a generally inefficient formula. We express the determinant as a sum over all n! permutations of the matrix elements. Each of the n! terms is unique, meaning memoization is actually adding more cost as we are not gaining any recomputations of the same inputs.\r\n\r\n**Recursive Laplace Expansion**\r\n\r\n<div align=\"left\">\r\n    <img src=\"assets/laplace.png\" alt=\"Leibniz\" height=\"150\" />\r\n</div>\r\nThe recursive Laplace expansion **is ideal** for memoization, as we calculate the determinant of an `n * n` matrix by reducing it to a sum of determinants of minors. This leads to many repeated calculations of the determinants of the same minors, so we are recomputing the same inputs many times.\r\n\r\n<h2>Getting Started</h2>\r\nMemoize can be installed using Wally or by the raw source release.\r\n\r\nGet started by adding this to your `wally.toml`\r\n\r\n```toml\r\nmemoize = \"djuntu/memoize@0.1.0\"\r\n```\r\n\r\nThen install using `wally install`.\r\n\r\n```console\r\nyou@bash:~$ sudo wally install\r\n```\r\n\r\nMemoize provides a simple API that allows developers exclusive freedoms over interaction, this is\r\nan example of a simple memoized function and its intended outputs.\r\n\r\n```lua\r\nlocal memoize = require(path.to.memoize)\r\n\r\n-- This function doesn't perform a specific mathematical function, rather acts\r\n-- as an 'expensive' function.\r\nlocal function myExpensiveFunction(a: number, b: number): number\r\n    print(\"doing a calculation...\")\r\n    return (a*b)^(b^2)\r\nend\r\n\r\nlocal memoizedFunction = memoize.memoize(myExpensiveFunction)\r\n\r\nprint(memoizedFunction(2, 4))\r\n-- output: doing a calculation...\r\n-- output: 281474976710656\r\nprint(memoizedFunction(2, 4))\r\n-- output: 281474976710656\r\nprint(memoizedFunction(2, 4))\r\n-- output: 281474976710656\r\n```\r\n\r\nWhat if I want to discard a memoized value and recalculate it at another time?\r\n\r\n- Memoize provides a `maxAge` option when memoizing a function, giving you scheduled\r\n  control over your memoization cache, where `{ maxAge = n miliseconds }`.\r\n\r\n```lua\r\nlocal memoizedFunction = memoize.memoize(myExpensiveFunction, { maxAge = 10000 }) -- expressed in miliseconds! (10 seconds)\r\n\r\nprint(memoizedFunction(2, 4))\r\n-- output: doing a calculation...\r\n-- output: 281474976710656\r\n\r\ntask.wait(5) -- 5 seconds elapsed\r\nprint(memoizedFunction(2, 4))\r\n-- output: 281474976710656\r\n\r\ntask.wait(6) -- 11 seconds elapsed\r\nprint(memoizedFunction(2, 4))\r\n-- output: doing a calculation...\r\n-- output: 281474976710656\r\n```\r\n\r\nBut what if I don't want a timer, and instead am relying on a signal, or another function and the expiration time is unknown?\r\n\r\n- Memoize provides a neat function where you can clear all timers and cache.\r\n\r\n```lua\r\nlocal memoizedFunction = memoize.memoize(myExpensiveFunction)\r\n\r\nprint(memoizedFunction(2, 4))\r\n-- output: doing a calculation...\r\n-- output: 281474976710656\r\nprint(memoizedFunction(2, 4))\r\n-- output: 281474976710656\r\n\r\n-- clear and rememoize function\r\nmemoize.memoizeClear(memoizedFunction)\r\nmemoizedFunction = memoize.memoize(memoizedFunction)\r\n\r\nprint(memoizedFunction(2, 4))\r\n-- output: doing a calculation...\r\n-- output: 281474976710656\r\nprint(memoizedFunction(2, 4))\r\n-- output: 281474976710656\r\n```\r\n\r\n<h2>Classes and Memoization</h2>\r\nMemoization with classes can seem daunting at first, but this package aims to simpify it as much as possible by using a **decorator-like approach**.\r\n\r\n- Due to Roblox's runtime, we cannot put decorators above methods, so Memoize advises you put them directly below the method.\r\n\r\n```lua\r\nlocal memoizeMethod = require([path.to.memoize].Method)\r\nlocal myClass = {}\r\nmyClass.__index = myClass\r\n\r\nfunction myClass.new()\r\n    return setmetatable({ num = 0 }, myClass)\r\nend\r\n\r\nfunction myClass:add(n: number): number\r\n    self.num += n\r\n    return self.num\r\nend\r\nmemoizeMethod()(myClass, 'add') -- decorate\r\n\r\n---------------------------------------------\r\n\r\nlocal object = myClass.new()\r\n\r\nprint(object:add(5)) -- 5\r\nprint(object:add(5)) -- 5\r\nprint(object:add(6)) -- 11\r\nprint(object:add(6)) -- 11\r\n```\r\n","readmeTruncated":false}