dhall-lang / dhall-json

This repository has moved to https://github.com/dhall-lang/dhall-haskell/tree/master/dhall-json
BSD 3-Clause "New" or "Revised" License
65 stars 6 forks source link

A bit of an expression problem #66

Open ababkin opened 5 years ago

ababkin commented 5 years ago

I'm trying to write a convenient eDSL in dhall to generate a particular yaml structure from a clean spec language. Say I want to generate the following yaml:

    steps:
    - checkout
    - run:
        command: mycommand
        working_directory: mydir

the list is not homogeneous so looks like I have to resort to the following rep before converting to the yaml rep.

let RunStep = { name              : Optional Text
, working_directory : Optional Text
, command           : Text
}
in let Step = < Checkout : Text | Run : { run : RunStep } >

and have helpers to construct the values

let checkout = < Checkout = "checkout" | Run : { run : RunStep } >

in let defRun = \(cmd : Text) ->
  let step =  { name              = None Text
              , working_directory = None Text
              , command           = cmd
              } : RunStep
  in < Checkout : Text | Run = { run = step } >

my problem is that I cannot figure out a convenient way to modify the default run step template. I would love to be able to do something like

let myRunStep = defRun // { working_directory = "mydir" }

or something similar but cannot figure out how. Are there any good patterns that can help me?

Gabriella439 commented 5 years ago

@ababkin: Just to clarify, my understanding of the question is: given a defRun of type:

defRun :  { name : Optional Text, working_directory : Optional Text, command : Text }

... how do you override the working_directory field to be [ "mydir" ] : Optional Text.

If I misunderstood the question, let me know.

The next release (1.18.0) of the Haskell interpreter comes out tomorrow and provides new support for Some/None for building optional values, so one way you can override that field is:

defRun // { working_directory = Some "mydir" }
ababkin commented 5 years ago

well not quite. It indeed would be convenient to modify defRun if its type would be { name : Optional Text, working_directory : Optional Text, command : Text } but i'm trying to avoid creating a specialized function to convert from this rep (type) into < Checkout : Text | Run : { run : RunStep } > which is required to serialize into the correct yaml. I tried to use this approach before, but this requires a complex rendering function that would convert a nested struct of such simple reps into the yaml-friendly one. In best case, I'd like to have only one dhall rep of the nested entities like in this example https://github.com/ababkin/dho/blob/master/example.dhall#L20-L34.

jneira commented 5 years ago

Well if you want to update the defRun function itself you can use compose:

    let RunStep =
          { name :
              Optional Text
          , working_directory :
              Optional Text
          , command :
              Text
          }

in  let Step = < Checkout : Text | Run : { run : RunStep } >

in  let MkStep = constructors Step

in  let checkout = MkStep.Checkout "checkout"

in  let defRun
        : Text → Step
        =   λ(cmd : Text)
          →     let step =
                        { name =
                            None Text
                        , working_directory =
                            None Text
                        , command =
                            cmd
                        }
                      : RunStep

            in  MkStep.Run { run = step }

in  let updateWorkingDir
        : Text → RunStep → RunStep
        =   λ(dir : Text)
          → λ(runStep : RunStep)
          → runStep ⫽ { working_directory = Some dir }

in  let updateRun
        : (RunStep → RunStep) → Step → Step
        =   λ(f : RunStep → RunStep)
          → λ(step : Step)
          → merge
            { Checkout =
                λ(c : Text) → MkStep.Checkout c
            , Run =
                λ(rs : { run : RunStep }) → MkStep.Run { run = f rs.run }
            }
            step

in  let comp =
          https://raw.githubusercontent.com/dhall-lang/Prelude/master/Function/compose

in  let defRunWithWorkingDir =
          comp Text Step Step defRun (updateRun (updateWorkingDir "myDir"))

in  let example = defRunWithWorkingDir "example"
in  example

Note that updateWorkingDir and updateRun are reusables in its own.

ababkin commented 5 years ago

This looks pretty good, thanks!