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

Allow to initialise Named Tuples with same notation as Objects #321

Closed
al6x opened this issue Jan 22, 2021 · 11 comments
Closed

Allow to initialise Named Tuples with same notation as Objects #321

al6x opened this issue Jan 22, 2021 · 11 comments

Comments

@al6x
Copy link

al6x commented Jan 22, 2021

It would be convenient to support both tuple and object initialisation for Named Tuples.

type Person = tuple[name: string]

echo Person(name: "John")
  1. Frequently code that uses tuple doesn't need to know how it's implemented - if it's an object or tuple. Say I decided to change Person from object to tuple - and now I have to update tons of old code Person(name: name).

  2. It just feels right and uniform.

@greenfork
Copy link

Could you share the use case where you prefer to use tuples instead of objects and therefore use this initialization syntax?

Currently it's also possible to initialize tuple like this: (name: "John").Person

In my experience tuples are clunkier to operate when used extensively:

  • No default value, e.g. can't write (x: 0.0).Point and omit y
  • No type conversion, e.g. can't write (x: 0, y: 0).Point if Point requires float for x and y
  • Can't write parameters out of order

I use objects and when I need a short notation for e.g. Point I write a converter that transforms tuple to object example

@al6x
Copy link
Author

al6x commented Jan 23, 2021

Could you share the use case where you prefer to use tuples instead of objects and therefore use this initialization syntax?

Like this one:

type Cash* = tuple[amount: float, currency: string]

let cash = Cash(amount: 1.0, currency: "USD")

I like ability to use shortcut initialisation like (1.0, "USD") but it feels strange and wrong that I can't use the normal initialisation Cash(amount: 1.0, currency: "USD").

P.S.

Some more cases

Price* = tuple[price: float, currency: string, time: Time]

type Point*  = (Time, float)

# With some conversion magic
let prices: seq[Point] = @[
  ((1990, 1, 1), 1.0),
  ((2000, 1, 1), 1.0), ((2000, 1, 2), 3.0),
  ((2000, 2, 1), 1.0)
]

Currently it's also possible to initialize tuple like this: (name: "John").Person

But why? Why add more and more ways to do the same thing? Wouldn't it be better and simpler to write Person(name: "John")? Compiler knows that Person is a tuple and has no problem figuring out how to resolve it.

@greenfork
Copy link

I can't answer "why" exactly but I have several thoughts about your use case.

I would use objects instead of tuples every time. Tuples have 2 benefits:

  1. Shorter notation (1.0, "USD") instead of Cash(amount: 1.0, currency: "USD")
  2. Simpler equivalence rules: any tuple with (amount: 1.0, currency: "USD") fields and values is equal to any other tuple, no need to construct a specific object for comparison

1st one is remedied by the use of converters, you can achieve any level of convenience in code. 2nd one is debatable at best especially considering that you need to do financial stuff. So to me it looks like "ugliness on purpose" that you shouldn't really use tuples for these use cases.

But the general idea about consistency with object initialization looks good. I don't see a use case where I explicitly need to know that this is object, not tuple construction.

@disruptek
Copy link

The real problem with using tuples instead of objects is that they expose field-order-based iteration which some knuckehead is going to end up depending upon. As soon as you want to upgrade to an object, you break that code.

I really don't understand why people want to typedef tuples; to me it's code smell every time.

@saem
Copy link

saem commented Jan 23, 2021

The real problem with using tuples instead of objects is that they expose field-order-based iteration which some knuckehead is going to end up depending upon. As soon as you want to upgrade to an object, you break that code.

Isn't the real answer here to instead to create field iteration differences rather than initialization differences? The the current approach is creating a referred pain at some initialization site as opposed to the site of any traversal (or parameter) that may be traversal order dependent.

@liquidev
Copy link

liquidev commented Jan 23, 2021

type Cash* = object
  amount: float
  currency: string

proc usd(amount: float): Cash = Cash(amount: amount, currency: "USD")
let cash = 1.usd

This is a style I've adopted into my code, it works pretty well, and I'd argue it looks much better than your (1.0, "USD"). If you need a lot of currencies, you can always create a sugar template for defining them:

import std/strutils

template currency(name: untyped) =
  const currencyName = astToStr(name).toUpperAscii
  proc `name`(amount: float): Cash {.inject.} =
    Cash(amount: amount, currency: currencyName)

currency usd
currency eur

@disruptek
Copy link

I agree that I'd rather have object/tuple construction that wraps identdefs and I'd rather have consistent syntax, but to me, it's more important to improve the documentation on tuples to prevent people from thinking that they are somehow buying themselves anything more than future grief.

Have fun adding a field to that tuple; you still have to change all your initializations...

@al6x
Copy link
Author

al6x commented Jan 23, 2021

There are different ways tuples could be used or even avoided.

But why have different syntax for it? Wouldn't it be better to keep syntax same and uniform?

P.S.

The real problem with using tuples instead of objects is that they expose field-order-based iteration

I think it's ok and convenient to use positional unpacking for tuples of size of 2-3, the problem you mentioned usually arises when the tuple size is >3.

And, maybe some day Nim would support name based unpacking for tuples and objects. So people would use key unpacking let { b, d } = obj instead of positional unpacking let (_, b, d) = obj.

@liquidev
Copy link

And, maybe some day Nim would support name based unpacking for tuples and objects. So people would use key unpacking let { b, d } = obj instead of positional unpacking let (_, b, d) = obj.

In my opinion a better syntax that would actually fit the language would be let (field1: a, field2: b) = obj.

@disruptek
Copy link

The real problem with using tuples instead of objects is that they expose field-order-based iteration

I think it's ok and convenient to use positional unpacking for tuples of size of 2-3, the problem you mentioned usually arises when the tuple size is >3.

Unpacking is yet another problem, but no less annoying to deal with regardless of how large your tuples are. It has the feature that you must hope that the names and types of the fields you're unpacking will not change, otherwise, you can't trust that your code will remain correct. Yet another reason not to use tuples except in very limited circumstances.

This is a partial solution: https://github.com/disruptek/foreach

It should probably be modified to simply use the identdef syntax as @liquidev proposes; maybe it should desugar assignments as well.

@Araq
Copy link
Member

Araq commented Oct 14, 2022

Rejected. Nobody is working on it and it's hardly important. Just start with object when modelling your domain and move less often from tuple to object.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants