kowainik / tomland

🏝 Bidirectional TOML serialization
https://kowainik.github.io/posts/2019-01-14-tomland
Mozilla Public License 2.0
120 stars 39 forks source link

Dynamic Keys #254

Closed everythingfunctional closed 4 years ago

everythingfunctional commented 4 years ago

I'm trying to write something to parse into a Map, where the keys in the toml file are the keys in the map. I can't seem to find any way of doing this.

Basically I need something like the following.

someFunc :: Codec a -> Codec (Map String a)

These keys are going to be in a table, so if that makes things more concrete, what I really want would be more like

genericTable :: Key -> Codec a -> Codec (Map String a)

An example of the toml file would be something like

[dependencies]
thing1 = { ... }
thing2 = "1.2.3"
...

So I know I'll need a Codec to match the values, since those aren't a simple type, and I know the table the keys will be directly under, but I need to parse each of the keys into my data structure.

Sorry if this was a bit disorganized. Hopefully that's enough info to see what I'm after.

chshersh commented 4 years ago

Hi @everythingfunctional, thanks for using tomland and opening an issue! Unfortunately, tomland doesn't have out-of-the-box solution for this case. However, I can see how this can be useful in some cases. So I don't mind having such function implemented in tomland :slightly_smiling_face:

A few notes about the implementation:

  1. I agree with the idea that it should work with the table and should take a table name as an argument.
  2. The Key type is represented as a list of Text pieces, so the returning type will be something like Map Key a or Map Text a. Libraries like aeson provide a custom abstraction like FromJSONKey so you can have your custom Map keys. But tomland uses combinator-based approach instead of typeclasses-based. https://github.com/kowainik/tomland/blob/6ca6ed3a3d6e1bbcd2b7b06ac82238ef56b00ccc/src/Toml/PrefixTree.hs#L78-L81

So I think that we can have a function like:

tableMapCodec
    :: Ord key
    => BiMap Key key  -- ^ Bidirectional converter between TOML and Map keys
    -> Key  -- ^ Table name key
    -> TomlCodec val  -- ^ Codec for map values
    -> TomlCodec (Map key val)
everythingfunctional commented 4 years ago

I'm still a bit new to doing stuff this advanced in Haskell. I'd be happy to take a shot at implementing such a function, but I'll need some guidance on where to start, where to put it, and what might be involved.

chshersh commented 4 years ago

@everythingfunctional No worries :slightly_smiling_face: I can give a few pointers to the implementation.

  1. BiMap e a b is just a pair of functions to convert between a and b, where conversion can fail with an error of type e. I believe a good starting point would be to implement a few standard bidirectional converters, like TomlBiMap Key Text andTomlBiMap Key String. SoTomlBiMap Key Textis a pair of functions to get some arbitraryTextfrom TOMLKeyand vice versa. I guess it's always possible to keyTextfromKey` but not vice versa. https://github.com/kowainik/tomland/blob/6ca6ed3a3d6e1bbcd2b7b06ac82238ef56b00ccc/src/Toml/Bi/Map.hs#L127-L131 https://github.com/kowainik/tomland/blob/6ca6ed3a3d6e1bbcd2b7b06ac82238ef56b00ccc/src/Toml/Bi/Map.hs#L201-L202
  2. The previous step may require patching the TomlBiMap error type by adding a constructor like InvalidKey Key. But in the beginning, implementation can simply use the ArbitraryError constructor. https://github.com/kowainik/tomland/blob/6ca6ed3a3d6e1bbcd2b7b06ac82238ef56b00ccc/src/Toml/Bi/Map.hs#L205-L216
  3. After that, you can try to implement tableMapCodec. You may found two existing functions useful to look at to get an idea of how to implement it: tableCodec and mapCodec. The general idea is the following:

    • When reading from TOML
      1. Lookup table by the given name (it has type TOML).
      2. Get list of fields: tomlPairs field of the type HashMap Key AnyValue.
      3. Apply given codec to this map and get Map Key val back.
    • When writing to TOML: inverse of the previous steps

    https://github.com/kowainik/tomland/blob/6ca6ed3a3d6e1bbcd2b7b06ac82238ef56b00ccc/src/Toml/Bi/Combinators.hs#L381-L397 https://github.com/kowainik/tomland/blob/6ca6ed3a3d6e1bbcd2b7b06ac82238ef56b00ccc/src/Toml/Bi/Combinators.hs#L481-L515

Feel free to ask any further questions :slightly_smiling_face:

chshersh commented 4 years ago

Resolved by #255. Thanks again, @everythingfunctional!