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

Migrate to Camel Case (Discussion) #1168

Closed
wants to merge 6 commits into from

Conversation

JordanMartinez
Copy link
Contributor

Description of the change

Continues #1154 to illustrate a way to approach the auto-migration problem. I figured I would open this as a separate PR to illustrate some changes before committing to them.

Comment on lines +111 to +124
migrationStep :: forall h a. ST h (YamlDoc a) -> YamlMigrationStep -> ST h (YamlDoc a)
migrationStep stDoc (YamlMigrationStep { key, toPrevKey, updateValue}) = do
doc <- stDoc
let oldKey = maybe key (\f -> f key) toPrevKey
when (has doc oldKey) do
value <- get doc oldKey true
for_ (toSeqItems value) \item ->
void $ migrationSteps (pure item) updateValue
when (isMap value) do
void $ migrationSteps (pure value) updateValue
for_ toPrevKey \_ -> do
delete doc oldKey
add doc key value
pure doc
Copy link
Contributor Author

Choose a reason for hiding this comment

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

First, starting with this code, this is the general idea we want to employ. Originally, I was using getIn and friends before I realized that just using get is simpler as it avoids the issue of a parent key being renamed before we mutate a value.

For sequence values, I'm iterating through each one automatically via the toSeqItems function. This uses a runtime check to determine whether or not the value cannot be modified using our function; rather, we need to modify each element in that sequence.

A similar check is done via isMap: we can recurse on the value if it's a Map but don't if it's a Scalar.

Lastly, we rename the key by deleting the old one and adding the new one. Since this is using type-level programming, we provide a function that maps the new key name to the old one. I've identified one problem with this but more on this later.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Second, the gist of this file is that we require every type ever mentioned in Config to have an instance of MigrateFromSnakeCase'. This ensures that we are migrating every possible key that has a correspondence to a config field (e.g. package_set) without modifying keys that are themselves values to some config value (e.g. prelude as a dependency).

The advantage of this approach is that we have a guarantee that every type was checked (at least upon initial write). The disadvantage is that some instances (e.g. WarningCensorTest) require referencing a row-based type to ensure the changes stay synced. We could use a hard-coded value but there's no guarantee that that will stay in sync. In other cases involving types from the Registry, I can't use the WarningCensorTest workaround. So, that means implementing instances that will likely go out-of-sync if the Registry types change their internal Yaml/JSON decoding.

Another disadvantage is having to define and implement an entire type classes for every migration we want to support.

Regardless, here's an awkwardness that arises. Due to the camel case being just one possible migration, I've defined the FromSnakeCase class in its own module because its verbose and otherwise would distract from the code in core/src/Config.purs.

However, this same migration needs to (should?) be done in the Lock file. Ideally, we would reuse the same type class defined in core/src/Config/Migrations/FromSnakeCase.purs. However, I don't see a reason for why registry-dev should implement instances for FromSnakeCase because this is a Spago concern, not a Registry one. But by not doing that, we can't reuse this type class. So, we have to define a new one just for the Lock type, reimplementing the same instance for a given type (e.g. SetAddress) that provides another way for things to go out-of-sync.

Comment on lines +41 to +50
-- Values under this field should not be modified
instance
( MigrateLockFromSnakeCase' v
) => MigrateLockFromSnakeCase' (Map k v) where
migrateLockFromSnakeCase' _ = List.singleton $
YamlMigrationStep
{ key: "foo"
, toPrevKey: Nothing
, updateValue: migrateLockFromSnakeCase' (Proxy :: Proxy v)
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Third, with the current design, this instance is incorrect as made obvious via .workspace.packages.<package>.test_dependencies. Similar to what I do with the sequence values, I'd need to modify YamlMigrationStep to specify whether the next value has a known key or whether one should just iterate through all of its keys.

Comment on lines +70 to +76
-- License is (at the time of writing) a newtyped String
instance MigrateFromSnakeCase' License where
migrateFromSnakeCase' _ = Nil

-- PackageName is (at the time of writing) a newtyped String
instance MigrateFromSnakeCase' PackageName where
migrateFromSnakeCase' _ = Nil
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fourth, these instances will go out-of-sync whenever these types change. And because they're from registry-dev, we'll encounter a similar issue as we did previously when spago-core need to be updated, then registry-dev, and then spago.

@JordanMartinez
Copy link
Contributor Author

A few other thoughts:

  • At what point do we provide full bindings to Yaml so that we can properly construct an AST using their values/types that we can navigate through and/or modify?
  • Is all this effort even worth it? Or would it be faster to just look for and rename the values we know (currently) to be in need of such renaming?
  • I've modified the file-reading/yaml-parsing process to do this conversion every time. At what point would we remove this conversion?
  • Am I misusing the yaml library when renaming a field? Perhaps my unfamiliarity with YAML is causing me to approach this entire issue incorrectly.

@f-f
Copy link
Member

f-f commented Mar 15, 2024

Superseded by #1202

@f-f f-f closed this Mar 15, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants