I've embraced functional programming and Clojure is my new hobby language.

Mahzkrieg

New member
Nov 13, 2007
585
31
0
Austin, Texas
tl;dr: I found out I like parentheses and want more of them in my life.

----

It bewilders me that it was only a few years ago (late 2010) that I asked this very subforum what languages I should pursue to build an app idea that I had. After extensively dabbling in PHP, Python, and Ruby, I went with Ruby and then Rails in particular.

Since then, I've come a long way. I landed my first and current Rails job last year.

But I've learn even more in the past few months. I've been consuming a lot of programming literature from people like Martin Odersky (Scala creator), Rich Hickey (Clojure creator), Sandi Metz (OO Design), and those respective communities.

----

Stumbling upon functional programming via Odersky's Coursera course on Scala was my initial eye-opening encounter.

In particular, it was the first time I encountered the concept of "immutability". And it was there that I realized that mutation is perhaps the #1 culprit of unreadable, unintelligible code that I run into on a daily basis.

How often do we run into the following example?

@x = 10
do_something()
@x == 10 # false. @x is now 42. how? *magic*

Every day.

@x is mutated somewhere. do_something() has side-effects that we now have to hunt down. Our mental stack grows another few layers and with every layer we risk an overflow.

do_something() probably calls methods and those methods probably have more side-effects in store for us and now we're playing a game of Blue's Clues in our codebase grepping for /@x/ just to see what the actual fuck is going on.

----

I first looked for providence in Scala. It felt like Ruby. Lisp's parentheses looked annoying so I initially ruled Clojure out. Haskell just didn't feel right.

But I had a lot of trouble setting up Scala with sbt. I hated Eclipse/Idea. And Vim is not a good tool for a compiled language when you're just exploring a new paradigm.

But recently I gave Clojure another look and this line in particular stuck out at me in its rationale:

Mutable stateful objects are the new spaghetti code
That's where I connected with the entire spirit of Clojure.

I started watching a some Rich Hickey talks starting with his famous Simplicity Made Easy. He even gave that talk at Railsconf last year where he expressed Easy != Simple.

That's where I realized my problem with nontrivial Rails apps: Rails makes things easy. `gem install hairball` makes things easy. It's easy to accrue technical debt. Instead, it should be simplicity that's on the pedestal.

----

I've been using Vim for 3 years. After trying Clojure in Vim, Eclipse, LightTable, and Idea, I ended up trying Emacs + Evil mode (Vim bindings) less than a week ago.

Pure bliss.

I've found a community that helped me vocalize my main frustration with code and I realize that immutable Lisp expressions truly allow you to compose large concepts with small functional operations.

----

I've been playing with Clojure for a short while but it's already made me a much better Ruby programmer. I look forward to the next few years and the novel a-ha moments I will have.

----

And, most importantly, I'm now an unbiased Emacs/Vim warrior. :love-smiley-086:

fn.png


Sent from my Emacs buffer.
 


good job man - its not easy to learn to program, sounds like you've dove in feet first. unfortunately i stopped coding years ago but still love emacs and remember all vi functions as well.
 
I'd recommend any programmer seriously try a functional programming language for at least a couple months, even if they never use it again.

It forces you to think very differently about things when you suddenly don't have access to variables, or even loops. I haven't seriously programmed in a functional programming language in a couple years (and I hated doing it at the time) but the knowledge gained from it was very valuable.
 
I can recommend 'the little schemer' for those looking to get started with functional programming, great little book.
 
I guess, cool story bro. No one cares.

jennifer-lawrence-oh-yeah-thumbs-up.gif


Vim/Emacs, lol.

cheryl-cole.gif


lrn2lrn rage9.

OP, good for you, this is what being a programmer is about, constantly improving.

I think any code that modifies stuff outside of local scope is horrible, everyone please stop doing that.
 
I really want to dive into functional coding just for the lulz and self-education but I certainly won't be using it for any production code. But alas, real life work gets in the way.

Was a Vim fanboi a few years ago but it's all GUIs now, Eclipse and PHPStorm. Just couldn't remember all the commands!
 
I think any code that modifies stuff outside of local scope is horrible, everyone please stop doing that.

You'd love PHP then. Makes that type of thing impossible, as variables get localized to their function. Give it a try, you'll love it!
 
PHP: Variable scope - Manual

Please do more research on the stuff you claim to be an expert in, in future, I only have limited time every day to perform five second google searches.

Right. You were bitching about people modifying variables outside of local scope, right?

That's impossible with PHP, because you don't need to localize variables, as PHP does it automatically. You can assign all the variables you want within a function, and they're going to stay within that function. They're not going to be available elsewhere in the software.

As with anything, there's pros and cons to that.
 
Right. You were bitching about people modifying variables outside of local scope, right?

That's impossible with PHP, because you don't need to localize variables, as PHP does it automatically. You can assign all the variables you want within a function, and they're going to stay within that function. They're not going to be available elsewhere in the software.

As with anything, there's pros and cons to that.

Please see the section referencing global on the page I linked. I see "impossible" stuff often in php.
 
When I made this thread, I originally crafted a post where I rewrote one of my Sinatra CRUD apps with Clojure but the forum ate it so I furiously went into hiding until now.

I'd like to port some of my Ruby scrapers/apps/scripts to Clojure to better represent what I'm talking about in this thread.

I'd also be interested in converting any Ruby that someone posts to Clojure.

----

Here's something I'd like to share:

1,000 different classes vs. a few different data-structures

After working with Clojure's paradigm of having 1,000 functions on a few data structures, I've become more impatient with the OO paradigm of a 1,000 different classes each with its own interface.

gv.png


I argue that many of the classes we write would actually be more convenient as hashmaps.

Here's how I'd write the above concept in Clojure:

gu.png


One of the benefits is that language's first-class data structures already have well-known interfaces.

Of course, nobody is stopping you from doing that in Ruby:

gw.png


But that's a downgrade in Ruby since the Human class lets you express encapsulation and define an interface. And you need that so that you can manage the ability of users/objects that collaborate with your Human class.

The hashmap version of the Ruby code on the other hand is a recipe for disaster. It's too loose. It's like playing a game of Telephone once it's been passed through a few functions of your fellow contributors. Every file that touches it probably adds and mutates keys/values.

Let's flesh out the Ruby Human class a lil more:

gx.png


This is idiomatic Ruby with a classic mutation on `hp`.

I used to think the above code was simple and elegant, but I've come to abhor this trivial mutability. @hp feels like volatile metadata on our Human instance.

As our Human class accumulates legacy debt, other objects in our system will start depending on @hp mutation, @hp will probably depend on some other destructive calculation, and chasing down the intersection of changes in Human instances will slowly take up more and more of our time.

Let's port it to Clojure:

gy.png


(And here are some examples to clarify some of those functions):

gz.png


Now a human is just a hashmap. Creating a human is a matter of merging a hashmap into a hashmap with default keys/values.

The attack simulation is just a function that takes two hashmaps and returns a new one representing the defender post-attack (less hp).

Imagine writing tests if all your code was deterministic like this.

In fact, in most Ruby codebases I've ever worked in that have no tests, the reason tests don't exist is because it's hard to write tests for non-deterministic code, and it's impossible to write tests for legacy code that was never written to be testable.

In Robert Martin's RailsConf09 talk "What killed Smalltalk could kill Ruby" [ame="http://www.youtube.com/watch?v=YX3iRjKj7C0"]What killed Smalltalk could kill Ruby[/ame], he posits that Ruby's testing culture, should it fail, will kill Ruby.

However, Ruby is an imperative language that encourages mutation. Its idioms encourage mutation and the Ruby Way is inherently destructive.

So writing testable deterministic code in Ruby is an exception reserved for enlightened developers instead of a community default. As I mature as a developer, I realize that my day-to-day bottleneck is making sense of code that isn't mine. And when code is hard to reason about, 99% of the time for me it's because I have to parcel out when mutation is happening, where, and why.

Let's extend our Human examples once again by allowing humans to sleep when they want to regain some health:

g-.png


As we keep going this route, a Human's hp becomes harder and harder to follow. The incantations on lines 29-33 are magical. There's no syntax to explain cause and effect. It's just a procedural daisy-chain of effects.

Let's add a `sleep` mechanism to the Clojure example:

g_.png


Here we have no magical state-changing incantations.

The battle between rage9 and mattseh remains easy to follow as we scale. Rage9 is weakened but then he sleeps off the pain and rejuvenates.

See, the Ruby code, `mattseh` and `rage9` represent the current state of two humans through time. Their @hp levels fluctuate and whenever we check their hp we never really know what the intermediate outcomes of their struggles were. All we have are snapshots.

But in the Clojure example, `mattseh` and `rage9` don't represent humans across time. They just represent the initial state of two different humans. When our program executes, rage9 never changes. Our program just returns a representation of rage9 after he's attacked by mattseh and gets a moment to rest.
 
I'd recommend any programmer seriously try a functional programming language for at least a couple months, even if they never use it again.

It forces you to think very differently about things when you suddenly don't have access to variables, or even loops. I haven't seriously programmed in a functional programming language in a couple years (and I hated doing it at the time) but the knowledge gained from it was very valuable.

This.

IMO every computer science course should teach a functional language before any other language. It gets people thinking about things in the right way, and also evens the playing field a bit, as many people who start university as a "coder" find it almost as challenging as the newbies do.

Chapters - Learn You a Haskell for Great Good!
 
This.

IMO every computer science course should teach a functional language before any other language. It gets people thinking about things in the right way, and also evens the playing field a bit, as many people who start university as a "coder" find it almost as challenging as the newbies do.

Chapters - Learn You a Haskell for Great Good!

ok fine, you convinced me, this weekend I am breaking my brain.
 
Another intro resource is Martin Odersky's course "Functional Programming Principles with Scala" on Coursera: https://www.coursera.org/course/progfun. He's the creator of Scala but the course only uses Scala to express the principles. It's not a course on Scala and you don't need to know Scala going into it.

Well-acclaimed and it's where the seed was originally planted in my head.

Functional programming is like Vim: everyone complains about the difficulty, but by the time you give it a shot on an afternoon you wonder what the fuss was about. In fact, you already are familiar with various tenets of FP unless you're writing assembly all day. You've just probably never had someone draw a line around it for you and delineate it from the other coding concepts that you unknowingly use every day.

FP isn't novel because it's hard. It's novel because it reveals the imperative style as a crutch and then gives you an elegant alternative to the constructs you're used to using.

That's why they say it makes you a better developer. I still use Ruby at my day job, but I'm writing better Ruby.
 
Another good Scala book (Scala for the Impatient):
[ame="http://www.amazon.com/Scala-Impatient-Cay-S-Horstmann/dp/0321774094"]Scala for the Impatient: Cay S. Horstmann: 9780321774095: Amazon.com: Books[/ame]


Racket (PLT Scheme) is a good functional language I got into about a year ago in a Languages course. It is based on LISP; once I saw how easy it was to implement graph algorithms with it, I started using it for some of my graph projects.
 
tl;dr: If you want to learn functional programming, I recommend diving into Clojure or Haskell.

Scala for the Impatient is the best Scala book I read for anyone that can already program.

Downsides of Scala:

* Complex. It's a motherfucker of a language.

* You can't escape the complexity as a newbie.

Books and even Odersky suggest focusing on the productive basics, but that doesn't work very well when you look up a basic method and you're exposed to a type signature like this:

Code:
def ++ [B >: A, That] (that: TraversableOnce[B])(implicit bf: CanBuildFrom
[List[A], B, That]) : That
A simple function like ++ exposes you to covariance and implicits?

* Its build tool (sbt) is also complicated.

It took me way too much time just to figure out how to recreate the Gemfile+hello_world.rb workflow with Scala.

I wanted to generate an app skeleton to test out Scalatra (Scala's Sinatra). Yet I find myself installing things like https://github.com/n8han/giter8 and Conscript just to do that?

* Community conventions and the Best Way To Do X are unclear.

* Too multi-paradigm. Scala is OO with the ability to write in a functional, immutable, referentially transparent way if ya want. But functional programming doesn't work if developers have to opt into it. This leads to a Scala ecosystem where every lib lands somewhere different on a continuum between these concepts.
 
See, the Ruby code, `mattseh` and `rage9` represent the current state of two humans through time. Their @hp levels fluctuate and whenever we check their hp we never really know what the intermediate outcomes of their struggles were. All we have are snapshots.

But in the Clojure example, `mattseh` and `rage9` don't represent humans across time. They just represent the initial state of two different humans. When our program executes, rage9 never changes. Our program just returns a representation of rage9 after he's attacked by mattseh and gets a moment to rest.

Please note that I've never looked at Ruby or Clojure before so please excuse my ignorance but always looking to improve my programming.

What happens in your Clojure example if another is also attacking rage9? You say that rage9 doesn't change and you've created a weakened-rage9 to capture it's new state, but what happens when multiple attacks occur? HP must drop based on it's current state and not it's original state. How does that get managed: could you show me how you would handle a series of attacks on rage9?

(Not knowing Ruby) but could the Ruby example be handled better by the attack function returning an int and then rage9.hp = mattseh.attack(rage9)

You're then in full control over changing the value of hp.

Great thread - has given me a lot to think about.