Crystal: Splatting with Tuple and NamedTuple argument restriction

Created on 30 Aug 2018  路  4Comments  路  Source: crystal-lang/crystal

https://carc.in/#/r/4uok

alias MyTuple = NamedTuple(
  foo: String,
)

def set(**values : MyTuple)
  pp! values
end

set(foo: "bar")
Error in line 9: no overload matches 'set', foo: String
Overloads are:
 - set(**values : MyTuple)

https://carc.in/#/r/4uov

alias MyTuple = Tuple(String)

def foo(*values : MyTuple)
  pp! values
end

foo("foo")
Error in line 7: no overload matches 'foo' with type String
Overloads are:
 - foo(*values : MyTuple)

It would allow to avoid big and ugly macros involvement.

My usecase is ORM Query builder with #where(id: 42, name: "John") like methods, which are wanted to be defined as following:

class User
  include Model

  # Generated in macro for each model
  alias Types = NamedTuple(id: Int32, name: String)
end

class Query(Model)
  def where(**values : Model::Types)
  end
end

Most helpful comment

tl;dr

Use **MyTuple in the restriction

Explanation

This is explained a bit in https://crystal-lang.org/docs/syntax_and_semantics/type_restrictions.html although a section is missing for double splats.

Basically, when you have:

def foo(**args : SomeType)
end

it means all types in args must be SomeType. For example:

def foo(**args : Int32)
end

foo x: 1, y: 2      # OK, all types are Int32
foo x: 1, y: "oops" # Error, one of the types is not Int32

If you want to restrict the whole named arguments value to some type, use ** in front of the type restriction:

def foo(**args : **{x: Int32, y: String})
end

foo x: 1, y: "foo" # OK
foo x: 1, y: 1 # Error, y must be an Int32

Of course in the last snippet you can use an alias:

alias MyType = {x: Int32, y: String}

def foo(**args : **MyType)
end

foo x: 1, y: "foo" # OK

Please close this issue if the above fixes your problem.

All 4 comments

tl;dr

Use **MyTuple in the restriction

Explanation

This is explained a bit in https://crystal-lang.org/docs/syntax_and_semantics/type_restrictions.html although a section is missing for double splats.

Basically, when you have:

def foo(**args : SomeType)
end

it means all types in args must be SomeType. For example:

def foo(**args : Int32)
end

foo x: 1, y: 2      # OK, all types are Int32
foo x: 1, y: "oops" # Error, one of the types is not Int32

If you want to restrict the whole named arguments value to some type, use ** in front of the type restriction:

def foo(**args : **{x: Int32, y: String})
end

foo x: 1, y: "foo" # OK
foo x: 1, y: 1 # Error, y must be an Int32

Of course in the last snippet you can use an alias:

alias MyType = {x: Int32, y: String}

def foo(**args : **MyType)
end

foo x: 1, y: "foo" # OK

Please close this issue if the above fixes your problem.

By the way, I think the above won't be helpful to you because you can't omit elements from the named tuple. But I wouldn't say this is a bug. You might need to code your code in a different way.

I'll close this because the original use case is solved.

@asterite is it possible to acquire values in compile time in def where(**values)?

Update: I mean, it's a NamedTuple and has to be known in the compile time:

def where(**values)
  pp! values.class
end

# => NamedTuple(foo: String)
def where(**values : **T) forall T
  # Now you can use T, and inspect it at compile time
  {% puts T.keys %}
end
Was this page helpful?
0 / 5 - 0 ratings