00:00:22.420
My name is Justin. I'm a self-taught developer, and Ruby is my first programming language. I've been working professionally with it for years. It’s the first language where I had the obligation to produce code that my teammates could understand, and it's also the first language where I had to comprehend code that someone else wrote. Additionally, I consider Ruby to be the first language I truly learned when I was younger. I tinkered around with BASIC as part of my school classes and played with JavaScript since high school. I wrote websites throughout the 2000s using PHP, but I never really learned these languages in depth; I simply cargo-coded by using code I found elsewhere that happened to work.
00:00:32.059
Ruby is the first language I've committed myself to learning. Because of that, I feel like I have a solid understanding of how Ruby works and how object-oriented design functions in general. I have decided to learn other languages because I kept hearing people discuss them and mention how they are different from what I was used to. For instance, why is everyone talking about this thing called functional programming? How could Erlang possibly be more robust by allowing things to fail? How can Haskell be pure, with no side effects, while still being useful? Why would anyone use Go, Rust, or even C when Ruby and Python seem sufficient for most tasks? People often say Ruby is slow, but is that really true? It doesn't feel slow to me, considering my day-to-day experience writing code on Twitter, where I often read discussions about languages and frameworks I’ve never used. This sparked my interest in finding out what else is out there.
00:01:04.511
My first experience with a language outside Ruby was with Clojure, which had a lot of hype at the time. I created a dice rolling API that returned JSON arrays of random numbers. Experimenting with Clojure felt a lot like when I was younger and would download an installation ISO to try Linux or BSD. I would install the system and then not really do anything with it; I never had any specific goals or projects in mind. I think having a project is essential to learning a new language. More abstractly, I think I need a goal for what I'm trying to achieve and what I want to learn.
00:01:29.900
When considering programming, I often think of web projects. Most of the projects I work on revolve around the Internet; they are either websites or APIs. Furthermore, many of these projects rely on HTTP services. Because of this, HTTP support is an important criterion for me when evaluating a new language. If I just have an HTTP endpoint that responds with data but I can’t write to it, I might as well be creating a static website. It would be better if I could send data to it, and if I’m persisting data, the server needs to store that information somewhere.
00:02:00.610
I prefer not to depend on an external database while exploring different languages, as I might want to do this in dozens of different languages. I could use Postgres or Redis for a real application, but that seems like too much effort when I’m just experimenting. So, I decided to keep the state of the world in memory, which also forces me to think about concurrency. Lastly, testing is extremely important to me, which translates to Test-Driven Development (TDD). I’m most comfortable writing code when I have tests that cover it because, frankly, I’m lazy. If I have to run an application manually to ensure it works, that sounds significantly more labor-intensive.
00:02:40.790
These are the requirements I had in mind for my project. I wrote down various ideas that could meet my goals and ultimately settled on creating a URL shortener. The URL shortener would be an HTTP API that returns a token for a given URL. When you access that path, it should redirect you to the original URL. For simplicity, I planned for the tokens to start at 1, but in a more sophisticated URL shortener, I might consider making the tokens random or compressing them using base62 encoding.
00:03:09.300
In the design, users would make a POST request with a URL in the form data, and the shortener would respond with the token created as part of the route path. For instance, a request to /1 would return the original URL associated with that token. If a request is made for a non-existent token, the API should respond with a 404 status. This was the baseline functionality I aimed to implement in various programming languages.
00:03:40.170
I started with Ruby. Here’s the sample code I devised to create a short URL using the Sinatra framework. We grab the URL from the request parameters and ask the shortener object to shorten it. This method returns a token, and we inform the user of the token or path with a response of 201 Created. To use a token, that request would prompt us to look it up with the `expand` method, which would return the full URL or nil if the token wasn’t found, prompting a 404 response.
00:04:00.620
Inside the shortener object, we implemented two methods: `short` and `expand`. The `short` method generates a new token, storing it in a hash of URLs. The `expand` method looks up the token in the hash. We also introduced something called an ID generator, which produces sequential numbers. Additionally, we incorporated a method to cache URLs by leveraging a singleton pattern with a class method called `instance`, ensuring that we always retrieve the same instance of the shortener.
00:04:34.049
This code, however, raises questions about thread safety: if I run this code on JRuby or Rubinius, will I encounter missing URLs or token collisions due to concurrent updates? If I deploy this on Puma or Unicorn, would it have warranted wrapping the code in a mutex? I’m not entirely sure, which leads me to discuss Clojure.
00:05:06.579
Clojure implements state management through atoms, allowing you to create an atom with an initial state. For instance, we start creating tokens at zero, and you can use Clojure’s swap function to pass in an update function that modifies the atom’s value. In this case, we can utilize an increment function to increment the number. An atom is a wrapper around a value, processing a queue of operations to ensure they are handled one at a time, similar to the actor model. When calling a function to generate the next token, you can increment the token and receive the next available number, which is a simple yet effective way to ensure safe concurrent access.
00:05:56.361
Next, let’s talk about Haskell. Haskell has an intimidating syntax at first, but here’s how our shortener function would look in Haskell. The input to our function is the URL, wherein we perform various operations, including incrementing the token and storing the URL, ultimately returning the token to the caller. Haskell is a statically typed language, and while you can typically infer most types, it’s customary to specify them. The type signature can aid in understanding the function better; for instance, you could read it as the shorter function taking a URL and returning a token, which is beneficial for readability.
00:06:32.049
When you attempt to replicate the functionality of the Ruby version in Haskell, you’ll quickly realize some key differences. Haskell is a pure language, preventing side effects like mutable state from existing outside a function scope. Thus, in Haskell, when you shorten a URL, you must also manage the state of the world, requiring you to pass in necessary state information. The output typically includes returning the new state of the world as well, necessitating additional handling in pure functions. You might create a map or hash of tokens and URLs stored, applying a function that only uses the state provided as input.
00:07:04.490
One notable feature of Haskell is that you can deduce what a function does just by inspecting its type signature. The Haskell compiler guarantees that the function will adhere to the specified types, enhancing reliability and predictability. Next, let’s explore Nim, which is a language that compiles to C code and is also statically typed like Haskell. Although the syntax is somewhat different, the implementation shares similar concepts, such as type safety.
00:07:38.040
In Nim, the function returns a URL type directly. However, it is important to be cautious if the URL isn’t found, as it does not handle the potential absence of a value like Haskell’s Maybe type does. This can result in runtime errors rather than compile-time safety checks seen in Haskell. In contrast, when using languages like Ruby, failure states are often communicated using nil, a practice that may obscure context about what went wrong.
00:08:18.460
When it comes to Go, there’s a common convention that many find unsightly: error handling typically involves returning two values: the result and an error. You then check if the error object matches nil. Although it might seem cumbersome at first, this pattern is designed to provide clarity on error handling. I came across a tweet about Bryan Liles, an experienced programmer who struggled with Go but ultimately embraced the language and became a keynote speaker a year later, reflecting significant personal growth.
00:08:51.430
On a different note, while Ruby utilizes nil to signify an absence of a value, Go embraces a more explicit form of error signaling. This leads us to consider Elixir and Erlang, languages that leverage pattern matching to ensure that returned results are what you expect. In Elixir, when calling a function, you should expect a back result in the form of an OK tuple, with a message or an error handling path if things do not unfold as anticipated.
00:09:26.300
Pattern matching allows you to elegantly control your code’s flow, handling the ‘happy path’ separately from any error states, ultimately simplifying your coding experience. While C lacks these constructs, it’s worth considering how they can enhance readability and robustness in code. Although Ruby doesn’t incorporate pattern matching in the traditional sense, improvements might come in the future with concepts like type inference hinted for Ruby 3.
00:10:00.100
It’s crucial to acknowledge that adding type checks and type inference might not limit Ruby but could potentially give us more flexibility in terms of its capabilities, allowing us to evolve as developers. Each of the languages I’ve used has slightly shifted my mindset and approach to programming, enhancing my skills as a Rubyist. Although I never had a defined goal to bring knowledge from other languages back to Ruby, it merely made sense that more learning would enhance my programming capabilities.
00:10:30.280
I didn’t expect functional languages would yield insights into object-oriented programming, nor did I think statically typed languages could offer lessons applicable to dynamic languages like Ruby. Companies like to see diversity in technology stacks among their employees. When individuals have the freedom to explore new technologies, they become more attractive hires, as this diversity often correlates with personal growth and job satisfaction.
00:11:02.800
This idea is echoed by many organizations; bored employees tend to leave. Another point I want to address is how testing evolves as you learn new languages. Testing can be quite challenging when you're unfamiliar with a language; for instance, RSpec in Ruby can be complex for newcomers. I often found it helpful to adopt an imperative coding style with local variables when testing in unfamiliar languages, avoiding complex mocking syntax that can obscure the core intent of the tests.
00:11:37.880
While I often test outside-in with Ruby, heavily relying on mocks and stubs, I find the process daunting when using a language I just installed and don’t yet comprehend. Understanding the core mechanics of functions and how to test effectively with different languages can be overwhelming initially. However, it’s crucial to start from whatever points of understanding you have, which allows you to build your knowledge incrementally.
00:12:10.270
For instance, Scala uses a tool called sbt that operates similarly to Rake in Ruby, enabling you to watch files and rerun tests seamlessly without needing to reload the JVM. This could be a game-changer for those looking for more efficient development practices. This convenience in programming environments can greatly aid new learners in transitioning to different languages, minimizing the amount of setup required.
00:12:43.880
Similarly, in many JVM languages, dependencies can be installed automatically when running a command. This kind of seamless integration can mitigate frustrating experiences when configuring environments. A great example would be comparing how Ruby objects are created using the `new` method against how other languages format object instantiation, such as using a more simplified syntax for records in functional languages.
00:13:20.520
There’s a gem called nuke that helps create a syntax for this in Ruby, which worked surprisingly well. While there might be some hiccups related to complicated metaprogramming practices, the end result still allows for more fluent object creation. It's widely accepted that one should learn a new programming language each year; however, the durations vary—from dipping your toes to committing fully to understanding language conventions and community practices.
00:13:58.890
The trick is to schedule a few hours a day to see if you enjoy it. As you devote a week to it, you’ll find yourself developing meaningful projects that prompt questions about deployment and dependency management. When the enchantment of a new language fades, you must grapple with community interaction and norms.
00:14:31.280
I recommend trying out creating a simple URL shortener or tackling common coding exercises like Conway’s Game of Life. Consider your interests and choose a language that aligns with them. Although I cannot dictate which language you should choose, I hold a few opinions. For example, I highly advocate for learning Haskell, as it offers extensive insights into type systems, and Clojure can drastically change your perspective regarding data manipulation.
00:15:03.780
Additionally, learning languages like Go is beneficial for understanding modern language conventions. Each language you explore can cultivate a sense of fulfillment as you work with languages that emphasize practical approaches and joy in programming. The enjoyment derived from programming should never be underestimated, and the ease of reading code written by others can reinforce the sense of community within a programming language.
00:15:33.890
In conclusion, assess the communities surrounding the languages you choose to engage with. Being welcomed when seeking assistance fosters a healthy learning environment, making the journey enjoyable. The Ruby community, for instance, exemplifies a supportive culture that encourages learning and growth.
00:16:00.130
I’ll leave you with a couple of resources: consider reading 'Seven Languages in Seven Weeks' and its sequel, which provide engaging introductions to various languages. Websites like Learn X in Y Minutes offer quick glimpses into language syntax—these resources can aid you as you embark on your journey into new programming languages.
00:16:16.270
And lastly, remember, sharing your enthusiasm with someone else can greatly enhance your learning experience. It’s perfectly normal to feel like you struggle when starting something new, but don’t forget that making mistakes is part of the learning process. Thank you!