elm-format icon indicating copy to clipboard operation
elm-format copied to clipboard

Align function parameters with their types

Open jerith666 opened this issue 5 years ago • 3 comments

Summary

I'd like to propose a way to make function declarations more readable, especially functions that take more than one or two arguments. Consider this declaration from the core List module:

foldl : (a -> b -> b) -> b -> List a -> b
foldl func acc list =

At a glace, it's a bit tricky to see how the types in the signature -- which contains five arrows -- line up with the arguments (only three of them) in the definition. To make this easier, I propose to align the arguments under their types, like so:

foldl : (a -> b -> b) -> b -> List a -> b
foldl   func             acc  list   =

Discussion

Rule

Roughly, the rule is to align the first character of each type with the first character of the corresponding argument, by padding whichever one is shorter. The = lines up under the - of the last ->. Or more generally for higher-order functions, the = lines up under the - of the -> after the last declared argument. In the special case of a function with no arguments, the = lines up under the :.

Destructuring in the arguments should follow similar rules in something of a recursive fashion. I realize there's some hand-waving here though. :) For example:

foo : (Int  , String) -> String
foo   (limit, s)      =

Pros and Cons

This lets you answer the questions like "what's the type of the list argument?" or "what's the name of the argument of type b?" by simply looking directly above or below. And it lets you answer the question "what's the return type of this function?" by scanning right for the = and then looking up.

The main con I can think of is that this will generally increase the length of function declaration lines.

Variants

I think some minor debates could be had, including: Do we align with parens or with the types within the parens?

foldl : (a -> b -> b) -> b -> List a -> b
foldl    func            acc  list   =

Do we push the -> out beyond the name of long arguments? (In other words, require that -> always have blank space beneath it?)

foldl : (a -> b -> b) -> b   -> List a -> b
foldl   func             acc    list   =

Examples

For more examples, I've formatted all of the type signatures from List using this scheme:

singleton : a     -> List a
singleton   value =

repeat : Int -> a     -> List a
repeat   n      value =

range : Int -> Int -> List Int
range   lo     hi  =

cons : a -> List a -> List a
cons =

map : (a -> b) -> List a -> List b
map   f           xs     =

foldl : (a -> b -> b) -> b -> List a -> b
foldl   func             acc  list   =

foldr : (a -> b -> b) -> b -> List a -> b
foldr   fn               acc  ls     =

filter : (a -> Bool) -> List a -> List a
filter   isGood         list   =

length : List a -> Int
length   xs     =

reverse : List a -> List a
reverse   list   =

member : a -> List a -> Bool
member   x    xs     =

all : (a -> Bool) -> List a -> Bool
all   isOkay         list   =

any : (a -> Bool) -> List a -> Bool
any   isOkay         list   =

maximum : List comparable -> Maybe comparable
maximum   list            =

minimum : List comparable -> Maybe comparable
minimum   list            =

sum : List number -> number
sum   numbers     =

product : List number -> number
product   numbers     =

append : List a -> List a -> List a
append   xs        ys     =

concat : List (List a) -> List a
concat   lists         =

intersperse : a -> List a -> List a
intersperse   sep  xs     =

sort : List comparable -> List comparable
sort   xs              =

head : List a -> Maybe a
head   list   =

tail : List a -> Maybe (List a)
tail   list   =

take : Int -> List a -> List a
take   n      list   =

drop : Int -> List a -> List a
drop   n      list   =

partition : (a -> Bool) -> List a -> (List a, List a)
partition   pred           list   =

unzip : List (a,b) -> (List a, List b)
unzip   pairs      =

jerith666 avatar Sep 05 '20 02:09 jerith666

Some issues I can see:

  • What to do when the type signature or argument list spans over multiple lines. (Just give up?)
  • What to do when the value is a function and so the argument list is a different length to the type? (In your example you just don't do anything, which seems inconsistent with aligning the = with the -> in the other cases.)

I see the intent of trying to link the type and argument name, but given the tendency to curry in Elm code, it often doesn't match up, so I'm not sure how helpful it actually ends up being.

Personally, I also feel like trying to align horizontally in text like this just makes things really unreadable, and it does essentially enforce the use of monospaced fonts, which not everyone uses. As far as I am aware, nothing in elm-format thus far does so.

Lattyware avatar Nov 26 '20 16:11 Lattyware

Yeah, multi-line signatures just aren't amenable to this approach at all, so I wouldn't change their formatting.

I mentioned the second case in the original description:

Or more generally for higher-order functions, the = lines up under the - of the -> after the last declared argument. In the special case of a function with no arguments, the = lines up under the :.

The only example in my original post was of that special case:

cons : a -> List a -> List a
cons =

if the function had some of its args explicit, it would look like this:

--argument name longer than type
cons : a       -> List a -> List a
cons   newHead =

--type longer than argument name
cons : elementType -> List elementType -> List elementType
cons   newHead     =

Conceptually what I'm going for is that the type to the right of the -> above the = corresponds to the type of the expression after the =.

jerith666 avatar Nov 26 '20 17:11 jerith666

I definitely always code in a monospaced font, and am always shocked on the rare occasions I see others coding in variable-width fonts! So thanks for the reminder.

This definitely would not play well with variable-width fonts -- as I can attest after trying to line things up in the github comment editor for my preceding comment! :-) So if that's a use-case that elm-format intends to encourage, we probably shouldn't do this. IMO elm-format should not encourage variable-width fonts though.

jerith666 avatar Nov 26 '20 17:11 jerith666