elm-format
elm-format copied to clipboard
Align function parameters with their types
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 =
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.
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 =.
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.