Search code examples
dhall

Reverse a N-N mapping of data in Dhall


Given is a config of apps and containers with an app being able to have multiple containers a container being able to have multiple apps. I want to be able to output them in 2 ways

  • Per app list the containers
  • Per container list the apps

The data format is simple, but I can't seem to find a way to get both of these representations without repeating the relation.

Example data when starting from containers having apps

let app1 = { name = "app1" }
let app2 = { name = "app2" }

let containers = [
  { name = "container1", apps = [ app1 ] },
  { name = "container2", apps = [ app1, app2 ] }
]

{- I can easily transform this data to the following -}

[
  { app = "app1", container = "container1" },
  { app = "app1", container = "container2" },
  { app = "app2", container = "container2" }
]

{- But I cannot seem to get it into the requested format -}
[
  "app1" = [ "container1", "container2" ]
  "app2" = [ "container2" ]
]

I think using identifiers as Text cannot work as there is no way to merge associated lists or something alike using equal identifiers.

Using records I can merge something like this {a1 = {c1 = True}} /\ {a1 = {c2 = True}} /\ {a2 = {c2 = True}}. Which would be {a1 = {c1 = True, c2 = True}, a2 = {c2 = True}}. But I can't get to this state in the first place because I can't 'reverse' records.

I don't care how I need to structure the config as long as I don't need to repeat the relation twice.


Solution

  • Yeah, it's not possible to do exactly what you're requesting because the language does not permit Text comparisons

    The closest solution I can think of is something like this:

    let Map = https://prelude.dhall-lang.org/v21.1.0/Map/Type.dhall
    
    let List/concatMap = https://prelude.dhall-lang.org/v21.1.0/List/concatMap.dhall
    
    let List/map = https://prelude.dhall-lang.org/v21.1.0/List/map.dhall
    
    let startingMapping
        : Map Text (Map Text {})
        = toMap
            { container1 = toMap { app1 = {=} }
            , container2 = toMap { app1 = {=}, app2 = {=} }
            }
    
    let desiredMapping
        : Map Text (Map Text {})
        = toMap
            { app1 = toMap { container1 = {=}, container2 = {=} }
            , app2 = toMap { container2 = {=} }
            }
    
    let transpose
        : ∀(a : Type) → Map Text (Map Text a) → Map Text (Map Text a)
        = λ(a : Type) →
            List/concatMap
              { mapKey : Text, mapValue : Map Text a }
              { mapKey : Text, mapValue : Map Text a }
              ( λ(x : { mapKey : Text, mapValue : Map Text a }) →
                  List/map
                    { mapKey : Text, mapValue : a }
                    { mapKey : Text, mapValue : Map Text a }
                    ( λ(y : { mapKey : Text, mapValue : a }) →
                        { mapKey = y.mapKey
                        , mapValue =
                          [ { mapKey = x.mapKey, mapValue = y.mapValue } ]
                        }
                    )
                    x.mapValue
              )
    
    in  assert : transpose {} startingMapping ≡ desiredMapping
    

    The assertion fails, though, with this error message:

    Error: Assertion failed
    
    [ - { mapKey = "app1"
        , mapValue = [ { mapKey = "container1", mapValue = {=} } ]
        }
    , - { mapKey = "app1"
        , mapValue = [ { mapKey = "container2", mapValue = {=} } ]
        }
    , + { mapKey = "app1"
        , mapValue =
          [ { mapKey = "container1", mapValue = {=} }
          , { mapKey = "container2", mapValue = {=} }
          ]
        }
    , …
    ]
    
    
    41│     assert : transpose {} startingMapping ≡ desiredMapping
    

    … because the result does not consolidate the two duplicate app1 keys like you were requesting.