Is there any plan for support double splatting a NamedTuple union?

Check following example:

    def foo(a, b, c = false)
      p [a, b, c]
    end

    args = {
      a: 1,
      b: 2,
    }

    # Both of following code not work

    if rand(5) > 3
      args = args.merge(g: true)
    end

    # ----------------------

    if rand(5) > 3
      args = {
	a: 1, b: 2
      }
    else
      args = {
	a: 1, b: 2, c: true
      }
    end

foo(**args)
Error: double splatting a union (??? | ???) is not yet supported

I consider this is not supported is okay if it is hard to be fix, because we have many other workaround.

e.g.

    args = {
      a: 1, b: 2
    }

    if rand(5) > 3
      foo(**args)
    else
      foo(**args.merge(c: true))
    end

OR

    args1 = {
      a: 1, b: 2
    }

    args2 = {
      a: 1, b: 2, c: true
    }


    if rand(5) > 3
      foo(**args1)
    else
      foo(**args2)
    end

OR

if rand(5) > 3
  args = {
    a: 1, b: 2, c: true
  }
else
  args = {
    a: 1, b: 2, c: false
  }
end


foo(**args)

The really issue is, the error message told us, is not YET supported, this give user a feeling, this feature will be support soon.

So, following is my concern.

  1. If this is fixable, easily, please fix it ASAP.
  2. If this hard to fix, and no planning to fix, please remove the YET in the error message.

I think the error message is clear that this is not supported right now, but it might be in the future. It’s not promising this ever going to happen, nor when.
There are currently no plans, and I don’t think there’s any priority on this one, considering nobody has ever asked for this before.

So my guess is your use cases is either very cornerish, or there could be other, more appropriate alternatives.

If no plans, why we add a YET in the error message?

Maybe I misunderstanding the English usage, but I think YET means will add it, confidently.

Ca i think this is drawing a picture for users that no plan to fulfilling?

I understand “yet” merely indicates that this feature is theoretically possible and might be supported at a later time. But not if there are any concrete plans about it.

I thought it is for this reason that many libraries I have used directly support named_tuple as the args, instead of name keyword(which need splatting)

args = {
  a: 100,
  b: 200
}

so, instead of foo(**args), many library prefer to let users pass like foo(args), because use former is too easy to failed with double splatting a union issue as the example in my post.

If you know well the history of ruby, @matz said, because the initial design of keyword args with flaws, which cause Ruby pass a hash as options became a trend, and bringing a lot of pain when upgrading to Ruby 3 which try to support named keyword completely, those issue still not solved completely until now(ruby 3.3.2)

I don’t know how others use double splatting when passing args, but code like following failed frequently makes me very frustrated.

def foo(a, b, c = false)
  p [a, b, c]
end

args = {
  a: 1,
  b: 2,
}

if rand(5) > 3
  args = args.merge(c: true)
end

foo(**args)
# Error: double splatting a union (NamedTuple(a: Int32, b: Int32) 
# | NamedTuple(a: Int32, b: Int32, g: Bool)) is not yet supported

It’s so common when write business logic, right?

ping @straight-shoota @beta-ziliani

I have never had this issue. Nor have there been any reports from users asking for this apart from yourself.
So I don’t think it’s a common use case.

1 Like

If no see really usage in the real world, please check following usage when use lucky with web development.

For avoid the above issue, i have to introduce args1 and args2 ugly.


similar cases happen on another one.

i have to merge directly in input parameter, to avoid above issue.


And following code even not work

I consider there are too few people using Crystal, otherwise someone would have raise this issue long long ago.

@straight-shoota

What kind of report would you have expected for something the compiler tells us is a known issue?

Asking because I’ve run into this quite a few times. Mostly, I work around it exactly as @zw963 mentioned — coincidentally, some of my most notable use cases have been for HTML components to support HTMX, just not with Lucky. But I would never have reported it because the compiler tells us you already know about this limitation.

2 Likes

Having also never ran into this before, I think this is a fairly solvable problem without having to change the compiler. Is there a reason Lucky’s implementation couldn’t be setup to have a NamedTuple (or Hash) overload such that you could pass your args directly without having to double splat at all? Even if you have to do something like extra_args: args that would already be an improvement.

It’s true, i can used it without double splat.

But this is another question anyway, why we limit the author of library must define another method overload to support passing NamedTuple Or Hash only because a long live bug in compiler still not fix YET?


This introduces unnecessary complexity for library authors obviously.

Does the core team not encourage us to use double splashing?

Isn’t using named keyword directly more clearer and more error friendly?

Why we introduce double splatting feature in Crystal?

The compiler knows about this bug, but as @straight-shoota said there are no plans to fix it. do you think anyone will still use double splatting after a few years? e.g. in version 2.0 ?

This is a bit of an exaggeration. It makes more sense to me to support both overloads as they serve different purposes. Like the **args version works great when I just want to add some extra args to the method call directly. E.g. input foo: "bar". But when you’re getting into building out some collection of args it makes more sense to be able to pass that collection directly.

It feels to me, double splatting in this context is more so a workaround for a missing API feature of the underlying lib than it is a use case to be able to double splat unions. Even then I would say it’s more of an unimplemented feature than a bug. I’d maybe bring this up in the Lucky repo, would be a pretty trivial change I’d imagine.

1 Like

Disgree, none of above example tends to pass a collection into. the purpose of introduce a NamedTuple is just for the need of business logic, this is not something cornerish, it’s so common when use other language, there is no another better way to replace this.

use Named keyword with correct double splatting support is always preferred for use case like above input foo: "bar", why you want pass a collection as the only args for input tag attributes instead of (clearly) named keyword? that’s because as a user, I have to, because the not yet supported feature.

To be clear, the only thing I’m suggesting you do is like input(args) instead of input(**args). Your args named tuple is a collection type and is merely a container for holding key/value tag pairs. Looking closer at the source code, they already do seem to support this:

As there is an overload that accepts only options, plus extra one-off named args. So if you stop trying to double splat your args, and just pass it as the collection type it is, it should just work as long as you can be assured the value of each item is a String.

Sorry, probably I am not describe clearly, I told you before i know lucky can be used like this. :joy:

Let me repeat my idea again, lucky support it and Crystal support double splatting are totally two things.

To put it another way, if lucky not support use like this, how to solve my above issue when compiler complain double splatting a union namedTuple is not yet supported ?

1 Like

It would be easier to bring support for this as its the better way to handle it. Double splatting gives you 0 benefit over just passing the collection type as is.

As I mentioned earlier

Why do you think I haven’t tried pass it as collection since try double splashing failed?

I edited my response once I realized it was a hypothetical question if Lucky didn’t support it.

EDIT: Tho I think to answer your question, as long as you ensure the value of each item in your named tuple is of the same type, String in this case, double splatting it should be fine. This is a reasonable requirement anyway as everything will be a string anyway.

The fact is, When I encountered this issue once or twice, I used the workaround i post in my post (jgaskins does same too), then I tried it, thanks lucky, it give me the option to pass a collection directly, that not have to force me to use that awkward workaround.

You means this?

def foo(a, b, c = "")
  p [a, b, c]
end

args = {
  "a": "1",
  "b": "2",
}

args = args.merge("c": "3") if rand(5) > 3

foo(**args)

# Error: double splatting a union (NamedTuple(a: String, b: String) | 
# NamedTuple(a: String, b: String, c: String)) is not yet supported
1 Like

Ah, so the problem is when double splatting unions of diff named tuple types themselves. That makes more sense.