Mixed types, functions with arguments with optional fields, gener

I’m not exactly certain what I’m asking, but I’m around 6 hours into learning Dhall and I think I have a pretty good handle on things.

I’m attempting to use Dhall to give a convenient DSL-like dialect for defining files, variables, commands and services; however the output of processing the Dhall input should be a graph, nodes and edges.

Ergonomics are important, as this is to be the basis for an internal bring-up-your-dev-env-from-shell-scripts-and-sticky-tape tool, addressing the large number of ancient projects we have which don’t have any kind of orchestration software in sight.

I have some types like this defined, and I really appreciate the syntactic sugar of the record completion operator:

let Command =
      { Type = { name : Text, cmd : Text, deps : List Text }
      , default.deps = [] : List Text
      }
let Service =
      { Type =
          { name : Text, cmd : Text, ports : List Natural, deps : List Text }
      , default = { ports = [] : List Natural, deps = [] : List Text }
      }

Ideally, I’d be able to put these in a list, and then fold over them to converd them into a list of records of nodes and edges, e.g:

in [ Service::{
        , name = "foo service"
        , cmd = "python3 -m http.server"
        , ports = [ 3000 ]
        }
      , Command::{ name = "get the date", cmd = "date" }
      ]

This should give me a few nodes get-the-date.result.success, foo_service.ports.3000, foo_service.running, and some edges foo_service.ports.3000 -> foo_service.running.

Of course, I can’t have mixed types in a list, and static record types don’t give me an ability to consume the records and emit new nodes and edges, so I tried to make a union:

let Resource
    : Type
    = < Service : ServiceDef.Type | Command : CommandDef.Type >

let resources
    : List Resource
    = [ Resource.Service::{
        , name = "foo service"
        , cmd = "python3 -m http.server"
        , ports = [ 3000 ]
        }
      , Resource.Command::{ name = "get the date", cmd = "date" }
      ] 

However, no combination of anything in the < .. > would yield working results, I seemed to be struggling, since the record completion syntax is then breaking with The completion schema must be a record. It seems that’s because unions and record completion syntax are not compatible? github_com/dhall-lang/dhall-lang/issues/821 (new discourse users can only post 2 links)

I’d like to get my DSL to a point that I can do something like this:

in List/map [ Command::{name = "a"}, Command::{name = "b"}, Service::{name="c"}, Service::{name="d", deps="c/running"} ]

I’d then squint a these docs here a bit, for map/fold to flatten the list of graph nodes into a single Graph Using map and fold to generate a list of strings — Dhall documentation

However all roads lead to needing to have unions with record completion syntax or something akin to that. I think ideally I’d like a simple syntax even, something like

let Node : Type = {}
let Graph : Type = { nodes: List Node, edges: List Edge }

in [
 Command {name = "foo"} -- a function, taking an anonymous record, which is merged with defaults, returning a 
 Command { name = "bar", cmd = "/usr/bin/date" }
]

My apologies for scatterbrain posting on the Discourse board, but I could use a little fundamental guidance on the idiomatic approach to this please!

You’re trying to record complete the union tag/constructor, not a record, is the problem. With

let Resource
    : Type
    = < Service : ServiceDef.Type | Command : CommandDef.Type >

Resource.Service is a function with type ServiceDef.Type -> Resource, so that’s what you need to pass your completed record too.

I think what you want is more:

    [ Resource.Service ServiceDef::{
        , name = "foo service"
        , cmd = "python3 -m http.server"
        , ports = [ 3000 ]
        }
      , Resource.Command CommandDef::{ name = "get the date", cmd = "date" }
      ] 

Thanks, that’s really helpful! And from here, I can map my mixed types (I’m hoping I can merge on union types to handle Command and Service (etc) differently when converting from them into a graph fragment!

Amazing, thanks for the fast response!

1 Like