{"id":437,"date":"2025-12-11T16:12:51","date_gmt":"2025-12-11T15:12:51","guid":{"rendered":"https:\/\/informedica.nl\/?p=437"},"modified":"2025-12-14T11:07:31","modified_gmt":"2025-12-14T10:07:31","slug":"heres-your-existing-text-with-a-new-section-added-at-the-end-that-shows-how-you-can-pull-in-and-run-tests-from-the-same-script-file-reusing-the-existing-test-code-and-libraries","status":"publish","type":"post","link":"https:\/\/informedica.nl\/?p=437","title":{"rendered":"Taming the Brown Field with F#: Interactive Refactoring for Mature Codebases"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\">Introduction<\/h2>\n\n\n\n<p>If you\u2019ve read the previous Informedica post on <a href=\"https:\/\/informedica.nl\/?p=432\" data-type=\"post\" data-id=\"432\" target=\"_blank\" rel=\"noreferrer noopener\">F# Interactive (FSI)<\/a> and why it\u2019s a powerful tool in your development toolbox, then this post takes the <em>next step<\/em>.\u00a0The first post introduced <em>what FSI is<\/em> and why it\u2019s useful. This post shows <em>how you can harness it practically on a brown-field project<\/em> such as <a href=\"https:\/\/github.com\/informedica\/GenPRES\" target=\"_blank\" rel=\"noreferrer noopener\">GenPRES<\/a>.\u00a0<\/p>\n\n\n\n<p>Most of us don\u2019t work in green-field environments where code is written fresh and clean. Instead, we struggle with <strong>brown-field codebases<\/strong>\u2014systems that have grown over years, accreted complexity, and become mission-critical. Changing code in these environments is often intimidating: any modification risks unexpected consequences somewhere else in the system.<\/p>\n\n\n\n<p>This is exactly where <strong>F# Interactive (FSI)<\/strong> becomes a superpower. <\/p>\n\n\n<p><!--more--><\/p>\n\n\n<p>Building on that, here I show how you can:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Extract and evolve existing production code<\/strong>, not just green-field prototypes<\/li>\n\n\n\n<li><strong>Bring entire modules into a script<\/strong>, refactor them interactively<\/li>\n\n\n\n<li><strong>Reuse your actual test suite in FSI<\/strong><\/li>\n\n\n\n<li><strong>Validate new features (like parallelism) without risking the main codebase<\/strong><\/li>\n<\/ul>\n\n\n\n<p>FSI lets us <strong>copy existing production code into a script file<\/strong>, make changes, and evaluate those changes instantly. Instead of performing risky refactors inside the main project, we isolate the code, try new ideas interactively, and only move changes back once we\u2019re confident they work.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Tips &amp; Tricks for Working with F# Interactive (FSI)<\/h2>\n\n\n\n<p>Here I list some tips and tricks I learned using the FSI for real production work.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">1. Always Set the Current Directory<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"fsharp\" class=\"language-fsharp\">Environment.CurrentDirectory &lt;- __SOURCE_DIRECTORY__<\/code><\/pre>\n\n\n\n<p><br>This ensures that relative paths work exactly as expected when loading or referencing other scripts or test assets.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">2. Load Project Context Automatically<\/h3>\n\n\n\n<p>Use a bootstrap file like:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"fsharp\" class=\"language-fsharp\">#load \"load.fsx\"<\/code><\/pre>\n\n\n\n<p>The bootstrap file loads all the dependent libraries, either local using source files or compiled libraries, or using nuget as shown below.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">3. Reference NuGet Packages Inline<\/h3>\n\n\n\n<p>FSI handles this gracefully:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"fsharp\" class=\"language-fsharp\">#r \"nuget: Expecto, 9.0.4\"<\/code><\/pre>\n\n\n\n<p>This lets you run tests, do property checks, and more without a full project file. It\u2019s perfect for lightweight explorations.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">4. Copy Only the Code You Need (Incrementally)<\/h3>\n\n\n\n<p>Instead of dragging entire modules into a script, start with just the functions you plan to modify or inspect \u2014 e.g.,  in the below example: the solve, solveAll, and helper functions from the Solver module.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">5. Reuse Your Existing Tests<\/h3>\n\n\n\n<p>As shown above, you can #load your actual test files (e.g., from your tests directory) and run them directly in FSI. That\u2019s an excellent way to verify behavior <em>before<\/em> touching the production code.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">6. Modularize Your Script<\/h3>\n\n\n\n<p>Break your script into logical regions (helpers, solver refactor, tests) with comments. That makes it easier to jump around interactively.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">7. Use Partial Evaluation<\/h3>\n\n\n\n<p>FSI lets you evaluate only parts of a script \u2014 use this to validate small functions or new approaches <em>without reloading the whole file<\/em>. Just select part of a script and send it to the FSI.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">8. Keep FSI Sessions Alive<\/h3>\n\n\n\n<p>Rather than invoking FSI afresh each time, keep a session running so you build up state interactively. This is where tools like MCP integration become even more interesting.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">9. Using FSI with MCP (Model Context Protocol) for Enhanced Interactive Workflows<\/h3>\n\n\n\n<p>You can go beyond the ordinary FSI experience by integrating it with an MCP-enabled server such as the open-source <strong><a href=\"https:\/\/github.com\/jovaneyck\/fsi-mcp-server\" target=\"_blank\" rel=\"noreferrer noopener\">fsi-mcp-server<\/a><\/strong>. This tool is a drop-in replacement for the default fsi.exe that exposes F# Interactive sessions over a Model Context Protocol (MCP) interface.\u00a0<\/p>\n\n\n\n<p>This unlocks powerful workflows:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>AI-assisted interactive sessions<\/strong> Your AI assistant (e.g., Claude Code, Copilot, or another MCP-compatible agent) can programmatically <em>send code<\/em> to be evaluated in the same FSI session you\u2019re using, inspect outputs, run tests, and more \u2014 without the classic friction of copy-paste or external execution.\u00a0<\/li>\n\n\n\n<li><strong>Seamless IDE Integration<\/strong> Replace your standard FSI executable in your editor (VS Code, Rider, etc.) with the MCP-enabled server, and the AI assistant becomes a <em>first-class collaborative partner<\/em> in your REPL workflow.<\/li>\n\n\n\n<li><strong>Shared REPL State Across Interfaces<\/strong> Both you (at the console or editor send-to-REPL) and the AI agent share the same state \u2014 the definitions, modules, and script context are visible and modifiable by both sides.&nbsp;<\/li>\n<\/ul>\n\n\n\n<p>This MCP integration keeps the tight interactive feedback loop of FSI alive even in more <em>connected and collaborative workflows<\/em>, especially useful when experimenting with large codebases or refactors like those in this blog.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">A Real World Example: GenPRES<\/h2>\n\n\n\n<p>In the open-source project <a href=\"https:\/\/github.com\/informedica\/GenPRES\" target=\"_blank\" rel=\"noreferrer noopener\">GenPRES<\/a>, the Solver.fs module is a great example. It contains the core logic for evaluating product and sum equations\u2014dense, interconnected, and essential. Before modifying it, we want a safe environment where we can experiment.<\/p>\n\n\n\n<p>Below is the relevant part of the <strong><a href=\"https:\/\/github.com\/informedica\/GenPRES\/blob\/c930eb1129e0a99c996c0f0564bd594b93cf8e22\/src\/Informedica.GenSolver.Lib\/Solver.fs\" target=\"_blank\" rel=\"noreferrer noopener\">original solver<\/a><\/strong>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Original solve Function (excerpt from Solver.fs)<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"fsharp\" class=\"language-fsharp line-numbers\">let solve onlyMinIncrMax log sortQue var eqs =\n\n    let solveE n eqs eq =\n        try\n            Equation.solve onlyMinIncrMax log eq\n        with\n        | Exceptions.SolverException errs -&gt;\n            (n, errs, eqs)\n            |&gt; Exceptions.SolverErrored\n            |&gt; Exceptions.raiseExc (Some log) errs\n        | e -&gt;\n            writeErrorMessage $\"didn't catch {e}\"\n            failwith \"unexpected\"\n\n    let rec loop n que acc =\n        match acc with\n        | Error _ -&gt; acc\n        | Ok acc -&gt;\n            let n = n + 1\n            if n &gt; (que @ acc |&gt; List.length) * Constants.MAX_LOOP_COUNT then\n                (n, que @ acc)\n                |&gt; Exceptions.SolverTooManyLoops\n                |&gt; Exceptions.raiseExc (Some log) []\n\n            let que = que |&gt; sortQue onlyMinIncrMax\n\n            match que with\n            | [] -&gt;\n                match acc |&gt; List.filter (Equation.check &gt;&gt; not) with\n                | [] -&gt; Ok acc\n                | invalid -&gt;\n                    invalid\n                    |&gt; Exceptions.SolverInvalidEquations\n                    |&gt; Exceptions.raiseExc (Some log) []\n\n            | eq :: tail -&gt;\n                let q, r =\n                    if eq |&gt; Equation.isSolvable |&gt; not then\n                        tail, Ok (eq :: acc)\n                    else\n                        match eq |&gt; solveE n (acc @ que) with\n                        | eq, Changed cs -&gt;\n                            let vars = cs |&gt; List.map fst\n                            acc |&gt; replace vars |&gt; fun (rpl, rst) -&gt;\n                            let que =\n                                tail\n                                |&gt; replace vars\n                                |&gt; fun (es1, es2) -&gt; es1 @ es2 @ rpl\n                            que, Ok (eq :: rst)\n\n                        | eq, Unchanged -&gt;\n                            tail, Ok (eq :: acc)\n\n                        | eq, Errored m -&gt;\n                            [], Error (eq :: acc @ que, m)\n\n                loop n q r\n\n    match var with\n    | None -&gt; eqs, []\n    | Some var -&gt; eqs |&gt; replace [var]\n    |&gt; fun (rpl, rst) -&gt;\n        match rpl with\n        | [] -&gt; Ok eqs\n        | _  -&gt;\n            rpl |&gt; Events.SolverStartSolving |&gt; Logger.logInfo log\n            loop 0 rpl (Ok rst)<\/code><\/pre>\n\n\n\n<p>This function works\u2014but it\u2019s intricate, highly recursive, and difficult to evolve safely.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Updated Solver in an .fsx Script (focused on differences)<\/h2>\n\n\n\n<p>To experiment safely, we moved a <strong><a href=\"https:\/\/github.com\/informedica\/GenPRES\/blob\/35df6bc0be902f0f5b05d16569b8aac4bde4907e\/src\/Informedica.GenSOLVER.Lib\/Scripts\/Parallel.fsx\" target=\"_blank\" rel=\"noreferrer noopener\">local copy<\/a><\/strong> of the solver into an F# script and add an alternative to the solving loop:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"fsharp\" class=\"language-fsharp\">\/\/\/ Solve equations in parallel.\n\/\/\/ Still an experimental feature.\n\/\/\/ Parallel distribution is cyclic\nlet parallelLoop onlyMinIncrMax log sortQue n rpl rst =\n\n    let solveE n eqs eq =\n        try\n            Equation.solve onlyMinIncrMax log eq\n        with\n        | Exceptions.SolverException errs -&gt;\n            (n, errs, eqs)\n            |&gt; Exceptions.SolverErrored\n            |&gt; Exceptions.raiseExc (Some log) errs\n        | e -&gt;\n            let msg = $\"didn't catch {e}\"\n            writeErrorMessage msg\n\n            msg |&gt; failwith\n\n    let rec loop n que acc =\n        match acc with\n        | Error _ -&gt; acc\n        | Ok acc  -&gt;\n            let n = n + 1\n            let c = que @ acc |&gt; List.length\n            if c &gt; 0 &amp;&amp; n &gt; c * Constants.MAX_LOOP_COUNT then\n                writeErrorMessage $\"too many loops: {n}\"\n\n                (n, que @ acc)\n                |&gt; Exceptions.SolverTooManyLoops\n                |&gt; Exceptions.raiseExc (Some log) []\n\n            match que with\n            | [] -&gt;\n                match acc |&gt; List.filter (Equation.check &gt;&gt; not) with\n                | []      -&gt; acc |&gt; Ok\n                | invalid -&gt;\n                    writeErrorMessage \"invalid equations\"\n\n                    invalid\n                    |&gt; Exceptions.SolverInvalidEquations\n                    |&gt; Exceptions.raiseExc (Some log) []\n\n            | _ -&gt;\n                let que, acc =\n                    que\n                    |&gt; List.partition Equation.isSolvable\n                    |&gt; function\n                    | que, unsolv -&gt; que, unsolv |&gt; List.append acc\n                \/\/ make sure that the equations with the lowest cost\n                \/\/ are prioritezed\n                let que = que |&gt; sortQue onlyMinIncrMax\n                \/\/ apply parallel equation solving to the\n                \/\/ first number of optimal parallel workers \n                let rstQue, (rpl, rst) =\n                    let queLen = que |&gt; List.length\n                    \/\/ calculate optimal number of workers\n                    let workers =\n                        if Parallel.totalWorders &gt; queLen then queLen\n                        else Parallel.totalWorders\n                    \/\/ return remaining que and calculate\n                    \/\/ in parallel the worker que\n                    if workers &gt;= queLen then []\n                    else que |&gt; List.skip workers\n                    ,\n                    que\n                    |&gt; List.take workers\n                    |&gt; List.map (fun eq -&gt;\n                        async {\n                            return eq |&gt; solveE n (acc @ que)\n                        }\n                    )\n                    |&gt; Async.Parallel\n                    |&gt; Async.RunSynchronously\n                    |&gt; Array.toList\n                    |&gt; List.partition (snd &gt;&gt; function | Changed _ -&gt; true | _ -&gt; false)\n\n                let rst, err =\n                    rst\n                    |&gt; List.partition (snd &gt;&gt; function | Errored _ -&gt; true | _ -&gt; false)\n                    |&gt; function\n                    | err, rst -&gt;\n                        rst |&gt; List.map fst,\n                        err\n                        |&gt; List.choose (fun (_, sr) -&gt; sr |&gt; function | Errored m -&gt; Some m | _ -&gt; None)\n                        |&gt; List.collect id\n\n                if err |&gt; List.isEmpty |&gt; not then (que |&gt; List.append acc, err) |&gt; Error\n                else\n                    let rpl, vars =\n                        rpl\n                        |&gt; List.unzip\n\n                    let vars =\n                        vars\n                        |&gt; List.choose (function\n                            | Changed vars -&gt; Some vars\n                            | _ -&gt; None\n                        )\n                        |&gt; List.collect id\n                        |&gt; List.fold (fun (vars : Variable list) (var, _) -&gt;\n                            match vars |&gt; List.tryFind (Variable.eqName var) with\n                            | None -&gt; var::vars\n                            | Some v -&gt;\n                                let vNew = v |&gt; Variable.setValueRange var.Values\n                                vars |&gt; List.replace (Variable.eqName vNew) vNew\n                        ) []\n                    \/\/ make sure that vars are updated with changed vars \n                    \/\/ in the remaining que\n                    let rstQue = \n                        rstQue\n                        |&gt; replace vars\n                        |&gt; function\n                        | es1, es2 -&gt; es1 |&gt; List.append es2\n                    \/\/ calculate new accumulator and que\n                    let acc, que =\n                        acc\n                        |&gt; List.append rst\n                        |&gt; replace vars\n                        |&gt; function\n                        | es1, es2 -&gt;\n                            es2 |&gt; Ok,\n                            es1\n                            |&gt; List.append rpl\n                            |&gt; List.append rstQue\n\n                    loop n que acc\n\n    loop n rpl rst<\/code><\/pre>\n\n\n\n<p>By copying the solver module into a script:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>You can <strong>refactor or extend the code incrementally<\/strong> without touching production.<\/li>\n\n\n\n<li>You can <strong>run the solver interactively<\/strong> on sample equations.<\/li>\n\n\n\n<li>You can introduce features (like parallelism) without fear.<\/li>\n\n\n\n<li>You gain <strong>instant feedback<\/strong> from FSI.<\/li>\n\n\n\n<li>When satisfied, you copy the improved code back into the main project.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Running the Existing Tests from the Same Script<\/h3>\n\n\n\n<p>The really nice part is that you don\u2019t just get to run ad-hoc experiments: you can also pull in your existing test suite and reuse it directly from the script.<\/p>\n\n\n\n<p>In the same .fsx file, we load the existing tests from the main solution and define a small test module that compares the behavior of the sequential and parallel solvers:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"fsharp\" class=\"language-fsharp\">\/\/ load the existing tests\n#load \"..\/..\/..\/tests\/Informedica.GenSOLVER.Tests\/Tests.fs\"\n\nopen MathNet.Numerics\nopen Expecto\nopen Expecto.Flip\nopen Informedica.GenSolver.Tests\nopen Informedica.GenUnits.Lib\n\nmodule Tests =\n\n    module ParallelTests =\n\n        open Informedica.Logging.Lib\n        open Informedica.GenSolver.Lib\n\n        let logger =\n            fun (_ : string) -&gt; ()\n            |&gt; SolverLogging.create\n\n        \/\/\/ Solve equations sequentially\n        let solveSequential onlyMinMax eqs =\n            Informedica.GenSolver.Lib.Solver.solveAll\n                false       \/\/ useParallel = false (sequential)\n                onlyMinMax\n                logger\n                eqs\n\n        \/\/\/ Solve equations in parallel\n        let solveParallel onlyMinMax eqs =\n            Informedica.GenSolver.Lib.Solver.solveAll\n                true        \/\/ useParallel = true\n                onlyMinMax\n                logger\n                eqs\n\n        \/\/\/ Helper to compare equation results\n        let eqsAreEqual eqs1 eqs2 =\n            match eqs1, eqs2 with\n            | Ok eqs1, Ok eqs2 -&gt;\n                let s1 =\n                    eqs1\n                    |&gt; List.map (Equation.toString true)\n                    |&gt; List.sort\n                let s2 =\n                    eqs2\n                    |&gt; List.map (Equation.toString true)\n                    |&gt; List.sort\n                s1 = s2\n            | Error _, Error _ -&gt; true\n            | _ -&gt; false\n\n        let mg = Units.Mass.milliGram\n        let day = Units.Time.day\n        let kg = Units.Weight.kiloGram\n        let mgPerDay = CombiUnit(mg, OpPer, day)\n        let mgPerKgPerDay = (CombiUnit (mg, OpPer, kg), OpPer, day) |&gt; CombiUnit\n\n        let tests = testList \"Parallel vs Sequential Solving\" [\n\n            test \"simple product equation gives same results\" {\n                let eqs =\n                    [ \"a = b * c\" ]\n                    |&gt; TestSolver.init\n                    |&gt; TestSolver.setMinIncl Units.Count.times \"a\" 1N\n                    |&gt; TestSolver.setMaxIncl Units.Count.times \"a\" 100N\n                    |&gt; TestSolver.setMinIncl Units.Count.times \"b\" 1N\n                    |&gt; TestSolver.setMaxIncl Units.Count.times \"b\" 10N\n                    |&gt; TestSolver.setMinIncl Units.Count.times \"c\" 1N\n                    |&gt; TestSolver.setMaxIncl Units.Count.times \"c\" 10N\n\n                let seqResult = eqs |&gt; solveSequential true\n                let parResult = eqs |&gt; solveParallel true\n\n                eqsAreEqual seqResult parResult\n                |&gt; Expect.isTrue \"sequential and parallel should give same results\"\n            }\n\n            test \"sum equation gives same results\" {\n                let eqs =\n                    [ \"total = a + b + c\" ]\n                    |&gt; TestSolver.init\n                    |&gt; TestSolver.setMinIncl Units.Count.times \"total\" 10N\n                    |&gt; TestSolver.setMaxIncl Units.Count.times \"total\" 100N\n                    |&gt; TestSolver.setMinIncl Units.Count.times \"a\" 1N\n                    |&gt; TestSolver.setMaxIncl Units.Count.times \"a\" 50N\n                    |&gt; TestSolver.setMinIncl Units.Count.times \"b\" 1N\n                    |&gt; TestSolver.setMaxIncl Units.Count.times \"b\" 50N\n                    |&gt; TestSolver.setMinIncl Units.Count.times \"c\" 1N\n                    |&gt; TestSolver.setMaxIncl Units.Count.times \"c\" 50N\n\n                let seqResult = eqs |&gt; solveSequential true\n                let parResult = eqs |&gt; solveParallel true\n\n                eqsAreEqual seqResult parResult\n                |&gt; Expect.isTrue \"sequential and parallel should give same results for sum\"\n            }\n\n            test \"multiple equations give same results\" {\n                let eqs =\n                    [ \"ParacetamolDoseTotal = ParacetamolDoseTotalAdjust * Adjust\" ]\n                    |&gt; TestSolver.init\n                    |&gt; TestSolver.setMinIncl mgPerDay \"ParacetamolDoseTotal\" 180N\n                    |&gt; TestSolver.setMaxIncl mgPerDay \"ParacetamolDoseTotal\" 3000N\n                    |&gt; TestSolver.setMinIncl mgPerKgPerDay \"ParacetamolDoseTotalAdjust\" 40N\n                    |&gt; TestSolver.setMaxIncl mgPerKgPerDay \"ParacetamolDoseTotalAdjust\" 90N\n                    |&gt; TestSolver.setMaxIncl kg \"Adjust\" 100N\n\n                let seqResult = eqs |&gt; solveSequential true\n                let parResult = eqs |&gt; solveParallel true\n\n                eqsAreEqual seqResult parResult\n                |&gt; Expect.isTrue \"sequential and parallel should give same results for complex equation\"\n            }\n\n            test \"chained equations give same results\" {\n                let eqs =\n                    [\n                        \"x = a * b\"\n                        \"y = x * c\"\n                        \"z = y * d\"\n                    ]\n                    |&gt; TestSolver.init\n                    |&gt; TestSolver.setMinIncl Units.Count.times \"a\" 1N\n                    |&gt; TestSolver.setMaxIncl Units.Count.times \"a\" 10N\n                    |&gt; TestSolver.setMinIncl Units.Count.times \"b\" 1N\n                    |&gt; TestSolver.setMaxIncl Units.Count.times \"b\" 10N\n                    |&gt; TestSolver.setMinIncl Units.Count.times \"c\" 1N\n                    |&gt; TestSolver.setMaxIncl Units.Count.times \"c\" 10N\n                    |&gt; TestSolver.setMinIncl Units.Count.times \"d\" 1N\n                    |&gt; TestSolver.setMaxIncl Units.Count.times \"d\" 10N\n                    |&gt; TestSolver.setMinIncl Units.Count.times \"z\" 1N\n                    |&gt; TestSolver.setMaxIncl Units.Count.times \"z\" 10000N\n\n                let seqResult = eqs |&gt; solveSequential true\n                let parResult = eqs |&gt; solveParallel true\n\n                eqsAreEqual seqResult parResult\n                |&gt; Expect.isTrue \"sequential and parallel should give same results for chained equations\"\n            }\n\n            test \"with value sets gives same results\" {\n                let eqs =\n                    [ \"dose = qty * freq\" ]\n                    |&gt; TestSolver.init\n                    |&gt; TestSolver.setMinIncl Units.Count.times \"dose\" 10N\n                    |&gt; TestSolver.setMaxIncl Units.Count.times \"dose\" 100N\n                    |&gt; TestSolver.setValues Units.Count.times \"freq\" [1N; 2N; 3N; 4N]\n                    |&gt; TestSolver.setMinIncl Units.Count.times \"qty\" 1N\n                    |&gt; TestSolver.setMaxIncl Units.Count.times \"qty\" 50N\n\n                let seqResult = eqs |&gt; solveSequential false  \/\/ use full solving with value sets\n                let parResult = eqs |&gt; solveParallel false\n\n                eqsAreEqual seqResult parResult\n                |&gt; Expect.isTrue \"sequential and parallel should give same results with value sets\"\n            }\n        ]\n\n        let run () =\n            tests\n            |&gt; runTestsWithCLIArgs [] [| \"--summary\" |]<\/code><\/pre>\n\n\n\n<p>With this in place, you can now:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Edit the solver implementation in the same script.<\/li>\n\n\n\n<li>Run Tests.ParallelTests.run() in FSI.<\/li>\n\n\n\n<li>Immediately verify that your new parallel implementation behaves the same as the existing sequential one across realistic scenarios taken from your domain.<\/li>\n<\/ul>\n\n\n\n<p>This is the essence of <em>taming<\/em> a brown field with F#: you don\u2019t just change code\u2014you <strong>bring the code, the tests, and the runtime together in a single interactive environment<\/strong>, and let FSI give you fast, tight feedback loops while you evolve a mature codebase.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion: From Green Field Ideals to Brown Field Reality<\/h2>\n\n\n\n<p>Green-field projects are where we learn languages and frameworks. Brown-field projects are where we actually <em>use<\/em> them.<\/p>\n\n\n\n<p>In a green-field setting, everything is possible: you control the architecture, the abstractions, and the direction of the code. In brown-field systems\u2014like GenPRES\u2014you inherit real constraints: production behavior that must not change, performance characteristics that matter, and code that has accumulated knowledge over time. The challenge is not writing new code, but <strong>changing existing code safely<\/strong>.<\/p>\n\n\n\n<p>This is where F# Interactive truly distinguishes itself.<\/p>\n\n\n\n<p>By lifting real production modules into an .fsx script, you create a <strong>safe experimental zone<\/strong> inside a mature system. You can refactor, optimize, and even introduce new execution models\u2014such as parallel solving\u2014while continuously validating behavior against the <em>actual test suite<\/em>. The feedback loop is immediate, the risk is contained, and confidence grows with every evaluated expression.<\/p>\n\n\n\n<p>Seen in this light, FSI is not just a REPL or a learning tool. It is a <strong>brown-field engineering instrument<\/strong>.<\/p>\n\n\n\n<p>If the earlier Informedica post showed <em>why<\/em> FSI is such a powerful tool for exploration and understanding, this follow-up shows <em>how<\/em> to apply it pragmatically to evolve a real, non-trivial codebase. And with newer approaches\u2014such as running FSI through an MCP-enabled server\u2014you can even extend that interactive loop to include AI-assisted workflows without sacrificing the shared state and immediacy that make FSI so effective.<\/p>\n\n\n\n<p>Brown-field development doesn\u2019t have to mean slow, risky, or opaque change. With F#, FSI, and a disciplined interactive workflow, it becomes iterative, observable, and surprisingly enjoyable.<\/p>\n\n\n\n<p>That, ultimately, is how you <strong>tame the brown field<\/strong>.<\/p>\n<div class=\"pvc_clear\"><\/div><p id=\"pvc_stats_437\" class=\"pvc_stats all  \" data-element-id=\"437\" style=\"\"><i class=\"pvc-stats-icon medium\" aria-hidden=\"true\"><svg aria-hidden=\"true\" focusable=\"false\" data-prefix=\"far\" data-icon=\"chart-bar\" role=\"img\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 512 512\" class=\"svg-inline--fa fa-chart-bar fa-w-16 fa-2x\"><path fill=\"currentColor\" d=\"M396.8 352h22.4c6.4 0 12.8-6.4 12.8-12.8V108.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v230.4c0 6.4 6.4 12.8 12.8 12.8zm-192 0h22.4c6.4 0 12.8-6.4 12.8-12.8V140.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v198.4c0 6.4 6.4 12.8 12.8 12.8zm96 0h22.4c6.4 0 12.8-6.4 12.8-12.8V204.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v134.4c0 6.4 6.4 12.8 12.8 12.8zM496 400H48V80c0-8.84-7.16-16-16-16H16C7.16 64 0 71.16 0 80v336c0 17.67 14.33 32 32 32h464c8.84 0 16-7.16 16-16v-16c0-8.84-7.16-16-16-16zm-387.2-48h22.4c6.4 0 12.8-6.4 12.8-12.8v-70.4c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v70.4c0 6.4 6.4 12.8 12.8 12.8z\" class=\"\"><\/path><\/svg><\/i> <img loading=\"lazy\" decoding=\"async\" width=\"16\" height=\"16\" alt=\"Loading\" src=\"https:\/\/informedica.nl\/wp-content\/plugins\/page-views-count\/ajax-loader-2x.gif\" border=0 \/><\/p><div class=\"pvc_clear\"><\/div>","protected":false},"excerpt":{"rendered":"<p>Introduction If you\u2019ve read the previous Informedica post on F# Interactive (FSI) and why it\u2019s a powerful tool in your development toolbox, then this post takes the next step.\u00a0The first post introduced what FSI is and why it\u2019s useful. This post shows how you can harness it practically on a brown-field project such as GenPRES.\u00a0 &hellip; <\/p>\n<p class=\"link-more\"><a href=\"https:\/\/informedica.nl\/?p=437\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Taming the Brown Field with F#: Interactive Refactoring for Mature Codebases&#8221;<\/span><\/a><\/p>\n<div class=\"pvc_clear\"><\/div>\n<p id=\"pvc_stats_437\" class=\"pvc_stats all  \" data-element-id=\"437\" style=\"\"><i class=\"pvc-stats-icon medium\" aria-hidden=\"true\"><svg aria-hidden=\"true\" focusable=\"false\" data-prefix=\"far\" data-icon=\"chart-bar\" role=\"img\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 512 512\" class=\"svg-inline--fa fa-chart-bar fa-w-16 fa-2x\"><path fill=\"currentColor\" d=\"M396.8 352h22.4c6.4 0 12.8-6.4 12.8-12.8V108.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v230.4c0 6.4 6.4 12.8 12.8 12.8zm-192 0h22.4c6.4 0 12.8-6.4 12.8-12.8V140.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v198.4c0 6.4 6.4 12.8 12.8 12.8zm96 0h22.4c6.4 0 12.8-6.4 12.8-12.8V204.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v134.4c0 6.4 6.4 12.8 12.8 12.8zM496 400H48V80c0-8.84-7.16-16-16-16H16C7.16 64 0 71.16 0 80v336c0 17.67 14.33 32 32 32h464c8.84 0 16-7.16 16-16v-16c0-8.84-7.16-16-16-16zm-387.2-48h22.4c6.4 0 12.8-6.4 12.8-12.8v-70.4c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v70.4c0 6.4 6.4 12.8 12.8 12.8z\" class=\"\"><\/path><\/svg><\/i> <img loading=\"lazy\" decoding=\"async\" width=\"16\" height=\"16\" alt=\"Loading\" src=\"https:\/\/informedica.nl\/wp-content\/plugins\/page-views-count\/ajax-loader-2x.gif\" border=0 \/><\/p>\n<div class=\"pvc_clear\"><\/div>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[9],"tags":[],"class_list":["post-437","post","type-post","status-publish","format-standard","hentry","category-programming"],"_links":{"self":[{"href":"https:\/\/informedica.nl\/index.php?rest_route=\/wp\/v2\/posts\/437","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/informedica.nl\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/informedica.nl\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/informedica.nl\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/informedica.nl\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=437"}],"version-history":[{"count":22,"href":"https:\/\/informedica.nl\/index.php?rest_route=\/wp\/v2\/posts\/437\/revisions"}],"predecessor-version":[{"id":482,"href":"https:\/\/informedica.nl\/index.php?rest_route=\/wp\/v2\/posts\/437\/revisions\/482"}],"wp:attachment":[{"href":"https:\/\/informedica.nl\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=437"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/informedica.nl\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=437"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/informedica.nl\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=437"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}