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

stdlib 0.40: change the implementation of dict.update #659

Open
wants to merge 15 commits into
base: main
Choose a base branch
from

Conversation

inoas
Copy link
Contributor

@inoas inoas commented Jul 10, 2024

Replaces #610

@inoas inoas mentioned this pull request Jul 10, 2024
@inoas inoas changed the title Replace deprecated update stdlib 0.40: change the implementation of dict.update Jul 10, 2024

/// A dictionary of keys and values.
/// A dictionary (dict) of keys and values.
Copy link
Contributor Author

@inoas inoas Jul 10, 2024

Choose a reason for hiding this comment

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

Should maybe "dict" in comments be replaced by Dict?

Copy link
Member

Choose a reason for hiding this comment

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

Revert this please 🙏

CHANGELOG.md Outdated
@@ -1,5 +1,11 @@
# Changelog

## v0.40.0 - Unreleased

- The previously deprecated `update` function of the `dict` has been replaced
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I am not fond of this changelog entry.

Copy link
Member

Choose a reason for hiding this comment

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

I think we can just say "The dict module gains the update function.".

@@ -474,13 +474,63 @@ pub fn upsert(
|> insert(dict, key, _)
}

@deprecated("This function has been renamed to `upsert`")
/// Returns a dict where the caller attempts to change a key-value pair in the
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I am not happy with this explanation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Any better ideas?

Copy link
Member

Choose a reason for hiding this comment

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

Perhaps something more concise?

Updates the value in a dict for a given key using a given function.

If there already was a value in the dict for the key then it is passed into the function.

If a value is returned from the function then it will be used as the new value in the dict. If no value is returned then any previous value is removed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If a value is returned from the function then it will be used as the new
value in the dict. If no value is returned then any previous value is removed.

But is this true?

The change callback can on None case add the key by returning Ok(value) (or Some(value))? Or did I miss something.

I am updating the tests so it is clearer that the change function is very powerful and flexible.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This shows an example where I cannot see that the function description above matches. Because it really depends on the change callback/function what happens here.

https://github.com/gleam-lang/stdlib/pull/659/files#diff-99caf068f3c916909bcf3231d839db8a8afdebb65dc04d0353eea53071729264R265-R278

src/gleam/dict.gleam Show resolved Hide resolved
@inoas
Copy link
Contributor Author

inoas commented Jul 10, 2024

I think this function should be called change.

I think the other implementation already done should be called update https://github.com/gleam-lang/stdlib/pull/610/files.

Update means that there already is something and it can fail to update. Update does not signal removal. Change signals removal. Change fits to this PR, update to the other.

@inoas inoas marked this pull request as draft July 10, 2024 15:39
@inoas inoas marked this pull request as ready for review July 18, 2024 18:47
Copy link
Member

@lpil lpil left a comment

Choose a reason for hiding this comment

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

Thank you

pub fn update(
in dict: Dict(k, v),
update key: k,
with fun: fn(Option(v)) -> Result(v, Nil),
Copy link
Member

Choose a reason for hiding this comment

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

Could you use Option for the return value here please

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I thought we are not using Option for Return values.
If we have regular functions following that pattern, we then cannot apply those in this update function?

CHANGELOG.md Outdated
@@ -1,5 +1,11 @@
# Changelog

## v0.40.0 - Unreleased

- The previously deprecated `update` function of the `dict` has been replaced
Copy link
Member

Choose a reason for hiding this comment

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

I think we can just say "The dict module gains the update function.".


/// A dictionary of keys and values.
/// A dictionary (dict) of keys and values.
Copy link
Member

Choose a reason for hiding this comment

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

Revert this please 🙏

@@ -474,13 +474,63 @@ pub fn upsert(
|> insert(dict, key, _)
}

@deprecated("This function has been renamed to `upsert`")
/// Returns a dict where the caller attempts to change a key-value pair in the
Copy link
Member

Choose a reason for hiding this comment

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

Perhaps something more concise?

Updates the value in a dict for a given key using a given function.

If there already was a value in the dict for the key then it is passed into the function.

If a value is returned from the function then it will be used as the new value in the dict. If no value is returned then any previous value is removed.

///
/// dict
/// |> dict.update("e", inc_if_exists_or_discard)
/// // -> from_list([#("a", 0), #("b", 1), #("c", 2)])
Copy link
Member

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 shows the functionality very clearly. One example per code block would be good

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I am not certain I follow. Per which code block?

@inoas
Copy link
Contributor Author

inoas commented Jul 26, 2024

@lpil are you certain about this being named update rather than change?

@lpil
Copy link
Member

lpil commented Jul 26, 2024

What's the motivation for it being change. I thought we did the whole deprecation cycle to free up the name update?

@inoas
Copy link
Contributor Author

inoas commented Jul 29, 2024

What's the motivation for it being change. I thought we did the whole deprecation cycle to free up the name update?

  1. update could be used for this impl if so desired https://github.com/gleam-lang/stdlib/pull/610/files#diff-8630634f9cc0ec3abcfa5aea55f3be0e549a2131a5cce9b7f551f880ceaaaac4R497-R502

  2. More importantly, the word update signifies that a key-pair value should exist and is updated, but the impl in this PR here may insert even if there is no key or remove if there is a key (see the test suite in this PR for examples), all depending on the change function/callback.

    It changes the dict based on the function, so IMHO change is much clearer, whereas if I read dict.update I would not assume that afterwads a key-value pair is potentially removed from the dict (or added if not existing prior).

// Utilize a function returning a `Result` in the change function.
let half_or_zero_else_noop = fn(x) {
case x {
Some(i) -> int.divide(i, 2)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Because other functions usually return Result rather than Option, I have opted for the change function to return Result not Option as well.

I have added a unit test example to show what I mean.

Are you certain you want to return Option instead?

ref #659 (comment) cc @lpil

@inoas
Copy link
Contributor Author

inoas commented Jul 29, 2024

After feedback I made the changes to return Option rather than Result and tried to simplify the docs and the unit tests

@inoas
Copy link
Contributor Author

inoas commented Sep 26, 2024

This is still ready to be reviewed/merged unless you want me to rename this to change and have update do the other impl (see above). Please let me know if there are still things to do.

@inoas inoas requested a review from lpil September 26, 2024 15:45
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.

2 participants