erpicci

Villager
Member
Joined
Feb 3, 2015
Messages
29
Reaction score
21
First Language
Italian
Primarily Uses
Dear all,

I'm relatively new to Ruby and I came up with this question: should I use a functional style as often as possible? Or the other way around?

For instance, consider this piece of code (a function which skips spaces in front of a string). Which approach should I prefer, and why?

def functional_style(string) return string unless string[0] == " " return functional_style(string[1..string.size])enddef imperative_style(string) i = 0 while(string == " ") i += 1 end return string[i..string.size]endNote: Of course I understand that there are better way to do a stupid trim space, this is intended as toy example. I also deliberately didn't check wheter string is empty in the first place, because it is not relevant.

Thank you in advance!
 

Zeriab

Huggins!
Veteran
Joined
Mar 20, 2012
Messages
1,326
Reaction score
1,531
First Language
English
Primarily Uses
Other
Choose imperative or declarative style as you prefer. Whatever you think is the cleanest in your given situation.

Personally I think the negative effects of Ruby's duck typing affects the functional style more than the imperative style.

*hugs*

 - Zeriab
 

Tsukihime

Veteran
Veteran
Joined
Jun 30, 2012
Messages
8,564
Reaction score
3,911
First Language
English
I generally avoid recursion unless the code would be much cleaner.


I tend to think recursion can be confusing compared to a loop.
 

TheoAllen

Self-proclaimed jack of all trades
Veteran
Joined
Mar 16, 2012
Messages
6,814
Reaction score
9,142
First Language
Indonesian
Primarily Uses
RMVXA
Pick the style as you want. But I think, the regular looping usually make your script more readable.
 

Zeriab

Huggins!
Veteran
Joined
Mar 20, 2012
Messages
1,326
Reaction score
1,531
First Language
English
Primarily Uses
Other
Oh, good point. Most scripters here are probably not used to seeing functional code, so take that into consideration should you want to write public code.
 

bgillisp

Global Moderators
Global Mod
Joined
Jul 2, 2014
Messages
14,241
Reaction score
15,317
First Language
English
Primarily Uses
RMVXA
I would go with the functional style just because of this:

One year later, when you look at your code, you are more likely to understand what you wrote and why it works if you use the loops and such like you did. On the other hand, in the other style, you are more likely to go "What in the $%^^@ did I write???". At least, that is what happens to me when I try to get cute and make my code too compact like that.
 

Zeriab

Huggins!
Veteran
Joined
Mar 20, 2012
Messages
1,326
Reaction score
1,531
First Language
English
Primarily Uses
Other
Erhm... The functional programming style focuses on function evaluation. It is the recursive variant.

You are arguing for using the imperative programming version.

Functional programming requires a different way of thinking to work with the different approach.

I recommend looking into it if you are unfamiliar with the programming paradigm. Being able to look at a problem from different angles can definitely be a strength.

*hugs*

 - Zeriab
 

Kaelan

Veteran
Veteran
Joined
May 14, 2012
Messages
811
Reaction score
567
First Language
Portuguese
Primarily Uses
RMMV
I find the imperative format easier to read most of the time, so I'd go with that. The only exception would be when writing code that lends itself well to recursion and doesn't have a very intuitive iterative solution, like (to pick a trivial example) tree traversal.
 

Tsukihime

Veteran
Veteran
Joined
Jun 30, 2012
Messages
8,564
Reaction score
3,911
First Language
English
Oh, good point. Most scripters here are probably not used to seeing functional code, so take that into consideration should you want to write public code.
Then again, as someone else said, not being used to it doesn't mean they should avoid it.


For example for tree-traversal/manipulation I'd go with a functional approach because everytime I try to use a loop, it turns into a nightmare.
 

Lemur

Crazed Ruby Hacker
Veteran
Joined
Dec 1, 2014
Messages
106
Reaction score
124
First Language
English
Primarily Uses
...wait, what now? Functional programming is not inherently tied to recursion you know.

The key ideas behind functional programming are:

  • Immutability - all variables are final
  • Idempotance - a function can be applied multiple times without changing the result
  • First Class Functions - You can pass functions as arguments
  • Composability - Programs are made by composing functions together to make larger functions
The key goal here is to eliminate state and mutability for code that will always perform the same way. By not having your functions mutate state, you can call functions in any order without worrying about it. This is also a lead in to safe parallelism.
Now in Ruby we have the enumerable module which allows large amounts of functional programming techniques without the need for recursion. Take for example summing an array of numbers:

numbers = [1,2,3,4,5] # or a range, but this is clearer for newbies# Imperativesum = 0for i in numbers sum += iendsum# => 15sum = 0numbers.each { |i| sum += i } # which is funny considering that's an anonymous function, but I digresssum# => 15# Functionalsum = numbers.reduce(0) { |accumulator, i| accumulator + i }# => 15sum = numbers.reduce:)+) # Shorthand# => 15Reduce in this case is a prime element of functional programming. It takes a set and reduces it into a single number. To lay that out for you step by step:

Code:
sum = numbers.reduce(0) { |accumulator, i| accumulator + i }# Initial loop (accumulator = 0, i = 1) returns 1, which goes into the value of accumulator for the next loop# (accumulator = 1, i = 2) returns 3# (accumulator = 3, i = 3) returns 6# (accumulator = 4, i = 6) returns 10# (accumulator = 10, i = 5) returns 15# There are no more numbers, the accumulator is returned
Now how does reduce work? Let's demonstrate using recursion:
Code:
reduce = -> collection, fn, accumulator {  if collection.empty? # We're out of elements, give back the accumulator    accumulator  else    head = collection.first  # Get the first element of the list    tail = collection[1..-1] # and the rest of the elements    reduce.call(      tail,                      # We send the rest of the list in for the next loop      fn,                        # and the reducing function      fn.call(accumulator, head) # while setting the accumulator equal to the result of the function    )  end}# And we do the same summation functionreduce.call(  [1,2,3,4,5],  -> acc, i { acc + i },  0)#=> 15
The other common staples of functional programming are the map and filter functions. In Ruby, we use:
Code:
numbers = [1,2,3,4,5]# Applies a function to each element of the listnumbers.map { |i| i * 2 }# => [2,4,6,8,10]# Returns a list in which all elements return true when given to the functionnumbers.select { |i| i.even? }# => [2,4]
Shall we implement these ones as well?
Code:
map = -> list, fn {  if list.empty? # If it's empty we hit the end    []  else    head = list.first  # Get the first element    tail = list[1..-1] # and the rest of them    [fn.call(head)].concat map.call(tail, fn) # apply the function to the head, and recur on the tail  end}map.call([1,2,3,4,5], -> i { i * 2 })# => 15select = -> list, fn {  if list.empty?    []  else    head = list.first  # Get the first element    tail = list[1..-1] # and the rest of them    matched_element = fn.call(head) # If the function returns true    if matched_element      [head].concat select.call(tail, fn) # We add the element to the list    else      [].concat select.call(tail, fn) # We add nothing to the list    end  end}select.call([1,2,3,4,5], -> i { i.even? })# => [2,4]
Now the nice thing is that Ruby already provides tons of these features, has first class functions, and it uses them widely (what do you think a block is? An anonymous function)Granted those are not concise implementations, I don't mean them to be. They're using tail recursion, something that should be used sparingly in Ruby unless you have the optimization turned on. This won't be an issue in most cases, but you end up with stack level too deep on larger problems. TCO fixes this issue, but I won't delve into that one.

Now what baffles me is that most people think that OO and Functional are opposites. No. They're not. You can still have OO in a functional style, mainly through usage of let and lambda, but that's in the purist kingdom of lambda the ultimate. The point is to eliminate superfluous state, not all of it. That's Haskell's main pull, but it will drive most newbies off a cliff unless they're math types.

RGSS is heavily heavily imperative, uses global variables freely, and in general makes a mess of things. I won't hold punches, it's a mess. That doesn't mean the code you write has to be as well. Avoid mutating variables when possible, keep your functions simple and succinct, and avoid the globals as much as possible.

As far as deep recursion, tree problems, and more complicated issues loops will not work. Dynamic programming is one such task that's impossible without recursion. There are several problems where imperative techniques will gimp you, and most of them happen to come up in job interviews.
 
Last edited by a moderator:

erpicci

Villager
Member
Joined
Feb 3, 2015
Messages
29
Reaction score
21
First Language
Italian
Primarily Uses
Well, first of all thank you for your replies, everybody!

Oh, good point. Most scripters here are probably not used to seeing functional code, so take that into consideration should you want to write public code.
I think that, more often than not, someone else will [have to] read your code, so...

The key ideas behind functional programming are:

  • Immutability - all variables are final
  • Idempotance - a function can be applied multiple times without changing the result
  • First Class Functions - You can pass functions as arguments
  • Composability - Programs are made by composing functions together to make larger functions
The key goal here is to eliminate state and mutability for code that will always perform the same way. By not having your functions mutate state, you can call functions in any order without worrying about it. This is also a lead in to safe parallelism.

[...]

tail recursion, something that should be used sparingly in Ruby

[...]
Couldn't agree more, both with this and the rest of the post. Still, there are some obscure points for a Ruby/RGSS beginner like me:

  • "First Class Functions - You can pass functions as arguments"... and return them (upwards funarg), so you could also return a locally defined function which relies on -say- variables living in the local scope of the "parent" function... when those variables are mutable (as it is in Ruby), isn't this a sneaky way to create a state? (Or is the answer "simply don't do this"?)
  • what's wrong with tail recursion in Ruby?
  • some things need to be done in an imperative fashion, I/O for instance; Haskell has a nice way to separate pure functional from imperative (monads), but Ruby looks a bit messed up to me (ok, there is that coding convention about the "!"...), so I don't know whether I should try to somehow separate the two (Haskell way) or to freely mix them. In the latter case, I'm probably losing some of the core aspect of functional programming, like immutability or idempotance, am I not?


[...]

RGSS is heavily heavily imperative, uses global variables freely, and in general makes a mess of things. I won't hold punches, it's a mess. That doesn't mean the code you write has to be as well. Avoid mutating variables when possible, keep your functions simple and succinct, and avoid the globals as much as possible.

[...]
Well, you got directly to the core of my doubts.

For instance... consider this problem where I have to implement a person class. This person is quite boring: it has a life where she constantly listen to a question, think_of_an_answer, talk saying the answer and then listen again.

For my (little) understanding of Ruby, the ruby-ish approach would be something like this:

class Person def initialize @question = "" @answer = "" live! end def live! loop do listen! think_of_an_answer! talk! end end def listen! @question = Input.read # User inserts a string message end def think_of_an_answer! @answer = some_decision_algorithm(@question) # Decide an answer based on the question end def talk! Input.write(@answer) # User is prompt with the answer endendOnce again, this is intended as a toy example.

So... is this a good approach? What are pro and cons?

As a pro, I would say that is should be quite easy to understand. As a cons... well, mutable (object) state.
 

Latest Threads

Latest Posts

Latest Profile Posts

Update on the Unity thing: I had to redo all my map graphics for Unity because the originals were from XP, but it actually looks so much better now in Unity, so I genuinely might make the switch.
Messing around with fragment shaders (filters). This thing is HUGE and I'm surprised almost no one makes good use of. Even the number of plugins that do this stuff are limited.
ScreenShot_6_28_2022_3_2_40.png

There's 7 main areas in the game that are all connected. This train get's you from an underground lab to the center of someone's subconscious. inside the train you can talk to some of the passengers. but going to the top of it, there's battles.
Speedrunning all character portraits just so they all have the same quality. Not sure if this was a good idea, but now I'm almost done.
We now have a gameplay video!

Forum statistics

Threads
123,154
Messages
1,154,460
Members
161,504
Latest member
yamilonewolf
Top