-
Notifications
You must be signed in to change notification settings - Fork 23
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
Nested tuple unpacking #194
Comments
I agree on 1 . |
I agree with 1 as well. I'm on the fence about 3 and 5. With regards to 2, 4, and 6, I just don't feel like there's enough of a use-case or frequency to justify the added syntax. |
Only 1 is acceptable, the rest is overkill. |
I can understand them being too much for the core language, though I don't know if it's worth creating some macro like |
Don't want this whole issue to go to waste just for nested tuples, so I wrote a better part on custom unpacking macros, hopefully it makes sense. |
instead of var (a, b) as x = (1, 2) we can build upon nim-lang/Nim#13508 (lvalue references) as follows: var x = (1, 2)
var (a,b) {.byaddr.} = x which should be a (conceptually) straightforward extension of nim-lang/Nim#13508 |
Made a library that does most of the unpacking syntax enhancements and has custom unpacking: https://github.com/hlaaftana/definesugar. I was thinking, would people like an RFC that proposes a variable pragma-macro syntax as said in this comment? I would reformat this RFC to only nested tuples, and the new RFC would propose like: import macros
macro foo(x: untyped): untyped =
expectKind x, {nnkVarSection, nnkLetSection}
expectLen x, 1
result = newVarStmt(ident(x[0][0].strVal & "Foo"), x[0][2])
var
x {.foo.} = 4
y = 5
echo xFoo # 4 Edit: Forgot about this issue, the RFC I was proposing in this comment already exists just not on this repo. nim-lang/Nim#6696. Also it's pretty much implemented with nim-lang/Nim#13508 |
General rant, couldn't think of a better place to put it: For the love of <whatever you believe in>, let's stop it with the piecemeal, special case, wouldn't-it-be-cool-if-we-could-do-that-too features with bells-and-whistles and special-sauce syntax. The concepts we are talking about have names: Structural matching, Destructuring and (in the case of nim-lang/Nim#245 also) Content matching and they are separate but connected. There's two options IMHO: a) go all in and implement them consistently in a general and obvious KISS way without nice-to-have bloat. Making users guess which additional special case of destructuring might be supported this month by lang core/stdlib is not helpful and frankly makes Nim look bad consistency-wise compared to other languages. If you like a), like me, here's some opinions:
type
A = tuple[s: string, n: int]
B = object
s: string
a: A
let
(x, _) = a1 # defines x like the existing tuple unpacking
(s: y, n: _) = a1 # every kind of constructor syntax works
B(s: z, a: (_, j)) = b1 # defines z and j, objects and structural depth work too, just like in constructor calls
(s: w, a: (_, k)) = b1 # we don't even need the object type name because of type inference
# insert other language core constructor kinds here... Similar to definesugar by @hlaaftana, but part of the language core without a need for special syntax. The compiler already does structural matching by figuring out the type and it already knows how to parse constructor calls. Everybody who knows constructor syntax can easily learn destructuring patterns, with no unnecessary exceptions.
|
First of all
I think it is an inferior solution because we already have six different libraries that do whatever is described in RFC and two more that partially implement features from the library
From my perspective it is quite clear that if ecosystem has ~9 different libraries implementing partially overlapping features (and in case of In addition to consolidating syntax from 6+ different libraries (how many should we get until it becomes a real issue of choosing pattern matching library?) this RFC also provides support for rust-like Increasing number of programming languages implement pattern matching support, and there is a big difference between something being implemented as third-party module and being shipped with stdlib. Latter one is considered part of the language while former is just "well, yeah, now I need to install package manager, find out how to make it work, install the package itself etc."
I'm not saying it is the best possible syntax ever for pattern matching, but I tried to make it more pragmatic rather than having minimal set of features. In my opinion pattern matching should not only work with sum types since real-world use cases extremely often consist of things like
And then, having support for these features naturally drags a couple of others
Suggestions are welcome. List things that you don't like, provide possible suggestions for improving syntax (or why some feature is not necessary).
Due to complexity of design space it was necessary to consider many possible implementations and use cases. If GitHub discussion thread seems overly erratic (it certainly is) you should look for last version of syntax draft and/or currently implemented tests. In addition - documentation will be provided, so users who are willing to read manuals won't be "guessing". Yes, this is a new library and users are expected to read documentation for it. Sequence keywords are chosen to be close to
This addition does not require modification in language core except for making
I think pattern matching should look like objects access. If you have
I certainly have strong opinion that writing let val = some("hello")
if Some(@x) ?= val:
echo x, "ok"
# and
case head[0]:
of Asgn([@lhs is Ident(), @rhs]): is better than let val = some("hello")
if val.isSome():
let x = val.get()
echo x, "ok"
# and
case head[0].kind:
of nnkAsgn:
if head[0][0].kind == nnkIdent:
let
lhs = head[0][0][1]
rhs = head[0][0][1]
else:
# deal with other case like `let (a, b)` but now this i in the
# same case branch with +1 level of conditional nesting. "But last" element - comparing implementations in different language (nim with new pattern matching library) butLast :: [a] -> a
butLast [] = error "Cannot take one but last from empty list!"
butLast [x] = error "Cannot take one but last from list with only one element!"
butLast [x:y] = x
butLast (x:xs) = myButLast xs func butLastGen[T](a: seq[T]): T =
case a:
of []: raiseAssert("List is empty")
of [_]: raiseAssert("Only one element")
of [@pre, _]: pre
of [_, all @tail]: butLastGen(tail)
else: raiseAssert("Not possible")
assertEq butLastGen(@["1", "2"]), "1" let rec second_to_last lst =
match lst with
| [] -> failwith "Empty list"
| x::[] -> failwith "Singleton list"
| fst::snd::[] -> snd
| hd::tl -> second_to_last tl |
Closing because too broad, doesn't even address |
This issue now proposes nested tuple unpacking in the core language, I have implemented some of the features I previously proposed in a small library: https://github.com/hlaaftana/definesugar.
Relevant issues on the Nim repo: nim-lang/Nim#13439, nim-lang/Nim#2436
The current tuple unpacking syntax is limited to:
This issue proposes:
Main use is for sugar/optimization in for loops:
Don't know if it's useful in any other context.
The old version of the RFC (long, only keeping as reference):
2. Multiple definitions of the same term
I thought of this syntax:
but this looks kind of ugly and in vars, this would copy instead of alias as the word
as
implies.The solution is just the way you do nested tuples now:
3. Unpacking in routine arguments
I realized after the fact that the macro solution for this is more expressive than the syntax solution.
vs:
4. Named tuple unpacking
By typing out the fields, you can make unpacking named tuples field-order-agnostic.
Note that this depends on any
var (a: int, b: int) = (1, 2)
syntax not existing, as it doesn't now.This could also be extended to objects/ref objects/anything with fields, but it would have to be duck typed. For objects, it would mean the left hand side of an assignment could have the side effect of throwing a FieldError, because of case object fields. This would be solved if the ProveField warning was turned into an error in the specific case of a case object field being unpacked, but still would have no solution for a general field access unpacking.
5. Splat/spread unpacking
I wrote this without postfix * in mind (in a macro it would be replaced with some other prefix) and I don't think it makes that much sense for tuples. Keeping it here for reference
Nim has no concept of a splat/spread operator as in other languages, so this would be a completely new idea to Nim. My suggestion in this case is a prefix
*
as in Python. The key use of this is only getting the first/last few elements of a tuple, using the _ special symbol, although most tuples are too small for this to matter.There can only be 1 splat/spread in a single unpack. Splatting could also work for named tuples, but the compiler would have to create a new named tuple type, so it would not work for objects.
6. Array/generic subscript unpacking
This was originally simpler but it doesn't fit the core language so I mixed it with other wild ideas and am keeping it here for reference.
You can either extend the
let (,)
syntax to work for anything you can index from (not the best idea), or introduce a newlet [,]
syntax for arrays, which is excessive because it implies you can do[a, b] = [1, 2]
as a normal assignment.Having a distinct syntax for arrays than for tuples would mean you could index by enums just like you can do in normal arrays.
Combined with splatting though, this syntax can get complex.
This could be extended for anything that you can index like seqs or strings, but it would have the same side effect problem as 4.
So you could have an alternative syntax that implies side effects are possible like so:
This is also weird syntax because
{}
constructs arrays of 2-tuples and sets and works as its own subscript operator, it has nothing to do with[]
subscripting.Custom unpacking macros:
This would be an experimental feature akin to case statement macros, term rewriting macros, for statement macros etc. that requires a parser change. I don't know how the parser works exactly, but the way I see it it would go like this:
let a = b
and var and const can have any untyped expression fora
thata = b
would allow, if it's not one of the predefined assignment syntaxes from Nim. For example, if Nim is parsing(a, b, (c, d))
, and reaches(a, b,
then gets the token(
, it rerecords the tokens starting from the first(
as an expression, then it's mapped to anunpack(lhs: untyped, rhs: T)
macro. This returns an nnkAsgn that gets remapped inside a let/var/const statement or stays as nnkAsgn if it's from an assignment. The fact thatlet (a, b)
andlet a
are parsed normally and not as expressions means those would not be handled by unpack, meaning you can return them as the concrete value of unpack.The only difference here between this and case statement macros is that case statement macros do not recheck for another case statement macro, but the unpack macro would. Ie:
prints "tuple yes". The problem with recursing is that it may go on infinitely, which is solved by just doing
echo result.repr
, maybe a explain macro like what concepts have or a hard limit like term rewriting macros have.Here is a JSON concept:
The text was updated successfully, but these errors were encountered: