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

Recursive alias, cast #4885

Closed
Nephos opened this issue Aug 25, 2017 · 9 comments
Closed

Recursive alias, cast #4885

Nephos opened this issue Aug 25, 2017 · 9 comments

Comments

@Nephos
Copy link
Contributor

Nephos commented Aug 25, 2017

Hello,

cf: #3803 , #2665

Is there a way to cast something using a recursive type ?

alias Type = Int32 | Array(Type)
[1].as(Type)

Error:

can't cast Array(Int32) to Type

Note: my aim is no to be able to cast it, it is to have a defined return type (def XXX : Type)

@RX14
Copy link
Contributor

RX14 commented Aug 25, 2017

[1].map(&.as(Type)).

@lbguilherme
Copy link
Contributor

Actually you should do [1].map(&.as(Type)) to first go from Array(Int32) to Array(Type). Then do [1].map(&.as(Type)).as(Type) to get a Type on the end. A little bit verbose, but makes sense.

@Nephos
Copy link
Contributor Author

Nephos commented Aug 25, 2017

Thank you, I see what you mean, but if the problem was to cast a simple Array, I would not have open an issue. The [1] is a simple example, but it should work with any valid Type.
Let me give you a complete example (I should have done that in the first place):

alias Type = Int32 | Array(Type)

def foo(a : Type) : Type
  a.as(Type)
end

foo 1
foo [1]
foo [1, [2]]
can't cast Array(Int32) to Type

Of course, you can remove the cast, but the problem becomes type must be Type, not Array(Int32)

The context is the following:

"I want to choose some functions to call at runtime. These functions can return several types of objects (its very generic)."

def foo
  "foo"
end

def bar
  0xba5
end

runtime = Hash(String, Proc(String | Int32)).new
runtime["foo"] = -> foo
runtime["bar"] = -> bar

But foo returns a String and bar a Int32, and they don't match (String | Int32).

So I could just cast the result of my functions.

alias Type = Int32 | String | Array(Int32)

def foo
  "foo".as(Type)
end

def bar
  0xba5.as(Type)
end

runtime = Hash(String, Proc(Type)).new
runtime["foo"] = -> foo
runtime["bar"] = -> bar

It also works with Generics but if there are recursive generics...
When "foo" and "bar" will become real functions, I do no want to finish my it with return X.map(.....) which might actually become VERY verbose when I'll come with Array(Hash(String, Hash(String, Float64)))

alias Type = Int32 | Array(Type)

def foo
  [0xf00].as(Type)
end

def bar
  0xba5.as(Type)
end

runtime = Hash(String, Proc(Type)).new
runtime["foo"] = -> foo
runtime["bar"] = -> bar
can't cast Array(Int32) to Type

Maybe I could workarround and declare
runtime = { "foo" => -> foo, ... } in one time so I have a big fat union, but its more tricky (requires me to do more macro for my project, because I have to keep a list of ALL the type I want in my union) and ugly. It requires to have a compile type array (size defined during the compilation itself) that keep a track of all the functions we want to add to the runtime list, so I can define the union of all of them using it.

macro runtime_init()
  RUNTIME = [] of String
end

macro runtime_def(name, &block)
  def _runtime_{{name.id}}
    {{yield}}
  end
  {{RUNTIME << name}}
end

macro runtime_end()
  CALLS = {
    {% for name in RUNTIME %}
      {{name}} => -> _runtime_{{name.id}},
    {% end %}
  }
end

macro call(name)
  CALLS[{{name}}].call()
end

runtime_init

runtime_def "foo" do
  [0xf00]
end

runtime_def "bar" do
  0xba5
end

runtime_end

pp call STDIN.gets.to_s.chomp

tell me there is another way

@asterite
Copy link
Member

[1] of Type

@Nephos
Copy link
Contributor Author

Nephos commented Aug 26, 2017

That's nice!
But the return of the function is not always as simple.

def foo(param1, param2) : Type
  param1.operate(param2) # well... can't define the type of "operate".
end

@lbguilherme
Copy link
Contributor

lbguilherme commented Aug 26, 2017

I usually do this with a helper function:

alias Type = String | Int32 | Array(Type) | Hash(String, Type)

def cast_to_type(x :  Array)
  return x.map { |e| cast_to_type(e).as(Type) }.as(Type)
end

def cast_to_type(x : Hash)
  h = Hash(String, Type).new
  x.each do |(k, v)|
    h[k] = cast_to_type(v).as(Type)
  end
  h.as(Type)
end

def cast_to_type(x)
  x.as(Type)
end

p cast_to_type(10)
p cast_to_type([10])
p cast_to_type({"hm" => [10, ["", 2, {"a" => "b"}]]})

@Nephos
Copy link
Contributor Author

Nephos commented Aug 26, 2017

That would work yes! But isn't it expensive to reallocate every Array/Hash just to do a cast ?

@lbguilherme
Copy link
Contributor

You have to. Because the memory layout of a Array(Int32) is different than Array(Type). The first always store elements of 4 bytes, while the later uses more space. Whatever you do, the whole array must be reallocated to the new format. This is why this process is not automatic, but explicit.

@Nephos
Copy link
Contributor Author

Nephos commented Aug 26, 2017

Yes, I should have learned my lessons :) Sometimes I forget that we care about memory in this langage.
Thanks for your time, you teach me something!

@Nephos Nephos closed this as completed Aug 26, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants