Convention of using record syntax with verb-prefixed field names


#1

I understand (hopefully) the way record syntax generates accessor functions, such that

data Person = Person { firstName :: String, lastName :: String }

will create firstName and lastName functions that take in a Person-type value and return the corresponding fields. I’ve also noticed that it seems to be common practice to create wrapped types with a single record field prefixed with un, which I assume is short for unwrap as it can be used to unwrap and retrieve the underlying value. (If this is wrong, please correct me!)

One thing that’s a bit confusing, though, is when the field name is prefixed with an action verb, like:

newtype Writer w a = Writer { runWriter :: (a, w) }

As far as I can tell, runWriter is merely an accessor function like firstName or lastName, returning what appears to be a tuple. Does it actually “run” anything when it’s called? If not, why is it common practice to use that field name?

My current guess: the Writer type is a monad and monads represent computations, so the retrieval of the underlying value is considered to be “executing” the monad.

A second guess: since Haskell is lazy, perhaps the underlying value of a monad isn’t evaluated until it gets unwrapped, setting off a chain of dependency evaluations… analogous to the Quantum Physics idea of observation triggering a wave function collapse.

Thoughts?

Edit: Looking at some example writer monad code, I’m definitely missing something here, as calling runWriter does appear to execute some code, rather than just returning a record field. This seems weird to me… am I conceptualizing record fields wrong? Why does firstName person just return a field while runWriter writer executes code?

Edit 2: I think I might get it now… I’m not thinking “lazily” enough. When I see runWriter myWriter and myWriter is defined as a do block, I think I should translate that in my head to runWriter(myWriter()), as myWriter is actually a function that returns the writer, and runWriter triggers the evaluation of that writer-returning function… I think?


#2

I never liked the naming either, I always interpreted it as you did. I guess it makes more sense for newtypes like State, where the accessor has a more “runny” signature (State s a -> s -> (a, s) – you give us something and you’ll get a computed state/value pair back).


#3

Thanks for the reply! I just added this 2nd edit to my post… am I on the right track here?

I think I might get it now… I’m not thinking “lazily” enough. When I see runWriter myWriter and myWriter is defined as a do block, I think I should translate that in my head to runWriter(myWriter()), as myWriter is actually a function that returns the writer, and runWriter triggers the evaluation of that writer-returning function… I think?

If I understand this correctly: functions with no arguments are essentially values representing the eventual outcome of the function, which isn’t evaluated until the value is passed into something else that needs it… this might be the framing that I was missing.

It’s still a weird naming scheme, like you said, but at least now I think I understand why it appears to “run” something in the first place…


#5

It seems that using run or get prefixes is an old naming convention, though very common in core libraries. Most people I know prefer to use un for newtypes as a short version of unwrap as you correctly guessed.

You may find the following discussion about names for newtypes interesting :slightly_smiling_face:


#6

I’ve never really found the runWriter convention particularly confusing. As far as I can tell, the idea is that:

runWriter :: Writer w a -> (a, w)

So if you think of a Writer as a monadic computation, then runWriter takes this computation and ‘runs’ it to get an (a, w) result.


(I’ve just noticed you actually did say this in your original post:

My current guess: the Writer type is a monad and monads represent computations, so the retrieval of the underlying value is considered to be “executing” the monad.

This is basically what I’m trying to say.)


#7

The equivalent of having “verbs” for Person would be:

data Person = Person {getFirstName :: String, getLastName :: String}

because then you can use something like:

getFirstName (jasper :: Person)

but it looks horrible on record construction, which is why it is rarely done:

jasper = Person {getFirstName = "Jasper", getLastName = "Van der Jeugt"}