ozra / onyx-lang

The Onyx Programming Language
Other
97 stars 5 forks source link

Type Reasoning Keywords #94

Open ozra opened 8 years ago

ozra commented 8 years ago

Was touched upon in https://github.com/ozra/onyx-lang/issues/74#issuecomment-212302086, and some places else, but I think it needs to be issued properly here.

I favour the following, except implements? which is too long imo. Whether they should be possible to express as both "calls"/"operator" and also as "method-calls", I am not sure (the latter being favoured in Crystal):


-- Operator and Call style

compile-time-type = ctype(foo)
current-runtime-type = itype(foo)

is-type = foo of? SomeType

has-trait = foo implements? SomeTrait
is-sub-type = foo implements? SomeType
has-method = foo implements? some-method

some-type-foo = foo as SomeType
some-type-foo-or-nil = foo as? SomeType

-- Method call style

compile-time-type = foo.ctype
current-runtime-type = foo.itype

is-type = foo.of? SomeType

has-trait = foo.implements? SomeTrait
is-sub-type = foo.implements? SomeType
has-method = foo.implements? some-method

some-type-foo = foo.as SomeType
some-type-foo-or-nil = foo.as? SomeType

And of course, there's the question of signatures or name only in implements? (see https://github.com/crystal-lang/crystal/issues/2549#issue-152756236)

Sod-Almighty commented 8 years ago

I think the operator style is much preferable.

Perhaps impl?

ozra commented 8 years ago

Yes, impl? is simple, and reasonable, given the mindshare such re-occuring language constructs gets.

ozra commented 8 years ago

Furthermore: of? operator should just be of. The literals syntax type declaration style [] of SomeType should likely be changed.

Sod-Almighty commented 8 years ago

I disagree. x as T means "cast x to type T", while x of? T is a predicate, and therefore a question. The operator should be of?, leaving of free to perform its current task.

As a corollary to this, I propose that in be replaced with in?.

ozra commented 8 years ago

Yes, the in is what got me thinking the opposite ;-)

-- reads well
if some-thing in other-thing then do-stuff

-- reads awkwardly
if some-thing in? other-thing then do-stuff

But - on the other hand - it makes the "keyword operator" pop out much more clearly (of course syntax highlighting helps, but as I often state: if the language looks clear even in black-white - that's awesome) - which very well might outweigh any other argument. So, I think I'll re-position my opinion on that.

ozra commented 8 years ago

I want to expand on the argument for "hard" language operator keywords rather than methods. Which means allowing method-call style as alternative might not be in.

Since they are recognized as special constructs (of a operator'ish syntactic nature) they can have a tighter operator precedence than ||, &&, etc. This means that less parentheses are required to obtain the intended behaviour in conditions, and less confusion for "methods" that aren't really methods and might behave slightly differently in parsing: confusing, surprising deviations with syntax falsely implying it is a common user-definable construct.

if foo of? MysticType and foo in? allowed-vals then do-stuff foo

Which natural precedence would be:

if ( (foo of? MysticType) and (foo in? allowed-vals) ) then do-stuff(foo)

Compare:

if foo.of?(MysticType) and allowed-vals.includes? foo then do-stuff foo

Since an "honestly" parsed method-call syntax otherwise would give: foo.of?(MysticType && allowed-vals.includes?(foo)) - which of course blows to bits.

Sod-Almighty commented 8 years ago

I still like the idea of user-defined operators.... ;)

ozra commented 8 years ago

PR! ;-)

Sod-Almighty commented 8 years ago

But there's pretty much zero chance of my understanding the code enough to implement such :(

ozra commented 8 years ago

Regarding the issue at hand, I had an idea during the day while chasing the bulls - (Yes! Three bulls ran away from the field this afternoon under my watch (damn!). They've been all over the fucking neighbourhood, all the way down to the lake - we just finally succeeded to lure them in to the barn minutes ago, yikes. X-/ ) - well. Phew. Had to get that stress weight out. Eh, back to train of thought: Interfaces.

Onyx has Traits. Those are nominal. Interfaces are structural. I figure like this: one simply use another way of testing capabilities, witch matches structurally against modules, traits or types, rather than nominally.

Normally you'd go if foo of? SomeTypeOrTrait do foo.stuff - which checks for relation nominally.

But, then, thinking about it: implements?. That's the exact thing that should check structurally!

if foo implements? SomeTypeOrTrait do foo.stuff

of? compares against super-types, modules and traits - nominally - "is foo made up of that type/trait in any way?" implements? should of course reasonably just check _"are the things in that type/trait implemented to full match within foo?"_, whether one is passing just a method signature or a whole Trait. In which case all it's defs should be checked to see that corresponding signatures are implemented in foo.

It's a matter of figuring out if it's plausible to do a sufficiently efficient routine so that it doesn't compile to slowly and that also generates fast-as-f**k final code.

It's an idea!

Sod-Almighty commented 8 years ago

So, you mean:

is_subclass_of_T = thing impl? T      -- i.e. is_a?

has_trait = thing impl? MyTrait

has_method = thing impl? to-string    -- i.e. respond_to?

has_specific_method = thing impl? to-string(Int)

Where thing is an instance or a type.

ozra commented 8 years ago

I think I was too exhausted still when I came in and wrote that, I'll clarify:

is_subclass_of_T = it of? T      -- "is_a?" - it's a derivation _of_ T

has_trait = it of? MyTrait       -- "is_a?" - it's made up also _of_ MyTrait

has_method = it impl? to-string  -- "respond_to?" - it _implements_ the method
                                 -- in contrast to deriving it _of_ some T

has_specific_method = it impl? to-string(Int)  -- same here, but more specific

has_all_methods = it impl? MyTrait -- it _implements_ all the methods with the
                                   -- specific signatures that are defined in
                                   -- MyTrait - in constrast to having derived
                                   -- the methods _of_ MyTrait

Where it is an instance. For types, constraint declarations would be awesomeness (both in arg restrictions and for generic constraints).

That, however, really need to be part of Crystal core in order to not break with the precious module universe. I'm trying to get the superficial things of Onyx in order faster so that I can help out more in progressing the actual core used in Crystal and, by derivation, Onyx. There's a lot of pressure on asterite, waj and the others, it would be nice to be able to help out in furthering the quality of Crystal, along with Onyx.

If x of? MyTrait is true then it will always be true that x impl? MyTrait is true. But not necessarily the other way around.

Let's clarify it in code:

trait MyTrait
  foo(x Int) -> Int: x + 1
  bar() -> "hey"
end

type Foo
  mixin MyTrait
end

type Bar
  @my-str = "yeay"

  foo(x Int) -> 1 + x
  bar() -> Str: @my-str
end

foo = Foo()
bar = Bar()

foo of? MyTrait    --> true: it has derived the "genes" of MyTrait
foo impl? MyTrait  --> true: it implements all the methods in MyTrait (duh!)

bar of? MyTrait    --> false: it isn't a "relative" of MyTrait - no "transfer"
bar impl? MyTrait  --> true: fully implements the definitions in MyTrait _seen_
                   --  as an "interface" (even though it _may_ have
                   --  implementations of it's own [like in this case] - unlike
                   --  interfaces). MyTrait could however have been entirely
                   --  abstract, and thus pose _even more_ as an interface.

Was that more clear?

ozra commented 8 years ago

Links the issue, while I'm at it: #72

Sod-Almighty commented 8 years ago

What's the point of of? then? We have is_a? for inheritance tests; and impl? for structure...

ozra commented 8 years ago

of? is Onyx naming for is_a? - a naming I find to be more exact in meaning, more terse, and better looking. "X" can't be a SuperType, TraitA and TraitB - however it can be made of all of those.

ozra commented 8 years ago

Regarding getting the declaration type vs the current type at run-time, which I didn't list above, I think these are reasonable telling choices (don't know about level of abbreviation / verbosity):

-- ~~the-type = declaration-type some-var~~ - too long
the-type = decl-type some-var
the-type = d-type some-var

curr-type = some-var.acting-type
curr-type = some-var.act-type
curr-type = some-var.a-type

I will remove the other variations (r-type, etc.) and make all the above available, to figure out which verbosity level gets most use in actual code.

Regarding the others - both method-style and operator-style are allowed for a time of trial. And both in and in?

ozra commented 8 years ago

Well. One more thought on variation for the get-types:

t = dtype-of some-var
t = some-var.atype-of

-- more verbose:
t = decl-type-of some-var
t = some-var.acting-type-of

-- or fucking simply:
t = dtypeof some-var
t = some-var.atypeof

Closer reflecting crystals typeof. With the -of, it's obvious the terse one-letter prefixes are preferable.

ozra commented 7 years ago

I'll just mention that in my local repo (not pushable atm) you can also use isnt and not (and redundantly is) in _operator position. Which works nicely with above:

isnt-some-type = x isnt of SomeType
-- vs:
isnt-some-type = not (x of SomeType)

ok-value = some-val isnt in forbidden-values

I think it's a great way of expressing negations with the operators!

[ed: it might be pushed since months back, but I don't think so]

Sod-Almighty commented 7 years ago

How about some-val not in forbidden-values?

ozra commented 7 years ago

Just checked the source — that's implemented too. :)