Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
... because Ruby isn't terse enough yet (raganwald.com)
17 points by mdemare on Oct 27, 2007 | hide | past | favorite | 27 comments


What is a "point-free holomorphism" ?

I study math, hack Scheme, and find myself totally confused by terminology.

Maybe it's a riddle or something -- "What does complex-smoothness have to do with lambda calculus?"


The point-free style means avoiding lambda expressions: http://rickyclarkson.blogspot.com/2007/09/point-free-program...


It was actually a typo. Fixed, thank you.


No, thank you.

I've just been forced to learn about a new language proposal, a new (to me) set of morphisms, and a nonstandard approach to proving stuff in topology. That's a rich link.


In Paul Graham's Accumulator Generator comparison (http://www.paulgraham.com/accgen.html) Ruby is now terser than any language except Arc.

    foo = 'n -> x -> n += x'.to_proc

    f = foo[4]
    f[2] # => 6
    f[2] # => 8
(This would be even terser:

    foo = &'n -> x -> n += x'
but the ampersand syntax is only allowed after a method call.)


I think Haskell is overall terser than Ruby. The trouble with the accgen test is that it's imperative, and Haskell isn't meant to be. Here's the Haskell implementation that I would submit:

  putStrLn "Don't do that, you nimwit!"


The Erlang version is unnecessarily verbose (and also semantically different) because it uses 2 processes. It can be rewritten as

Foo = fun(N) -> put(var, N), fun(X) -> V = get(var) + X, put(var, V), V end end.

It's not as concise as Ruby or Arc, but it's a one liner. However, similar to Haskell, this style of programming is discouraged in Erlang.


In ML you can get a similar terseness to that of Haskell, but with support for imperative programming. If we allow the increment function to be predefined (as the Arc example does):

 let inc a b = a := !a +/ b; !a
then foo becomes:

 let foo n = inc (ref n)
or, with the help of the composition operator:

 let (<<) f g x = f (g x)

 let foo = inc << ref


For comparison, the original Ruby version:

  def foo (n)
    lambda {|i| n += i } end
It's five characters longer, but approximately 350% more readable.

Of course, a lot of that is because I'm more familiar with standard Ruby syntax than I am with this hack... but doesn't that tend to prove my point?


Other measures of brevity:

The original ruby definition is 15 tokens, the .to_proc version is 13 tokens.

Orthogonality:

to_proc

        3 variables (foo, n, x)
        1 method call (to_proc)
        1 type of separator (')
        2 assignment ops (=, +=)
        2 binary operators (->, .)

        9 different kinds of tokens, in 5 (arbitrary) categories.

        Original:
        
        3 keywords (def, lambda, end)
        3 variables (foo, n, i)
        5 Separators (|, {, }, (, ))
        1 assignment (+=)

        12 different kinds of tokens, in 4 categories.
So the only way the original ruby version comes out ahead by these measures is that it has "fewer categories" of token types that are required. Also I think that with the 'def foo' declaration, 'foo' is not technically a variable. I don't really know ruby well enough. But you get the idea.


Well, by definition it's more readable, since nobody is familiar with the new notation yet. But objectively it has problems: unnecessary def and lambda keywords, using () and || for argument lists, using def .. end and {} for scopes. There's room for improvement.

Personally, I like many things from Haskell's syntax. I wish I could say this in Ruby:

    foo = n -> x -> n += x 


The python implementation looks overly complicated. I was thinking that the ruby and python implementations would be very similar. Here it goes

def foo(n):

    return lambda i:n+i
Tried it (Python 2.5) and it works.


That should read:

    def foo(n):
        n = [n]
        def bar(i):
            n[0] += i
            return n[0]
        return bar
Python really needs a scoping syntax to allow assignment to closed variables.


Actually it doesn't work - calling the returned lambda should increment n - your implementation doesn't.


That holds true for the given ruby implementation as well, since n is incremented only in the scope of foo and not in the calling scope.

def foo (n)

    lambda {|i| n += i } 
end

n=5

a = foo(n)

puts a.call(3) #prints 8

puts n # still prints 5. n is not incremented

I agree that the python one is strictly not according to the problem definition, but for all practical purposes both python equivalents are the same, aren't they? or am I missing something?


This is shorter than both the Ruby and the Arc versions:

{x set y+eval x}

It's in q, a verbose version of a language called k.

Applying it to a symbol/variable `n will give you a functional projection you can apply to any i.


This is cute but first-class functions would be better. I've had to switch to Python to find them.


The Perl Golf anti-pattern.


I tend to agree with you. It's obvious to me that the following code evaluates to 6:

  r = 2
  foo = lambda {|x| x * r}
  foo.call(3)
And it's obvious to me that the following code raises an ArgumentError:

  foo = lambda {|x,r| x * r}
  foo.call(3)
  
But what does this do?

  r = 2
  bar = 'x*r'.to_proc
  bar(3)
Do I get the 6, or do I get the ArgumentError?

UPDATE: I presume the answer is "ArgumentError" (because the alternative appears to be positively evil), and that what I really need to do is this:

  r = 2
  bar = 'x -> x*r'.to_proc
  bar(3)

Why is this such an improvement over lambda? Couldn't these folks just fix lambda (e.g. to remove the need for that annoying .call method every time I use the result) rather than deciding that every Ruby programmer needs to learn yet another argument syntax?


"these folks"

There are at least two kinds of folks: those who are "fixing" Ruby the language, such as the Rubinius team, and those who are "using" the language.

I am in the latter camp. So sometimes I complain about what is broken--http://weblog.raganwald.com/2007/02/why-ruby-is-not-acceptab... sometimes I hack around it. But no, I am in no position to change the language. If I was, I would actually implement the point-free and underscore conventions natively.

If you want to use closures, you want to use lambda, not String#to_proc. If you dislike using .call, Ruby also allows [] syntax, so:

    r = 2
    foo = lambda {|x| x * r}
    foo.[3]
May be a little less objectionable.


I cannot get foo.[3] to work:

  syntax error, unexpected '['
But please don't spend too much time explaining what I'm doing wrong, because I'm afraid I don't like foo.[3] anyway. :)

Let me demonstrate my problem by taking a simulated tour through my brain as it encounters foo.[3] for the first time:

"WTF?

"Did someone mistype foo[3]?

"No, wait, surely it's supposed to be foo(3). I'm pretty sure that foo is a function. Didn't we set it equal to a lambda expression three lines ago?

"Maybe I need glasses. Let's adjust my monitor. No, those are square brackets, all right. And that really is a period between "foo" and the brackets.

"Maybe Google can help. But I'm not sure how to google '.[]'

"Oh, no -- is this one of those mysterious edge cases of the Ruby syntax? Do I hear _Why laughing at me? Does foo.[] magically target the eigenclass, or the metaclass, or the zetaclass, or something? Or did one of the libraries secretly redefine the core language behind my back?

"omigod, I should have learned Python.

"No, wait! The dot means that we're calling a method. And I know that [] is a method -- we redefine it all the time. So foo.[3] is just an obscure-ass way of calling foo[3] .

"Which makes no sense, because I thought foo was a function. So I must be wrong.

"WTF?"

[ten minutes later]

"Aargh! I went through ten minutes of confusion just to save three characters?"

I may have exaggerated this a little. And, yet, I really did have no idea that .[] works, and I still have no idea how it works. (Of course, in my version of Ruby, it apparently doesn't work, which makes it hard to investigate.)

Now, don't get me wrong -- Ruby is my favorite programming language. Quite often, after the tortured syntax, the monkey-patched class, or the strange new DSL stops you dead in your tracks, you'll struggle through the learning curve and discover that you have a powerful new technique. This starts on day zero of Ruby, when you see your first block. It looks like nothing on Earth, but it turns out to be insanely great.

Symbol#to_proc is a borderline case. I like the idea, but I hate the syntax. The &: doesn't bother me as much as the lack of curly brackets. To me, idiomatic Ruby is about blocks -- I expect map() and inject() and delete_if() to take blocks, not arguments. I would really prefer this:

  my_array.map {_.get_funky}
Reading this makes me happy. There's a map() call, so I expect a block, and look -- there are the curly brackets. And because it's a block, the stuff inside it must be a method call. And, in fact, the underscore and the dot mean that we're invoking a method on a placeholder. All is well with the world, and no higher brain functions need to be engaged.

But, you know, life isn't perfect. Language is about communication, and ultimately the language you speak is determined by what other people are speaking. Symbol#to_proc is powerful enough to become popular, and popular enough to be idiomatic, so we all have to learn it. Perhaps I'll even start using it.

But I'm afraid String#to_proc, like foo.[x], is past the point of diminishing returns. Do I really need a version of lambda that is not only utterly unlike Ruby's usual lambda, but is even less like Scheme's lambda? And are my colleagues really going to forgive me for asking them to learn Haskell syntax in order to read something as trivial as lambda {|x,y| x+y} ? I'm not even sure if I could forgive myself!


"I cannot get foo.[3] to work"

Hey, neither can I. Quite possibly because foo.[3] actually doesn't work, it's foo[3] that works and I made a typo.

Sorry about that, and especially too bad considering how your first reaction was to consider the possibility that your version of Ruby was at fault instead of the possibility that I made a careless error. I appreciate the implied vote of confidence, but I'm aghast at the consequences.

I appreciate your taking the time to share your reaction. I'm mulling over exactly what I prefer about the point-free syntax, and the prod to think thinks over may help me understand my own views in more depth.


So, part of me is tempted to use this as an example of the reason why languages benefit from a certain amount of redundancy and consistency. :)

The other part of me is quite embarrassed, because I thought I had tried foo[3] and failed. I guess that's why everyone in the Ruby community prefers automated testing!

Of course, if I thought that applying an array reference to a Proc made any sort of intuitive sense, I might have tried it more carefully. :) I still wish that I could just say foo(x).

Anyway, I'm glad you appreciated my report from the field. And of course you prefer the point-free syntax -- you know Haskell, you know Lisp, you seem to know a certain amount of mathematics. It all makes perfect sense to you, just as I can only dimly remember the time when vector calculus didn't make perfect sense to me.

But I shouldn't indulge my love for German by randomly German syntax into my English prose inserting. Unless I have a very good reason: perhaps I want to borrow the uniquely compact German word shadenfreude, or perhaps essential it is the alien character of Yoda to establish. When you break the rules in Ruby code that you intend to show to someone else (and is there really any other kind of code?) you need to convince the reader that you had a very good reason, because you're making him or her work harder to understand you.

Keep playing with the syntax for a while. You may find that it's so elegant that it is worth defending against the inevitable skeptics. And, if not, at least golf is a very pleasant way to pass the time!


"golf is a very pleasant way to pass the time!"

Golf is a good walk spoiled => Golf is good code spoiled.


It's foo.[](3)


Okay, I thought you were pulling my leg. But, in fact, that does work. Thanks!

Meanwhile: My head just exploded.


hmm, i dunno... self-modifying code can lead to insanity... :)




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: