Skip to content

Conversation

@kanwren
Copy link
Collaborator

@kanwren kanwren commented Jun 27, 2021

Tracking PR for WIP implementation of automatic ADT generation.

Currently, the API is as follows:

  • Call adt.new with a set of constructor names to constructor specifications to automatically generate an ADT. The returned set includes a match function, a check function, and an attrset of the constructors

    • adt.struct is shorthand for ADTs with only one constructor, and names the constructor make.
  • Every constructor has a specification for its fields, which can be typed or untyped. Available specifications are in adt.fields.

    • positional [ { name = "x"; type = types.int; } { name = "y"; type = types.int; } ] would make a constructor like make 1 2, and a match function like { make = x: y: ...; }
    • positional_ [ "x" "y" ] is the same, but untyped
    • record { x = types.int; y = types.int } would make a constructor like make { x = 1; y = 2; }, and a match function like { make = { x, y }: ...; }
    • record_ ["x" "y"] is the same, but untyped
    • none is for unary constructors, like adt.struct "unit" adt.fields.none
    • anon [ types.int types.int ] is like positional, but the internal field names are automatically generated
    • anon_ 2 is like positional_, but the internal field names are automatically generated
  • Currently, the only typechecking is via check. Constructors do not typecheck their arguments, since this would be awful for slow-to-check types. check will check both the field types and adt's autogenerated annotations

  • match is used the same as list.match, etc. While match could hypothetically be a key in the value, I kept it separate for consistency with these functions.

Some questions to answer:

  1. Which operations should be typechecked? Should typechecking occur anywhere else other than check?
  2. Should adt.struct name the single constructor make, new, etc., or name it after the type name? For example, unit = adt.struct "unit" adt.fields.none would currently have unit.ctors.make, which doesn't make too much sense, but the user can always unpack the result and rename everything.
  3. Pattern matching on a single-constructor type like those generated by adt.struct used to allow optionally using a plain function (e.g. point.match p (x: y: x + y)) instead of the full single-element attrset (e.g. point.match p { make = x: y: x + y; }) but this was found to be ambiguous on types with one value, where you naturally could just use a value here. For example, should unit.match x { make = 5; } return 5 or { make = 5; }? As a solution, I temporarily removed the shorthand. One possible solution is to arbitrarily require a one-argument function for such a type that we arbitrarily pass null to, but this is pretty inconsistent and not that great either.
  4. What should be the format of the return value be? Should the constructors go at the top level, or should they stay inside a ctors field and we just have the user unpack everything into the API they want to expose?
  5. Should we also expose some custom __toString? Could we support user-generated __toStrings?
  6. Several of our implicit types could benefit from ADTs, like adt.new "ordering" { gt = adt.fields.none; eq = adt.fields.none; lt = adt.fields.none; }. Where would we put these?

Also could use tests and much better docs.

kanwren added 11 commits June 25, 2021 15:54
If we optionally allow providing a function as a matcher on a
single-constructor type, then it follows that we would allow a value for
matching on a unary type as well:

```
> unit = adt.struct "unit" adt.fields.none
> unit.match unit.ctors.make 5
5
```

However, this would make using the long-form attrset for matching on the
same type ambiguous:

```
> unit = adt.struct "unit" adt.fields.none
> unit.match unit.ctors.make { make = 5; }
\# should this be `5` or `{ make = 5; }`?
```

To prevent this, this switches back to requiring attrsets to avoid
ambiguity.
@chessai
Copy link
Owner

chessai commented Dec 4, 2023

i had a thought that maybe an adt framework should be implemented outside of nix-std. i'm not sure though. either that or we can implement it here, and make sure it's known to be very experimental.

@dzmitry-lahoda
Copy link

dzmitry-lahoda commented Mar 18, 2024

what it is ?

https://en.wikipedia.org/wiki/Abstract_data_type ?

sounds fits LISP (NIX). like define type system and rules and generate types adhering these.

@kanwren
Copy link
Collaborator Author

kanwren commented Mar 18, 2024

No; algebraic data types, a la structs and Rust-style enums

@dzmitry-lahoda
Copy link

dzmitry-lahoda commented Mar 18, 2024

for Nix to handle K8S YAMLs it kind of should be somewhat there (enum), they often has several types for same YAML node value.

none of K8S nix integration do that (yet), neither modules do that too https://github.com/NixOS/nixpkgs/blob/master/lib/options.nix

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.

3 participants