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

Another signal 11 for recursive generic type declaration #9936

Open
BrucePerens opened this issue Nov 19, 2020 · 12 comments
Open

Another signal 11 for recursive generic type declaration #9936

BrucePerens opened this issue Nov 19, 2020 · 12 comments
Labels
kind:bug A bug in the code. Does not apply to documentation, specs, etc. topic:compiler:semantic

Comments

@BrucePerens
Copy link

BrucePerens commented Nov 19, 2020

This gives a signal 11 when I attempt to compile it:

alias AllowedValue = Bool|Allowed
alias Allowed = Hash(Symbol, Bool|Hash(Symbol, AllowedValue))
h : Allowed
a = { :symbol => true } of Symbol => Bool|Allowed
h = { :symbol => a }    of Symbol => Bool|Allowed
@asterite
Copy link
Member

I think this is essentially #3804

@straight-shoota
Copy link
Member

Sadly, we haven't managed to remove recursive aliases for three years now (#5155) 😢

I'd recommend everyone to not use them. They're unnecessary and the implementation broken in many ways.

@BrucePerens
Copy link
Author

BrucePerens commented Nov 20, 2020

@straight-shoota if you don't want people to use recursive aliases, stdlib should contain an alternative that they can be pointed to. This little demo assumes that Hash is a common case, there would also be Array and I don't know what others. As a relative Crystal neophyte this is the best I could do this evening, surely you can do better:

# Implementation of a pseudo-generic that contains itself without use of
# recursively-defined aliases, which are problematical and/or broken
# in the compiler.
macro recursive_hash(name, keytype, valuetype)
  class {{name.id}}
    struct Type
      property value : {{valuetype.id}}
      def initialize(@value)
      end
    end
    
    @h = Hash({{keytype.id}}, Type).new

    def []=(key, value)
      @h[key] = Type.new(value)
    end
  end
end

recursive_hash(MyHash, Symbol, String|MyHash|Array(MyHash))

h = MyHash.new
h[:itself] = h
h[:array] = [MyHash.new]
p h.inspect

@asterite
Copy link
Member

@BrucePerens What's your specific use case here?

@BrucePerens
Copy link
Author

BrucePerens commented Nov 20, 2020

@asterite The use case is an interpretive language. It has arrays which contain its programs and all of their data types, potentially including more arrays and hashes, and hashes that contain its data (and optionally more hashes and arrays). I coded it using aliases for self-containing generics: Hash and Array. The language is potentially multi-threaded and I will probably eventually add Mutex to these containers, as I am dubious about container write being atomic with regard to a simultaneous read.

There were some bumps on the way, including the compiler getting a signal 11 without emitting any debugging information that would help me to understand why (a signal catcher that emits an internal stack dump might be helpful, I've not yet attempted running lldb on the compiler and the multiple threads will be a complication if I start).

@asterite
Copy link
Member

I see.

Yes, in that case making your own data type (something like Ruby's VALUE in C) is probably better than using recursive alias, specially because of how buggy its implementation is.

@BrucePerens
Copy link
Author

Yes. It is annoying that the cost of this is that I need to implement a pass-through method for every method of Hash and Array. At least I need only do it once, and I'll package the macros for others.

While recursively-typed aliases might not be the solution, it would be nice for there to be a lot of repair on generics, and for there to be broader rules on the use of the generic type arguments - which are not currently in scope when used for much other than the right side of variable : or as arguments to another generic. I would also like for generics to take a type keyword like itself so that I could instantiate them this way: Hash(Symbol, String|itself|Array(itself)).

Thanks

Bruce

@straight-shoota
Copy link
Member

straight-shoota commented Nov 20, 2020

@BrucePerens You might take a look at https://github.com/straight-shoota/crinja/blob/7511403d058d6dc505ced2344a78491bd0fe9cc3/src/runtime/value.cr#L95 Seems like a similar role to what you're trying to do.

@BrucePerens
Copy link
Author

@straight-shoota OK, I will do that. I see you use tap and I have to get my head around that - I have not had much clue what it's for up until now.

@Sija
Copy link
Contributor

Sija commented Nov 20, 2020

@BrucePerens Also, you can check any_hash shard.

@BrucePerens
Copy link
Author

@Sija Thank you, there are lots of interesting lessons I need to learn in there.

@BrucePerens
Copy link
Author

Initial code at https://github.com/BrucePerens/recursive_generic . Not ready for you to use.

@straight-shoota straight-shoota added kind:bug A bug in the code. Does not apply to documentation, specs, etc. topic:compiler:semantic labels May 7, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind:bug A bug in the code. Does not apply to documentation, specs, etc. topic:compiler:semantic
Projects
None yet
Development

No branches or pull requests

4 participants