Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A CEK Evaluator #223

Merged
merged 1 commit into from
Oct 31, 2024
Merged

A CEK Evaluator #223

merged 1 commit into from
Oct 31, 2024

Conversation

doyougnu
Copy link
Collaborator

@doyougnu doyougnu commented Jan 14, 2024

Still a draft but all but one test is passing.

TODOs:

  • A more robust note
  • add proper error handling
  • rebase onto main
  • remove the old evaluator

Summary by CodeRabbit

  • New Features

    • Enhanced evaluation process in the REPL for improved error handling and result display.
    • Added new language extensions for advanced type handling and pattern matching.
    • Introduced new functions for better error reporting and type evaluation.
  • Bug Fixes

    • Improved error handling and evaluation flow in the CEK interpreter.
  • Documentation

    • Updated method names and signatures for clarity and consistency.
  • Refactor

    • Renamed functions to better reflect their usage and purpose across modules.
  • Style

    • Enhanced formatting for error messages and evaluation results for better user experience.

@doyougnu doyougnu force-pushed the doyougnu/wip/useful-repl branch 2 times, most recently from fa83318 to ea1aea6 Compare January 14, 2024 00:20
@gelisam
Copy link
Owner

gelisam commented Jan 14, 2024

I'm experimenting with an AI-based code review tool called "CodeRabbit", please let me know if its comments are annoying and I'll turn it off.

@gelisam gelisam added the CodeRabbit summons the AI reviewing bot label Jan 20, 2024
@gelisam gelisam mentioned this pull request Jan 20, 2024
Copy link

coderabbitai bot commented Feb 12, 2024

Walkthrough

The recent updates enhance the evaluation process by introducing a new evaluateIn function, restructuring the Evaluator module with new data types and functions, and refining error handling. These changes improve the CEK interpreter's logic and control flow. Additionally, the Expander module sees function renaming and refactoring, while the Pretty module adds a new error case for better error messaging.

Changes

Files Change Summaries
repl/Main.hs Replaced runExceptT and runReaderT with evaluateIn; updated error handling and result printing logic.
src/Evaluator.hs Added language extensions, new data types (EvalErrorIdent, EState, Kont), and multiple new functions for CEK interpreter logic.
src/Expander.hs, src/Expander/Monad.hs Renamed expandEval to evalInCurrentPhase; refactored functions and adjusted error handling.
src/Pretty.hs Added error case for EvalErrorIdent in Pretty VarInfo EvalError instance.

Sequence Diagram(s) (Beta)

sequenceDiagram
    participant User
    participant REPL
    participant Evaluator
    participant Expander

    User->>REPL: Enter expression
    REPL->>Evaluator: evaluateIn(expression)
    Evaluator->>Expander: evalInCurrentPhase(expression)
    Expander-->>Evaluator: Expanded expression
    Evaluator-->>REPL: Evaluated result
    REPL-->>User: Display result
Loading

Poem

🐇 In the realm of code so grand,
A rabbit hops with changes planned.
Evaluations now refined,
Errors clearer, functions aligned.
With evaluateIn we stride,
Through logic’s flow, we now glide.
Hopping forward, side by side. 🌟


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@doyougnu doyougnu force-pushed the doyougnu/wip/useful-repl branch from 056bf16 to aa970da Compare February 24, 2024 00:34
@doyougnu
Copy link
Collaborator Author

Should be ready for review now. Its a bit slower than what we had. Why is that? Well:

$  perf stat -- cabal test --test-show-details=streaming
 Performance counter stats for 'cabal test --test-show-details=streaming':

         18,349.25 msec task-clock:u                     #    0.996 CPUs utilized          
                 0      context-switches:u               #    0.000 /sec                   
                 0      cpu-migrations:u                 #    0.000 /sec                   
           717,329      page-faults:u                    #   39.093 K/sec                  
    77,585,648,085      cycles:u                         #    4.228 GHz                    
   169,000,946,915      instructions:u                   #    2.18  insn per cycle         
    36,205,500,147      branches:u                       #    1.973 G/sec                  
       497,651,939      branch-misses:u                  #    1.37% of all branches        
   387,384,692,920      slots:u                          #   21.112 G/sec                  
   111,272,326,760      topdown-retiring:u               #     19.8% Retiring              
   335,353,029,163      topdown-bad-spec:u               #     59.7% Bad Speculation       
    75,804,429,827      topdown-fe-bound:u               #     13.5% Frontend Bound        
    39,530,241,508      topdown-be-bound:u               #      7.0% Backend Bound         

      18.416893454 seconds time elapsed

      17.278805000 seconds user
       1.124952000 seconds sys

Because it destroys the branch predictor in my laptop! {59.7% bad speculation)

@doyougnu doyougnu requested a review from gelisam February 24, 2024 22:14
@doyougnu doyougnu marked this pull request as ready for review February 28, 2024 00:04
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Status

Actionable comments generated: 2

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between 67d4eed and aa970da.
Files selected for processing (5)
  • repl/Main.hs (2 hunks)
  • src/Evaluator.hs (4 hunks)
  • src/Expander.hs (13 hunks)
  • src/Expander/Monad.hs (2 hunks)
  • src/Pretty.hs (1 hunks)
Additional comments: 18
repl/Main.hs (1)
  • 149-151: The replacement of runExceptT and runReaderT with evaluateIn directly affects the error handling mechanism. This change simplifies the control flow by directly invoking evaluateIn for evaluation, which could potentially make the error handling more specific and tailored to the needs of the REPL environment. However, ensure that this new approach does not overlook any errors that were previously caught by the monadic computations. It's also important to verify that the performance impact, if any, is within acceptable limits given the concerns raised about the new evaluator's efficiency.
src/Pretty.hs (1)
  • 627-627: Adding the case pp env (EvalErrorIdent v) to handle errors related to binding identifiers to non-values is a valuable enhancement to the error reporting mechanism. This addition ensures that such errors are reported in a user-friendly manner, aiding in debugging and error resolution. It's crucial to ensure that this new error case is adequately tested to confirm that it correctly identifies and reports the intended error scenarios.
src/Evaluator.hs (3)
  • 87-90: The introduction of the EvalResult data type with constructors like ExampleResult and IOResult is a significant change. It's important to ensure that all parts of the codebase that interact with evaluation results are updated to handle these new cases appropriately.
  • 111-168: The Kont data type is central to the CEK interpreter's operation, representing various states of the continuation. The detailed constructors like InArg, InFun, InLetDef, etc., are well-documented. However, it's crucial to ensure that all pattern matches against Kont are exhaustive to prevent runtime errors.
  • 450-666: > 📝 NOTE

This review was outside the diff hunks and was mapped to the diff hunk with the greatest overlap. Original lines [441-666]

The helper functions and the top-level API (evaluate, evaluateIn, evaluateWithExtendedEnv, projectError, erroneousValue) are crucial for the evaluator's usability and integration with the rest of the codebase. It's important to ensure that these functions are correctly implemented and that their interfaces are clear and consistent with the rest of the project.

Verification successful

The usage of the top-level API functions (evaluate, evaluateIn, evaluateWithExtendedEnv, projectError, erroneousValue) across various parts of the project, including testing, the REPL, expansion, and within the evaluator itself, confirms their correct implementation and integration. Their application in different contexts aligns with their intended purposes, indicating that these functions are crucial for the evaluator's usability and integration with the rest of the codebase. Based on this analysis, the review comment is verified as accurate.

Scripts Executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Search for usage of top-level API functions to ensure they are correctly integrated.
rg "evaluate|evaluateIn|evaluateWithExtendedEnv|projectError|erroneousValue" --type hs

Length of output: 3679

src/Expander/Monad.hs (2)
  • 9-9: The addition of the FlexibleContexts language extension is noted. This extension allows for more flexible context specifications in type signatures, which can be necessary for certain function signatures or typeclass instances involving complex type constraints. Given the changes in the expandEval function, this addition seems justified.
  • 864-871: The signature of expandEval has been changed from Eval a -> Expand a to Core -> Expand Value, indicating a shift in how evaluation is performed. This change aligns with the PR's objective to introduce a new CEK evaluator. The function now directly takes a Core expression and evaluates it within the current environment, handling errors by throwing a MacroEvaluationError with the current phase and the projected error. This approach simplifies the evaluation process by directly working with core expressions and leveraging the existing environment, which is a positive change in terms of maintainability and clarity. However, it's crucial to ensure that all calls to expandEval throughout the codebase have been updated to match this new signature to avoid type mismatches.
src/Expander.hs (11)
  • 86-86: The import statements for Util.Set and Util.Store as Set and S respectively have been added. Ensure that these modules are used appropriately in the file and that their functions are called correctly to avoid any runtime errors or unexpected behavior.
  • 275-275: The call to expandEval within the evalDecl function for the Define case indicates a significant change in how expressions are evaluated during the declaration expansion process. Ensure that expandEval is correctly handling the evaluation of expressions and that the error handling mechanisms are robust enough to catch and report errors effectively.
  • 302-302: Another instance of expandEval being used, this time within the Example case of evalDecl. It's crucial to verify that the evaluation context and environment are correctly set up before calling expandEval to ensure that the expression evaluation proceeds as expected.
  • 305-305: The use of expandEval in the Run case of evalDecl highlights the importance of ensuring that the evaluated expression is of the expected type, especially when dealing with IO actions. Proper type checking and error handling are essential to prevent runtime type errors.
  • 316-316: In the DefineMacros case, the call to expandEval signifies a critical point where macro definitions are evaluated. It's important to ensure that the macro evaluation environment is correctly set up and that macros are expanded and applied correctly to avoid macro expansion errors.
  • 556-556: The handling of the bind-IO macro action involves a complex interaction between IO actions and function application. Ensure that the environment and closure application are correctly managed to avoid issues with variable scoping, especially in the context of IO actions.
  • 562-563: The use of evaluateWithExtendedEnv within the bind-IO macro action processing suggests a nuanced handling of environments during the evaluation of expressions in an extended environment. Verify that the environment extension and subsequent evaluation are correctly implemented to maintain the integrity of variable bindings and evaluations.
  • 908-921: The handling of type cases in the AwaitingTypeCase task processing involves branching based on the type of the expression being evaluated. It's crucial to ensure that the type case handling is robust and correctly interprets the types to execute the appropriate branch of code.
  • 936-936: The evaluation of macro implementations within the AwaitingMacro task processing requires careful handling of the macro expansion environment. Verify that the macro implementation is evaluated in the correct phase and that the environment is appropriately extended to support macro expansion.
  • 952-952: In the AwaitingDefn task processing, the evaluation of definitions involves expanding and evaluating expressions in the current environment. Ensure that the environment is correctly managed and that definitions are evaluated and bound correctly to support subsequent expansions.
  • 1314-1335: The processing of user-defined macros involves evaluating the macro implementation and applying it to the syntax object. It's important to ensure that the macro application is correctly handled and that any errors during macro expansion are appropriately reported to aid in debugging macro definitions.

src/Evaluator.hs Outdated
Comment on lines 11 to 48
{- Note [The CEK interpreter]:

The Klister interpreter is a straightforward implementation of a CEK
interpreter. The interpreter keeps three kinds of state:

-- C: Control ::= The thing that is being evaluated
-- E: Environment ::= The interpreter environment
-- K: Kontinuation ::= The syntactic context of the thing that is being interpreted

Why a CEK? A CEK interpreter allows us to have precise control over the
evaluation of a klister program. For example, because the interpreter keeps a
reference to the kontinuation we can provide stack traces. This handle also
makes a more advanced debugger possible. Upon an evaluation error we could save
the kontinuation stack, over write a variable in the environment a la common
lisp or even rewind the evaluation

See Matthias Felleison's course website for a good reference:
https://felleisen.org/matthias/4400-s20/lecture23.html

The bird's eye view:

The evaluator crawl's the input AST and progresses in three modes:

-- 'Down': meaning that the evaluator is searching for a redex to evaluate and
-- therefore moving "down" the AST.

-- 'Up': meaning that the evaluator has evaluated some redex to a value and is
-- passing that valua "up" the execution stack.

-- 'Er': meaning that something has gone wrong, the stack is captured and the Er
-- will float up to be handled by the caller of the evaluator.

All interesting things happen by matching on 'Kont', the continuation. This
allows the evaluator to know exactly what needs to happen in order to continue.

-- TODO: #108 describe the how the debugger hooks in

-}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The detailed comments explaining the CEK interpreter provide a clear overview of the interpreter's design and operation. This documentation is valuable for both current and future developers working on the project. However, the TODO comment on line 46 about describing how the debugger hooks in suggests that further documentation is needed for complete clarity.

Would you like assistance in documenting the debugger integration?

src/Evaluator.hs Outdated Show resolved Hide resolved
src/Evaluator.hs Outdated Show resolved Hide resolved
-- TODO DYG: Move to separate module
projectError :: EState -> EvalError
projectError (Er err _env _k) = err
projectError _ = error "debugger: impossible"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that repl/Main.hs has

case evaluateIn ... of
  Left evalErr -> print $ erroneousValue $ projectError evalErr
  Right val    -> ...

so is this "impossible" in the sense that when evaluateIn returns a Left eState, that EState is always an Er? In that case, wouldn't it make more sense to return Left err or Left (err, _env, _k), to make impossible states unrepresentable?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that would make more sense, but I specifically left it this way because this state will become important for the debugger. The debugger will be typed debugger :: EState -> DebugM EState or something like that. But the key point is that the debugger should be able to input EState which is actually at Er ... and return an EState that is not in Er. So I left as much information as possible until the very last projection or pretty printing because when evaluateIn returns a Left (bunch of state) the debugger could take that state and then resume the computation. Perhaps I should add a TODO in a comment

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the explanation, that makes sense. But the call to error still seems wrong. I can see several possible solutions:

  1. Add a precondition to projectError's documentation, e.g. "should only be called on the EState inside a Left returned by one of the evaluateX functions.
  2. Have the evaluateX functions return an opaque representation of the error, let's say ErrState, containing (err, env, k). Then, provide a total function projectError :: ErrState -> EvalError and a total function ErrState -> EState, so that the resulting EState can be given to debugger :: EState -> DebugM EState.
  3. Have the evaluateX functions return Left (err, estate), so they can both display the error and resume from the estate, without knowing that the two are related under the hood.

src/Expander/Monad.hs Outdated Show resolved Hide resolved
case out of
Left err -> error (T.unpack (pretty err))
case (evaluateWithExtendedEnv env [(ident, var, vx)] body) of
Left err -> error (T.unpack (pretty $ projectError err))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

woah! why do we fail with error here? I think runEval used to only fail if there was a bug in our implementation of the compiler, e.g. if our type checker allowed the construction of an ill-typed Core term. but nowadays evaluating a Core expression can fail with EvalErrorCase or EvalErrorUser if there is a bug in the user's .kl code too, so we should probably use throwError here. this is unrelated to the current PR, so I'll create a separate ticket to track this: #237

$ MacroEvaluationError p
$ evalErrorType "macro action"
$ erroneousValue
$ projectError err
Copy link
Owner

@gelisam gelisam Apr 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this error-handling logic is repeated several time (sometimes with the evalErrorType "macro action" bug, sometimes without), wasn't it nicer when expandEval was running in the Expand monad and was thus able to throw this error right away?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm hesitant to DRY it out because I still want to workshop the design of the debugger and how it hooks into expandEval.

throw this error right away

This action will be calling the debugger. Immediately exiting the debugger should then be throwing the error and reporting the error back to the user. Perhaps Expand will have to be a monad transformer over DebugM? I'm honestly not sure if anyone has made a CommonLisp style debugger in Haskell yet.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that I understand what erroneousValue does, I don't think this code is correct. It claims that the problem is that there is a value which should be a macro action, but its actual type is something else. But that problem is raised below, on L1001:

throwError $ MacroEvaluationError p $ evalErrorType "macro action" other

The problem on this line is some unknown thing which happened during evaluation, not necessarily a type error. Instead of using err to construct a type error, we should propagate err upwards as-is.

src/Expander.hs Outdated Show resolved Hide resolved
src/Evaluator.hs Outdated Show resolved Hide resolved
@doyougnu doyougnu force-pushed the doyougnu/wip/useful-repl branch 2 times, most recently from b2de4f2 to 906a85e Compare June 2, 2024 14:07
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between aa970da and 906a85e.

Files selected for processing (5)
  • repl/Main.hs (2 hunks)
  • src/Evaluator.hs (4 hunks)
  • src/Expander.hs (14 hunks)
  • src/Expander/Monad.hs (3 hunks)
  • src/Pretty.hs (1 hunks)
Additional context used
Path-based instructions (5)
repl/Main.hs (1)

Pattern **/*.hs: Dont mention changes to the import statements nor to the language extensions at the top of the file.

Dont point out missing error-handling code, the type-checker already detects those mistakes at compile-time.

Do explain what the new code does and how changes to existing functions affect their behavior.

src/Pretty.hs (1)

Pattern **/*.hs: Dont mention changes to the import statements nor to the language extensions at the top of the file.

Dont point out missing error-handling code, the type-checker already detects those mistakes at compile-time.

Do explain what the new code does and how changes to existing functions affect their behavior.

src/Evaluator.hs (1)

Pattern **/*.hs: Dont mention changes to the import statements nor to the language extensions at the top of the file.

Dont point out missing error-handling code, the type-checker already detects those mistakes at compile-time.

Do explain what the new code does and how changes to existing functions affect their behavior.

src/Expander/Monad.hs (1)

Pattern **/*.hs: Dont mention changes to the import statements nor to the language extensions at the top of the file.

Dont point out missing error-handling code, the type-checker already detects those mistakes at compile-time.

Do explain what the new code does and how changes to existing functions affect their behavior.

src/Expander.hs (1)

Pattern **/*.hs: Dont mention changes to the import statements nor to the language extensions at the top of the file.

Dont point out missing error-handling code, the type-checker already detects those mistakes at compile-time.

Do explain what the new code does and how changes to existing functions affect their behavior.

Additional comments not posted (17)
repl/Main.hs (1)

149-151: The integration of evaluateIn in the REPL's evaluation flow.
This change leverages the new CEK evaluator's evaluateIn function to process expressions. It effectively handles errors by projecting them through projectError and then extracting the erroneous value for display. This is a crucial part of the REPL's functionality, ensuring that evaluation errors are properly managed and communicated to the user.

src/Pretty.hs (1)

627-627: Handling of new error type in pretty-printing.
The addition of the EvalErrorIdent case allows the system to specifically address and display errors related to attempts to bind identifiers to non-values. This enhances the error reporting capabilities of the system, making it easier for users to understand the nature of the errors encountered.

src/Evaluator.hs (4)

11-29: The introductory note provides a clear and concise explanation of the CEK interpreter's design and its components (Control, Environment, Kontinuation). This documentation is crucial for understanding the operational mechanics of the interpreter.


654-658: The evaluateWithExtendedEnv function extends the environment for evaluation. This is a critical operation, and it's important to ensure that the environment extension is handled correctly to avoid any potential issues during evaluation.

Verification successful

The evaluateWithExtendedEnv function appears to handle the environment extension correctly and efficiently using foldl' to insert elements into the environment. The usages in src/Expander.hs and src/Evaluator.hs also seem to correctly pass the environment and extensions.

  • src/Evaluator.hs:

    • Line 654: Definition of evaluateWithExtendedEnv.
    • Line 656: Uses foldl' to insert elements into the environment.
  • src/Expander.hs:

    • Line 1: Usage of evaluateWithExtendedEnv with correct parameters.
  • src/Evaluator.hs:

    • Line 1: Usage of evaluateWithExtendedEnv with correct parameters.
    • Line 2: Usage of evaluateWithExtendedEnv with correct parameters.
Scripts executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Description: Verify the correct handling of environment extensions in `evaluateWithExtendedEnv`.

# Test: Search for incorrect or inefficient environment extensions. Expect: Environment is extended correctly and efficiently.
ast-grep --lang haskell --pattern $'evaluateWithExtendedEnv($_, $_, $_) { $$$ }'

Length of output: 81



Script:

#!/bin/bash
# Search for the definition of `evaluateWithExtendedEnv` in the codebase.
ast-grep --lang haskell --pattern 'evaluateWithExtendedEnv($_, $_, $_) = $_'

# Search for all usages of `evaluateWithExtendedEnv` in the codebase.
rg 'evaluateWithExtendedEnv'

Length of output: 601



Script:

#!/bin/bash
# Display the definition of `evaluateWithExtendedEnv` in `src/Evaluator.hs`.
cat src/Evaluator.hs | sed -n '654,658p'

# Display the usages of `evaluateWithExtendedEnv` in `src/Expander.hs`.
rg 'evaluateWithExtendedEnv' src/Expander.hs

# Display the usages of `evaluateWithExtendedEnv` in `src/Evaluator.hs`.
rg 'evaluateWithExtendedEnv' src/Evaluator.hs

Length of output: 748


187-358: The step function, which is central to the CEK machine's operation, handles various states and transitions. It's crucial to ensure that each case is handled correctly to maintain the integrity of the evaluation process. Given the performance concerns mentioned in the PR, consider optimizing the handling of these transitions to reduce complexity and improve efficiency.

Consider refactoring complex cases in the step function to separate helper functions to improve readability and maintainability.


441-442: The apply function and its helper applyInEnv are critical for handling function applications. Ensure that these functions are robust against various edge cases, especially given the new evaluator's performance issues.

Also applies to: 502-510

src/Expander/Monad.hs (2)

9-9: The addition of the FlexibleContexts language extension is appropriate given the changes in function signatures and types.


864-871: The renaming of expandEval to evalInCurrentPhase and its implementation are well-suited to its purpose. The function effectively uses the current environment for evaluation and handles errors appropriately.

src/Expander.hs (9)

25-25: Review the addition of evalInCurrentPhase and related entities.

The addition of evalInCurrentPhase and related entities in the export list is consistent with the changes described in the PR summary and AI-generated summary. This function seems to be central to the new evaluation strategy.


275-275: Review the use of evalInCurrentPhase in evalDecl.

The use of evalInCurrentPhase here for evaluating expressions within declarations is appropriate and aligns with the new evaluation strategy. This ensures that expressions are evaluated in the correct phase of the language's compilation or interpretation process.


302-302: Review the use of evalInCurrentPhase in Example case.

Using evalInCurrentPhase to evaluate the expression in the Example case is correct. It ensures that the example expressions are evaluated in the current phase, which is crucial for testing and example code blocks within the language.


305-305: Review the use of evalInCurrentPhase in Run case.

The use of evalInCurrentPhase in the Run case is appropriate. It ensures that runtime actions are evaluated in the current phase, which is essential for executing IO actions or other runtime tasks.


316-316: Review the use of evalInCurrentPhase in DefineMacros case.

The use of evalInCurrentPhase in the DefineMacros case is appropriate. It ensures that macro definitions are evaluated in the current phase, which is crucial for macro expansion and execution.


556-556: Review the handling of function application in bind-IO.

The handling of function application in the bind-IO primitive is correctly implemented. It uses dynamic environment extension and proper error handling, which are essential for functional programming languages that support first-class functions.


908-920: Review the error handling in AwaitingTypeCase.

The error handling in the AwaitingTypeCase task is robust, correctly distinguishing between different types of macro evaluation errors and providing detailed error messages. This is crucial for debugging and maintaining the language's macro system.


935-935: Review the evaluation of macro implementations in AwaitingMacro.

The evaluation of macro implementations in the AwaitingMacro task is correctly handled. It uses inEarlierPhase to ensure that the macro is evaluated in the correct phase, which is essential for maintaining the phase consistency of the language.


1313-1334: Review the handling of user macros in expandOneForm.

The handling of user macros in expandOneForm is sophisticated and well-implemented. It correctly uses phase adjustments and dynamic scope management to ensure that macros are expanded correctly and efficiently.

src/Evaluator.hs Show resolved Hide resolved
Comment on lines +660 to +665
projectError :: EState -> EvalError
projectError (Er err _env _k) = err
projectError _ = error "debugger: impossible"

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tip

Codebase Verification

The EState type has three constructors: Down, Up, and Er. The projectError function only handles the Er constructor explicitly and defaults to an error for other cases. This means it does not handle the Down and Up states, which could lead to runtime errors if these states are passed to projectError.

  • Location to address:
    • src/Evaluator.hs lines 660-663: Ensure projectError handles all possible states of EState.
Analysis chain

The projectError function is designed to extract errors from the evaluator's state. Given its critical role in error handling, ensure that it robustly handles all possible error states.

Scripts executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Description: Verify the robustness of the `projectError` function.

# Test: Search for potential issues in error extraction by `projectError`. Expect: All errors are correctly extracted.
ast-grep --lang haskell --pattern $'projectError($_) { $$$ }'

Length of output: 62



Script:

#!/bin/bash
# Description: Identify all possible states of `EState` and verify if `projectError` handles them.

# Step 1: Find the definition of `EState` to list all possible states.
ast-grep --lang haskell --pattern $'data EState = $$$'

# Step 2: Check where `projectError` is used to ensure it handles all states.
rg 'projectError'

Length of output: 780



Script:

#!/bin/bash
# Description: Locate the definition of `EState` and list all possible states.

# Step 1: Find the definition of `EState` to list all possible states.
rg 'data EState' --type haskell

Length of output: 68



Script:

#!/bin/bash
# Description: Extract the full definition of `EState` from `src/Evaluator.hs`.

# Step 1: Extract the full definition of `EState`.
sed -n '/data EState/,/where/p' src/Evaluator.hs

Length of output: 14015

@doyougnu doyougnu force-pushed the doyougnu/wip/useful-repl branch from 906a85e to d3cca7a Compare June 2, 2024 14:16
res <- interpretMacroAction prob act
case res of
Left err -> throwError
$ ValueNotMacro
Copy link
Owner

@gelisam gelisam Jun 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this should be a ValueNotMacro error. In this branch, looking up transformerName did successfully find a macro definition of type (-> Syntax (Macro Syntax)). That function was then applied to a Syntax argument, and the evaluation of that function failed for some reason, perhaps because (error 'msg) was called.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ha! The existing code already misuses ValueNotMacro. When expanding the macro call site (my-macro), many things can go wrong:

  1. my-macro might not be in scope, in which case resolve (line L1201) throws Unknown and the user sees Unknown: my-macro.
  2. my-macro resolves to EUserMacro transformerName, but that transformerName is somehow not the MacroVar which was added to the transformer environment by (define-macros ([my-macro ...])) at the same time as my-macro was bound to EUserMacro in the binding table. For example, maybe transformerName is not in the binding table at all. This is a bug in Klister, so InternalError is thrown; error would have been fine too.
  3. Same thing, but transformerName is bound to something other than a (-> Syntax (Macro Syntax)). This is also a bug in Klister, but ValueNotMacro is thrown. This is definitely wrong, since the error message associated with ValueNotMacro is "Not a macro monad value", but it's not even a (Macro Syntax) which is expected, it's a (-> Syntax (Macro Syntax)).
  4. transformerName is bound to a value of type (-> Syntax (Macro Syntax)), but applying it to a Syntax value fails before we can get a (Macro Syntax) value out. The current code fails inside expandEval with MacroEvaluationError, whereas this branch fails here with ValueNotMacro. I think it should be MacroEvaluationError.
  5. we do get a value out, but it's not a (Macro Syntax), so ValueNotMacro is thrown. If this happens, that means there is a bug in Klister's type-checker, so InternalError or error would have been fine too.
  6. interpretMacroAction encounters an error while executing the macro action. it fails somewhere inside interpretMacroAction via code which isn't relevant here.

As a conclusion, I think this should be MacroEvaluationError, not ValueNotMacro, but the existing code isn't very good at using the right error constructor, so this isn't a big deal.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the same mistake as the other erroneousValue call site. Maybe erroneousValue shouldn't exist at all, since it seems so easy to misuse?

ValueMacroAction act -> interpretMacroAction prob act
other -> throwError $ ValueNotMacro other
view (expanderWorld . worldEnvironments . at phase) $ s
case applyInEnv env closure boundResult of
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

another place where this MR highlights a mistake in the original code: all of

phase <- view (expanderLocal . expanderPhase)
s <- getState
let env = ...

and the withEnv env is dead code, because apply overwrites this env with its own call to withEnv. So there is no need for an applyInEnv separate from apply.

Copy link
Owner

@gelisam gelisam Jun 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here's what I would write:

          phase <- view (expanderLocal . expanderPhase)
          case apply closure boundResult of
            Left err -> throwError
              $ MacroEvaluationError phase
              $ projectError err

( EvalError (..)
, EvalResult (..)
, TypeError (..)
, evaluate
Copy link
Owner

@gelisam gelisam Jul 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

evaluate has type Core -> Either EState Value, but EState is not exported. That seems needlessly restrictive: the caller can store the result in a variable, return it to its own caller, but if they do so then they cannot write down a type signature. Would it make sense to at least export it as an opaque type?

✏️ this is not a blocker, but after this MR is finally merged, I plan to come back and fix this.

( EvalError (..)
, EvalResult (..)
, TypeError (..)
, evaluate
Copy link
Owner

@gelisam gelisam Jul 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

evaluate looks like it is the main API, with evaluateIn and evaluateWithExtendedEnv being a secondary APIs for special cases. But I see that evaluate is not used anywhere! Perhaps it would make more sense to drop evaluate and to make evaluateIn the main API?

this is not a blocker.

, projectError
, erroneousValue
, applyInEnv
, apply
Copy link
Owner

@gelisam gelisam Jul 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

apply behaves very similarly to the evaluateX functions, would it perhaps make more sense to call it something like evaluateFunctionApplication?

this is not a blocker.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same for doTypeCase. evaluateTypeCase?

@@ -38,8 +103,343 @@ data EvalError
| EvalErrorType TypeError
| EvalErrorCase SrcLoc Value
Copy link
Owner

@gelisam gelisam Jul 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know we discussed this, but I still find it unintuitive that a SrcLoc is needed for case and type-case, but not for anything else. We should document the reasoning somewhere. Let's see, that reasoning is...

That SrcLoc is used to construct the error message in the case in which no cases match. By comparison, other errors such as looking up a Var don't need a SrcLoc because scope errors are caught during expansion time, before evaluation begins. Are case and type-case really the only ways in which evaluation can fail at runtime, assuming expansion already caught scope errors and type errors?

✏️ this is not a blocker, but after this MR is finally merged, I plan to come back and add a comment explaining this.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, the current datatype exhaustively lists all the errors which can happen at evaluation-time, and #237 already classifies which ones should already be caught in an earlier phase and which ones really can happen at evaluation-time. There is EvalErrorCase, and EvalErrorUser. EvalErrorUser does not need a separate SrcLoc because the error message is a Syntax, which is already associated with a SrcLoc.

Since we're separating between Case and TypeCase everywhere else in the codebase, I wonder why there isn't a separate EvalErrorTypeCase here? 🤔

Anyway, that's not something which changed in the current MR, moving right along...

, applyInEnv
, apply
, doTypeCase
, try
Copy link
Owner

@gelisam gelisam Jul 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is Control.Exception.try re-exported? you don't even throw exceptions in this file 😅

✏️ this is not a blocker, but after this MR is finally merged, I plan to come back and fix this.

makePrisms ''EvalError
instance Exception EvalError
Copy link
Owner

@gelisam gelisam Jul 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EvalError is never thrown as an exception, did you perhaps initially intend to use it as an exception and later changed your mind to use Er evalError?

✏️ this is not a blocker, but after this MR is finally merged, I plan to come back and fix this.


data EvalResult
= ExampleResult SrcLoc VEnv Core (Scheme Ty) Value
| IOResult (IO ())
Copy link
Owner

@gelisam gelisam Jul 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EvalResult is not even used in this file 😅
Perhaps it should be defined next to evalMod instead?

Let's do that in a separate MR, as this MR does not introduce EvalResult, it merely moves it to the top of the file.

This is not a blocker.

data EvalResult
= ExampleResult SrcLoc VEnv Core (Scheme Ty) Value
| IOResult (IO ())

-- TODO: more precise representation
type Type = Text
Copy link
Owner

@gelisam gelisam Jul 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Text is actually just fine since the type checker has (hopefully) already caught all the type errors, so it doesn't make sense to spend extra effort on type errors which slip through to evaluation-time.

This is not a blocker.

src/Evaluator.hs Outdated
-- ^ Marks the evaluator finishing

-- functions
InArg :: !(CoreF TypePattern ConstructorPattern Core) -> !VEnv -> !Kont -> Kont
Copy link
Owner

@gelisam gelisam Jul 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why use CoreF TypePattern ConstructorPattern Core instead of the shorter-and-isomorphic Core?

This is not a blocker.

src/Evaluator.hs Outdated Show resolved Hide resolved
src/Evaluator.hs Outdated Show resolved Hide resolved
-- lists
InConsHd :: !Core -> !(CoreF TypePattern ConstructorPattern Core) -> !VEnv -> !Kont -> Kont
InConsTl :: !Core -> !Syntax -> !VEnv -> !Kont -> Kont
InList :: !Core -> ![Core] -> ![Syntax] -> !VEnv -> !Kont -> Kont
Copy link
Owner

@gelisam gelisam Jul 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a shame that you had to waste your time reimplementing those outdated direct syntax-manipulating primitives! I only ever use open-syntax and close-syntax now. We should get rid of this old stuff. In a later MR, of course.

✏️ this is not a blocker, but after this MR is finally merged, I plan to come back and delete those outdated primitives.

src/Evaluator.hs Outdated
InArg :: !(CoreF TypePattern ConstructorPattern Core) -> !VEnv -> !Kont -> Kont
-- ^ The argument is being evaluated, so hold onto the function symbol.
InFun :: !Value -> !VEnv -> !Kont -> Kont
-- ^ The function is being evaluated, so hold onto the evaluated argument.
Copy link
Owner

@gelisam gelisam Jul 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These comments are really helpful, let's add one for each constructor!

  -- functions
  InArg :: !(CoreF TypePattern ConstructorPattern Core) -> !VEnv -> !Kont -> Kont
  -- ^ The argument is being evaluated, so hold onto the function expression
  InFun :: !Value -> !VEnv -> !Kont -> Kont
  -- ^ The function is being evaluated, so hold onto the evaluated argument.

  InLetDef :: !Ident -> !Var -> !(CoreF TypePattern ConstructorPattern Core) -> !VEnv -> !Kont -> Kont
  -- ^ Inside the rhs of (let [x rhs] body), so hold the name x and the body.

  -- constructors
  InCtor :: !Constructor -> ![CoreF TypePattern ConstructorPattern Core] -> ![Value] -> !VEnv -> !Kont -> Kont
  -- ^ Inside arg_i of (Ctor arg_1 ... arg_i ... arg_n), so hold Ctor, the
  -- already evaluated values value_1 ... value_{i-1}, and the
  -- yet-to-be-evaluated expressions arg_{i+1} ... arg_n.

  -- Cases
  InCaseScrut     :: ![(SyntaxPattern, Core)] -> !SrcLoc -> !VEnv -> !Kont -> Kont
  InDataCaseScrut :: ![(ConstructorPattern, Core)] -> !SrcLoc -> !VEnv -> !Kont -> Kont
  InTypeCaseScrut :: ![(TypePattern, Core)] -> !SrcLoc -> !VEnv -> !Kont -> Kont
  -- ^ Inside the scrutinee of (case scrutinee [pat_1 rhs_1] ...), so hold
  -- the branches.

  -- lists
  InConsHd :: !Core -> !(CoreF TypePattern ConstructorPattern Core) -> !VEnv -> !Kont -> Kont
  -- ^ Inside the head of (cons-list-syntax-cons head tail loc), so hold the tail.
  InConsTl :: !Core -> !Syntax -> !VEnv -> !Kont -> Kont
  -- ^ Inside the tail of (cons-list-syntax-cons head tail loc), so hold the already-evaluated head.
  InList   :: !Core -> ![Core] -> ![Syntax] -> !VEnv -> !Kont -> Kont
  -- ^ Inside arg_i of (list-syntax (arg_1 ... arg_i ... arg_n) loc), so hold
  -- the already-evaluated syntax values stx_1 ... stx_{i-1}, and the
  -- yet-to-be-evaluated expressions arg_{i+1} ... arg_n.

  -- idents
  InIdent :: !Core -> !VEnv -> !Kont -> Kont
  InIdentEqL :: !HowEq -> !Core -> !VEnv -> !Kont -> Kont
  -- ^ Inside the lhs of (<how>-identifier=? lhs rhs), so hold <how> and rhs.
  InIdentEqR :: !HowEq -> !Value -> !VEnv -> !Kont -> Kont
  -- ^ Inside the rhs of (<how>-identifier=? lhs rhs), so hold <how> and the
  -- already-evaluated lhs.

  -- Macros
  InPureMacro :: !VEnv -> !Kont -> Kont
  -- ^ Inside the expr of (pure expr), so nothing to hold.
  InBindMacroHd :: !Core -> !VEnv -> !Kont -> Kont
  -- ^ Inside the expr1 of (>>= expr1 expr2), so hold expr2.
  InBindMacroTl :: !MacroAction -> !VEnv -> !Kont -> Kont
  -- ^ Inside the expr2 of (>>= expr1 expr2), so hold the already-evaluated
  -- expr1.

  -- atomics
  InInteger :: !Core -> !VEnv -> !Kont -> Kont
  InString  :: !Core -> !VEnv -> !Kont -> Kont
  InLoc     :: !Core -> !VEnv -> !Kont -> Kont
  -- ^ Inside the loc of (replace-loc loc stx), so hold the stx.
  InLocStx  :: !SrcLoc -> !VEnv -> !Kont -> Kont
  -- ^ Inside the stx of (replace-loc loc stx), so hold the already-evaluated
  -- loc.

  -- scope
  InScope :: !(ExprF Syntax) -> !VEnv -> !Kont -> Kont
  -- ^ Inside the loc of something like (cons-syntax-list head tail loc), so
  -- remember the Syntax-Contents which needs a scope-set and location.

  -- logs and errors
  InLog   :: !VEnv -> !Kont -> Kont
  -- ^ Inside the expr of (log expr), so nothing to hold.
  InError :: !VEnv -> !Kont -> Kont
  -- ^ Inside the expr of (error expr), so nothing to hold.

✏️ this is not a blocker, but after this MR is finally merged, I plan to come back and fix this.

Copy link
Owner

@gelisam gelisam Sep 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

turns out I was wrong about some of those, as some of those hold a reversed list.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • add comments for each constructor after merge.

src/Evaluator.hs Outdated Show resolved Hide resolved
src/Evaluator.hs Outdated Show resolved Hide resolved
(InSyntaxErrorLocations msg_syn [] dones env kont) ->
Up (ValueMacroAction
$ MacroActionSyntaxError (SyntaxError { _syntaxErrorMessage = msg_syn
, _syntaxErrorLocations = dones
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
, _syntaxErrorLocations = dones
, _syntaxErrorLocations = reverse (v : dones)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this is a type error:

  • v : Value
  • dones : [Syntax]

here is what I believe the original case is in eval:

eval (Core (CoreSyntaxError syntaxErrorExpr)) = do
  syntaxErrorValue <- traverse evalAsSyntax syntaxErrorExpr
  pure $ ValueMacroAction
       $ MacroActionSyntaxError syntaxErrorValue

is there an example that exhibits this bug?

Copy link
Owner

@gelisam gelisam Oct 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this is a type error: [...]

You're right, a call to evalAsSyntax is also needed.

is there an example that exhibits this bug?

Here is one:

#lang kernel
(import (shift kernel 1))

(define-macros
  ([m (lambda (stx)
        (syntax-error 'error-message
                        'location-1  -- line 7
                        'location-2  -- line 8
                        'location-3  -- line 9
                        'location-4  -- line 10
                        ))]))

(example (m))

on main, this program outputs

toy.kl:7.26-7.36:
    Syntax error from macro:
    #[toy.kl:6.24-6.37]<error-message>Additional locations:
    toy.kl:8.26-8.36
    toy.kl:9.26-9.36
    toy.kl:10.26-10.36

whereas on this branch, this program outputs one fewer location, and outputs them in the opposite order:

toy.kl:9.26-9.36:
    Syntax error from macro:
    #[toy.kl:6.24-6.37]<error-message>Additional locations:
    toy.kl:8.26-8.36
    toy.kl:7.26-7.36

The first location is distinguished because it is printed before the error message, so focusing on the line numbers, let's use the shorthand [7], 8, 9, 10 to describe the output on main and [9], 8, 7 to describe the output on this branch.

Line 10 ('location-4) is missing, because step (Up v e k) is supposed to insert the value v into the context k, but here when the context is InSyntaxErrorLocations, the v is not inserted, it is dropped on the floor.

And the locations are listed in the opposite order because as you traverse InSyntaxErrorLocations's [Core] from left to right, you prepend each output to its [Syntax], so the right-most Core ends up being the leftmost Syntax. You have fixed this elsewhere by calling reverse in the InCtor [] and InList [] cases, you just forgot to also do it in the InSyntaxErrorLocations [] case.

src/Evaluator.hs Outdated Show resolved Hide resolved
src/Evaluator.hs Outdated Show resolved Hide resolved
erroneousValue _ =
error $ mconcat [ "erroneousValue: "
, "Evaluator concluded in an error that did not return a value"
]
Copy link
Owner

@gelisam gelisam Sep 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like this partial function, I think every single call site is incorrect, but I think we should (finally!) merge this and then I'll open a separate MR to replace all the call sites with something more sensible.

✏️ this is not a blocker, but after this MR is finally merged, I plan to come back and fix this.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good 👍

other -> Er (evalErrorType "function" other) e k

where app (FO (FOClosure{..})) =
let env = Env.insert _closureVar _closureIdent value (_closureEnv <> e)
Copy link
Owner

@gelisam gelisam Sep 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bug. When applying a closure, the body of the closure must be evaluated in the captured environment, not in the union of the captured environment plus the environment at the call site. That is, the following code should not be accepted because y is not in scope where f is defined:

f = \() -> y
g y = f ()

❌ this is a blocker.

Copy link
Collaborator Author

@doyougnu doyougnu Oct 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm having a hard time writing a test case for this because the patch errors as expected with (I believe) the correct error message. Here is my translation to klister:

-- in examples/non-examples/bad-lexical-env.kl
#lang kernel

(define f (lambda (ff) y))
(define g (lambda (y) (f)))

(example (g 2))

Note that I have to pass ff or else I get and error about no arguments:

❯ cat examples/non-examples/bad-lexical-env.golden
Expected 1 entries between parentheses, but got
  #[bad-lexical-env.kl:3.19-3.22]<()>

this is a sidenote but I'm surprised we cannot define a function of type f : () -> <whatever>.

Here is what main shows:

Test suite klister-tests: RUNNING...
All tests
  Golden tests
    bad-lexical-env: OK (0.01s)
...
❯ cat examples/non-examples/bad-lexical-env.golden
Unknown: #[bad-lexical-env.kl:4.24-4.25]<y>

Notice that this is what we should expect, an error saying unknown y.

and here is the patch:

❯ cabal test --test-show-details=streaming --test-options '-p bad-lexical-env --accept'
...
Test suite klister-tests: RUNNING...
All tests
  Golden tests
    bad-lexical-env: OK (0.01s)
      Accepted the new version
...
❯ cat examples/non-examples/bad-lexical-env.golden
Unknown: #[bad-lexical-env.kl:3.24-3.25]<y>

That is, I accept the new output for the golden test and we get the same error as main from the patch.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm having a hard time writing a test case for this

Makes sense: scope errors are caught at expansion time, precisely so that they cannot occur at evaluation-time, so it should not be possible to write a Klister program which triggers this bug. Okay, this isn't as big of an issue as I thought then.

✏️ this is no longer a blocker, but after this MR is finally merged, I plan to come back and fix this.

Copy link
Owner

@gelisam gelisam Oct 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that I have to pass ff or else I get and error about no arguments:

❯ cat examples/non-examples/bad-lexical-env.golden
Expected 1 entries between parentheses, but got
#[bad-lexical-env.kl:3.19-3.22]<()>

this is a sidenote but I'm surprised we cannot define a function of type f : () -> <whatever>.

I agree that it is surprising, but it makes sense if you think about it. (lambda () 42) is a function from zero arguments, not a function from the unit type to Integer. Racket supports functions from zero arguments, but Haskell does not. Like Haskell, Klister only supports functions of one argument, so (lambda (x y) 42) desugars to (lambda (x) (lambda (y) 42)), and if it wasn't rejected (lambda () 42) would desugar to 42. The error message could definitely be improved though!

Copy link
Collaborator Author

@doyougnu doyougnu Oct 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great explanation! You're right, I guess I see some scheme or lisp and assume that I can define a function with 0-arity.

Makes sense: scope errors are caught at expansion time, precisely so that they cannot occur at evaluation-time,

I wonder if we could define an enhanced debug build that checks for this. Something like building the compiler/interpreter with extra assertions.

@doyougnu doyougnu force-pushed the doyougnu/wip/useful-repl branch 2 times, most recently from ea4086f to cac10f2 Compare October 6, 2024 13:06
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Outside diff range and nitpick comments (3)
src/Pretty.hs (1)

628-628: Consider adding more context to the IOResult pretty-printing

The implementation for IOResult is simple and consistent with how other opaque values are printed. However, consider adding a bit more context to make it clearer that this is a result of evaluation.

Here's a suggested improvement:

-  pp _env (IOResult _) = text "IO action"
+  pp _env (IOResult _) = text "Evaluation resulted in an IO action"

This change provides more clarity about the nature of the result without exposing implementation details.

src/Evaluator.hs (2)

106-106: Add documentation for the EvalErrorIdent constructor

The addition of EvalErrorIdent Value to EvalError introduces a new error case related to identifier handling. Consider adding Haddock documentation or inline comments to explain its purpose and when it is used. This will improve code maintainability and help other developers understand error handling.


223-224: Consistent error handling in InTypeCaseScrut

In the continuation for InTypeCaseScrut, the error handling passes err without modification. Ensure that this is consistent with the rest of the codebase's error handling practices and that any necessary context is included in the error.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 906a85e and cac10f2.

📒 Files selected for processing (5)
  • repl/Main.hs (2 hunks)
  • src/Evaluator.hs (4 hunks)
  • src/Expander.hs (12 hunks)
  • src/Expander/Monad.hs (3 hunks)
  • src/Pretty.hs (1 hunks)
🧰 Additional context used
🔇 Additional comments (7)
src/Pretty.hs (1)

627-627: LGTM: New error message for EvalErrorIdent

The new pretty-printing implementation for EvalErrorIdent is clear, informative, and consistent with the style of other error messages in this instance.

src/Evaluator.hs (3)

455-457: Ensure all EvalError cases are handled in evalErrorText

The evalErrorText function now includes a case for EvalErrorIdent, which is good. Verify that all possible EvalError constructors are accounted for in this function to prevent incomplete pattern matches and ensure comprehensive error messaging.


1-4: Verify compatibility of added language extensions

The inclusion of KindSignatures, BangPatterns, and GADTSyntax extensions enhances type safety and performance. However, ensure that these extensions are compatible with the project's target GHC versions to prevent any build issues.

Run the following script to check the minimum required GHC version:

#!/bin/bash
# Description: Verify that the GHC version supports the newly added language extensions.

# Expected: GHC version 8.0 or higher supports these extensions.
ghc --version

# Alternatively, check the project's `.cabal` file for the GHC version constraint.
grep -i 'build-depends.*base' *.cabal

551-576: Verify all calls to doTypeCase are updated

With the doTypeCase function signature changed to include VEnv, ensure that all invocations of this function pass the correct environment parameter. Missing or incorrect environment arguments may lead to runtime errors or unexpected behavior.

Run the following script to locate calls to doTypeCase:

src/Expander/Monad.hs (2)

38-38: Exporting evalInCurrentPhase

Adding evalInCurrentPhase to the export list makes it available to other modules as intended.


864-872: Implementation of evalInCurrentPhase is correct

The new function evalInCurrentPhase correctly evaluates the Core expression within the current environment and handles errors appropriately by throwing a MacroEvaluationError with the current phase.

src/Expander.hs (1)

25-25: Consistent Renaming to evalInCurrentPhase

The renaming of expandEval to evalInCurrentPhase throughout the module enhances readability and clarity, accurately reflecting the function's role in evaluating expressions within the current phase.

Also applies to: 275-316

Comment on lines +496 to +505
applyInEnv old_env (FO (FOClosure {..})) value =
let env = Env.insert _closureVar
_closureIdent
value
(_closureEnv <> old_env)
in evaluateIn env _closureBody
applyInEnv _ (HO prim) value = return $! prim value

apply :: Closure -> Value -> Either EState Value
apply (FO (FOClosure {..})) value =
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Refactor applyInEnv and apply to reduce code duplication

The functions applyInEnv and apply share similar logic, with the primary difference being the environment used. To adhere to the DRY (Don't Repeat Yourself) principle, consider refactoring these functions by extracting the common logic into a helper function. This will improve maintainability and readability.

Example refactor:

-- Extract common logic into a helper function
applyClosure :: VEnv -> Closure -> Value -> Either EState Value
applyClosure env (FO (FOClosure {..})) value =
  let newEnv = Env.insert _closureVar _closureIdent value env
  in evaluateIn newEnv _closureBody
applyClosure _ (HO prim) value = return $! prim value

-- Refactor applyInEnv
applyInEnv :: VEnv -> Closure -> Value -> Either EState Value
applyInEnv oldEnv closure value = applyClosure (_closureEnv closure <> oldEnv) closure value

-- Refactor apply
apply :: Closure -> Value -> Either EState Value
apply closure value = applyClosure (_closureEnv closure) closure value

Comment on lines +664 to +665
projectError _ = error "debugger: impossible"

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Handle all EState cases in projectError

The projectError function currently throws an error if called with an EState that is not Er. Since projectError might be used in contexts where the state isn't guaranteed to be an error state, handling all cases prevents unexpected crashes.

Consider pattern matching all constructors or documenting the function's precondition.

projectError :: EState -> Maybe EvalError
projectError (Er err _env _k) = Just err
projectError _                = Nothing

Comment on lines +663 to +665
projectError (Er err _env _k) = err
projectError _ = error "debugger: impossible"

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Avoid partial functions to prevent runtime exceptions

The projectError function uses a partial pattern match that calls error when the EState is not an Er state. Relying on partial functions can lead to runtime exceptions. Consider refactoring projectError to return a Maybe EvalError or handle all possible EState cases to improve robustness.

Refactored code suggestion:

-projectError :: EState -> EvalError
+projectError :: EState -> Maybe EvalError

-projectError (Er err _env _k) = err
-projectError _                = error "debugger: impossible"
+projectError (Er err _env _k) = Just err
+projectError _                = Nothing
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
projectError (Er err _env _k) = err
projectError _ = error "debugger: impossible"
projectError :: EState -> Maybe EvalError
projectError (Er err _env _k) = Just err
projectError _ = Nothing

Comment on lines +519 to +521
let env = Env.insert _closureVar _closureIdent value (_closureEnv <> e)
in Down (unCore _closureBody) env k
app (HO prim) = Up (prim value) mempty k
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Correct environment usage in closure application

In applyAsClosure, the environment combines the closure's environment with the current environment using (_closureEnv <> e). This could lead to scoping issues, where variables from the caller's environment unintentionally affect the closure's execution. To maintain proper lexical scoping, only the closure's environment should be used.

Apply this diff to fix the environment:

-let env = Env.insert _closureVar _closureIdent value (_closureEnv <> e)
+let env = Env.insert _closureVar _closureIdent value _closureEnv
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let env = Env.insert _closureVar _closureIdent value (_closureEnv <> e)
in Down (unCore _closureBody) env k
app (HO prim) = Up (prim value) mempty k
let env = Env.insert _closureVar _closureIdent value _closureEnv
in Down (unCore _closureBody) env k
app (HO prim) = Up (prim value) mempty k

@@ -6,6 +6,7 @@
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE FlexibleContexts #-}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Confirm the necessity of FlexibleContexts extension

The addition of {-# LANGUAGE FlexibleContexts #-} enables more flexible type class constraints. Please ensure this extension is required for the current changes. If it's unnecessary, consider removing it to keep the codebase clean from redundant extensions.

Copy link
Owner

@gelisam gelisam left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved! We can finally merge this! Sorry it took so long.

@doyougnu
Copy link
Collaborator Author

doyougnu commented Oct 7, 2024

Approved! We can finally merge this! Sorry it took so long.

No worries, it was a thorough review!

@doyougnu doyougnu force-pushed the doyougnu/wip/useful-repl branch from cac10f2 to ec6d08c Compare October 31, 2024 14:25
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Outside diff range and nitpick comments (8)
src/Evaluator.hs (3)

11-48: Documentation needs clarification about Control (C) in CEK.

The note explains Environment (E) and Kontinuation (K) well, but doesn't clearly explain what Control (C) represents in line 16. This is particularly important as line 43 states "All interesting things happen by matching on 'Kont'", which seems to contradict the importance of Control.

Consider expanding the documentation to clarify:

  • What Control represents in the CEK machine
  • How Control interacts with Kontinuation
  • Why most operations focus on Kontinuation despite Control being part of CEK

115-170: Enhance Kont constructor documentation and naming.

While the Kont data type is well-structured, it could benefit from:

  1. Comprehensive documentation for each constructor
  2. More consistent naming conventions

Consider:

  • Adding documentation for each constructor explaining its purpose and invariants
  • Renaming InLoc and InLocStx to InReplaceLocL and InReplaceLocR for clarity
  • Using consistent naming patterns (e.g., InCtorArg instead of InCtor)

189-196: Address performance degradation concerns.

The CEK interpreter shows significant performance issues (59.7% bad speculation). This could be due to:

  1. Complex pattern matching in the step function causing branch mispredictions
  2. Environment combinations creating allocation pressure
  3. Insufficient strictness annotations

Consider:

  1. Adding more strictness annotations using BangPatterns
  2. Simplifying pattern matching by splitting the step function
  3. Using a more efficient environment representation
  4. Adding benchmarks to track performance improvements
src/Expander/Monad.hs (2)

864-871: Consider performance optimizations for evaluation.

The current implementation might be contributing to the branch predictor issues mentioned in the PR (59.7% bad speculation). Consider these optimizations:

  1. Add strictness annotations to force early evaluation of env and out
  2. Consider using continuation-passing style to reduce branching

Apply this diff to add strictness:

 evalInCurrentPhase :: Core -> Expand Value
 evalInCurrentPhase evalAction = do
-  env <- currentEnv
-  let out = evaluateIn env evalAction
+  !env <- currentEnv
+  let !out = evaluateIn env evalAction
   case out of
     Left err -> do
       p <- currentPhase

Line range hint 873-890: Optimize environment combination for better performance.

The current implementation of environment combination could be optimized:

  1. Use strict pattern matching for environment lookups
  2. Consider caching the combined environment if it's frequently accessed

Apply these changes to optimize environment handling:

 currentEnv :: Expand VEnv
 currentEnv = do
   phase <- currentPhase
-  globalEnv <- fromMaybe mempty . view (expanderWorld . worldEnvironments . at phase) <$> getState
-  localEnv  <- fromMaybe mempty . view (expanderCurrentEnvs . at phase) <$> getState
+  !globalEnv <- fromMaybe mempty . view (expanderWorld . worldEnvironments . at phase) <$> getState
+  !localEnv  <- fromMaybe mempty . view (expanderCurrentEnvs . at phase) <$> getState
   return $! globalEnv <> localEnv
src/Expander.hs (3)

908-920: Enhance error messages for better debugging.

While the error handling is correct, consider enhancing the error messages to include:

  1. The expected and actual types
  2. The phase context where the error occurred
  3. The evaluation context

This would make debugging easier, especially given the complex nature of the CEK evaluator.

-            throwError
-              $ MacroEvaluationError p
-              $ projectError err
+            throwError
+              $ MacroEvaluationError p
+              $ addContext "Type case evaluation"
+              $ projectError err

Line range hint 935-1001: Standardize error handling in macro evaluation.

As noted in past reviews, the error handling here should be consistent with the rest of the codebase. Consider:

  1. Using MacroEvaluationError instead of ValueNotMacro for evaluation errors
  2. Providing more context in error messages
  3. Ensuring error types align with their semantic meaning
-              throwError $ MacroEvaluationError p $ evalErrorType "macro action" other
+              throwError $ MacroEvaluationError p $ 
+                addContext "Macro evaluation" $
+                evalErrorType "macro action" other

After analyzing the evaluation code, I can now provide a specific response about the branching patterns and performance concerns:

Consider restructuring pattern matching evaluation for better branch prediction

The evaluator shows several areas where branch prediction could be impacted:

  1. Deeply nested pattern matching in match functions with multiple case expressions that fall through to next handlers
  2. Type-based dispatch patterns in evalAs* functions that could be reorganized
  3. Scattered error handling paths that may interfere with the CPU's ability to predict the common case

Specific suggestions:

  • Consider flattening the nested pattern matches in the match functions by using helper functions
  • Reorder the cases to put the most common patterns first
  • Group similar patterns together to help the branch predictor recognize patterns
  • Consider using lookup tables or other techniques for the type-based dispatching
🔗 Analysis chain

Line range hint 275-302: Consider optimizing evaluation for better branch prediction.

Given the performance metrics in the PR description (59.7% bad speculation), consider:

  1. Reducing conditional branching in the evaluation path
  2. Making the evaluation pattern more predictable
  3. Using strict evaluation where possible to avoid thunk creation

This could help address the branch predictor issues and improve the overall performance.


Let me analyze the branching patterns in the evaluation code more carefully to verify the performance concerns.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Analyze branching patterns in evaluation code
rg -A 5 'case|if|>>=|=<<|>>|<<' | grep -v '^--' | grep -v '^$'

Length of output: 337368


Script:

#!/bin/bash
# Look for branching patterns specifically in the evaluator code
rg -A 5 'case|if|>>=|=<<|>>|<<' src/Evaluator.hs

Length of output: 8016

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between cac10f2 and ec6d08c.

📒 Files selected for processing (5)
  • repl/Main.hs (2 hunks)
  • src/Evaluator.hs (4 hunks)
  • src/Expander.hs (12 hunks)
  • src/Expander/Monad.hs (3 hunks)
  • src/Pretty.hs (1 hunks)
🔇 Additional comments (7)
repl/Main.hs (2)

149-151: ⚠️ Potential issue

Performance degradation needs investigation.

The new CEK evaluator shows significant performance issues, particularly with branch prediction (59.7% bad speculation). Consider the following recommendations:

  1. Profile the evaluateIn function to identify bottlenecks
  2. Consider adding strictness annotations to reduce thunk buildup
  3. Investigate opportunities for fusion optimization
  4. Consider adding bang patterns to force evaluation where appropriate

Let's analyze the evaluation patterns:


149-151: Verify the evaluation environment setup.

The phaseEnv runtime currentWorld setup needs verification to ensure it's correctly initialized for the CEK evaluator.

Let's check the environment setup:

✅ Verification successful

Let me gather more information about the evaluation environment setup.


Let me check one more aspect about the World initialization and environment setup.


CEK evaluator environment setup is correctly implemented

The evaluation environment is properly initialized and structured:

  • The phaseEnv correctly extracts environment for runtime phase (Phase 0)
  • The World structure maintains separate environments per phase
  • The evaluation environment is properly passed to the CEK evaluator through evaluateIn
  • The environment initialization chain (World -> IORef -> currentWorld -> phaseEnv) is complete
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Analyze the phase environment initialization

# Test: Look for phaseEnv usage patterns
echo "=== Phase environment initialization ==="
rg 'phaseEnv\b.*runtime' -A 5

# Test: Look for runtime phase definitions
echo "=== Runtime phase definitions ==="
ast-grep --pattern 'runtime = $_'

Length of output: 540


Script:

#!/bin/bash
# Look for evaluateIn implementation and usage
echo "=== evaluateIn implementation ==="
ast-grep --pattern 'evaluateIn $_'

# Look for Phase environment setup
echo "=== Phase environment setup ==="
rg "phaseEnv" -A 3

# Look for currentWorld usage
echo "=== currentWorld usage ==="
rg "currentWorld" -A 3

Length of output: 1540


Script:

#!/bin/bash
# Look for World initialization and environment setup
echo "=== World initialization ==="
rg "theWorld.*<-" -A 3

# Look for World environment structure
echo "=== World environment structure ==="
ast-grep --pattern 'data World'

# Look for Env implementation
echo "=== Env implementation ==="
rg "type Env" -A 3

Length of output: 1611

src/Pretty.hs (1)

627-627: LGTM! Clear and consistent error message formatting.

The implementation follows good practices by:

  • Using consistent formatting with other error messages
  • Providing clear and descriptive error text
  • Properly utilizing the pretty-printing combinators
src/Evaluator.hs (2)

1-10: LGTM! Language extensions are well-chosen.

The added language extensions are appropriate for implementing a CEK interpreter:

  • KindSignatures enables explicit kind annotations
  • BangPatterns helps with performance by enforcing strict evaluation
  • GADTSyntax provides a clear and type-safe way to represent interpreter states

662-665: ⚠️ Potential issue

Make projectError total to prevent runtime errors.

The function is partial and could crash at runtime with non-error states.

Replace with a total function:

-projectError :: EState -> EvalError
-projectError (Er err _env _k) = err
-projectError _                = error "debugger: impossible"
+projectError :: EState -> Maybe EvalError
+projectError (Er err _env _k) = Just err
+projectError _                = Nothing

Likely invalid or redundant comment.

src/Expander/Monad.hs (1)

Line range hint 902-908: LGTM: Clean implementation of kind inference scheduling.

The function follows good practices:

  • Creates a fresh kind variable
  • Properly schedules type expansion
  • Maintains consistency with the module's patterns
src/Expander.hs (1)

25-25: LGTM: Clear and descriptive function renaming.

The renaming from expandEval to evalInCurrentPhase better reflects the function's purpose and behavior.

Comment on lines +149 to +151
case evaluateIn (phaseEnv runtime currentWorld) expr of
Left evalErr -> print $ erroneousValue $ projectError evalErr
Right val -> prettyPrintLn val
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider adding performance metrics logging.

Given the performance concerns, it would be helpful to add optional performance logging to track evaluation time and memory usage.

Add performance metrics with this diff:

                        currentWorld <- readIORef theWorld
+                       start <- getCurrentTime
                        case evaluateIn (phaseEnv runtime currentWorld) expr of
-                            Left evalErr -> print $ erroneousValue $ projectError evalErr
-                            Right val    -> prettyPrintLn val
+                            result -> do
+                                end <- getCurrentTime
+                                let duration = diffUTCTime end start
+                                when (duration > 0.1) $  -- Log slow evaluations
+                                    hPutStrLn stderr $ "Evaluation took: " ++ show duration
+                                case result of
+                                    Left evalErr -> print $ erroneousValue $ projectError evalErr
+                                    Right val    -> prettyPrintLn val

Committable suggestion was skipped due to low confidence.

Comment on lines +518 to +521
where app (FO (FOClosure{..})) =
let env = Env.insert _closureVar _closureIdent value (_closureEnv <> e)
in Down (unCore _closureBody) env k
app (HO prim) = Up (prim value) mempty k
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Review environment handling in closure application.

The current implementation in applyAsClosure combines environments with (_closureEnv <> e), which could lead to unexpected variable shadowing and potential performance issues.

Consider:

  1. Using only the closure's environment: _closureEnv instead of (_closureEnv <> e)
  2. Adding a comment explaining the environment combination if it's intentional
  3. Investigating if this contributes to the reported performance issues (59.7% bad speculation)

Comment on lines +1008 to +1012
val <- evalInCurrentPhase definiens
modifyState $ over (expanderCurrentEnvs . at p) $
\case
Nothing -> Just $ Env.singleton x n val
Just env -> Just $ env <> Env.singleton x n val
\case
Nothing -> Just $ Env.singleton x n val
Just env -> Just $ env <> Env.singleton x n val
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider using strict environment updates.

The current environment update pattern could be optimized by:

  1. Using strict pattern matching
  2. Pre-allocating environment capacity
  3. Using strict fields in environment data structures

This could help reduce memory churn and improve performance.

-            modifyState $ over (expanderCurrentEnvs . at p) $
-                \case
-                  Nothing -> Just $ Env.singleton x n val
-                  Just env -> Just $ env <> Env.singleton x n val
+            modifyState $ over (expanderCurrentEnvs . at p) $ \case
+                Nothing -> Just $! Env.singleton x n val
+                Just env -> Just $! env <> Env.singleton x n val
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
val <- evalInCurrentPhase definiens
modifyState $ over (expanderCurrentEnvs . at p) $
\case
Nothing -> Just $ Env.singleton x n val
Just env -> Just $ env <> Env.singleton x n val
\case
Nothing -> Just $ Env.singleton x n val
Just env -> Just $ env <> Env.singleton x n val
val <- evalInCurrentPhase definiens
modifyState $ over (expanderCurrentEnvs . at p) $ \case
Nothing -> Just $! Env.singleton x n val
Just env -> Just $! env <> Env.singleton x n val

Comment on lines +1313 to +1334
macroVal <- inEarlierPhase
$ pure
$ apply macroImpl
$ ValueSyntax
$ addScope p stepScope stx
case macroVal of
ValueMacroAction act -> do
res <- interpretMacroAction prob act
case res of
Left err -> throwError
$ ValueNotMacro
$ erroneousValue
$ projectError err
Right mv -> case mv of
ValueMacroAction act ->
interpretMacroAction prob act >>= \case
StuckOnType loc ty env cases kont ->
forkAwaitingTypeCase loc prob ty env cases kont
Done expanded ->
case expanded of
ValueSyntax expansionResult ->
forkExpandSyntax prob (flipScope p stepScope expansionResult)
other -> throwError $ ValueNotSyntax other
other ->
throwError $ ValueNotMacro other
other ->
throwError $ ValueNotMacro other
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Improve phase handling in macro expansion.

Based on past review discussions about phase handling:

  1. The inEarlierPhase usage needs to be consistent
  2. Phase transitions should be more explicit
  3. Consider adding phase assertions for debugging
-              macroVal <- inEarlierPhase
-                          $ pure
-                          $ apply macroImpl
+              macroVal <- inEarlierPhase $ do
+                assertPhase "Macro expansion"
+                pure $ apply macroImpl

Committable suggestion was skipped due to low confidence.

Comment on lines +1418 to +1428
view (expanderWorld . worldEnvironments . at phase) $ s
case applyInEnv env closure boundResult of
-- FIXME DYG: what error to throw here
Left err -> throwError
$ ValueNotMacro
$ erroneousValue
$ projectError err
Right v ->
case v of
ValueMacroAction act -> interpretMacroAction prob act
other -> throwError $ ValueNotMacro other
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Simplify environment application logic.

As noted in past reviews, this code contains redundant environment handling. Consider:

  1. Removing the redundant environment lookup and withEnv call
  2. Simplifying the error handling path
  3. Making the control flow more direct
-          phase <- view (expanderLocal . expanderPhase)
-          s <- getState
-          let env = fromMaybe Env.empty .
-                    view (expanderWorld . worldEnvironments . at phase) $ s
-          case applyInEnv env closure boundResult of
+          case apply closure boundResult of

Committable suggestion was skipped due to low confidence.

@doyougnu doyougnu merged commit cd88e9a into gelisam:main Oct 31, 2024
5 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Jan 4, 2025
6 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CodeRabbit summons the AI reviewing bot
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants