Skip to content
This repository has been archived by the owner on Nov 18, 2021. It is now read-only.

Service support? #69

Closed
matthewmueller opened this issue Jul 31, 2019 · 7 comments
Closed

Service support? #69

matthewmueller opened this issue Jul 31, 2019 · 7 comments
Labels
FeatureRequest New feature or request FeedbackWanted Further information is requested NeedsDesign Functionality seems desirable, but not sure how it should look like. Proposal protobuf

Comments

@matthewmueller
Copy link

I'm very interested in CUE for inter process communication. I was wondering if there are any plans to add function signatures to enforce how two processes can communicate with each other?

Something like services in Thrift / Protobuf or queries & mutations in a GraphQL schema.

This seems a bit outside of the core motivation for CUE, but it does seem like CUE would fit very well here too.

@jlongtine
Copy link
Contributor

@matthewmueller I think some work has been done on Protobuf: https://github.com/cuelang/cue/tree/master/encoding/protobuf

@mpvl would have a better idea of the state of that code, though.

@mpvl
Copy link
Contributor

mpvl commented Aug 1, 2019

@matthewmueller This is not planned at the moment, although I have given it some thought. The main reason not though is that I didn't have a concrete use case and I'm not sure how that would look like.

But as @jlongtine said, the infrastructure for Proto parsing and conversion is already in place. So it would be useful to know what exactly expect to do with such a feature.

@mpvl
Copy link
Contributor

mpvl commented Sep 20, 2019

@matthewmueller if you have a concrete example of how you would want to be using CUE this way I would be quite interested.

@matthewmueller
Copy link
Author

matthewmueller commented Sep 20, 2019

Hey @mpvl! I'm imagining an implementation of protobuf's generators that adds constraint-based validation.

Tons of unknowns here, but conceptually I was thinking something like this:

user.cue

email: "\w+@\w+\.\w{2,}" // some regex for email

CreateInput: {
    email: email
    password: string // perhaps @len(string) > 0 <= 30
    born: >= 1900 <= 2019
}

CreateOutput: {
    id: >=0
    email: email
    born: >= 1900 <= 2019
}

UpdateInput: {
    email?: email
    password?: string
    born?: >= 1900 <= 2019
}

UpdateOutput: {
    ok: true | false
}

User: {
  create(in: CreateInput): CreateOutput
  update(in: UpdateInput): UpdateOutput
}

Then, run cue generate user.cue --out cue/user.go. This will create cue/user.go. This file will contain a User struct (or maybe just an interface?) and a Marshal and an Unmarshal. The Marshal and Unmarshal bake in constraint-based validation.

Usage would look something like this:

main.go

package main

import "cue/user"

func main() {
  buf, err := ioutil.ReadAll(os.Stdin)
  if err != nil {
    panic(err)
  }
  var u user.CreateInput
  if err := user.Create.Unmarshal(buf, &u); err != nil {
    panic(err)
  }
  fmt.Println("got a user.CreateInput!", u)
}

@neelance do you have some thoughts on this? Our discussion sparked this initial issue 😊

@mpvl mpvl added FeatureRequest New feature or request FeedbackWanted Further information is requested NeedsDesign Functionality seems desirable, but not sure how it should look like. labels Nov 30, 2019
@mpvl
Copy link
Contributor

mpvl commented Dec 5, 2020

Just to add some thoughts here: with the upcoming proposal to generalize and harmonize the syntax, something that gets close to the above rpc signatures is:

create: {in: CreateInput}: CreateOutput // POSSIBLE NEW NOTATION FOR RPC DEFINITIONS

or maybe

create: {in: CreateInput} :: CreateOutput

(The scanner still recognizes ::, even though it is unused)

Especially the former would require a minimal syntactic adjustment and generalizes, syntactically, what expressions are allowed as a label.

We are also considering allowing the call syntax for structs. Consider, this,

foo: {a: int, b: int, out: a + b } // struct with embedded scalar

call: (foo&{a: 1, b: 2}).out        // 3

On tip, which supports embedded scalars, this can be written as:

foo: {#a: int, #b: int, #a + #b } // struct with embedded scalar

call: foo&{_, #a: 1, #b: 2}         // 3

The ideas is to allow an extension of the builtin syntax for structs as well, allowing

foo: { #a: int, #b: int, #a + #b } // struct with embedded scalar

call: foo(#a: 1, #b: 2) // 3

Where foo(#a: 1, #b: 2) is a shorthand for foo&{#a: 1, #b: 2}, with the additional requirement that #a and #b MUST be defined in foo, even if foo is an open struct.

This would also allow builtins to have Swift-style named arguments, which would allow some things to be written clearly, like:

range(from: 1, to: <5, by: 1)

Conversely, we could consider unnamed arguments for structs to map to the fields with name #0, #1, etc. Note that these are illegal identifiers right now to allow introducing this without causing backwards incompatibility.

These extensions to the call syntax also suggest the following possible syntax for rpc calls:

create: (in: CreateInput): CreateOutput

The problem with this is that there is an ambiguity with the proposed syntax (#165) for computed labels, especially if we want to allow unnamed arguments. A possible solution for this is to repurpose :::

create: (in: CreateInput) :: CreateOutput  // ALTERNATIVE NEW NOTATION FOR RPC DEFINITIONS

This would remove that ambiguity and this notation has precedence in functional languages. A disadvantage of this approach is that it introduces more syntax (although limited). An advantage of doing so, though, is that it also would allow named return arguments without complications:

create: (in: CreateInput) :: (out: CreateOutput)

Another advantage of using :: here is also that it makes it clearer that this is not defining data.

Note that with both proposed syntaxes, the existing field syntax would allow attributes to be defined along the arguments. This means it allows attributing additional meaning to arguments that are irrelevant to CUE, such as:

create: (in: CreateInput @grpc(stream)) :: (out: CreateOutput @grpc(stream)) 

or

create: {in: CreateInput @grpc(stream)}: {out: CreateOutput @grpc(stream)}

So so far, the best candidates are either of the form {}: {} or () :: (), where in both cases the return value can be a normal expression. The {}: {} form will require less additional syntax, but may be too indistinguishable from normal data. The () :: () form requires more additional syntax, but stands out more and also allows writing unnamed signatures like int :: int.

Thoughts welcome.

@mpvl mpvl added the Proposal label Dec 5, 2020
@verdverm
Copy link
Contributor

verdverm commented Dec 5, 2020

I'm in favor of the () :: () syntax for its visual standout properties. This will help with readability, especially when I revisit code after a long time away, and likely easier for new users to understand what is going on. The call syntax as it is today can be confusing at first.

@cueckoo
Copy link

cueckoo commented Jul 3, 2021

This issue has been migrated to cue-lang/cue#69.

For more details about CUE's migration to a new home, please see cue-lang/cue#1078.

@cueckoo cueckoo closed this as completed Jul 3, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
FeatureRequest New feature or request FeedbackWanted Further information is requested NeedsDesign Functionality seems desirable, but not sure how it should look like. Proposal protobuf
Projects
None yet
Development

No branches or pull requests

5 participants