Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for union alternatives without fields #438

Merged
merged 13 commits into from
Mar 25, 2019

Conversation

Gabriella439
Copy link
Contributor

Fixes #224

This adds support for unions with empty alternatives that don't store any
values. In the simple case where the union has all empty alternatives it
degenerates to an enum.

For example:

let Role = < Wizard | Fighter | Rogue >

let show : Role → Text
    show =
        λ(x : Role)
      → merge { Wizard = "Wizard", Fighter = "Fighter", Rogue = "Rogue" } x

in  show Role.Wizard

However, these empty alternatives can be mixed with non-empty alternatives, too,
with handlers to match:

let Status = < Empty | NonEmpty : Text >

let concatSep
    : ∀(separator : Text) → ∀(elements : List Text) → Text
    =   λ(separator : Text)
      → λ(elements : List Text)
      → let status =
              List/fold
              Text
              elements
              Status
              (   λ(element : Text)
                → λ(status : Status)
                → merge
                  { Empty = Status.NonEmpty element
                  , NonEmpty =
                        λ(result : Text)
                      → Status.NonEmpty (element ++ separator ++ result)
                  }
                  status
              )
              Status.Empty

        in  merge
            { Empty = "", NonEmpty = λ(result : Text) → result }
            status

in  concatSep

Handlers for empty alternatives no longer have to bind unused arguments of
type {} and constructors for empty alternatives no longer have to take
an input of {=}.

Fixes #224

This adds support for unions with empty alternatives that don't store any
values.  In the simple case where the union has all empty alternatives it
degenerates to an enum.

For example:

```
let Role = < Wizard | Fighter | Rogue >

let show : Role → Text
    show =
        λ(x : Role)
      → merge { Wizard = "Wizard", Fighter = "Fighter", Rogue = "Rogue" } x

in  show Role.Wizard
```

However, these empty alternatives can be mixed with non-empty alternatives, too,
with handlers to match:

```
let Status = < Empty | NonEmpty : Text >

let concatSep
    : ∀(separator : Text) → ∀(elements : List Text) → Text
    =   λ(separator : Text)
      → λ(elements : List Text)
      → let status =
              List/fold
              Text
              elements
              Status
              (   λ(element : Text)
                → λ(status : Status)
                → merge
                  { Empty = Status.NonEmpty element
                  , NonEmpty =
                        λ(result : Text)
                      → Status.NonEmpty (element ++ separator ++ result)
                  }
                  status
              )
              Status.Empty

        in  merge
            { Empty = "", NonEmpty = λ(result : Text) → result }
            status

in  concatSep
```

Handlers for empty alternatives no longer have to bind unused arguments  of
type `{}` and constructors for empty alternatives no longer have to take
an input of `{=}`.
Copy link
Collaborator

@FintanH FintanH left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fantastic!

< x | xs₀… > ⇥ < x | xs₁… >


The language still supports a deprecated union literal syntax for selecting one
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the plan then to eventually normalize this syntax to the new syntax?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@singpolyma: That's what I originally hoped, but the issue I ran into is that we can't (easily) do so because we don't have immediate access to the type of the selected alternative.

In other words, if we try to normalize < a = x > to < a : T >.a x we need to be able to infer the type T of x, but doing so would require interleaving type-checking and normalization.

It's still possible that dhall lint might do this since it's fine if it's implementation is more complicated

Copy link
Member

@f-f f-f left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Gabriel439 looks great! 👏

It's probably worth adding some parser and typecheck tests to verify that the new literal syntax also parses and typechecks correctly

@Gabriella439
Copy link
Contributor Author

@f-f: Yes, I've almost got the matching change to the Haskell implementation ready so that I can use it to generate additional golden test results

@singpolyma
Copy link
Collaborator

singpolyma commented Mar 21, 2019 via email

@Gabriella439
Copy link
Contributor Author

@singpolyma: In my view, the main goal of normalizing the deprecated syntax to the newer syntax is so that lazy users don't have to migrate their code manually. If they have to provide a type annotation to take advantage of the migration then it feels to me like they don't benefit much because they could have instead migrated their code directly.

However, I think in the common case most users won't need to migrate their code anyway because they are likely already using the Union.constructor syntax instead of using the deprecated union literal syntax (which is much less ergonomic).

A union constructor is now in normal form and does not normalize down to
a λ-expression
Gabriella439 added a commit to dhall-lang/dhall-haskell that referenced this pull request Mar 22, 2019
@Gabriella439
Copy link
Contributor Author

Matching change to the Haskell implementation up here: dhall-lang/dhall-haskell#863

@Gabriella439 Gabriella439 merged commit 9b259d3 into master Mar 25, 2019
@Gabriella439 Gabriella439 deleted the gabriel/nullary_alternatives branch March 25, 2019 15:01
Gabriella439 added a commit that referenced this pull request Mar 25, 2019
... as part of #438 I
mistakenly changed the large expression binary test result, even though it
did not need to be changed.  I don't remember exactly what confusion of events
led to me to computing the wrong golden test result while working on that
change.

This change reverts it back to where it was before.
Gabriella439 added a commit that referenced this pull request Mar 26, 2019
... as part of #438 I
mistakenly changed the large expression binary test result, even though it
did not need to be changed.  I don't remember exactly what confusion of events
led to me to computing the wrong golden test result while working on that
change.

This change reverts it back to where it was before.
Gabriella439 added a commit to dhall-lang/dhall-haskell that referenced this pull request Mar 27, 2019
... as standardized in dhall-lang/dhall-lang#438

This also adds `dhall-json` support for empty alternatives

In particular, this translates empty alternatives to strings encoding the alternative name

```haskell
-- ./example.dhall

let Role = < Wizard | Fighter | Rogue >

in  [ Role.Wizard, Role.Fighter ]
```

```
$ dhall-to-json <<< './example.dhall'
["Wizard","Fighter"]
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants