Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

In the "things learned from Ruby" section:

  Focus on strong object oriented programming
What I've learned from Ruby is to favor functional programming concepts - minimize side effects, isolate state mutation, etc. Ruby is definitely OO, but when my Ruby code is more functional it's less buggy, easier to understand and move around, and generally less convoluted.


Like Gary Bernhardt's notion of a "Functional Core, Imperative Shell" — https://www.destroyallsoftware.com/screencasts/catalog/funct...


Damn, I knew I got this idea from somewhere, I just totally forgot where. Thanks for posting this link - I'll have to rewatch it.


Brian Goetz has talked about this as well - see FP is Dead - Long Live FP (https://youtu.be/ROL58LJGNfA).


I watched that video in my earlier 20s and it changed how I wrote code.


I've always had better luck restricting FP to using things like map, reduce, filter, (etc) inside methods as an implementation detail, but having the structure of the app use OOP.


Incidently that is how FP in done in Smalltalk as well.

All those methods, and reactive patterns (Observer and such) were already present in Smalltalk-80.


Elixir, I suppose, would be a 'modern' example too.


This is where I've landed when writing Ruby code, as well, and it's served me really well.


Ruby used functionally is great, but it takes an already time-inefficient language and amplifies that weakness (lots of creating new collections and returning them, whereas normally in Ruby you would modify elements within the collection).


Isn’t there something called LazyEnumerator that deals with this? In terms of chained .map at least


Lazy enumerators don't make each individual step of the iteration any more efficient. They just let you stop the iteration partway through without traversing the entire original enumerable. Given this contrived example:

   a = (1..1000).lazy.select(&:even?).map{|i| i*2 }.map{|i| i + 1 }.map(&:to_s).map(&:reverse)
There is no benefit from lazy if you do `a.join(",")`, since that iterates over the entire sequence. But if you do `a.take(10)` or `a.detect{|s| s == "14" }`, the chain will only be executed for each item in turn until 10 elements are produced or an element is the string "14", respectively.


Ruby's composabilty really puts Python to shame. I can't imagine how Ruby lost out to Python.


While I much prefer writing Ruby to Python, Python is just dead simple to get real work done with. It's not elegant, but it's still so much less nasty than C++ or Java.

Ruby's #1 problem, for me at least, is that your small project someday runs into a performance wall. I don't know the latest benchmarks, but last I recall Python was about 8x faster than Ruby when both are being interpreted. Yes, there's JRuby, but that's not something you can drop into a new system and do useful things with immediately (without more setup).

And with Clojure, you get all the elegance and lovely collection manipulation tools you can possibly want, much faster performance, and a huge stable pile of Java libraries (compared to Ruby).

So with Python and Clojure as one's main tools, life is quite nice.


Say what? Ruby has been faster than Python for interpreted code for years now.

Granted, Python is faster for tasks like those Numpy solves, but in arbitrary execution performance, python is just slower. 8x has NEVER been true. That must have been you doing some really bad stuff in one of them but not both.


I stand corrected. I used Ruby for 6 years, up to 2015. I wasn't using Python during those years, but I thought I had heard or read about the 8x speed difference in favor of Python.

For sure, Ruby was slow for what we were doing... processing millions of rows of data was so slow that it caused me to decide to try Go (and the same Go program was hundreds of times faster). But I didn't try Python then.


I think there was a short period about 11 years ago before the release of Ruby 1.9 where the Python VM was basically re-written to be as fast as YARV, but YARV wasn't released yet.


MRI Ruby has been slightly faster than Python for about a decade and the next release of Ruby will contain a basic JIT compiler giving MRI Ruby a significant advantage over CPython.


What's your evidence for Python ever having been 8 times faster than Ruby? Apart from the numeric libraries, written mostly in C or Fortran, Python has at best only ever gained a very marginal speed lead over Ruby and that has been wiped out in recent releases of MRI. Ruby's startup time is a bit slower than Python's but once they're off to the races both of these horses have been neck and neck for a long time.


There is absolutely a benefit to lazy if you're doing more than one combinator even if you ultimately need to iterate over the whole list such as the join case.

The lazy version basically turns into

   a = ""
   (1..1000).each { |i|
       next if i.odd?
       i *= 2
       i += 1
       a << i.to_s.reverse + ", " # ya ya, trailing comma
   }
Where as the non lazy version turns into:

    odds = []
    (1..1000).each { |i| odds << i if i.even? }
    doubled = []
    odds.each { |o| doubled << o * 2 }
    incremented = []
    doubled.each { |d| incremented << d + 1 }
    strs = []
    incremented.each { |i| i.to_s }
    a = ""
    strs.each{ |s| a << s.reversed + ", " }
Which means that you have you went from N iterations to 5*N iterations, with 5 intermediate arrays in this case.


That's how it could work theoretically, but in practice lazy iterators are much less efficient due to their implementation. I benchmarked it here:

https://gist.github.com/mboeh/bb480d93c71046e23816f2c24c23e3...

When applied to the same amount of work, lazy iterators consume more memory and take more CPU time than the equivalent non-lazy chained iteration.

There's a place for lazy iterators, but you should pretty much always profile first. If you have performance problems due to a long chain of iterators, you're always going to get better results by merging some of those iterators into a single block than by making the whole chain lazy. The best use case for lazy iterators is if you're trying to avoid having the original collection in memory (if it's being streamed from a file or the like). And at that point, if you're trying to optimize for performance, you should avoid chained iterations, period.

The good news is that in the vast majority of cases this just doesn't matter. Chaining iterators works fine until you start running into performance problems.


Thanks a lot for the insight!


I've learned to think of objects as not exactly monadic, but monad-like in their pattern of use: whenever there's a bit of state that I need to track and isolate from all other state in the system, wrap that in a class. The allowable state transitions then become methods.


Right, and most of the times, if state have different life times, then it should be in different classes.


What do you mean by life times?


Is the state eternal or not (ie is it immutable)? Will the state typically change at the same time as some other state (like an address, or stuff related to some other event)?


As someone stated, good OO and good functional is approaching the same goal from different sides. Your code being functional doesn't really make it less OO.


Except if you're using immutable data, pure functions and explicit state there's a lot less room for OO which (as presented by ruby, java etc) involves mutable data accessed/changed via methods that conceal their state.


Nothing in the concept of OO enforces mutability. The paradigm doesn't enforce anything at all there. You want immutable objects that never changes once created? Fine, go for it.


I'm literally writing a blog post about this now (I'm building an objects system from scratch using only FP techniques in JavaScript... slightly eclectic ;-) ). The very interesting thing about this exercise is that immutability solves a huge host of problems in OO systems. As a very small example, even the diamond inheritance issue just goes away because nothing is ever updated. If B and C both have the base class A, you can safely instantiate A twice because it doesn't matter which one you use.

I'm about half way done. When I'm done, I'll post it on HN, but if you are interested here is what I've got so far: https://github.com/ygt-mikekchar/oojs/blob/master/oojs.org If anyone ends up reading it, I'm very happy to receive criticism, so feel free to leave an issue.


Sure, you can simulate immutability in Ruby or Python but it's not real immutability. It's just shallow freezing. The real point, however, is that immutability is not idiomatic in OOP languages. Objects are intended to store state and have it modified. It's not what a language can be made to do that matters. It's what is idiomatic.


And yet 99% of code written in Java, Ruby, <Generic OOP language here> is based on mutable objects.

Its not about what is possible, we're all familiar with the turing completeness of everything. It's about what is: the default + easy + encouraged in the ecosystem.


Yes - Ruby makes it easy.

I'm run into the downside, though, that Ruby isn't optimized for immutable operations - tons of object instances created. I've needed to 'pythonize' my Ruby code in the worst cases - using mutable functions to dramatically reduce memory usage.


Can you say more about this Pythonisation process? How is Python any better at immutable operations? Ruby and Python are both OO languages which can be used in a procedural style. Hell, you can write a whole app in Ruby without creating a single class if you so choose.


I think what they mean by 'pythonize' is ditching fluent functional style and embracing dumb imperative mutability for the sake of efficiency. Rewriting sequence compositions as stateful for-loops. Idiomatic Python is more pragmatic than Ruby in that regard, stupid=good (yet ironically Ruby's strings are mutable and Python's aren't :p).


IMO, Python is worse at immutable operations. Ruby makes it easy (but inefficient). Python makes mutable code the easy path.




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

Search: