openlang

Private union cases, smart constructor, and still `Colors []` after remove?

Hello all,

I have a small swatch editor with a three-case palette union. A fresh edit is NotEntered, a deliberate clear is Empty, and Colors holds a list. I made the cases private and exposed only ofColors, where ofColors [] gives Empty. My expectation was that the compiler would make Colors [] impossible to build.

In a scripted session (fresh, add blue, remove blue, Save), Save prints "0 colors" instead of "empty (chosen)". Reset behaves correctly. Rider shows no squiggle and the build is clean.

I suspected List.filter first, then Rider using a different SDK than global.json, so I rebuilt from the terminal. The result was the same.

This is the function I think is responsible:

let remove (name: string) (p: Palette) =
    match p with
    | Colors cs -> Colors(cs |> List.filter (fun c -> c.Name <> name))
    | other -> other

Is the privacy only checked outside Palette.fs, so my own module can build Colors [] freely? The access control page seems to suggest so, but I am not certain. If so, is routing add and remove through ofColors the normal fix, or is the type itself wrong? I am also wondering whether a plain list plus an entered flag would be simpler here.

Thank you, Oskar

The conversation continues 1

Follow the reasoning
  1. Permalink

    Thanks for the clear write-up, Oskar. I think you're right about the privacy: inside the defining module the constructors are visible, so remove can build Colors [] freely. Haskell works the same way with hidden constructors, so this isn't F#-specific.

    Before choosing a fix, I'd like to know one thing. What does Save actually match on? If it has a Colors cs case that prints the count, it will print "0 colors" for any Colors [], however that value got built. Does the fresh-to-add step go through ofColors or build Colors directly? I ask because the order of the steps (add, then remove, then Save) decides which function produces the final value.

    If you route remove through ofColors, would add need it too? I'm also curious why you ruled out making Colors carry a non-empty list.

Other sides of the same question

  1. Is a read-only List<Int> a snapshot?

    List<Int> is a read-only view, not a snapshot. It tells a consumer which operations it can call through that reference. It pr…

    Replies 4Latest activity kotlin
  2. Units of measure as cheap ids

    For integer ids I've been trying units of measure instead of single-case unions, and I'd like a second opinion before it spre…

    Replies 1Latest activity fsharp
  3. Validating JSON before the discriminated union

    Small tool that reads status dumps from my backup hosts and prints one line each. Each file should be ready with items, or fa…

    Replies 4Latest activity typescript