Remix.run Logo
kace91 a day ago

Coming from a language with functions as first class objects, blocks felt a bit limited to me, because it feels as if you almost have functions but not really, and they get inputted by a back door. Used for example to:

let isLarge = a => a>100;

numbers.filter(isLarge)

Blocks let you do the same but without extracting the body as cleanly. Maybe it’s a chronological issue, where Ruby was born at a time when the above wasn’t commonplace?

>When you write 5.times { puts “Hello” }, you don’t think “I’m calling the times method and passing it a block.” You think “I’m doing something 5 times.”

I’m of two minds about this.

On the one hand, I do agree that aesthetically Ruby looks very clean and pleasing. On the other, I always feel like the mental model I have about a language is usually “dirtied” to improve syntax.

The value 5 having a method, and that method being an iterator for its value, is kinda weird in any design sense and doesn’t seem to fix any architectural order you might expect, it’s just there because the “hack” results in pretty text when used.

These magical tricks are everywhere in the language with missing_method and the like, and I guess there’s a divide between programmers’ minds when some go “oh that’s nice” and don’t care how the magic is done, and others are naturally irked by the “clever twists”.

WJW a day ago | parent | next [-]

> The value 5 having a method, and that method being an iterator for its value, is kinda weird in any design sense and doesn’t seem to fix any architectural order you might expect, it’s just there because the “hack” results in pretty text when used.

I don't think this is particularly weird, in Ruby at least. The language follows object orientation to its natural conclusion, which is that everything is an object, always. There is no such thing as "just data" in Ruby, because everything is an object. Even things that would just be an `int` in most other languages are actually objects, and so they have methods. The `times` method exists on the Integer classes because doing something exactly an integer number of times happens a lot in practice.

kace91 a day ago | parent | next [-]

I don’t have an issue with the “everything’s an object” part, because it _is_ consistent, even though it gets a bit trippy when classes are objects as well and they are implementation of a Class class which is an implementation of itself (trickery again!).

The issue is more with this part:

>The `times` method exists on the Integer classes because doing something exactly an integer number of times happens a lot in practice.

It is practical, but it breaks the conceptual model in that it is a hard sell that “times” is a property over the “5” object.

The result is cleaner syntax, I know, but there is something in these choices that still feels continually “hacky/irky” to me.

WJW 21 hours ago | parent | next [-]

Perhaps I've been doing Ruby for too long, but it's still not that weird to me. The quantity "5" is very abstract without anything to have "5" of. That is why "5.days" and "5.times" exist, among others. Mathematically it makes just as much sense to start with the amount and add the unit later than it does to start with the unit and add the amount later (ie like `time_to_wait = SECONDS_IN_A_DAY * 5` as you might do in some other languages).

kace91 21 hours ago | parent [-]

Maybe it is clearer if I explain it in syntactic terms? In my mental model objects are nouns (described entities) and methods are verbs - actions over the noun.

process.start() is the action of starting done by the the noun that is the process.

It's not exactly a matter of naming, as some methods are not explicitly verbs, but there is almost always an implicit action there: word.to_string() clearly has the convert/transform implication, even if ommitted for brevity.

I see no path where 5 is a noun and times the verb, nor any verb I can put there that makes it make sense. If you try to stick a verb (iterate?) it becomes clear that 5 is not the noun, the thing performing the iteration, but a complement - X iterates (5 times). Perhaps the block itself having a times object with 5 as an input would make it more standard to me (?).

But I do understand that if something is extremely practical a purist/conceptual argument doesn't go very far.

bigtunacan 18 hours ago | parent | next [-]

It’s not just about practicality. Ruby is using message passing, not method calling. This is fundamentally different and a bit foreign to the larger community. Then ruby layers syntactic sugar on top that hides this.

Behind the scenes everything is a message passed using __send__ and you can do this directly as well, but you generally don’t.

So when you write

5.times { puts "Hello" }

It’s sort of expected by the average programmer that you are telling 5 to call the times method and expect it to exist and do what it’s told.

In reality you have indirectly sent a message that looks like

5.__send__(:times) { puts "Hello" }

What we are really doing is sending a message to 5 (the receiver) and giving it the opportunity to decide how to respond. This is where method_missing comes in to allow responding in a custom fashion regardless if a method was explicitly defined.

So you’re not telling 5 to call the method times, rather you are asking, “Hey 5, do you know how to handle the message times?”

These are fundamentally different things. This is actually super important and honestly hard to really grok _especially_ in ruby because of the syntactic sugar. I came from a C/C++ background originally, then Java and then moved to Ruby. After a few years I thought I understood this difference, but honestly it wasn’t until I spent a couple years using Objective-C where message passing is happening much more explicitly that I was able to truly understand the difference in a way that it became intuitive.

anamexis 12 hours ago | parent [-]

I’m a rubyist, but how is message passing fundamentally different from method calling? I get that method_missing adds a twist, but your comment doesn’t explain what the fundamental difference is.

Especially in the context of Fixnum#times. How does message passing vs method calling matter there? #times is just a method on Fixnum.

isr 10 hours ago | parent [-]

Because it leaves it up to the object being called what to actually do with the message. The object you're talking to might be forwarding its messages to another object in another ruby instance on another machine (if the current machine is getting fully loaded, etc), and the caller would be none the wiser. And crucially, the caller wouldn't have to be modified to enable this. The logic for this would be entirely within the object being called.

So the difference isn't just with method_missing.

With "method calling" as you put it, the program blows up if that object doesn't have that method, WHEN YOU CALL IT.

Basically, this smalltalk oo paradigm is about shifting where you put more of your logic. At & around the "call" site, or within the object whom you're calling & entrusting to do something useful with it.

All hearkening back to Alan Kay's original ideas about biology influencing how we organise code, and having a program be 1000's of "little black boxes" where work gets done by all these boxes talking to each other.

Which is why smalltalk (& ruby implements the Smalltalk object model to its entirety) actually has an awful lot in common with Erlang & other BEAM runtimes, even though those are functional languages. Because once you get beyond the techy buzzwords, the main ethos behind them is actually quite similar.

anamexis 9 hours ago | parent [-]

I guess what I’m getting at, is that I don’t understand how the difference actually informs anything concretely, as in the example of Fixnum#times, where this discussion started. Why is it super important to understand this fundamental difference?

bigtunacan 9 hours ago | parent [-]

Fixnum#times isn’t a great example, I only used it since the parent used it to illustrate their confusion and quite frankly a concrete useful example is to complex for this format.

ActiveRecord has changed a lot over the years, but as an example in the original ActiveRecord you used dynamic finders. None of the finder methods existed initially, but if you passed a message to an active record object for a non existent method rather than fail it would determine if that should be a method and then it would build and persist a method to the process for future calls.

It allows for some really interesting and powerful applications in horizontally scaling as well.

oezi 20 hours ago | parent | prev [-]

I have been doing Ruby for so long that it feels very natural to apply a method in this way on the instance.

false.not

applies the not method on the false instance in the same way that

car.start

in every OO language calls the start method on car as the receiver.

So filter(list) feels just wrong when you are clearly filtering the list itself.

nasmorn 20 hours ago | parent | next [-]

Although I prefer Elixir currently I agree that ruby at least goes all the way in on OO and not having to remember which feature is implemented as a language syntax and what is just a method invocation is a strength not a weakness. It is different in other languages for historical performance reasons really.

kace91 20 hours ago | parent | prev [-]

list.filter is ok! Filtering is an action that applies to a list

false.not is borderline but if read as false.negate it makes sense (negating is an action that applies to a Boolean value). That wording screws the chaining though.

5.times is where the pattern breaks: times is not an action that applies to a number (nor an action at all). It’s the block the one that should repeat/iterate - but Ruby breaks the rule there and blocks are not an object (!). If they were you could block.repeat(5) which IMO is cleaner.

hakunin 3 hours ago | parent | next [-]

I think I feel you. However, I think you have conceptually loaded "method" with more meaning. I think a less loaded way to think of the object/method is that the object is the first argument to the method that is called on it. So

    5.times { puts 'hi' }
is equivalent to

    times(5) { puts 'hi' }
which you could expand to

    my_function = -> { puts 'hi' } 
    repeat_times(5, &my_function)
And here is another reason for the disconnect: in a purely functional language repeating a function five times is useless, you're doing something for side effects only. Looping in itself is kind of a wrong (i.e. incomplete) abstraction for functional dev, because you're usually thinking in higher level concepts, such as `reduce`-based transformations. Maybe that's another part of the reason why `5.times { … }` feels off.

After my foray into functional programming, I actually ended up appreciating Ruby more, because it lets you have it both ways: program your computer directly, and harness functional concepts. Since computer hardware is not functional I don't want the extra ceremony and abstraction over it for the sake of purity.

All that said, going back and forth between Ruby and Elixir really conceptually crystallized for me that the method call receiver is basically just the first argument to the method, accessible with the keyword `self` (which in Python is made explicit for example).

chao- 20 hours ago | parent | prev | next [-]

There is a bit of personal preference in what "applies to a number", but I see what you mean.

As a slight correction, a block is indeed an object! They are received by methods as an instance of the Proc class:

  def inspects_block(&block)
    puts block
    puts block.class
  end
  inspects_block { "foo" }
  # => #<Proc:0x0000000000000000>
  # => Proc
You can even add a 'repeat' method to these in the way that you specified, although you will need to add '->' to declare the block (as a lambda, which is also just an instance of Proc) before you call #repeat on it:

  class Proc
    def repeat(n)
      n.times { self.call }
    end
  end
  ->{ puts("foo") }.repeat(3)
  # => foo
  # => foo
  # => foo
kaiuhl 16 hours ago | parent | prev | next [-]

Blocks are actually instances of the Proc class. There are helper methods to handle blocks passed to methods in a lightweight manner but you can also accept the block as a method argument, e.g.,

  class Integer
    def times(&blk)
      i = 0
      while i < self
        blk.call(i)
        i += 1
      end
    end
  end
kevinmchugh 11 hours ago | parent | prev | next [-]

Fun fact - there's no Boolean class in Ruby. True is an instance of TrueClass and false is an instance of FalseClass

isr 10 hours ago | parent | prev [-]

Hmm, but that's not really a "breakage" in ruby, it's more an aesthetic argument over which objects should have which logic. It's like naming things. Smalltalk (& ruby) has 'select'. Everyone else uses 'filter'.

To some, 5.times seems very readable & logical. It's like arguing over the "right" colour scheme to use while coding (BTW, the correct answer is solarised light, but with black foreground text!!)

gray_-_wolf 21 hours ago | parent | prev | next [-]

> that “times” is a property over the “5” object

Maybe here is the confusion, ruby is based on message passing, so the `times` is a message you are sending to 5, not a property of it.

bigtunacan 18 hours ago | parent [-]

I think you’re right, but I also suspect that doesn’t clear up anything for most people as in my experience they generally don’t grok the difference unless they’ve already spent a significant amount of time in something like smalltalk or Objective-C

majormajor 14 hours ago | parent | prev | next [-]

When you're new it just looks like a weird-but-reasonable syntax.

When you learn the language you really fall into two camps:

- ah, yes, this is unusual, but it's consistent and now i understand the language

- this is way too clever

I'm more in the first camp.

cortesoft 11 hours ago | parent | prev [-]

Times isn’t a property of the 5 object, it is a method of the 5 object.

layer8 13 hours ago | parent | prev | next [-]

With similar justification, you could argue that blocks should have a method `times` taking an integer, because repeating a block an integer number of times happens a lot in practice. I’d even argue that it is conceptually closer to blocks than to integers, and therefore the method belongs on the block. Hence you’d write `{ puts “Hello” }.times 5`.

But blocks are not objects in Ruby, so you can’t do that, and everything actually isn’t an object in Ruby.

Also, it’s even more common to do something depending on whether a condition is true or false, but true and false in Ruby don’t have a method to (not) execute a block on them, and you use a non-OOP `if` instead, so what’s up with that?

bashkiddie 11 hours ago | parent | prev | next [-]

> everything is an object, always.

I beg to differ. What object does the method `puts` belong to? Why do you not call puts with its objects name?

Ruby has a concept of mixins (Golang interfaces), these are not objects. Neither is `puts`

chao- 11 hours ago | parent | next [-]

>Ruby has a concept of mixins (Golang interfaces), these are not objects.

Ruby "mixins" are the affordance of sharing instance methods from a module via a keyword. Modules are objects, and are instances of class Module:

  module ProvidesFooMixin
    def foo = "foo"
  end

  class Bar
    include ProvidesFooMixin
  end

  Bar.new.foo
  # => "foo"

  puts(ProvidesFooMixin.class)
  # => Module

  ProvidesFooMixin.object_id
  # => (some integer value)
>Neither is `puts`

Like all methods, `puts` is an object:

  method(:puts)
  # => #<Method: Object(Kernel)#puts(*)>

  method(:puts).class
  # => Method

  method(:puts).object_id
  # => (some integer value)
Here you see evidence of where `puts` comes from: Kernel#puts via Object, which I will now explain in detail.

>What object does the method `puts` belong to?

It belongs to the object you are calling it from within. You don't need to call `puts` with a receiver because it is an instance method, just like you don't need to call an instance method `foo` via `self.foo`. But you could choose to use a receiver, since the `puts` you know and love is just another instance method. You can try `self.puts` for yourself in some context!

Your classes (and their instances) inherit this `self.puts` instance method from the Object class, which includes the Kernel module, which provides `Kernel#puts`. So the only reason you can send it as a message without a receiver is because it is just another instance method (again, the same as calling instance method `#foo` without using `self.foo`).

Caveat: You can build an "alternate universe" object hierarchy by inheriting from BasicObject, and in your alternate universe, you can choose to not `include Kernel`, and you will see that instances of your new objects do not have access to `puts` in their instance scope.

WJW 11 hours ago | parent | prev | next [-]

`puts` is just a method of the Kernel module: https://ruby-doc.org/3.4.1/Kernel.html#method-i-puts, just like `p` and many others. Kernel is included in the Object class that is the root of the class hierarchy, so its methods are available in every ruby object.

Mixins are just modules, which are objects, which you can call methods on. (Or rather, send messages to) You can easily verify this in irb calling a method on (for example) the Enumerable module:

    irb(main):001> Enumerable.class
    => Module
You are right that a module is not a class, and it is not possible to call `.new` on it. But the module itself is very much an object.
cortesoft 11 hours ago | parent | prev | next [-]

Well, “puts” is a method defined in the Kernel module, which is included in the Object class, which means it is available in all contexts (since every object is a subclass of the ‘Object’ class). So, to answer your question, the puts method belongs to the Kernel module object, and the Kernel module object is an instance of a Module object.

kazinator 11 hours ago | parent | prev | next [-]

Answerable by a few minutes of googling. Sort of:

puts is a method which has a class: the Method class:

  irb(main):001:0> method(:puts)
  => #<Method: main.puts>
  irb(main):002:0> method(:puts).class
  => Method
Everything being a confused muddle in Ruby, there is evidently some Kernel base class that is injected into every Object, and puts is a private method in that:

  irb(main):003:0> 3.puts()
  Traceback (most recent call last):
          2: from /usr/bin/irb:11:in `<main>'
          1: from (irb):3
  NoMethodError (private method `puts' called for 3:Integer)
The Method class of puts is a real class with methods and all:

  irb(main):004:0> method(:puts).class.methods
  => [:allocate, :superclass, :<=>, :<=, :>=, :==, :===, :autoload?, :autoload, :included_modules, :include?, :name, :ancestors, :attr, :attr_reader, :attr_writer, :attr_accessor, :instance_methods, :public_instance_methods, :protected_instance_methods, :private_instance_methods, :constants, :const_get, :const_set, :const_defined?, :class_variables, :remove_class_variable, :class_variable_get, :class_variable_set, :class_variable_defined?, :public_constant, :freeze, :inspect, :deprecate_constant, :private_constant, :const_missing, :singleton_class?, :prepend, :class_exec, :module_eval, :class_eval, :include, :<, :>, :remove_method, :undef_method, :alias_method, :protected_method_defined?, :module_exec, :method_defined?, :public_method_defined?, :to_s, :public_class_method, :public_instance_method, :define_method, :private_method_defined?, :private_class_method, :instance_method, :instance_variable_set, :instance_variable_defined?, :remove_instance_variable, :instance_of?, :kind_of?, :is_a?, :tap, :instance_variable_get, :instance_variables, :method, :public_method, :singleton_method, :define_singleton_method, :public_send, :extend, :to_enum, :enum_for, :pp, :=~, :!~, :eql?, :respond_to?, :object_id, :send, :display, :nil?, :hash, :class, :singleton_class, :clone, :dup, :itself, :yield_self, :taint, :tainted?, :untrust, :untaint, :trust, :untrusted?, :methods, :frozen?, :protected_methods, :singleton_methods, :public_methods, :private_methods, :!, :equal?, :instance_eval, :instance_exec, :!=, :__send__, :__id__]
WJW 11 hours ago | parent | next [-]

It's not as much a mystery as you think, the Kernel module is pretty well documented and states right in the first paragraph that its methods are available in all Ruby objects: https://ruby-doc.org/core-3.0.2/Kernel.html

cortesoft 11 hours ago | parent | prev [-]

> Everything being a confused muddle in Ruby

It really isn’t a confused muddle, the rules are very clear. Just because it doesn’t match what you expect from your other language experience doesn’t mean it isn’t clear.

pxc 10 hours ago | parent | next [-]

I'm not a Rubyist really, but I started using it at work a few weeks ago for a very small script. I'm not allergic to RTFM, so I picked up the canonical reference book on Ruby and occasionally visit the official docs for the language. I agree: the structure is clear and straightforward and there's nothing difficult about learning it.

It's also the opposite of magic; magic is when language features can't be described in terms of the language itself.

kazinator 6 hours ago | parent | prev [-]

Clear, like the glass in Liberace's candelabra.

jfabre 11 hours ago | parent | prev | next [-]

Modules (mixins) are objects, classes are also objects (of type Class).

The Kernel module is included in the Object class, which means its methods are available to every Ruby object and can be accessed from virtually any scope, including the top-level (global) context where self is an instance of Object called "main."

9 hours ago | parent | prev | next [-]
[deleted]
pxc 10 hours ago | parent | prev [-]

> I beg to differ. [...] Why do you not call puts with its objects name?

In the first place, I'd say what you're asking for goes beyond "everything is an object".

But I think your questions can be answered in a way that affirms that "everything is an object" in Ruby anyway.

> Why do you not call puts with its objects name?

Because it belongs to whatever object you're working in already; `puts` is identical to `self.puts`. And yes, you're always working in an object: https://bparanj.gitbooks.io/ruby-basics/content/chapter1.htm...

> What object does the method `puts` belong to?

As indicated above, it belongs to the object `self`. It gets added object via the mixing-in Kernel module into Object. Kernel is itself an instance of class Module: https://docs.ruby-lang.org/en/3.4/Module.html

The `puts` in Kernel delegates to `puts` from IO, which is likewise an instance method belonging to the object you can refer to by the name `$stdout`: https://docs.ruby-lang.org/en/3.4/IO.html

> Ruby has a concept of mixins [and], these are not objects.

Sure they are. Mixins themselves inherit from Module, and Modules are also objects (just like classes are).

Some highlights from Chapter 27 ("Library Reference: The Class Model") of the recent edition of the pickaxe book (emphasis mine):

> The Kernel module is included by class Object, so its methods are available in every Ruby object. One of the reasons for the Kernel module is to allow methods like `puts` and `gets` to be available everywhere and even to look like global commands. Kernel methods allow Ruby to still maintain an "everything is an object semantics".

and regarding mixins:

> The Module class is the class of any module you declare with the `module` keyword. Each module is an instance of class Module.

on Object:

> Object is the parent class of (almost) all classes in Ruby unless a class explicitly inherits from. BasicObject. [...] Object mixes in the Kernel module, making the built-in functions globally accessible.

tl;dr: mixins in Ruby are instances of class Module, and their methods end up bound to instances of class Object. Abstract module methods that don't belong to a concrete instance of some class that mixed in their module belong to the Object that is the module itself (the instance of class Module). (The same kind of thing is how class methods work.)

TOGoS 15 hours ago | parent | prev [-]

`5.times` is not so outlandish, though it would seem better for that to be in a `Loop` library or something (`Loop.times(5) { do some stuff }`).

The `5.days` example that was posted somewhere else in this thread might be a better example. It is not, as far as I can tell, part of Ruby's core library, thank goodness, but it is the kind of thing the methods-as-hacks-for-syntax culture seems to encourage. My question being "why the heck should the number 5 know anything about days? What does 5.days even mean, given that there are various ways to interpret 'a day'?"

This kind of bad design has made its way all over the place. The Java mocking libraries that my coworkers like to use are full of it. Long chains of method calls that appear like they're trying to 'look like English' but make no damn sense, so you have to dig into each method call and what kind of thing it returns to understand what this chain actually means.

zem 6 hours ago | parent [-]

you can view it as serving the same role as universal function call syntax, the latter being a feature in some other languages that rewrites `a.f(x)` to `f(a, x)` if `a` doesn't have a method `f`. in ruby you can just add the method to `a`'s class directly, but the idea is the same - you're adding some user defined function that is closely related to the type of `a`, and you're using method call syntax because that reads nicely.

judofyr a day ago | parent | prev | next [-]

Blocks are fundamentally different from functions due to the control flow: `return` inside a block will return the outer method, not the block. `break` stops the whole method that was invoked.

This adds some complexity in the language, but it means that it’s far more expressive. In Ruby you can with nothing but Array#each write idiomatic code which reads very similar to other traditional languages with loops and statements.

vidarh 16 hours ago | parent | next [-]

More specifically blocks (and "proc"'s) return from the defining scope. This is just a minor clarification, but it matters, because if you pass a block down from where it is defined, and the block calls "return" it will still not just exit from the method where it was called, but from the method where it was defined.

This can sometimes be useful: A calling method can pass down a block or proc to control if/when it wants an early return.

Basically Ruby has two types of closures:

* A return in a lambda returns to the calling scope. So basically, it returns to after where the "call" method is invoked.

* A return in a block or a proc returns from the scope in which it was defined (this is also why you get LocalJumpError if you try to return a block or a proc, but not a lambda to the method calling the one where the block or proc is defined).

When you name a block, you get a Proc object, same as you get when you take the value of a lambda or proc.

In practice, that blocks in MRI are not Proc objects already is just an implementation detail/optimisation. I have a long-standing hobby project to write a Ruby compiler, and there a "proc" and a bare block are implemented identically in the backend.

oezi 20 hours ago | parent | prev [-]

You are right on return (use next in a block), but break uses block scope.

judofyr 19 hours ago | parent [-]

Maybe I explained it a bit imprecise. I was trying to explain the following behavior:

    def foo
      p 1
      yield
      p 2
    end

    foo { break }
This only prints "1" because the break stops the execution of the invoked method (foo).
Mystery-Machine 16 hours ago | parent [-]

WAT? I'm a 12+ years Ruby developer and I didn't know this.

jhbadger a day ago | parent | prev | next [-]

If you are familiar with a true object-oriented language like Smalltalk (rather than the watered-down form of OO in C++, Java, etc.), an integer like 5 having methods makes sense because it (like everything else) is an object. Objects in Ruby aren't just window dressing -- they are its core.

ck45 21 hours ago | parent [-]

But then Ruby only goes half way, not unlike the "watered-down form" in your term. Why is `#times` a method of Integer, but `#if` (or `#ifTrue`) not a method of booleans like in Smalltalk? Ruby does the same cherry picking from Smalltalk like everybody else, just different cherries. When looking at Ruby, it feels like the simple examples are all nice and clean but then the weird details start to appear and the language feels more hacky than others (like Ned Flander's house in Simpsons S08E08).

chao- 21 hours ago | parent | next [-]

#if and #ifTrue are yours if you want them:

  class TrueClass
    def if = true
    def ifTrue = true
  end

  class FalseClass
    def if = false
    def ifTrue = false
  end

  true.if
  # => true
  false.if
  # => false
wild_egg 20 hours ago | parent [-]

In Smalltalk those methods don't return `true`. They take a block and evaluate it if the boolean receiving the message

    (a > b) ifTrue: [ "do something" ]
EDIT: to clarify what's happening there, `>` is a message sent to `a` that will result in a boolean. The True class and False class both understand the ifTrue: message and `True>>ifTrue:` executes the block whereas `False>>ifTrue:` just throws it away.

There's no `if` keyword in the language. Control flow is done purely through polymorphism.

chao- 20 hours ago | parent | next [-]

I apologize for my lack of Smalltalk knowledge. As you can imagine, you can do similar in Ruby by defining ifTrue to accept a block, even adding ifTrue on other all objects and defining something similar:

  class TrueClass
    def ifTrue(&block) = block.call
  end

  class FalseClass
    def ifTrue(&block) = nil
  end

  class Object
    def ifTrue(&block) = block.call
  end
      
  class NilClass
    def ifTrue(&block) = nil
  end
If ck45's core complaint was that this is not baked into the language, I will agree that it is less convenient for lack of a default.
oezi 20 hours ago | parent | prev [-]

Certainly possible: add ifTrue as a method to TrueClass and FalseClass.

It just isn't very fast.

codesnik 19 hours ago | parent [-]

problem is not with ifTrue, and not with it's performance, it's easy to do. it is "ifTrue:ifFalse:"

also it is common to do assignments in the "if", and with actual method and blocks scope of the introduced variable would be different and everyone would be tripping on it all the time.

codesnik 19 hours ago | parent | prev [-]

basically it's because of "else" and "elsif". While ".each" works the same as "for .. in ...; end", it's harder to do "if else" as method which will also return value of the block inside the branch. Smalltalk can do it because "ifTrue:ifFalse:" is _one_ message, ruby didn't go that way syntactically.

vidarh 10 hours ago | parent | prev | next [-]

This is your example in Ruby:

    isLarge = -> {|a| a > 100 }

    numbers.filter(&isLarge)
Or you could replace the first line:

    isLarge = -> { _1 > 100 }
Some people hate that syntax, though. I think for trivial predicates like this, it's fine.
chao- 21 hours ago | parent | prev | next [-]

The "aesthetically pleasing" aspect of blocks is not mutually exclusive with real, first-class functions! Ruby is really more functional than that. Ruby has both lambas and method objects (pulled from instances). For example, you can write:

  let isLarge = a => a>100;
as a lambda and call via #call or the shorthand syntax .():

  is_large = ->(a) { a > 100 }
  is_large.call(1000)
  # => true
  is_large.(1000)
  # => true
I find the .() syntax a bit odd, so I prefer #call, but that's a personal choice. Either way, it mixes-and-matches nicely with any class that has a #call method, and so it allows nice polymorphic mixtures of lambdas and of objects/instances that have a method named 'call'. Also very useful for injecting behavior (and mocking behavior in tests).

Additionally, you can even take a reference to a method off of an object, and pass them around as though they are a callable lambda/block:

  class Foo
    def bar = 'baz'
  end

  foo_instance = Foo.new
  callable_bar = foo_instance.method(:bar)
  callable_bar.call
  # => 'baz'
This ability to pull a method off is useful because any method which receives block can also take a "method object" and be passed to any block-receiving method via the "block operator" of '&' (example here is passing an object's method to Array#map as a block):

  class UpcaseCertainLetters
    def initialize(letters_to_upcase)
      @letters_to_upcase = letters_to_upcase
    end

    def format(str)
      str.chars.map do |char| 
        @letters_to_upcase.include?(char) ? char.upcase : char
      end.join
    end
  end

  upcase_vowels = UpcaseCertainLetters.new("aeiuo").method(:format)
  ['foo', 'bar', 'baz'].map(&upcase_vowels)
  # => ['fOO', 'bAr', 'bAz']
This '&' operator is the same as the one that lets you call instance methods by converting a symbol of a method name into a block for an instance method on an object:

  (0..10).map(&:even?)
  # => [true, false, true, false, true, false, true, false, true, false, true]
And doing similar, but with a lambda:

  is_div_five = ->(num) { num % 5 == 0 }
  (0..10).map(&is_div_five)
  # => [true, false, false, false, false, true, false, false, false, false, true]
kace91 19 hours ago | parent [-]

That is interesting! I haven't explored Procs much, since I use ruby for a shared codebase at work and I was originally a bit afraid of trying to push unidiomatic ideas in the codebase.

In your experience, is it ok to use Procs for example for extraction of block methods for cleanliness in refactors? or would I hit any major roadblocks if I treated them too much like first-class functions?

Also, is there any particular Rails convention to place collections of useful procs? Or does that go a bit against the general model?

vinceguidry 16 hours ago | parent | next [-]

You shouldn't have much difficulty, Ruby converts blocks to Procs whenever it needs an actual object. Their semantics are intentionally kept the same. This is unlike lambdas, whose semantics are closer to methods.

Pass the wrong number of of arguments to a Proc or block, it will pass nil for missing args and omit extras. Pass the wrong number of arguments for a method or lambda and you get an ArgumentError. Use the return keyword in a lambda, it returns from the lambda, just like if you call return in a method. In a block or Proc, it returns from the calling method.

So I would feel comfortable leaning on them for refactoring as it's as Ruby intended. Just use lambdas when you want to turn methods into objects and Procs when you want to objectify blocks.

You should get ahold of a copy of Metaprogramming Ruby 2 if you find yourself refactoring a lot of Ruby. It's out of print, but ebooks are available.

vidarh 15 hours ago | parent [-]

Just to clarify here: Both lambdas and procs are Proc objects. Blocks gets turned into Proc objects when you take their value.

So just be clear about whether you're talking about a proc or a Proc...

> In a block or Proc, it returns from the calling method

No, in block or a Proc it returns from the scope where it was defined.

Usually this is the same, and so it doesn't usually matter much, but if you pass a proc or a block down to another method, then a return within the block will still return from the method where it was defined.

This can occasionally be useful, as you can use it to pass in a predicate to a method that can control if/when you want to return, irrespective of how deeply nested.

vidarh 10 hours ago | parent [-]

Too late to edit now, and this is what I get for quibbling about casing:

> No, in block or a Proc it returns from the scope where it was defined.

Should of course read:

> No, in a block or a proc it returns from the scope where it was defined.

Mystery-Machine 16 hours ago | parent | prev [-]

This sounds like a really innovative idea. I haven't seen a dedicated place for "collection of useful procs", but one emerging pattern is to use `app/services` and then have a bunch of single-responsibility service classes that each have call or perform method and then you use the service when you need some shared functionality. It's like a proc, but instead it's a class with `#call` method.

vidarh 10 hours ago | parent [-]

> It's like a proc, but instead it's a class with `#call` method.

It's called the "callable" pattern.

somewhereoutth 20 hours ago | parent | prev [-]

Interestingly, in the Lambda Calculus, where everything is a function, a standard representation for a natural number n (i.e. a whole number >= 0), is indeed a function that 'iterates' (strictly, folds/recurses) n times.

E.g. 3:

(f, x) => f(f(f(x)))