Declaring a constant without a capital letter

I would like to declare the Euler–Mascheroni constant, but it’s represented by the Greek letter gamma, γ, and the compiler considers it as a local variable.

γ = 0.5772156649015329

def calc
  1 - γ  # Error: undefined local variable or method 'γ' for top-level
end

puts calc

Is it possible to declare a constant without using an English capital letter?

No, I don’t think so. It would be the same like Math::PI where you can’t use π to define it. You’d just have to use GAMMA

No, but it could be a method.

def γ
  0.5772156649015329
end

However, it would probably be more readable to use a constant that says what the value is in a more English way.

2 Likes

Tangential to OP’s question:

I very much agree with @Blacksmoke16. I work with mathematicians but am not myself one, and it’s just way too much work to get up to speed on what sigma or k is in a given method. EulerMascheroniConstant is going to be a much more clear name, and if it’s too long then assign it in the same file you’re using it in: Sigma = EulerMascheroniConstant. Another concern using γ directly is that it looks like y at a glance.

If you’re insistent on using γ, though, you could always prefix it: Const_γ. It’s not pretty, but it’ll work.

Thanks @Blacksmoke16, creating a method is a simple workaround.

Mathematics put aside, allowing non English characters may be useful for people who use another alphabet. Crystal already have a good support for foreign characters and variables, symbols and methods can be declared with them so why no constants?

# Example, declaring a constant in Cyrillic don't work although capital letters are used
НДС = 20.0 # VAT
3 Likes

Wouldn’t help you much with

Γ = 0.5772156649015329

:grinning:

1 Like

In general, I agree with you, but the compiler has to have some way of knowing that it’s a constant, and Crystal doesn’t use declaration keywords (e.g. const or static), so when designing the language, capital letters were chosen. Even if that rule were extended to all writing systems which have capitalization (a change which I don’t oppose), there are still writing systems which lack capitalization, so it wouldn’t be possible to extend the concept there. Readers and writers of those systems would just have to use another writing system which has capitalization. Maybe you could use some other concept within each writing system to determine whether a variable is a constant, but that way lies madness for anyone reading non-Latin-alphabet code and trying to remember which rule is used for that writing system.

At this point, it would be a wild breaking change to change the method of marking a constant (e.g. from capitalization to, I don’t know, using vertical bars?), and probably a questionable choice to add an additional method (like a constant keyword), so I’m not sure there are any very satisfying options for making constants more compatible with non-Latin writing systems.

1 Like

Thank you for the explanations @RespiteSage. I will not insist, using a method works perfectly fine for me and it’s faster. At first I thought that constants were faster because it’s a simple value stored in memory that never change and a method, slower, because it involves some logic. But it seems to be the opposite.

I had to evaluate a polynomial with constant coefficients and storing the coefficients into a method is 2.7x faster than a constant.

require "benchmark"

COEF = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0}

def coef
  {1.0, 2.0, 3.0, 4.0, 5.0, 6.0}
end

def polevl(x, c)
  c[0]        + 
  c[1] * x    +
  c[2] * x**2 +
  c[3] * x**3 +
  c[4] * x**4 +
  c[5] * x**5
end


x = Random.new.rand(1.0..10.0)
c = m = 0.0

Benchmark.ips(warmup: 0.01, calculation: 0.1) do |b|
  b.report("constant") {c = polevl(x, COEF)}
  b.report("method")   {m = polevl(x, coef)}
end

puts c
puts m

Output:

constant  85.94M ( 11.64ns) (± 1.67%)  0.0B/op   2.74× slower
  method 235.07M (  4.25ns) (± 2.25%)  0.0B/op        fastest

This is mostly fixed already and it will be available in the next version.

2 Likes

FYI I see still 1.5x slower on nightly.

constant 228.51M (  4.38ns) (± 5.75%)  0.0B/op   1.52× slower
  method 347.44M (  2.88ns) (± 6.32%)  0.0B/op        fastest

Is this thread paving the way for me to use emoji’s in crystal?

💩 = "icky"

def 💩
  "icky"
end

That’s already perfectly valid code.

This thread is about whether uppercase pile of poo would be a constant or a variable.

4 Likes

Oh I didn’t know, but that does bring up a good point. In the top-level this would be conflicting, and need to be context driven… but I don’t see a way you can make it strictly a constant or local variable using crystal. Crystal would have to detect if it’s being used in the top-level and allow constants like this to be assigned to (since it COULD be a constant or a variable). Obviously when assigning to a constant it won’t compile. Because of this it doesn’t seem like a great idea since you might accidentally overwrite your constants and turn them into variables…

this vs that

That’s already perfectly valid code.

Wow! I didn’t know. Interesting…

The new name for 2020

8 Likes

I believe this is LLVM’s fault. I see the definition of COEF in the generated program, maybe loading that value from the global variable is slower. LLVM is able to inline the method call.

That said, I believe Crystal could define a lot of globals as true constants: primitive values and tuples of primitive values.

1 Like

This is probably the best way for OP to get the desired outcome.

GAMMA = 0.5772156649015329

@[AlwaysInline]
def γ
  GAMMA
end
6 Likes

Great use of the @[AlwaysInline] annotation. Easy to forget that exists.

Using a macro is also an other possibility:

GAMMA = 0.5772156649015329

macro y
  GAMMA
end

An other way:

macro const(assign)
  @[AlwaysInline]
  def self.{{assign.target}}
    {{assign.value}}
  end
end

module A
  # This comment will be shown in the API docs.
  const pi = 3.14
end

p A.pi

Somehow the above example only work inside a type (even when removing self.)

1 Like

At the end a standard class_getter can do the job too.