Roger-luo / Configurations.jl

Options & Configurations made easy.

Home Page:https://configurations.rogerluo.dev/stable

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Fields same as the default values are omitted.

chenspc opened this issue · comments

First of all, thanks for this package! It looks exactly like the functionality I was looking for.

I noticed that when a value in the dictionary is the same as the default, it will be omitted in the struct. Although this probably doesn't affect the use of the struct, it does affect the to_dict function (as well as to_toml). For example, with the example in README.md, if I change float in dict d to be the default 0.3 instead of 0.33, the float field won't show up in the generated dict or the toml string.

julia> using Configurations

julia> "Option A"
       @option "option_a" struct OptionA
           name::String
           int::Int = 1
       end

julia> "Option B"
       @option "option_b" struct OptionB
           opt::OptionA = OptionA(;name = "Sam")
           float::Float64 = 0.3
       end

julia> d = Dict{String, Any}(
           "opt" => Dict{String, Any}(
               "name" => "Roger",
               "int" => 2,
           ),
           "float" => 0.3
       );

julia> option = from_dict(OptionB, d)
OptionB(;
    opt = OptionA(;
        name = "Roger",
        int = 2,
    ),
)

julia> option.float
0.3

julia> Configurations.to_dict(option)
OrderedCollections.OrderedDict{String,Any} with 1 entry:
  "opt" => OrderedCollections.OrderedDict{String,Any}("name"=>"Roger","int"=>2)

julia> to_toml(option)
"[opt]\nname = \"Roger\"\nint = 2\n"

Is there a simple way to include the defaults?

this is a feature, see also #6. I don't think you need to include defaults in most cases, simpler is better, e.g when someone sends their data through REST API, not including defaults is definitely preferred. If someone wants to convince me there should be a way to include defaults, a more concrete use case is needed.

I think I agree with you that in most cases simpler is indeed better. However, in our field we have a simulation package written in C++, but with multiple bindings (MATLAB, Python, and possibly Julia soon). It is hard for people to switch from their favourite languages because most people are not programmers (some don't even program but can run hand written scripts). We are working on a solution where each binding can read/write to a common plain text file (e.g. .toml) that includes all the information needed to reproduce the results from other people.

This may sound unnecessarily complex from a programmer's perspective, but AFAICT it has three additional advantages.

  • People who don't program much and only need a quick simulation to compare with experimental results can modify a .toml file and run it.
  • The file itself can act like a record for the simulation, with all the parameters (default and modified).
  • Easier for people to switch between languages when needed. Sometimes, people would rather be using three languages just to keep things working as before. (e.g. MATLAB for simulation, Python for neural network, Julia for things that need performance).

We could potentially ask everyone who maintain the MATLAB, Python, Julia bindings to use the same set of defaults. But having a defaults.toml with another params.toml for every simulation feels like a more complicated solution than adding an include_defaults option.

People who don't program much and only need a quick simulation to compare with experimental results can modify a .toml file and run it.

use a template file, with comments explaining what are these options, check Jekyll templates. including the defaults doesn't solve the problem

The file itself can act like a record for the simulation, with all the parameters (default and modified).
Easier for people to switch between languages when needed. Sometimes, people would rather be using three languages just to keep things working as before. (e.g. MATLAB for simulation, Python for neural network, Julia for things that need performance).

why without defaults can't?

But having a defaults.toml with another params.toml for every simulation feels like a more complicated solution than adding an include_defaults option.

why is that, why can't you just define the defaults in the program? if the defaults are different, why do you define them as default in the program after all? this doesn't make sense to me. If the defaults defined in your packages are not using the same convention, then I believe you need a redesign of your package rather than modifying this package - your simulation code itself just sounds confusing to a normal user now.


IMHO, from my point of view, the logic of your simulation code sounds inconsistent between languages, the defaults defined in a program are conventions of the API, they should always stay the same. This is why we are not including them here. Different default conventions between languages sounds like a very bad design to me.

the default semantic in this package simply means if you don't write it in your configuration file, it will use this value. Thus if you ask your user to write the default in the configuration file every time, then you shouldn't define them as defaults, but instead, use a template configuration file so that your users can copy-paste.


I'm not changing the API for this, but you can always overload to_dict for your own type, but maybe you want to consider my suggestion seriously and change your program logic here.

use a template file, with comments explaining what are these options, check Jekyll templates. including the defaults doesn't solve the problem

We've already got template/example files with comments in the MATLAB version. My concern is more about when one imports an input.toml with TOML.parsefile() and from_dict, but can't dump all the information back to another output.toml if it has some fields that are the same as defaults (accidentally).

why without defaults can't?

It's possible to completely avoid defaults, and force user to give a complete list of params. There are some inconveniences due to the specific design of the core package. For example, not every parameter is strictly needed for all the modes, but the program expects something. Also the parameter list is rather long. I agree this could/should be fixed in the simulation package. But in reality it's a bigger fix than finding a way to export the defaults. It's also easier to debug and ask for help if the simulation result is confusing – one can send one file to someone who knows more.

why is that, why can't you just define the defaults in the program? if the defaults are different, why do you define them as default in the program after all? this doesn't make sense to me.
IMHO, from my point of view, the logic of your simulation code sounds inconsistent between languages, the defaults defined in a program are conventions of the API, they should always stay the same. This is why we are not including them here. Different default conventions between languages sounds like a very bad design to me.

Yes, defaults should be the same. This is more of a historical problem. The person who coded the c++ core package made the MATLAB binding and include the defaults in the MATLAB part. And then someone wrote a python binding so he needed to include a default class. And I'm hoping to write a Julia binding, presumably need to do something similar. We can try to be thorough about it, but have an extra layer of checking isn't a bad thing IMO. That's why I thought having the ability to dump all parameters into one file is something desirable. Maybe in the future, I can get everyone around a Zoom table and make the change. My proposal was indeed a temporary and less ideal solution. But for the people I've talked to, it seems to be an acceptable alternative because it doesn't break too many things in the short term, and it doesn't stop us from fixing it in the future. I'm still learning C++ so it might take some time :).

I'm not changing the API for this, but you can always overload to_dict for your own type, but maybe you want to consider my suggestion seriously and change your program logic here.

I will absolutely consider your suggestions – I actually agree with you on most points, especially from a technical point of view. However, I have to say I don't quite understand why you seem very against the idea of adding an option to include defaults. The fact that d and to_dict(from_dict(OptionB, d)) are different feels counterintuitive. I don't think I'm the only one since this was also asked in #6 not long ago. Is there a reason to not have such an option? Not trying to change your mind, but just curious. Something like to_dict(d; include_defaults=false) doesn't look particularly awful to me..

The fact that d and to_dict(from_dict(OptionB, d)) are different feels counterintuitive.

It will be the same if you follow the same convention for defaults - this is also compatible with TOML format where there are no null types. And it is not possible to have d and to_dict(from_dict(OptionB, d)) in this context, I'm just choosing the simpler convention. I can also say that my d without defaults is different from the to_dict(from_dict(OptionB, d)) under your convention.

Something like to_dict(d; include_defaults=false) doesn't look particularly awful to me..

please read the documentation, this is not possible, to_dict already supports to_dict(d; kw...) for override, which is a much more useful thing than this sorry, actually yeah, I forget this interface was not implemented. Yeah, I agree that we can have such an option for to_dict and to_toml.

please feel free to submit a PR.

The print output of this example does not contain age.

Although IMO it would be a nice behaviour to have for from_dict.

The example is a bit out of date.

you can't define this behavior for from_dict. it's the behavior for Base.show.

Although IMO it would be a nice behaviour to have for from_dict.

As I explained above, The design decision is to not have the default behavior to include defaults.