Live data from Hacker News

The Dynamic Def – abusing Ruby's def statement

weblog.jamisbuck.org

71–80 of 87 posts

Re: The Dynamic Def – abusing Ruby's def statement

#71
post #14

I've somehow always found Ruby to be opposite to the Unix philosophy of doing one thing and doing it well. While Ruby may seem trivial and fun in the beginning, it tends to be cumbersome and maintainable as the size of the repository grows. Coming from a Python world, my first reaction to Ruby was that it was more like Perl where there are many ways to achieve the same thing, and no it was not really helpful if you i…

> opposite to the Unix philosophy Bash: $ foo() { bar () { echo I am bar } } $ bar bar: not found $ foo $ bar I am bar Pretty much the same thing as the Ruby example. This is simply because function defining is a kind of statement or expression with a side effect which must be evaluated. The side effect is global (a name is globally associated with a function). So if the side effect is in a function body, its evaluat…

If you're a sufficiently advanced Unix purist (aka a Plan 9 user), the Bourne shell is a violation of Unix principles. Contrast the v6 shell, where this construct is impossible because there are no functions. Now that's do-one-thing-do-it-not-quite-terribly.

Does this work in Plan 9 rc?

Re: The Dynamic Def – abusing Ruby's def statement

#72
post #70

Earlier quoted context omitted.

The example you gave is clear, though. They are variables because they appear in a LET form. It's clear from the context what is going on. What I'm referring to, in Ruby, is something like: def some_method what_is_this end You don't know what what_is_this is. It could be an instance variable, a method (anywhere in the inheritance tower), or an autogenerated method from method_missing. It's impossible to tell without…

> But the problem there is, you can't simply grep the code for things like this. You could end up with 100s of uses of the identifier and never find the source. Personally, I see this as the entire point of the original Smalltalk-esque OOP paradigm: an object is a living, mutating black box that responds to messages, where an object's "type" is just "object": a thing with a protocol for sending and receiving arbitrar…

> People who say Erlang is a functional language are looking on the wrong level of abstraction. Processes are objects!

People thinking those two are mutually exclusive are not getting the difference between the map and the territory :).

I worked a bit with Erlang professionally. Erlang is both functional and object-oriented, if you think about Smalltalk-style OOP and not Java-style OOP (the constant confusion between the concepts of those two is incidentally why I think that most things written about OOP are bullshit - they focus on wrong details; I've seen even university courses confusing the shit out of students by calling C++ methods 'message passing' and objects as equivalent to 'actors').

RE the problem of black boxes - black boxes are cool, what's not cool is if the interface you use to talk to black boxes is in itself confusing. In the example GP posted, it's not clear on first and second thought what exactly does the middle line mean - if it is a value, evaluates to a value, evaluates to a value with side effects, possibly taking your control flow for a sightseeing trip around the Moon, etc.

Also, in real world, we have to have some idea of what the black box is really doing. The only value that comes from assuming something is like "a remote web service" is knowing that it probably sucks, it's totally unreliable, if it responds at all it's after time noticeable to end user of your program, and you have to plan for it disappearing at any moment because the founder takes exit money or forgets to renew a domain. Yes, you could program treating everything like a web startup, but few simple assumptions like "this is inside my program so it responds fast and lives as long as the rest of the program does" can help tremendously improve the speed of coding and the speed of the program itself.

Re: The Dynamic Def – abusing Ruby's def statement

#73

Earlier quoted context omitted.

The example you gave is clear, though. They are variables because they appear in a LET form. It's clear from the context what is going on. What I'm referring to, in Ruby, is something like: def some_method what_is_this end You don't know what what_is_this is. It could be an instance variable, a method (anywhere in the inheritance tower), or an autogenerated method from method_missing. It's impossible to tell without…

def some_method what_is_this end > You don't know what what_is_this is. It could be an instance variable, a method (anywhere in the inheritance tower), or an autogenerated method from method_missing. It's impossible to tell without digging through the code. We know it's not an instance variable -- that would be @what_is_this. It would have to be a local variable, but plainly there is no such variable local to this me…

Right. Once you understand how to use run-time tooling, debugging dynamic languages becomes rather straightforward.

Re: The Dynamic Def – abusing Ruby's def statement

#74
post #73

Earlier quoted context omitted.

def some_method what_is_this end > You don't know what what_is_this is. It could be an instance variable, a method (anywhere in the inheritance tower), or an autogenerated method from method_missing. It's impossible to tell without digging through the code. We know it's not an instance variable -- that would be @what_is_this. It would have to be a local variable, but plainly there is no such variable local to this me…

Right. Once you understand how to use run-time tooling, debugging dynamic languages becomes rather straightforward.

True. You have to learn to treat programs written in dynamic languages as living, mutable, interactive things instead of designs set in stone. However, when a simple syntax derails your reading, it is a thing of concern.

Then again, you could make similar shenanigans in Common Lisp with `symbol-macrolet', but it's obscure feature that pretty much by definition will be used only by people who know when to use it :).

Re: The Dynamic Def – abusing Ruby's def statement

#75
post #9

Earlier quoted context omitted.

In its place, being able to redefine equality on value types really helps clarify code. Misused, it creates confusion. The problem is that it's easy to think you've got a case where it helps, when actually it's not well-defined. Usually that revolves around there being state you care about which is missed from the equality comparison. Another trap is redefining #== without also looking at #eql?, which means Hash does…

Can you give me an example of where redefining equality makes sense?

My personal favorite: a safe, constant time equality method for cryptographic stuff.

Re: The Dynamic Def – abusing Ruby's def statement

#76

Earlier quoted context omitted.

> opposite to the Unix philosophy Bash: $ foo() { bar () { echo I am bar } } $ bar bar: not found $ foo $ bar I am bar Pretty much the same thing as the Ruby example. This is simply because function defining is a kind of statement or expression with a side effect which must be evaluated. The side effect is global (a name is globally associated with a function). So if the side effect is in a function body, its evaluat…

This is indeed the basic feature of any language that's evaluated at runtime. When working with such a language, one needs to learn the program as a dynamically growing construct instead of a vision cast in stone when you press the "compile" button.

Or rather, one needs to learn which constructs destructively manipulate a global environment, and which perform lexical binding.

In Python, an inner def will lexically bind a function, creating a closure. Python is not less dynamic than Ruby.

In Common Lisp, a defun inside a defun will behave similarly to Ruby; but if you want lexically scoped local functions, you use a different operator, namely flet or labels.

Scheme has a define which is lexical: it brings a lexical identifier into the scope for forms which follow.

Lexically scoped items, even in a dynamic language, in fact can be "cast in stone when you press the compile button"; they are cast in that stone which is the entire compiled environment of the surrounding function.

Re: The Dynamic Def – abusing Ruby's def statement

#77
post #59
post #55

Earlier quoted context omitted.

It was merged for MRI 2.1: https://bugs.ruby-lang.org/issues/8426

That change was reverted: https://bugs.ruby-lang.org/projects/ruby-trunk/repository/re... It remains an open issue: https://bugs.ruby-lang.org/issues/9262

I insist, it's mostly solved: http://tmm1.net/ruby21-method-cache/

Re: The Dynamic Def – abusing Ruby's def statement

#78

Ruby is really fun to program in, but debugging it can be hell. I would not be very enthusiastic about debugging this code. But still, this is a really cool trick that I didn't know you could pull off with Ruby, so thanks for sharing!

Debugging Ruby, in general, is often a pain in the ass. It stems from an awful combination of terseness and metaprogramming. The terseness comes from using an identifier to both represent variable reference and method invocation. Contrast this to Lisp-like langauges, where function (or macro) invocation only happens in the first position of an S-expression. In Lisp, it's clear that an identifier is either a variable…

Particularly if one of those gems is Rails.

Re: The Dynamic Def – abusing Ruby's def statement

#79
post #70

Earlier quoted context omitted.

> But the problem there is, you can't simply grep the code for things like this. You could end up with 100s of uses of the identifier and never find the source. Personally, I see this as the entire point of the original Smalltalk-esque OOP paradigm: an object is a living, mutating black box that responds to messages, where an object's "type" is just "object": a thing with a protocol for sending and receiving arbitrar…

> People who say Erlang is a functional language are looking on the wrong level of abstraction. Processes are objects! People thinking those two are mutually exclusive are not getting the difference between the map and the territory :). I worked a bit with Erlang professionally. Erlang is both functional and object-oriented, if you think about Smalltalk-style OOP and not Java-style OOP (the constant confusion between…

> Yes, you could program treating everything like a web startup, but few simple assumptions like "this is inside my program so it responds fast and lives as long as the rest of the program does" can help tremendously improve the speed of coding and the speed of the program itself.

I think the distinction between regular Smalltalk OOP (designed before distributed programming was a thing) and what people actually mean when they call a language "actor-modelled" is that, in a language like Erlang, you get to lean on the language itself (or in Erlang's case, the OTP framework) to be pessimistic about other objects' behaviors for you.

One thing that I think is missing from today's OOP language landscape, though, is a concept of protocol parameter adjustment for long-lived peers, ala TCP window scaling. While I wouldn't expect this of anyone's one-off RPC protocol, the frameworks like OTP, specifically made to cleverly handle OOP RPC stuff, should be capable of multiple levels of "formality" in how objects speak to one-another, where an object that e.g. repeatedly sends messages to a named service should be eventually JIT-optimized into one that grabs the PID of that named service and messages it directly. Of course, if that named service dies, the process will crash—but like any other JITed code, that's just the point at which the JIT abort-traps back into the un-optimized codepath and re-runs the function. This can be generalized to an arbitrary degree; you can go so far as to imagine e.g. distributed Erlang nodes that pass bytecode to one-another and gossip about code-changes, in order to be able to JIT-inline remote (pure) function calls.

Re: The Dynamic Def – abusing Ruby's def statement

#80
post #20

Zork-alikes aside... I'm looking for practical applications :) I recently re-watched Yaron Minsky's "Effective ML" talk where (towards the end) he talks about making read-only and read-write types. That's where I thought Jamis was going with the state machine example: one could tell an object "yo, make yourself immutable!" (ie redefine your methods so that you can't change yourself). But in an OO world that seems mor…

One use might be to create simple singletons. That said, I'm not entirely sure how you would do it, maybe have `Object#initialize` redefine `Object#new` to always return the singleton.

Yup, here's how to use it to define a Singleton that is transparent to the caller:

    class S
      class  #
    b = S.new
    #=> #
    a.value = "zzz"
    b.value # "zzz"
     
    a.object_id == b.object_id
    #=> true
That said... doing this may win you great sorrow.
Post reply on HN