F# Mentorship Week 2— Contributing to the Compiler
The F# mentorship program is a 6 week community driven event where mentors volunteer to help you learn topics related to the language.
There are many topics you can get guidance on. Like a beginner’s introduction to F#, contributing to open source, or F# for machine learning.
This post is part of a series documenting my experience as a mentee, learning about and contributing to the F# compiler.
Week 2 Summary
- Beginning Our Contribution
- The First Two Errors
- Investigating the Invalid Field Label Error
- Resolving the Invalid Field Label Error
The Beginning Of Our Contribution
At the second meeting of the F# mentorship program, Avi Avni and I discussed my progress in Week 1 and our next adventure through the compiler!
After gaining some experience of working with the codebase we decided it was time to start looking at our contribution.
The Problem
If you have created nested record types in F# and tried to use the Copy and Update Record Expression, you will have come across the issue Vasily Kirichenko mentions above.
Once you start performing copy and updates more than one level deep the code starts to become convoluted.
Using the with keyword does highlight the immutable nature of record types and the issue can be resolved in someway through using functions but F# is known for being succinct and expressive.
If we could reduce the complexity of performing a copy and update, no matter how many levels deep, it would improve productivity and further align Copy and Update Expressions with F#’s language design principles.
The Solution
The solution Avi and I propose is to allow accessing a nested record field from a single Copy and Update Expression.
This is a very useful addition. It greatly reduces the amount of boilerplate code required to update nested record fields. It is explicit in declaring its functionality and succinct in style — writing less code is always nice but only if it is still readable!
I was excited when Avi suggested that this was the feature we worked on together and couldn’t wait to get started!
I was set the task of compiling the new nested Copy and Update Expression and to investigate how to solve the first error.
The First Two Errors
Avi suggested putting a break-point at NameResolution.fs, line 3062.
I updated my test.fs file to contain the code above and started debugging the compiler.
See: Week 1 — Debugging the Compiler.
When compiling the new Copy and Update Expression, these errors related to type checking are thrown.
Compilation is failing when the function ResolveFieldPrim
attempts to resolve the record field access, Address.Street.Name
.
Invalid field label
The initial error — Invalid field label — means that the record field access failed because it can’t resolve the Name
, back to a field that’s part of the Person
record.
This expression was expected…
The second error — This expression was expected to have type — occurs because it thinks the with
keyword is being used to copy and update the Address
record type. Instead of copy and update the Person
record by copying its nested records and updating the Address.Street.Name
record field.
A similar error complaining about a Street
record being giving instead of an Address
type would occur if we wrote the following.
Accessing nested properties like this, within a Copy and Update Expression, is failing because the compiler can’t resolve access of the record field back to the higher level record type during the type checking stage of compilation.
Investigating the Invalid Field Label Error
Understanding the conditions where an error does and does not occur is the first step in understanding how to solve it.
When writing this post, I created the following example to demonstrate different scenarios where the “This expression was expected…” error would happen.
However, when compiling it I noticed something. This type of nested record field access does not result in the same “Invalid field label” error!
It appears that if there is only one level of nesting in the new Copy and Update Expression then the error does not occur.
So where is the error thrown?
The error is being thrown from ResolveFieldPrim
(NameResolution.fs, ln 3138) when a check against the size of a list is greater than zero.
rest
is populated as part of a function within ResolveFieldPrim
called tyconSearch
(NameResolution.fs, ln 3096) that resolves record field access from a list of identifiers. The list contains the identifiers that were not used in resolving the record field access.
For example, when resolving Address.Street.Name
the rest
list contains the identifier Name
. The type system has resolved the access as Address.Street
not Address.Street.Name
.
Depth
It is quite clear from the example above, and the case where the error isn’t thrown, that the cause of this error is depth.
ResolveFieldPrim
does not handle the case where a record field access, within a Copy and Update Expression, has a depth greater than one.
But the compiler can already handle nested record access?
F# uses nested record field access for accessing and binding record field values.
let name = person.Address.Street.Name
When compiling this code, the function ResolveExprDotLongIdent
is used to build up a list of the record fields being accessed.
It calls into the same function as tyconSearch
— ResolveLongIdentInTyconRefs
— to resolve the record fields in the format (Record, Field)
. For example the let
expression above would be represented as.
[(person, Address), (Address, Street), (Street, Name)]
Resolving the Invalid Field Label Error
To update ResolveFieldPrim
so it can handle nested record field access, tyconSearch
should follow a similar pattern to that used for resolving accessing and binding record field values.
When presented with a list of identifiers greater than two (more than one level of nesting) it should continue to try and resolve the fields instead of returning the rest
list straight away.
The rest
list should still be asserted as empty but only after attempting to resolve the list of identifiers. This allows the code to cover the previous case, where an identifier is not part of a valid record field access, and the new nested record field access.
After updating tyconSearch
and compiling the test file. It now returns a warning instead of “Invalid field label”.
ResolveFieldPrim
can now resolve nested record field access but the calling code cannot.
Before making any further changes I decided this was a good time to stop and catch-up with Avi.
During my second week with the F# compiler I began investigating the steps required in adding the new nested Copy and Update Expression and updated the first part of code to include this feature. To progress further, the update should be reviewed and the new warning looked at.
I hope I have clearly expressed the work undertaken in evaluating the first error and I can’t wait to write the next post on what followed!