Stats4224
Stats4224

Reputation: 808

How to make a recursive Dhall sum type containing data

Here is my example code. I haven't been able to figure out how to make my State sum-type recursive, while still allowing it to be used like a sum-type elsewhere. Likewise with my StateMachine type.

let State =
      < Task : { Comment : Text, Resource : Text, End : Bool }
      | Map : { Comment : Text, Iterator : StateMachine }
      | Parallel : { Comment : Text, Branch : List State }
      >

let StateMachine
    : Type
    = { Comment : Optional Text
      , StartAt : Text
      , States : List { mapKey : Text, mapValue : State }
      }

let test
    : StateMachine
    = { Comment = Some "A simple minimal example"
      , StartAt = "Hello World"
      , States =
        [ { mapKey = "Hello World"
          , mapValue =
              State.Task { Type = "Task", Resource = "Test", End = True }
          }
        ]
      }

in  test

Is there a reasonable way to do this that doesn't blow up code size and make the types ergonomic for an end user to import and use? In case it wasn't apparent from the example, I'm trying to mode a state machine.

I have tried the following, but I get a "Not a record or a Union" error for State.Task:

let State
    : Type
    =   ∀(_State : Type)
      → ∀(Task : { Type : Text, Resource : Text, End : Bool })
      → ∀(Map : { Type : Text, Iterator : _State })
      → _Stat

Upvotes: 1

Views: 164

Answers (1)

Gabriella Gonzalez
Gabriella Gonzalez

Reputation: 35089

First, I believe the type you meant to say was:

let State
    : Type
    = ∀(State : Type) →
      ∀(Task : { Type : Text, Resource : Text, End : Bool } → State) →
      ∀(Map : { Type : Text, Iterator : State } → State) →
        State

in  State

To answer your question, the closest thing you can get to a recursive union type is this equivalent representation:

let State
    : Type
    = ∀(State : Type) →
      ( < Task : { Type : Text, Resource : Text, End : Bool }
        | Map : { Type : Text, Iterator : State }
        > →
          State
      ) →
        State

in  State

... but you wouldn't get union constructors for free so I wouldn't recommend using the latter representation anyway.

For either approach you'd have to create functions that behave the same way as constructors; the language won't generate them for you like it does for non-recursive union types.

I'll illustrate how to write the constructors for the first type since that's the representation I'd recommend using:

let Types =
      let Task = { Type : Text, Resource : Text, End : Bool }

      let Map = λ(State : Type) → { Type : Text, Iterator : State }

      let State
          : Type
          = ∀(State : Type) →
            ∀(Task : Task → State) →
            ∀(Map : Map State → State) →
              State

      in  { Task, Map, State }

let -- Record of constructors similar to what you would get from a union type
    State =
      let Task
          : Types.Task → Types.State
          = λ(x : Types.Task) →
            λ(State : Type) →
            λ(Task : Types.Task → State) →
            λ(Map : Types.Map State → State) →
              Task x

      let Map
          : Types.Map Types.State → Types.State
          = λ(x : Types.Map Types.State) →
            λ(State : Type) →
            λ(Task : Types.Task → State) →
            λ(Map : Types.Map State → State) →
              Map (x with Iterator = x.Iterator State Task Map)

      in  { Task, Map }

in  State.Map
      { Type = "Foo"
      , Iterator = State.Task { Type = "Bar", Resource = "CPU", End = False }
      }

For more details, see:

Upvotes: 1

Related Questions