# Endless methods for Crystal

In looking at doing this raytracer code better/faster, I started rewriting it to conform with my personal style of writing code.

Here is a sample of orignal code that I rewrote to simplify it (to me).

``````class Sphere < Thing

def initialize(@center : Vector, radius : Float64, @_surface : Surface)
end

def normal(pos)
Vector.norm(Vector.minus(pos, @center))
end

def surface
@_surface
end

def intersect(ray)
eo = Vector.minus(@center, ray.start)
v = Vector.dot(eo, ray.dir)
dist = 0.0
if (v >= 0)
disc = @radius2 - (Vector.dot(eo, eo) - v * v)
if (disc >= 0)
dist = v - Math.sqrt(disc)
end
end
if (dist == 0)
return nil
end
Intersection.new(self, ray, dist)
end
end

class Plane < Thing
def initialize(@_norm : Vector, @offset : Float64, @_surface : Surface)
end

def normal(pos)
return @_norm
end

def intersect(ray)
denom = Vector.dot(@_norm, ray.dir)
return nil if (denom > 0)
dist = (Vector.dot(@_norm, ray.start) + @offset) / (-denom)
return Intersection.new(self, ray, dist)
end

def surface
@_surface
end
end

abstract class Surface
end

class ShinySurface < Surface
def diffuse(pos)
return Color_white
end

def specular(pos)
return Color_grey
end

def reflect(pos)
return 0.7
end

def roughness
return 250
end
end
``````

I like to read/write code horizontally (as oneliners for single concepts) when possible. It creates code noise (to me) to write a simple oneline concept as a 3 line definition. So hereâs how I rewrote the above code to make it cleaner and simpler.

``````class Sphere < Thing

def initialize(@center : Vector, radius : Float64, @_surface : Surface)
end

def normal(pos); Vector.norm(Vector.minus(pos, @center)) end

def surface; @_surface end

def intersect(ray)
eo = Vector.minus(@center, ray.start)
v  = Vector.dot(eo, ray.dir)
dist = 0.0
if (v >= 0)
disc = @radius2 - (Vector.dot(eo, eo) - v * v)
dist = v - Math.sqrt(disc) if (disc >= 0)
end

return nil if (dist == 0)

Intersection.new(self, ray, dist)
end
end

class Plane < Thing
def initialize(@_norm : Vector, @offset : Float64, @_surface : Surface) end

def normal(pos); @_norm end

def surface;  @_surface end

def intersect(ray)
denom = Vector.dot(@_norm, ray.dir)
return nil if denom > 0
dist = (Vector.dot(@_norm, ray.start) + @offset) / (-denom)
Intersection.new(self, ray, dist)
end
end

abstract class Surface end

class ShinySurface < Surface
def diffuse(pos); Color_white end

def specular(pos); Color_grey end

def reflect(pos); 0.7 end

def roughness; 250 end
end

class CheckerboardSurface < Surface
def diffuse(pos)
return Color_white if ((pos.z).floor + (pos.x).floor) % 2 != 0
Color_black
end

def specular(pos); Color_white end

def roughness; 250 end

def reflect(pos)
return 0.1 if ((pos.z).floor + (pos.x).floor) % 2 != 0
0.7
end
end
``````

`endless` methods (oneline method definitions that donât need the `end` keyword).

I think adding this to Crystal would have many benefits too.

1. It would allow compatibility with Ruby 3+ going forward.
2. It would make source code much shorter, and easier to read/write/maintain.
3. It would be simpler to read for newbies coming to the language.
4. It could possibly encourage more concise thinking and refactoring.
5. It wouldnât affect any legacy code.

The only possilbe âconâ I can think of (beside having to change the parser to
accommodate the change), is the emotional resistance to it (see Rubyâs discussions).

Here is what those code examples would look like using `endless` methods, using Rubyâs syntax. Too me, this is not only more `beautiful` code, but is concise, very easy to understand, and easier to write and read, and eliminates allot of unnecessary syntactical code noise.

``````class Sphere < Thing

def normal(pos) = Vector.norm(Vector.minus(pos, @center))

def surface = @_surface

def intersect(ray)
eo = Vector.minus(@center, ray.start)
v  = Vector.dot(eo, ray.dir)
dist = 0.0
if (v >= 0)
disc = @radius2 - (Vector.dot(eo, eo) - v * v)
dist = v - Math.sqrt(disc) if (disc >= 0)
end

return nil if (dist == 0)

Intersection.new(self, ray, dist)
end
end

class Plane < Thing
def initialize(@_norm : Vector, @offset : Float64, @_surface : Surface) end

def normal(pos) = @_norm

def surface = @_surface

def intersect(ray)
denom = Vector.dot(@_norm, ray.dir)
return nil if denom > 0
dist = (Vector.dot(@_norm, ray.start) + @offset) / (-denom)
Intersection.new(self, ray, dist)
end
end

abstract class Surface end

class ShinySurface < Surface
def diffuse(pos) = Color_white

def specular(pos) = Color_grey

def reflect(pos) = 0.7

def roughness = 250
end

class CheckerboardSurface < Surface
def diffuse(pos)
return Color_white if ((pos.z).floor + (pos.x).floor) % 2 != 0
Color_black
end

def specular(pos) = Color_white

def roughness = 250

def reflect(pos) = ((pos.z).floor + (pos.x).floor) % 2 != 0 ? 0.1 : 0.7
end
``````

This was brought up before, and ultimately rejected iirc.

Thanks, I wasnât aware it was formally requested before.

However, that discussion was in April 2020, before Ruby 3.0.0 was released on December 25, 2020, which does include it.

I understand from a devs perspecitve, it may be seen as allot of work to create an unnecessary feature (life will go on with or without it). However, for the reasons stated in the Crystal discussion on it, and the longer discussions in the Ruby community, that ultimately won over Matz to include it, it is definitely a winner for the programmer writer, and code readers.

Now that Crystal has hit 1.0.0, I would like it to be reconsidered as a future feature, now that the language has reached a point of significant stability.

I think those are very different things sometimes.

One example is Ruby having method aliases whereas Crystal discourages them.

So Ruby is optimized for writers. Coming from different languages makes you feel at home when you try some method name and it works. For the same reason it would hurt other people reading the code with different names for methods used by different people.

Crystal on the other hand is optimized for readers. It would make people learn the proper name for method coming from different language (harder to write - you have to learn the name of method in Crystal), but then reading is easier as there is only one name for method and people wouldnât be confused by seeing method `detect` and wondering what it does. Oh, itâs the same as `select`?

3 Likes

I donât think what you name methods is relevant to this discussion here, which is about language syntax for defining methods.

It may be helpful to see the Ruby discussions leading to its inclusion. They mirror most of the issues raised in the Crystal discussion.

https://bugs.ruby-lang.org/issues/16746

copy from `kotlin`

I donât like the idea of Crystal becoming indent sensitive (seems like it would open up potential formatting typo issues, too easy to overlook). But a block-like alternative format would be fine with me; as shown in https://bugs.ruby-lang.org/issues/16746:

``````# original
def value; @val; end

# proposed - is that conflicting one?
def value { @val }
``````

Endless method is not indent sensitive

1 Like

name= is setter

Parentheses for method definitions are required in Crystal so thereâs no amboguity.

`def setter=foo` is a getter
`def setter=(foo) = @foo = foo` is a setter

Regardless, this was discussed in the past and rejected.

This only adds a new syntax for method definitions, so itâs not a big feature enabler. It also adds more ways to do the same thing so people will start discussing styles even more, which is just a waste of time.

4 Likes

Gee @asterite, you seem to have changed your opinion from the April 2020 discussion, where you were more considerate.

As I said in raising this issue, the greatest resistance will be emotional and not technical.
But try to look at it solely from the perspective of the (potential) user.

Technically, itâs a trivial change. You clearly set the syntax for doing it correctly, and its done. Like with everything else, if the user messes it up the compiler will tell them.

So letâs get to the larger, and more important, factors of resistance.

A) Itâs just a âstyleâ thing.
You ignore style at your own peril.

There are libraries full of books, and total academic and industrial fields, devoted to how to make people `want` to buy your product. And after products get to a certain point of technical equality, what differentiates one car, hammer, shoe, software language from another is how it makes the (potential) user feel. Thatâs why you can get the same thing in different colors, textures, and sizes.

Scala, Kotlin, and now Ruby have it because 1) it was simple enough to do, 2) it makes them easier|faster for their users to read|write code, and 3) their users wanted it. And if thatâs a little stylistic thing that will attract more users then itâs worth doing.

B) We donât have to do everything Ruby does!
OKâŚbreathe. I know thereâs allot of pride (ego), control, and ideology when someone(s) creates anything, especially a software language (or a Linux distro). Thereâs a reason a person feels their thing is âbetterâ, or why do it (other just for pure personal fun). But it would be foolhearty not to objectively look at what others are doing in your same domain of interest, and understanding why things are being done certain ways, and where the winds of change are blowing. It doesnât mean you have to immediately jump on these bandwagons, but you need to acknowledge the trends, and prepare to adapt. Even Microsoft is now forced to âactâ friendly with Linux, because they have to to build their Cloud business using it.

C) Parents donât control their children after they grow up.
This is a big one for creators to deal with, but will have to at some point.

When you have children, theyâre not yours (your property). Your job is to get them to a point where they become functional members of the species that you donât have to provide for at some point. The animal kingdom exhibits this quite well, which we should learn more from.

For especially an open source project, once it becomes ââstableââ, and creates a community around it, and users start to depend on it, it no longer belong to its creator(s). It belongs to its users. And the users will (should) ultimately determine its future, because all parents|creators die.

Since Crystal now has shouted to the world, hey yall, weâs stable and ready to rumble, users will (have already) start requesting new features, usually from languages theyâre familiar with. Users want to play with your child in ways youâd never imagine, and disapprove of maybe in some cases. But remember, you can only control your child for so long, and then you have to accept you need to set it free.

This is what happened with Perl, Python, Ruby, Linux, et al. The communities around these technologies will determine what they will become, for good and for bad.

So the issue of whether to, or not, provide `endless methods` will become a distant triviality compared with the decisions that will need to be made concerning Crystalâs future growth. So the more comfortable and flexible the devs are at allowing the language to become whatâs its users want, the faster it will grow, and the more loved it will become.

Good points. In particular, I found point B more persuasive about why we should look to Ruby than what Iâve understood from your arguments on that point in the past. C isnât really a relevant point to me, since Iâm barely an active Crystal developer (I rarely use it at work, and I donât have any very active side projects), let alone a designer of the language.

A, though, is mostly where I disagree. I do think youâre right that if this change were 1) simple enough to do, 2) easier and faster for users to read and write code, and 3) widely desired, it would be the right move to include it. I canât speak to whether itâs easy, and I donât personally want it (but clearly at least several people do), but the reason I donât want it is that I think it would make Crystal code harder to read, and I donât think it provides enough of a benefit on the writing end to justify that. Youâre removing an `end` line and putting your (presumably one-liner) body into the declaration line, but thatâs also cluttering up your declaration line and creating a whole new method syntax to learn. Overall, that feels like a net negative change on ease of reading. Itâs quicker to write, to be sure, but typing time is by far the minority of software work.

I also just donât think it reads well. One of the benefits of well-written (yes, subjectively) Crystal is that it tends to say what itâs doing fairly clearly in English*. To me, thatâs a huge benefit from a maintenance perspective, and while I donât think this is a huge hit to that, it seems to me like a step away from it.

* Iâm a native English speaker, so I lack the perspective on how valuable this actually is to non-native speakers.

1 Like

Just a note, a one-line method can already be written without semicolons.

``````# Valid syntax
def add(x, y) x + y end
``````
4 Likes

Interesting. Didnât know that.

OTOH `crystal tool format` would change that into multiline, so not very usable if you are to follow standard formatting.

Also the `def add(x, y) x + y; end` will also be changed into multiline, same results.