crystal-lang / crystal

The Crystal Programming Language
https://crystal-lang.org
Apache License 2.0
19.47k stars 1.62k forks source link

[RFC] Remove `type` from the language #10031

Open asterite opened 3 years ago

asterite commented 3 years ago

Inside a lib declaration you can use type similar to an alias but making its own type:

lib LibSomething
  type Foo = Void*

  fun some_function : Foo
end

The idea behind this is using type when the C library exposes an opaque type whose structure is not known, hidden behind a pointer, but where you still want to consider it a different type, not always equal to Void*.

The problem with the existing type is that:

But the most important point is that type can be easily done right now: using a struct.

lib LibSomething
  # This is similar to `type Foo = Void*`
  struct Foo
    data : Void*
  end

  fun some_function : Foo
end

I know, it's a bit more typing, but it's just for C libraries.

Alternatively, we can keep type but remove its "value" part. That is, you write type Foo and that's it, and it's assumed that it's a Void* internally. Or we could allow macros to be invoked inside lib declarations and introduce a macro for defining a wrapper struct, so you can write wrapper_struct Foo, Pointer(Void) or similar.

asterite commented 3 years ago

Another option: keep type, but give an error if the underlying type is not Void*. Then we can document it as just being a short syntax for a struct with a single member.

Yet another option: keep type, but document it and make it work like a wrapper struct. Right now type has a few special rules in the language which I can't even remember, but making it work like a wrapper struct is probably better.

bcardiff commented 3 years ago

Adding a macro newtype similar to Haskell could work. IIRC newtype can have only one type argument, they are type-safe way to give a new meaning to an existing type.

newtype Temperature = Float32

newtype Length = Float32 do
  def +(other : Length)
    # ...
  end
end

We could standardize the conversion in both directions inside the macro and it would be able to work inside and outside lib.

asterite commented 3 years ago

Yet another option, probably my favorite: you can use type, but you get a compilation error saying to use a wrapper struct (with example code). If you run crystal tool format it changes type to those wrapper structs so the fix is really simple.

Eventually for Crystal 1.1 or 2.0 we can remove it.

That way we remove it, but we still provide a really easy upgrade path.

asterite commented 3 years ago

Oh, yeah, newtype could be another option.

That said, it seems in Haskell you have data and newtype, and the only difference is performance and something related to bottom. In Crystal you can already do newtype: it's record with just one field. So that could be another option (I wouldn't introduce newtype if there's no difference with record with just one field).

straight-shoota commented 3 years ago

I don't think those problems are much of a deal. Yes, it's another concept but you only have to learn it if you write C bindings. That's most likely a minority of developers. Most probably don't care about lib and what goes on inside. If you do care, you need to learn some new concepts. The semantics of type aren't hard to learn if your familiar with C types.

Sure, it could be improved and some of the ideas here sound good. But unless there was something really broken, I wouldn't make it a priority. It works and keeping it the way it is has not much cost.

asterite commented 3 years ago

The thing is, using type with a Proc type works, and if you call new on it you get a Proc type which is counter intuitive (you don't get the type type). I wanted to make Proc.new be defined in the standard library instead of the compiler, but the above breaks. I guess it's fine? It's a breaking change, but type is used the wrong way there (it should be an alias instead).

asterite commented 3 years ago

(removing type doesn't make it a breaking change because, well, type is gone)

oprypin commented 3 years ago

Please, ... this will break all the bindings in the wild. We don't need that.

type Foo = Void* as a feature is needed. The syntax is non-intrusive. struct is a workaround. If you want to allow only Void\** there, maybe that could work.

asterite commented 3 years ago

Sounds good. I guess I won't be making the changes I had in mind then.

straight-shoota commented 3 years ago

@asterite Seems you've wanted to that a long long time ago: https://github.com/crystal-lang/crystal/issues/4682#issuecomment-328349647

Reading the comments there I better understand that type as a feature is really unnecessary and eventually should be removed.

asterite commented 3 years ago

I think another alternative is to keep type but not make it special anymore: it's just a wrapper struct. I will try that some time later.

Fryguy commented 3 years ago

type vs alias confused the heck out of me for a long time, particular because I was using crystal-lib to generate bindings and it would generate all of type Foo = Void*, alias bar : LibC::Int, and type Baz = SomeStructDeclaredEarlier. Of course, the generator is not perfect, so it would occasionally miss some, and in order to fix it I'd be pulling my hair out trying to understand which one was the "right" one I needed to add back in. Add to that that I was new to crystal and couldn't really understand the difference between alias and type. For me personally, removing type would have reduced some cognitive load.

If we really needed a dedicated keyword, I think type is too generic and is part of the confusion... newtype is good or perhaps even opaque, opaque_type, new_opaque_type.