Some question(or improvement?) for Crystal

Don’t know if appropriate create this thread here, anyway, i am quite newbie for crystal, and still learning, following is some confusing things(and maybe improvement advice) for now.


  1. i consider following code should not a valid syntax.
# a blank space added between new with left parenthesis
SomeClass.new ("String")  # this is bad, i propose this is a invalid syntax.

SomeClass.new("String") # this is good

Anyway, unified format is better (as go), we don’t want to write another rubocop for crystal, just for change those bad syntax habit, right?


  1. getter :name, should not accept symbol as argument, we prefer to use only one form(as python) to do same things, right?
getter :name  # => bad

getter name  # => good

  1. What the Int in the method parameter type stand for?

# following code just work
def fact(n : Int) : Int32
  p typeof(n)
  if n < 0
    puts "n must be positive!"
    exit
  end
  n == 0 ? 1 : n * fact(n - 1)
end

# But follow declare not work.
x : Int;  # => can't use Int as the type of variable yet, use a more specific type

i am so curious, why we can use Int as a alias for Int32 in parameter, but, can not use
it for declare, and in fact, change to following code not work too.

def fact(n : Int) : Int
  p typeof(n)
  if n < 0
    puts "n must be positive!"
    exit
  end
  n == 0 ? 1 : n * fact(n - 1) # no overload matches 'Int32#*' with type Int

  1. This more like a question, for curious, what is the real type of the tuple key? why we can visit the value correctly use both String or Symbol?
foo = {aaa: 100}   # => what is the key type?

p foo["aaa"] # => 100
p foo[:aaa]  # => 100

  1. because crystal run "$@" is the default of crystal "$@",

e.g.

$: crystal run test.cr
$: crystal test.cr   # same as above

There is almost no arguments supported when use crystal without sub command, except -h and -v, so, can we pass all arguments of crystal run --?? to crystal --???

e.g.

$: crystal run test.cr --error-trace  # this work
$: crystal test.cr --error-trace       # this not work, --error-trace was ignored
Showing last frame. Use --error-trace for full trace.
...
Error ...

Thank you.

1 Like

I’m not sure I would go as far as to make that invalid syntax. They get parsed/ran exactly the same. Plus running the formatter (crystal tool format) removes the empty space so we already have a unified format as enforced by the formatter.

I think this is just a product of how the macro works. E.g. you can do getter foo, :bar, "baz" and all of them generate the same thing. I don’t really see that form of it used too often as most people do like getter name : String versus a list or untyped getter. Could make that more strict, but debatable what the benefit would be, plus would need to wait until 2.0 anyway? :person_shrugging:

Int would be Int - Crystal 1.4.1. I.e. the base type of all integer types. When used as a method parameter restriction, it allows any int type. There’s also number autocasting now so like even if you did def foo(id : Int64) you could still do foo 10 and 10 just gets automatically converted to an Int64.

The error you get is just a limitation of the compiler at the moment where it prevents you from using certain types to restrict a local or ivar. E.g. you can’t do getter id : Int either. Don’t recall if there’s an issue for this, or any plans to allow it.

NamedTuples have a key type of Symbol. But the API allows accessing the value by both the String and Symbol representation. I.e. are overloads for each type, but internally the keys are always Symbol.

IIRC the difference is that when using run the arguments are passed to the the compiler while when not using it, they’re passed to the program. In order to pass args to the program you need to add them after --. For example:

crystal run test.cr --error-trace          # --error-trace flag is passed to the compiler
crystal run test.cr --error-trace -- --foo # --error-trace flag is passed to the compiler, --foo flag passed to program
crystal --foo --error-trace.               # Both flags are passed to the program
4 Likes

Regarding getter and similar macros: Yeah, I think it would make sense to restrict the allowed argument types. Maybe there is already some rule in ameba, not sure.
It’s really just that implementation is based on {{ argument.id}} which makes a lot of things work. Even some really weird things like getter ::foo(), for example :person_shrugging:
I would definitely that that’s not well-formatted. Perhaps the formatter could apply some corrections. At least ameba should (if not already). And maybe in 2.0 we could actually restrict the allowed types for arguments.
I think it should always be Var to match Assign for declarations with value.

3 Likes

I propose do this, Crystal still not use so widely, we have chance to fix it anyway,
following is reason.

  1. use formatter is optional, this encourage bad code style
  2. It may cause issue, at least, maybe write not clear code, following is a example.
def foo(arg1)
  p arg1
end

arg1 = "String"

# what is the argument passed to foo?  true or 7?
foo (arg1.responds_to? :size) && arg1.size + 1

# is this same as above?
foo(arg1.responds_to? :size) && arg1.size + 1

If we don’t permit add space between, we can always clear, all part of (arg1.responds_to? :size) && arg1.size + 1 is argument.

anyway, i consider do this change for Crystal is not a big deal, because most of developer use formatter, right?

1 Like

That won’t work if you want a getter called end or begin, because those are keywords. That’s the main reason why we also let symbols here.

8 Likes

https://play.crystal-lang.org/#/r/d5ts

:crazy_face:

macros be crazy lol

FWIW this works the same as if you just defined the methods directly. I.e. It doesn’t work just because you used the getter macro:

class Foo
  def end
    "hmm"
  end
    
  def begin
    "yup"
  end
end

crystal be crazy lol

1 Like

By the way, getter begin is invalid syntax, but getter begin : B is valid. In fact Range does exactly that. Regex::MatchData also defines #begin and #end.

def fact(n : Int) : Int
  n == 0 ? 1 : n * fact(n - 1)
end

fact(5) # Error: no overload matches 'Int32#*' with type Int

I am not so sure if this should fail. The return value restriction never types the actual returned value, only that the value’s type is a subtype of Int, so fact(n - 1) shouldn’t be typed to the abstract struct Int here. This may be a nuance in the type inference algorithm when it comes to recursive methods, though.

2 Likes

@HertzDevil Oh, good point. At one point we added a rule that if a recursive method couldn’t be typed right away, try to use the return type. But it seems that introduced the bug that you mention.