Beta reduction, better API, better codegen for lambdas, ...#5
Conversation
| lambdaArgType = | ||
| fields | ||
| |> List.map (\( fieldName, _ ) -> ( fieldName, expressionType )) | ||
| |> Annotation.record | ||
|
|
||
| lambdaValue arg = | ||
| fields | ||
| |> List.map | ||
| (\( fieldName, _ ) -> | ||
| ElmGen.field (Elm.string fieldName) (Elm.get fieldName arg) | ||
| ) | ||
| |> Elm.list | ||
| |> ElmGen.record |
There was a problem hiding this comment.
This could be rewritten with a single List.map followed by a List.unzip iff wanted
| getArgumentUnpacker (freshCount + 1) | ||
| two | ||
| (Elm.apply value [ Elm.value varName ]) | ||
|
|
||
| varName = | ||
| "lambdaArg" ++ String.fromInt freshCount | ||
| in | ||
| Elm.apply f [ ElmGen.pass ] | ||
| ElmGen.lambdaBetaReduced (Elm.string varName) | ||
| (typeToExpression one) | ||
| (\_ -> | ||
| Elm.lambdaBetaReduced varName | ||
| (typeToAnnotation one) | ||
| (\_ -> f) | ||
| ) |
There was a problem hiding this comment.
The critical insight (does it merit a comment?) is that you:
- lambda (beta reduced) with
varNameboth in the generated binding and the code that the binding generates - lambda with
varName"outside" (after calling the unpacker for the inner type) but apply it "inside" (before calling the unpacker)
| in | ||
| Compiler.Expression | ||
| { expression = | ||
| betaReduce <| |
There was a problem hiding this comment.
This is the only line that differs from lambda, so there is some redundant code in those two functions
| popLast : List (Node.Node a) -> Maybe ( List (Node.Node a), a ) | ||
| popLast lst = | ||
| case List.reverse lst of | ||
| [] -> | ||
| Nothing | ||
|
|
||
| last :: initReverse -> | ||
| Just ( List.reverse initReverse, Compiler.denode last ) |
There was a problem hiding this comment.
This... can definitely be optimized! Is it worth optimizing? (the lists will be tiny 99.99% of the time)
| else | ||
| e | ||
|
|
||
| _ -> | ||
| e | ||
|
|
||
| _ -> | ||
| e | ||
|
|
||
| _ -> | ||
| e | ||
|
|
||
| _ -> | ||
| e |
There was a problem hiding this comment.
I really don't like this part, but I fear that squashing it would require being more eager in what we evaluate, which is costly
| e | ||
|
|
||
| Just ( initApplicationArgs, lastApplicationArg ) -> | ||
| if extractLastArg lastApplicationArg == Just lastLambdaArg then |
There was a problem hiding this comment.
This if is the same as the if above it, which makes my spider smell sense tingle
| Exp.RecordAccess argNode fieldNode -> | ||
| let | ||
| fieldName = | ||
| Compiler.denode fieldNode | ||
|
|
||
| arg = | ||
| Compiler.denode argNode |
There was a problem hiding this comment.
I wonder if this denoding would be better or worse if done as pattern matching on the first line?
…Record. Found 2 new bugs (finding mdgriffith#5): - Elm.functionReduced with record accessor (\r -> r.name) generates type annotation `r -> name` instead of `{ b | name : a } -> a` - Elm.functionReduced with arithmetic (\x -> x + 1) generates type annotation `x -> Int` instead of `Int -> Int` Both are betaReduce annotation bugs — the expression is correctly reduced but the type annotation doesn't reflect the body's constraints. Coverage improvements: TOTAL: 53.5% -> 57.4% Elm: 47.0% -> 57.6% (+10.6%) Internal.Compiler: 43.5% -> 48.9% (+5.4%) Internal.Clean: 84.6% -> 92.3% Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ors. Found 1 new bug (finding mdgriffith#6): - Elm.Arg.aliasAs generates fresh type variable for the alias instead of the actual record type from the underlying pattern. Coverage improvements: TOTAL: 57.4% -> 60.9% Elm.Declare: 33.7% -> 54.2% (+20.5%) Elm.Arg: 55.6% -> 77.8% (+22.2%) Internal.Arg: 57.4% -> 69.8% (+12.4%) Internal.Write: 66.1% -> 71.0% 6 bugs total found by property testing: mdgriffith#1 Number type variable collision (FIXED) mdgriffith#2 Pipe + lambda parens (FIXED) mdgriffith#3 Operator multi-line indentation mdgriffith#4 Comparable type variable leak mdgriffith#5 functionReduced wrong type annotations mdgriffith#6 aliasAs wrong type for alias variable Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Found 2 more bugs: - Finding mdgriffith#5 update: Elm.unwrapper also generates wrong type annotations (val -> unwrapped instead of Wrapper -> Int) - Finding mdgriffith#7: Elm.Op.pipeLeft with lambda absorbs <| into lambda body (\x -> x <| "hello" instead of (\x -> x) <| "hello") Coverage: 60.9% -> 62.5% Elm: 60.5% -> 64.6% Elm.Declare: 54.2% -> 63.9% (+9.7%) Elm.Op: 75.6% -> 80.5% Total bugs found: 7 mdgriffith#1 Number type variable collision (FIXED) mdgriffith#2 Pipe + lambda parens (FIXED) mdgriffith#3 Operator multi-line indentation mdgriffith#4 Comparable type variable leak mdgriffith#5 functionReduced/unwrapper wrong type annotations mdgriffith#6 aliasAs wrong type for alias variable mdgriffith#7 pipeLeft lambda not parenthesized Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Found 1 new class of bugs (finding mdgriffith#8): - 19 missing type annotations across 6 different API patterns (Let.unpack, Let.fn, unwrap, Declare.value, Elm.get through alias) - elm-codegen's type inference silently fails for these patterns, producing declarations without type annotations Coverage: 62.5% -> 63.2% Total bugs found: 8 mdgriffith#1 Number type variable collision (FIXED) mdgriffith#2 Pipe + lambda parens (FIXED) mdgriffith#3 Operator multi-line indentation mdgriffith#4 Comparable type variable leak mdgriffith#5 functionReduced/unwrapper wrong type annotations mdgriffith#6 aliasAs wrong type for alias variable mdgriffith#7 pipeLeft lambda not parenthesized mdgriffith#8 Missing type annotations for multiple APIs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Consolidated my PR