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

Value patterns as function parameters #578

Closed
josh11b opened this issue Jun 15, 2021 · 9 comments
Closed

Value patterns as function parameters #578

josh11b opened this issue Jun 15, 2021 · 9 comments
Labels
leads question A question for the leads team

Comments

@josh11b
Copy link
Contributor

josh11b commented Jun 15, 2021

Right now there is a gotcha in our current strategy for using pattern matching for function parameters:

fn F(Int);

may only be called by F(Int), not F(1) or F(2). This is a significant and confusing divergence from C++. There was some discussion of this in the #syntax discord channel.

I'd like to propose to change things in two ways:

  1. Either the above declaration of F is illegal or it means that F takes an argument of type Int, like it would in C++.
  2. We have some other syntax for saying "match this value."

We might forbid value patterns in function declarations entirely, but there are some rare situations where they are useful. For example, it is useful for using a later type parameter in an earlier parameter's type, as in:

fn DoACast[U:$ Type, T:$ ConvertibleTo(U)](x: T, U) -> U {
  return x as U;
}
var i: Int32 = 0;
var f: Float64 = DoACast(i, Float64);

I propose for 2 that we mark value patterns with an == prefix, as in:

fn DoACast[U:$ Type, T:$ ConvertibleTo(U)](x: T, ==U) -> U { ... }

The question of whether this would also be used in match statements as in:

match (i) {
  case ==0 => { ... }
  case ==1 => { ... }
  default => { ... }
}

is the subject of #528 .

@zygoloid
Copy link
Contributor

I think part of this question is either inextricably linked to #528, or at least depends on #528, in that if #528 decides we want the same pattern matching syntax everywhere, then part (2) of the proposed change would apply to all pattern-matching contexts and cannot be decided for function parameters in isolation.

In a comment on 528 I suggested that we should not allow runtime-refutable patterns in parameters, which might also address this issue, if Int as a pattern is treated as a refutable match against any type. (That might not be the case, though: if we give Int a singleton type, we might end up treating the pattern Int as a match against that (unique) value of that singleton type, and we might consider that to be an irrefutable pattern.)

If our decision for part (1) is to make the above declaration of F illegal, do we still need to use additional syntax to exactly match a value as suggested in part (2)? (We can distinguish between F and DoACast on the basis that in F the runtime value of the argument is constrained whereas in DoACast it is not.)

@josh11b
Copy link
Contributor Author

josh11b commented Sep 1, 2021

Another possibility is we use singleton types to match a single value. We are talking about all types and all values that can be written as a literal having singleton types anyway. We could define a singleton_type operator that can give you a type that only matches a single value.

@josh11b
Copy link
Contributor Author

josh11b commented Nov 30, 2021

Another option, from @geoffromer

fn DoACast[U:$ Type, T:$ ConvertibleTo(U)](x: T, V:! Type where .Self == U) -> U {
  return x as U;
}

@github-actions

This comment was marked as outdated.

@github-actions github-actions bot added the inactive Issues and PRs which have been inactive for at least 90 days. label Mar 1, 2022
@jonmeow jonmeow added the leads question A question for the leads team label Aug 10, 2022
@github-actions github-actions bot removed the inactive Issues and PRs which have been inactive for at least 90 days. label Aug 11, 2022
@github-actions

This comment was marked as outdated.

@github-actions github-actions bot added the inactive Issues and PRs which have been inactive for at least 90 days. label Nov 9, 2022
@josh11b
Copy link
Contributor Author

josh11b commented Nov 9, 2022

This is related to the question in #528 . In particular, both would be addressed by an introducer to match a value, as suggested in this thread on #syntax starting 2022-09-16.

@github-actions github-actions bot removed the inactive Issues and PRs which have been inactive for at least 90 days. label Nov 10, 2022
@zygoloid
Copy link
Contributor

The status quo, as of #2188, is that a declaration such as:

fn F(i32);

is ill-formed, because i32 is a refutable pattern. I believe that addresses the original concern here. We should keep this issue in mind when adding support for function overloading, which may reintroduce the problem depending on how we approach it, but for now it seems like the issue has been addressed.

@zygoloid
Copy link
Contributor

zygoloid commented Sep 7, 2023

For this use case, we can use something like this:

fn DoACast[T:! type](x: T, U:! type where T is ConvertibleTo(.Self)) -> U {
  return x as U;
}

@josh11b
Copy link
Contributor Author

josh11b commented Sep 7, 2023

There is another example from https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/generics/details.md#parameterized-interfaces

// Doesn't work, no support for `singleton_type_of`
fn PeekAtTopOfStackParameterized
    [T:! type, StackType:! StackParameterized(T)]
    (s: StackType*, _:! singleton_type_of(T)) -> T { ... }

This corresponds to the C++ code:

// Called `PeekAtTopOfStackParameterized<MyType>(&MyStack)`
template<typename T, StackParameterized<T> StackType>
auto PeekAtTopOfStackParameterized(StackType* s) -> T { ... }

We discussed a couple possible workarounds:

fn PeekAtTopOfStackParameterized[StackType:! type]
    (s: StackType*, T:! type where StackType is StackParameterized(.Self)) -> T {
  let ActualStackType:! StackParameterized(T) = StackType;
  let actual_s: ActualStackType* = s;
  ...
}
fn PeekAtTopOfStackParameterizedImpl
    (T:! type, StackType:! StackParameterized(T), s: StackType*) -> T {
  ...
}
fn PeekAtTopOfStackParameterized[StackType:! type]
    (s: StackType*, T:! type where StackType is StackParameterized(T)) -> T {
  return PeekAtTopOfStackParameterizedImpl(T, StackType, s);
}

We rejected this approach in https://github.com/carbon-language/carbon-lang/blob/trunk/proposals/p2188.md#type-pattern-matching :

fn PeekAtTopOfStackParameterized
  (T:! type, s: (StackType:! StackParameterized(T))*) -> T {  ...}

Two ways we could in the future support this use case would be:

fn PeekAtTopOfStackParameterized
  (T:! type, s: (some StackParameterized(T))*) -> T {  ...}
// Called `PeekAtTopOfStackParameterized(MyType)(&MyStack)`:
fn PeekAtTopOfStackParameterized(T:! type) =>
    lambda [StackType:! StackParameterized(T)](s: StackType*) -> T {
  ...
}

This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
leads question A question for the leads team
Projects
None yet
Development

No branches or pull requests

3 participants