Investigating single-letter type variables

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
7 messages Options
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Investigating single-letter type variables

Jeffrey Brown
Haskellers tend to use uninformative single-letter type variables. A case in point:

    Megaparsec> :i ParsecT                                                         
    type role ParsecT nominal nominal representational representational
    newtype ParsecT e s (m :: * -> *) a ...

I've gotten used to the conventions that "a" stands for anything and "m" stands for monad -- but without digging into the code it wasn't initially obvious to me whether "s" stood for stream or state.

Single-letter type variables don't seem to always be the standard, though:
    
    Megaparsec> :i between
    between :: Applicative m => m open -> m close -> m a -> m a
            -- Defined in ‘Control.Applicative.Combinators’

My questions are: (1) Are the brevity gains worth the confusion costs? (2) If I'm looking at a new library for the first time and trying to figure out what a type variable stands for, is there a canonical way to do it? Flailing through documentation at random? Unifying types by hand?

--
Jeff Brown | Jeffrey Benjamin Brown
Website   |   Facebook   |   LinkedIn(spammy, so I often miss messages here)   |   Github   

_______________________________________________
Haskell-Cafe mailing list
To (un)subscribe, modify options or view archives go to:
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Only members subscribed via the mailman list are allowed to post.
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Investigating single-letter type variables

Chris Smith-31
The following probably captures community practice:

a) If the type is truly arbitrary, such that there is no meaningful info to add beyond the positions it occurs in the type, then give it an arbitrary name, like 'a', 'b', or 'c'.
b) If there is a convention associated with a type or class that occurs in the type signature, to which the type variable is a parameter, then use that (usually one-letter) name.
c) Otherwise, use a longer name.

You seem to be making the case that (b) is confusing.  You may be right, and I have no particular desire to defend the current practice.  But if you're wondering where one would be expected to find the meaning, that's it: find the primary type (or class) to which it's a parameter, and there is usually a convention set forth there.

On Thu, Aug 10, 2017 at 3:29 PM, Jeffrey Brown <[hidden email]> wrote:
Haskellers tend to use uninformative single-letter type variables. A case in point:

    Megaparsec> :i ParsecT                                                         
    type role ParsecT nominal nominal representational representational
    newtype ParsecT e s (m :: * -> *) a ...

I've gotten used to the conventions that "a" stands for anything and "m" stands for monad -- but without digging into the code it wasn't initially obvious to me whether "s" stood for stream or state.

Single-letter type variables don't seem to always be the standard, though:
    
    Megaparsec> :i between
    between :: Applicative m => m open -> m close -> m a -> m a
            -- Defined in ‘Control.Applicative.Combinators’

My questions are: (1) Are the brevity gains worth the confusion costs? (2) If I'm looking at a new library for the first time and trying to figure out what a type variable stands for, is there a canonical way to do it? Flailing through documentation at random? Unifying types by hand?

--
Jeff Brown | Jeffrey Benjamin Brown
Website   |   Facebook   |   LinkedIn(spammy, so I often miss messages here)   |   Github   

_______________________________________________
Haskell-Cafe mailing list
To (un)subscribe, modify options or view archives go to:
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Only members subscribed via the mailman list are allowed to post.


_______________________________________________
Haskell-Cafe mailing list
To (un)subscribe, modify options or view archives go to:
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Only members subscribed via the mailman list are allowed to post.
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Investigating single-letter type variables

Tikhon Jelvis
In reply to this post by Jeffrey Brown
I've tried using longer type variable names in some of my personal projects and found a surprising problem: it became harder to tell types from value-level expressions at a glance. In a lot of cases, I actually found the resulting code harder to read on net.

There are still times when I use longer type variable names in my code, but I now think that single-letter names are actually a good default—I need a good reason to deviate from them.

I think this happens because type signatures tend to be short, relatively self-contained and often repeated. I want to be able to parse type signatures quickly, at a glance. Longer type variables might be clearer the first time you encounter a library, but they often become a pain once you're familiar with it. (I follow the same reasoning for using short names for function arguments and local definitions that are used in a restricted scope.)

:browse through Parsec and consider how many ParsecTs you see, often with multiple in a single type signature. With this much repetition, variables consistently named by connection become useful. Sure, you might need to learn the convention, but the up-front work pays off for itself in any non-trivial use of the library. Of course, there should also be clear documentation on the type definition defining what the variables mean. Gabriel Gonzalez's Pipes library is a perfect example of how this can be done.

On Aug 10, 2017 6:32 PM, "Jeffrey Brown" <[hidden email]> wrote:
Haskellers tend to use uninformative single-letter type variables. A case in point:

    Megaparsec> :i ParsecT                                                         
    type role ParsecT nominal nominal representational representational
    newtype ParsecT e s (m :: * -> *) a ...

I've gotten used to the conventions that "a" stands for anything and "m" stands for monad -- but without digging into the code it wasn't initially obvious to me whether "s" stood for stream or state.

Single-letter type variables don't seem to always be the standard, though:
    
    Megaparsec> :i between
    between :: Applicative m => m open -> m close -> m a -> m a
            -- Defined in ‘Control.Applicative.Combinators’

My questions are: (1) Are the brevity gains worth the confusion costs? (2) If I'm looking at a new library for the first time and trying to figure out what a type variable stands for, is there a canonical way to do it? Flailing through documentation at random? Unifying types by hand?

--
Jeff Brown | Jeffrey Benjamin Brown
Website   |   Facebook   |   LinkedIn(spammy, so I often miss messages here)   |   Github   

_______________________________________________
Haskell-Cafe mailing list
To (un)subscribe, modify options or view archives go to:
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Only members subscribed via the mailman list are allowed to post.

_______________________________________________
Haskell-Cafe mailing list
To (un)subscribe, modify options or view archives go to:
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Only members subscribed via the mailman list are allowed to post.
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Investigating single-letter type variables

Albert Y. C. Lai
In reply to this post by Jeffrey Brown
On 2017-08-10 06:29 PM, Jeffrey Brown wrote:
> Haskellers tend to use uninformative single-letter type variables. A

Do you prefer this?

id :: domain ~ codomain => domain -> codomain
id argument = return_value
   where
     return_value = argument
_______________________________________________
Haskell-Cafe mailing list
To (un)subscribe, modify options or view archives go to:
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Only members subscribed via the mailman list are allowed to post.
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Investigating single-letter type variables

Jeffrey Brown
it became harder to tell types from value-level expressions at a glance

Font differences seem like a more natural way to distinguish those.

> Do you prefer this?

> id :: domain ~ codomain => domain -> codomain ...

Lol no.

Maybe a good solution would be, rather than longer names, some automated way for a user to ask whether a type variable like 'a' carries any semantics, or is truly free to be anything. 

If I'm learning a library from the top down, it's not (as far as I can remember) a problem. But sometimes one library uses a single function buried deep in another library. In those situations, the current status-quo recipe, "just flail around (reading documentation or manually unifying types) until you get it," seems improvable.

But if y'all gurus don't think it's a big deal, I'll take your word for it, and wait for those flailings to become instinctual.

On Thu, Aug 10, 2017 at 4:08 PM, Albert Y. C. Lai <[hidden email]> wrote:
On 2017-08-10 06:29 PM, Jeffrey Brown wrote:
Haskellers tend to use uninformative single-letter type variables. A

Do you prefer this?

id :: domain ~ codomain => domain -> codomain
id argument = return_value
  where
    return_value = argument

_______________________________________________
Haskell-Cafe mailing list
To (un)subscribe, modify options or view archives go to:
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Only members subscribed via the mailman list are allowed to post.



--
Jeff Brown | Jeffrey Benjamin Brown
Website   |   Facebook   |   LinkedIn(spammy, so I often miss messages here)   |   Github   

_______________________________________________
Haskell-Cafe mailing list
To (un)subscribe, modify options or view archives go to:
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Only members subscribed via the mailman list are allowed to post.
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Investigating single-letter type variables

William Yager
In reply to this post by Jeffrey Brown
Without actually addressing the question, here are some rules of thumb I use for identifying and choosing type variables:

a - whatever. Usually return type, thing inside functor, etc. If you need more than one, you can use b/c.

m - usually monad, sometimes just applicative. Can use n if you need more.

s - state or state token

f - usually functor or more. Can use t if you need more.

e - error

i - Index

r/o - "Result" or "Output" with no real consistency on typeclass constraints.

k/v - Key/Value

w - result monoid

These are by no means particularly hard and fast rules, but they're the first things I jump to when I see a type variable. I do try to use more descriptive names if I don't think it will be fairly obvious from context.

--Will

> On Aug 10, 2017, at 4:29 PM, Jeffrey Brown <[hidden email]> wrote:
>
>
>     newtype ParsecT e s (m :: * -> *) a ...
>
> I've gotten used to the conventions that "a" stands for anything and "m" stands for monad -- but without digging into the code it wasn't initially obvious to me whether "s" stood for stream or state.
>
_______________________________________________
Haskell-Cafe mailing list
To (un)subscribe, modify options or view archives go to:
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Only members subscribed via the mailman list are allowed to post.
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Investigating single-letter type variables

Phil Ruffwind
In reply to this post by Jeffrey Brown
> (2) If I'm looking at a new library for the first time and trying to figure
> out what a type variable stands for, is there a canonical way to do it?

Type variables inherently don't have any meaning – the meaning only
comes from how they are used and how they are constrained.

Therefore, you need to look for clues based on (1) the constraints on
the type variable, if any, and (2) the types that use the variable.

For parsec it's pretty easy because the type variables are often
constrained by Stream and used by the ParsecT type.  The documentation
of parsec is really detailed: both Stream and ParsecT tell you precisely
what 's' and 'm' are, and the library is very consistent with this
convention.  IMO, good documentation should always explain every type
variable in every `data` or `newtype` declaration.

I would say part of the reason Haskell uses single-letter variables a
lot is that a lot of the code are very general and the authors did not
want to impose their domain-specific interpretation on it.  In some
situations (e.g. application code, domain-specific libraries), writing
out the variables in full can be meaningful, but in library code this is
rare.
_______________________________________________
Haskell-Cafe mailing list
To (un)subscribe, modify options or view archives go to:
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Only members subscribed via the mailman list are allowed to post.
Loading...