The Crystal Programming Language Forum

What is the use case for delegate?

I’m starting to see delegate used, and I’m trying to understand its use case.

What is the difference between

class StringWrapper
  def initialize(@string : String)
  end

  delegate downcase, to: @string
  delegate gsub, to: @string
  delegate empty?, capitalize, to: @string
  delegate :[], to: @string
end

wrapper = StringWrapper.new "HELLO"
wrapper.downcase       # => "hello"
wrapper.gsub(/E/, "A") # => "HALLO"
wrapper.empty?         # => false
wrapper.capitalize     # => "Hello"

And:

wrapper = "HELLO" 
pp wrapper.downcase       
pp wrapper.gsub(/E/, "A")  
pp wrapper.empty?          
pp wrapper.capitalize      

Or:

class String2
  property s : String
  def initialize(@s : String)
  end
end

stringer = String2.new("HELLO")
 
pp stringer.s.downcase       
pp stringer.s.gsub(/E/, "A")  
pp stringer.s.empty?          
pp stringer.s.capitalize

From the docs:
https://crystal-lang.org/api/master/Object.html#delegate(*methods,toobject)-macro

Delegate methods to to .

Okay? But why? When you can just use string literal syntax " " and call the string methods. Or, use a class with a s property (String type), that uses the innate String methods. If you need a wrapper, the developer can use method overloading (or implement their own methods!) on their class, which follows OOP design.

What am I missing here?

The main use case is allowing the user to use some methods on an internal ivar within a class, without exposing the actual ivar to the user.

Example

class Validator
  def valid?(value : String)
    value == "foo"
  end
end

class WithoutDelegate
  getter validator : Validator = Validator.new
end

# Notice in this case you have to make a getter
# to the validator class
pp WithoutDelegate.new.validator.valid? "foo"

class WithDelegate
  delegate :valid?, to: @validator
  
  @validator : Validator = Validator.new
end

# Notice in this case we can keep the validator
# instance private and just expose a select few 
# methods on it.  Also notice the UX is nicer
# since you dont have to have that extra
# .validator in there
pp WithDelegate.new.valid? "foo"

In the example you provided they’re similar, but the StringWrapper is only allowing a subset of String's methods to be used. If you tried to do wrapper.tr("l","") it wouldn’t compile since StringWrapper doesn’t have a tr method defined, and you’re not delegating it to the internal string.

Wait, I thought the entire point of an ivar is so it can be used by a class? Why would a developer not want it to be “exposed”?

In the example you provided they’re similar, but the StringWrapper is only allowing a subset of String 's methods to be used

Isn’t this a negative though? Why limit the potential methods of a base type (String) to a developer? So, if that developer needed to call an innate String method, they would now… have to add a delegate method, before being able to use the method that the String type already offers? Wtf

IMHO, this adds another layer of anti-brevity, and another layer of complexity that can already be done with a class. More importantly, yet another keyword, a new developer has to understand and process while reading code.

It’s perfectly fine to have an ivar be used internally by the class but not exposed to the user, i.e. one that doesn’t have a getter method.

That example was just showing how delegate works, this isn’t specific to strings.

In the end delegate is just a macro that defines a new method to make writing like

class WithDelegate 
  @validator : Validator = Validator.new

  def valid?(value : String)
    @validator.valid? value
  end
end

a bit easier, as that’s exactly what it does. It’s just a macro to reduce boilerplate, like getter or property.

That’s even worse though, because now the developer has to add a delegate to any type they delegated, just to use an innate METHOD that already exists for that type. That’s an unnecessary step and is objectively a negative.

That would be a local variable, not an ivar? An instanced variable is… a property of a class. It should be freely accessed, no? If not, why?

No because you can’t share a local variable between methods. An ivar is able to be referenced from any instance methods within the class, as its tied to the object instance not a specific method.

Then just dont use it. It has its uses when, like in my validator example, you want an easier way to write a shortcut method to “delegate” a specific method to an internal ivar/method, without writing out a whole new method for it. You shouldn’t go around creating wrapper types and delegating all the methods to that internal ivar, as that’s kinda pointless.

So…a developer can’t use an innate method of a type, like so:

class StringWrapper
  def initialize(@string : String)
  end

  delegate downcase, to: @string
end

wrapper = StringWrapper.new "hello"
wrapper.downcase        
pp wrapper.capitalize      

Outputs:

error in line 12
Error: undefined method ‘capitalize’ for StringWrapper

They now, even after writing .capitalize after the variable name, need to add another delegate to support capitalize so it can be used, while String already supports a capitalize method?

So it would look like this:

class StringWrapper
  def initialize(@string : String)
  end

  delegate downcase, to: @string
  delegate capitalize, to: @string
end

wrapper = StringWrapper.new "hello"
wrapper.downcase       
pp wrapper.capitalize  

?

Yes but that is a contrived example just showing how delegate works. Given that specific class as it is there is no point for it to exist when you can just define a local variable like "hello" and use that.

It becomes more useful when you replace string with a custom type. Again please do realize that delegate is nothing more than a helper macro like property or getter. There is no magic going on here.

To bold: Why would a developer want a specific ivar to not be exposed?

If it’s an ivar, it should be freely available as it’s a property of a class, no?

Why is it “hidden” when used with a delegate. Doesn’t make sense, and seems overly complex for what it’s doing. Especially when the alternatives are much simpler and offer far more explicitness, and most importantly, allow the developer to use innate methods of a type and not restrict them to “delegated methods”.

Which now, the developer has to add a new delegate whenever they want to call a method, instead of just calling that method that is innate to that type (which already exists!). Again, an added layer of complexity that shouldn’t be there.

If it’s an implementation detail and not part of the public API. There are quite a few examples in the standard library, for Hash, JSON::PullParser, etc.

Again, mainly for the implementation detail and only wanting to expose what the user needs. Functionally there is no difference between

class Klass 
  getter validator : Validator = Validator.new
end

Klass.new.validator.valid? "foo"
class Klass 
  delegate :valid?, to: @validator

  @validator : Validator = Validator.new
end

Klass.new.valid? "foo"
class Klass 
  @validator : Validator = Validator.new

  def valid?(value : String)
    @validator.valid? value
  end
end

Klass.new.valid? "foo"

They all work the same, just the the last 2 do not allow the user to access the actual @validator object. If it’s okay for the user to access that then the first example would be fine, or could be used in combination with one of the latter two examples to eliminate the need to include the .validator when wanting to call valid? while still exposing the full object to the user.

I believe it’s always okay for a user to access that? Why is restricting a developer access to something ever okay?

That code doesn’t compile for me btw. And I have no idea what you are trying to explain with those code examples. That you like the word validator?

Again for the third time, if its an implementation detail and shouldn’t be used by the end user. The developer should be using the public API, as its the main interface for interacting with an object. The public API won’t change without some warning, while the internals of how that object functions can change at will and break the developer’s application if they were relying on it.

It does if you scroll up and look at the full example What is the use case for delegate?. I’m going to bed now, so i’ll try one more time to explain what is going on.

delegate is a helper macro, just like property or getter that simply expands to define a method that calls some other method on the delegated type. E.x.

delegate :valid?, to: @validator
# Expands to
def valid?(*args, **options)
 @validator.valid?(*args, **options)
end

There is no magic going on here.

The first example was showing how it would work without delegate and with the validator object exposed in the public API. The second example was showing how it would work with delegate and the validator object being an implementation detail. The third example is essentially the same as the second, but showing what the delegate does without actually using it. A fourth example could be added like

class Klass 
  delegate :valid?, to: @validator

  getter validator : Validator = Validator.new
end

obj = Klass.new

# The developer can get the full validator object
val = obj.validator

# Which can call its #valid? method
valid = val.valid? "bar"

# The developer also has a shortcut method to the validators #valid? method directly on `Klass`
obj.valid? "foo"

I don’t agree with your argument about it being “anti-brevity” as IMO its more clear to what its doing than having to manually define the methods. In the end it’s just a tool in the developer’s toolbox to help reduce boilerplate like property and getter, or record does. Any dev new to any language is going to have a learning curve, saying something shouldn’t exist just because someone new has to learn it doesn’t mean it’s a bad thing that should go away.

Restricting the end-user to only a public API is bad design and very limiting. For example, there are cases where the end-user can’t access a certain method that is private, which could help them. I’ve seen this in Godot’s repo numerous times where a user couldn’t access a method/variable in a class. They had to create a GitHub issue and ask for it to become public. Restricting access to methods/variables is never a good idea, and it ultimately will waste the time of contributors and lead developers.

My argument is you don’t need to “manually define the methods”, because those methods are innately inherited from the base type (String) in this case. Adding another delegate line just to access a method that is already accessible is an unnecessary step, and doesn’t make any sense. It’s… redundant.

I think you are proving my point. All that code you are trying to show me is unnecessary and overly complex, for no reason other than “oh my gawd, I want to restrict a user to access something!” which is inherently flawed.

Developers should have access to any method, variable, or whatever, it’s their project.

Definition of their

1 : of or relating to them or themselves especially as possessors, agents, or objects of an action

Possessor is the keyword here. We own the code we are writing, if you own something, you should be allowed access to it whenever you want.

This delegate macro is equivalent to owning a computer (that you paid for), but you can’t plug anything into the USB port without first asking your parents. It’s extremely silly, restrictive, and makes no sense. You should freely be able to plug in whatever you want into that USB port without asking for permission.

Consider a class that intends to offer mutable string functionality.

class MutableString
  @chars = [] of Char

  delegate size, to: @chars
end

You’re arguing that one should be able to access @chars directly to ask for its size.
Okay, this is currently implemented as an array of Char and therefore it makes sense to just use that array’s size to get the size of the mutable string.
But consider what would happen if I decided that the way to go is to implement MutableString differently and now internally uses an array of bytes, or a pointer, or whatever.
Now the #size method must be implemented in some other way, but whoever uses
MutableString doesn’t need to know or care about how I go about making it work, only that when they call #size they get the correct answer. That’s what an implementation detail is. You design an interface that users can rely on, and the way you manage to get it working is your problem as a developer, not the user’s.

If in the future you find that a class in Crystal hides something that you want to use, just override whatever you need to override to suit your needs. That’s one possible use of Crystal being as open as it is. But if the implementation changes and your code stops working because you rely on the internal workings of something it will be up to you to adapt.

I actually agree with this (bolded). However, a developer might need to access that “internal pointer, array of bytes, etc”. That’s why I don’t understand why it has to be “protected” or “private”.

What is wrong with allowing the user to have access to it? If they access it and it breaks, that’s their problem. If they access it and it fixes their issue (this is a good thing!)

For the same reason you’re not allowed entry to a restaurant’s kitchen. You’re there to eat what they serve, not to argue with the cook and tinker with the stove.

Crystal is more lax than that. There are ways to circumvent any attempt of encapsulation if need be. But establishing a clear demarcation between what your class provides and how, is a good thing.

I don’t see it as a demarcation, I see it as a limiting factor which is a hindrance to the developer.

I’m not not allowed into a restaurant’s kitchen because I don’t own the place, I’m a customer.

I own my code I write, I am the Chef. I can go into my kitchen whenever I want, for any reason. Same should be true for code (access anything I want, at anytime).

You do own the code you write. Go ahead and don’t ever use private and protected methods/classes/properties.
Others will likewise do, with the code that they write, as they see fit.
And in the end we’re all happy.

1 Like

Thank you.

Yes, it’s just hard because when I see these kinds of keywords out in the wild, it confuses me to no end. Maybe I should just worry about my own code.

In some cases people with more experience actually do know what they are doing and why.

You can write code in any way you like, but it can be beneficial to listen to those people and learn from them. There almost always is a reason for “the way things are done” as there are reasons why other things are not done.

You might be one of those people who only learn from their own mistakes, and refuse to learn from others. That is fine too, just be aware that you are ignoring years of experience and knowledge that way.