A 'prompt' saying 'dirname' superimposed onto a background of blurry text. The command line control icon is barely visible in the lower right corner.

When you need to get the directory name of a file but you only have the full path to the file, what do you do? You could use some fancy parameter expansion, sure, but you might run into some weird edge cases. Personally, I’d suggest just using dirname.

If you’re familiar with Python and the os library, the behavior is the same as os.path.dirname, but even if you’re not, it’s still pretty straightforward. Let’s look at the man page for the command. It states that given a path, it will output it “with its last non-slash component and trailing slashes removed.” If the path contains no path separators, it will output ., which is the current directory.

So in a nutshell, that means it will normalize path/to/file.extension to path/to, and f.txt to .. Because it removes trailing slashes and because it’s not dependent on any file extensions, you can use it repeatedly to move further and further up the tree:

So when is this useful? Well, if you’ve got something in your path and you want to find out what else is in that directory, you could do something like this:

Maybe you want to navigate to that directory or open it in some other command? Yeah, you can do that too.

If you’re still not convinced that you should use dirname, check out this amazing stack overflow reply about some of the differences between parameter expansion and dirname.


Rust 2020

Compile-time constraints, ergonomics, and docs
The Rust logo with

Something, something, hindsight.

The end of the year is fast approaching and that, my friends, means that it’s time for a deluge of end-of-year Rust blogs. The Rust core team has put out a call for blogs to help decide where to take the language next, as they do every year. I’ve been quite invested in Rust for a few years now, so I thought it was about time I joined the fray.

Considering that I spend a disproportionately large amount of time thinking and reading about Rust compared to how much I actually use it, do take my thoughts and wishes here with a grain of salt; they may not be what the community needs the most, it’s just what I’d want in my ideal, little world.

Stabilize slice patterns (#![feature(slice_patterns)])

This is the one language feature I find myself wanting stabilized more than any other. Coming from languages like Haskell—and to a lesser extent Python and JavaScript—that have simple ways to destructure a list of values, doing this can feel quite clunky in Rust. Especially if all you want to do is get the head and the tail of a Vec or a slice. Yes, the split first function on slice does do that, but using it is tricky. In particular, how you handle destructured lists using case of statements in Haskell feels really ergonomic and intuitive to me.

Luckily, it seems we’re headed in that direction. There is a tracking issue for the slice_patterns feature on GitHub, and save for some remaining compiler issues, it should be about ready to go in. For now, you can check it out on the nightly channel with the feature toggle #![feature(slice_patterns)].

What’s more, because rust matches on slices and not linked lists like in Haskell, we get some extra goodies, such as being able to extract not only the first n items, but also the last m items. Here’s a little playground link that I put together demonstrating some cool uses for slice patterns. (Note: you probably can do this in Haskell somehow—a little bit of searching tells me the ViewPatterns compiler extension might do the trick—but it’s not baked into the base language like this.)

Easy async (documentation!)

I was holding a Rust workshop for some colleagues recently and one of them asked me to create an example of how you’d fetch web data using Rust. I thought this would be a great chance to have a little sneak peek at async/await before it stabilizes and get familiar with it: I figured I’d try and write a super simple application that fetches some data from an endpoint and dumps it to the terminal. However, I could not make it compile, and if it compiled, it didn’t run properly (presumably because the future was never started).

What I’d like to see is more information on how to do basic async programming in Rust. The Async Book has a chapter on it, but I still find it confusing with all the different traits and unstable features. I’ve tried following the example with the Hyper server to make a request, but I keep running into trait bound errors. I’m sure if I sat down and spent some more time on it, it would work, but it feels like it’s missing that ‘get started in 2 minutes’ section.

I’m sure this will improve very quickly over the next few weeks with the stabilization of async/await, but for now it’s a hurdle.

Generic Associoted Types (GATs) and const generics

I’ve lumped these two together because I’ve not really followed either particularly closely, but reading up on them for the purpose of this post, I’m suddenly quite excited by both. In general I’m all for anything that makes the type system more expressive and allows us to create more compile-time constraints.

GATs
Const generics

In summary

I already had some ideas in mind, so I thought writing this post would be quick and easy, but I ended up diving down multiple rabbit holes, and in the end it took much longer than expected. That said, I learned a lot along the way, and I’m even more excited about the language and where we’re headed now.

Apart from this short wish list, I also think it’s important that we stay aware of the community and keep them in mind. As has been mentioned in a number of other blogs, let’s try and make Rust sustainable for everyone: maintainers, core team, working groups, and community members alike. Let’s also try and move forward with game development, GUI development, and web development. We’re already great. Now let’s be even better. And no matter where we go from here, I’m confident that as long as the community stays warm, welcoming, and inclusive, we’ll have something to be proud of.

Much love ❤️


Excerpt from the Programming Haskell From First Principles book cover, showing just the title. There is an overlay saying 'Chapter 6' across the top right corner.

Back in ... purple?

It’s been a while, but welcome back to yet another installment in our read-through of Haskell Programming from First Principles. This time we’re looking at typeclasses.

What are typeclasses?

Typeclasses are like interfaces to your data. The description that I find the most intuitive is that a typeclass is a set of functions that must be implemented for your type to be an ‘instance’ of a typeclass. In classic OO-terms, this is very much like an interface; a contract that you must fulfill to have your type be able to stand in for this interface. In this way, typeclasses are a vehicle for ad-hoc or constrained polymorphism, and let you write code that operates on a more generic set of types than concrete implementations.

Typeclasses allow us to generalize over a set of types and to write functions that can abstract over concrete implementations. For instance: if all a function does with one of its input parameters is test it for equality, then the Eq typeclass contains all the functionality we need, and we can make that function take any type that implements the Eq typeclass. Similarly, all numeric literals and their various types implement the typeclass Num, which defines a set of standard numeric operations, allowing functions to work on any numeric value.

To get a better feel for how this works, let’s look at some of the basic typeclasses and some implementations!

Eq

Eq, the typeclass for equality. A very simple typeclass that will let you decide whether two items are the same or not.

The definition is as follows:

Pretty simple, right? All it needs is a definition for (==) and one for (/=), and even better, the minimal complete definition — that is, the least we need to implement to have a complete definition — is just one of the two functions. The one we don’t define will default to being the negation of the one we do define.

Most data types you’ll work with implement this one already, so you don’t need to worry much about it. Interestingly, because Haskell deals with structural equality, whether a tuple is an instance of Eq or not, depends on its components. If all the components can be compared for equality, then so can the tuple. This also applies to lists and a range of other data structures.

Writing an instance of the Eq typeclass

As an aside, here’s how you’d write your own instance of the Eq typeclass. It’s a simple instance to write, but that makes it easier to learn from. Let’s look at a basic example using a simple type we define, Bool2:

So to create an instance, we put instance, the name of the typeclass, the name of the type to implement it for, and the where keyword. What follows is an implementation of the functions required for the typeclass. As mentioned above, the minimal complete definition of the Eq typeclass only requires one of the functions to be defined, so this is a valid implementation.

Num

The Num typeclass is one we’ve already looked at quite a bit previously, so we’re not going to dwell on it for too long. It’s a typeclass implemented by most of the numeric types, and the definition is as follows:

Fractional

There are various other typeclasses related to Num, but let’s look at Fractional, a subtype of Num:

This typeclass requires its type argument, a, to have an instance of Num. This means that a Fractional is a specialized Num and can be used anywhere a Num can, but also specifies an extra set of functionality that is not required of the regular Num typeclass.

Ord

This typeclass takes care of ordering things, and as such, has a definition concerned with orders and comparisons:

Note that it has a typeclass constraint of Eq. If you’re going to be comparing things, you’re going to have to be able to tell whether they are equal or not. These functions all work the way you’d expect them to.

Ord instances (when derived automatically) rely on the way the data type is defined. Looking back on our Bool2 data type from earlier (data Bool2 = No | Yes), Yes will be greater than No because it is defined later. This is similar to how enums work in the C-familiy of languages where they’re usually just fancy names for successive integers starting at an arbitrary point and incrementing by one for each entry. Naturally, if you want to change the ordering, you can, but you’re going to have to implement the typeclass yourself.

Enum

The Enum typeclass is quite different (yet weirdly similar) to most other things known as enums in other programming languages (at least in my experience). In Haskell, the Enum typeclass is for types that are enumerable, that have known predecessors and successors. What does this mean? It means you can use instances of this typeclass to generate ranges and lists.

Definition:

The most obvious instance of this typeclass would be something like integers, where succ 4 is 5 and pred 3 is 2. Similarly, characters can be enumerated just as well, as can ordered data types like Bool.

Some of the function names listed above are a bit confusing (enumFromThenTo anyone?), but when you look at it in context it makes more sense. For instance,

Where this becomes more interesting is when you see that you can use them in list comprehensions and figure out that there’s alternative ways of using these methods. Here’s the equivalent written as a list comprehension instead:

Show

Show is a typeclass that provides human-readable string-representations of structured data. GHCi uses this to create String values that it can print in the terminal. In fact, to have a type be printed in the terminal, it must implement Show.

Unless you’re writing this for your own data structures, you don’t need to worry much about this one. It’s already implemented for all basic types. The minimal complete definition requires either show or showsPrec.

Definitions

We’ve covered a lot of ground here, so let’s take a step back and look at some definitions for this chapter.

Typeclass inheritance
When a typeclass has a superclass. This means that only data types that implement the super class can have an instance of the subclass. Going back to the Num and Fractional typeclasses: Every Fractional is also a Num, but not every Num is a Fractional. This makes Num a superclass of Fractional.
Instance
An instance is the definition of how a typeclass should work for a given type. Instances must be unique for a given combination of typeclass and type, i.e. you cannot have two different instances of a typeclass T for a single type a.
Derived instances
Some typeclasses are so obvious or common that they can be automatically implemented for a data type. Automatically deriving typeclasses like this is done by specifying it after the data declaration: data MyType = A | B deriving (Eq, Show)
First Prev Page 1 Next Last