Justin Campbell
Make Up Your Own "Hello, World!"

Make Up Your Own "Hello, World!"

by Justin Campbell

In the video titled "Make Up Your Own 'Hello, World!'" presented by Justin Campbell at MountainWest RubyConf 2015, the main theme revolves around exploring programming languages beyond Ruby and understanding their strengths and intricacies. Justin, a self-taught developer with extensive experience in Ruby, shares insights into the importance of language exploration, the process of learning new programming languages, and the impact of various languages on programming skills.

Key points discussed include:

  • Initial Background: Justin's journey in programming began with Ruby, which he committed to understanding deeply. His prior experiences include dabbling in BASIC, JavaScript, and PHP but without deep learning.
  • Motivation for Learning New Languages: His curiosity about functional programming and discussions around the efficiency of various programming languages led him to explore languages such as Elixir, Go, Haskell, Rust, and Scala.
  • Project-Based Learning: Justin emphasizes the significance of having a project or a goal when learning a new language, using the example of a URL shortener he decided to create.
  • Core Language Requirements: He lists criteria important for language exploration, such as HTTP support, ease of testing, and the ability to keep the state in-memory during experiments.
  • Comparison of Language Features: The video features detailed code snippets and comparisons of implementing a URL shortener across Ruby, Clojure, Haskell, Nim, Go, and Elixir, highlighting unique features such as Haskell's pure functional nature and Go's error handling paradigm.
  • Mindset Shift: Each programming language has shifted Justin's mindset about object-oriented design, functional programming, and practical approaches to coding, enhancing his skills as a Rubyist.
  • Industry Relevance: Justin discusses how exposure to diverse technologies can make developers more appealing to employers and contribute to their personal and professional growth.
  • Learning Resources and Community: He concludes with recommendations on resources for learning new languages and emphasizes the importance of engaging with respective programming communities for support.

The main takeaway from the session stresses that testing and transitioning to new languages can be challenging but lead to significant growth and fulfillment in software development. Justin encourages the audience to regularly explore new programming languages to enhance their capabilities and enrich their coding experience.

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!