Removing redundancy from type annotations

Introduction

Currently, for every definition (in this case, function or variable) with explicit type annotation, it is necessary to type the name of the definition twice, in both type annotation and declaration. This is due to Elm’s syntax origins, which was inspired in Haskell’s syntax.
The reasons why in Haskell it is needed to declare redundantly the definition name (in both type annotation and declaration) are the following: (1) it is possible to declare, without an order, some type annotations, and only later its implementations and (2) it has a more complex and not unified syntax (that is, there are, at least, 2 ways of describing type annotations).

For example, this is a valid (but not desirable) Haskell program.

sumAndConvertToStr a b c d = show (a + b + c + d)

fooA :: Int -> Int
second :: Int

(second, fooA, fourth) = (2, \x -> x + 1, 4)

third, fourth :: Int
third = 3

sumAndConvertToStr :: Int -> Int -> Int -> Int -> String

main = putStrLn (sumAndConvertToStr (fooA 2) second third fourth)

The redundancy here is needed to especify what type annotation refers to each implementation. Otherwise in Elm, there is only one way to describe a type annotation and it is obligatory to have an implementation below it.
For example, the same snippet could be translated this way in Elm:

import Html

fooA : Int -> Int
fooA x =
    1 + x

second : Int
second =
    2

third : Int
third =
    3

fourth : Int
fourth =
    4

sumAndConvertToStr : Int -> Int -> Int -> Int -> String
sumAndConvertToStr a b c d =
    toString (a + b + c + d)

main =
    Html.text (sumAndConvertToStr (fooA 2) second third fourth)

This way, we could observe that the syntax is redundant, and typing twice can be tedious and error-prone.

Proposal

It would be more interesting if we had an unique way to describe the type annotation above the implementation, in a way that we do not repeat the definition name. In this context, the Elm snippet could be converted, for example, into this:

import Html

@ Int -> Int
fooA x =
    1 + x

@ Int
second =
    2

@ Int
third =
    3

@ Int
fourth =
    4

@ Int -> Int -> Int -> Int -> String
sumAndConvertToStr a b c d =
    toString (a + b + c + d)

main =
    Html.text (sumAndConvertToStr (fooA 2) second third fourth)

The symbol @ is just an example. The community could choose the symbol they prefer (i.e. @, #, : or other that is not currently used in Elm’s syntax), but in this context there would be only one way to do it (and then, the problem of the redundancy would be solved).

Conclusion

This is a very simple proposal that would simplify type annotation removing its redundancy in a way that it would be very easy to convert existing code bases. For beginners it would not be harder to learn it, and compilation speed should increase slightly since it would not be necessary anymore to check if there is an implementation with the same name of the type annotation.

7 Likes

The redundant declaration is somewhat error prone, and a frequent source of errors for me. (small and quickly caught by the compiler) I regularly forget the definition line, use -> instead of =, or omit the = entirely.

Note that in type aliases we use colons to show the relationship between field names and the types of those fields. As it stands now the colon is the same in both places, which is more consistent, though more verbose than this proposal. Because Elm is meant to be easy for beginners to pick up, changes that break consistency are unlikely to be adopted. What do we think about type aliases that look like the following?

type alias Foo = {
  bar @ String,
  baz @ Int
}

Do you use elm-format? I use it in my IDE and run it on every save. I notice that if you get the repeated definition wrong, elm-format will recognise that and insert two newlines between the type declaration and the value declaration. I generally find this works well to provide me with quick feedback should I make that particular typo - I tend to hit CTRL+S to save after typing in a new function definition say.

Af for me, this way of declaring annotations hits readability without giving anything in return. Possible errors in annotations will be immediately caught by the compiler. Given one doesn’t have to write annotations at all (and get them guessed by the compiler) I can hardly see any point of changing this part of language.

This is a very good point… I wonder if there is something elm-format could reasonably do to reasonably make this less error-prone? If an annotation and an immediately following definition had mismatched names, should one of them just be ignored? Which would be more appropriate to consider more correct?

@avh4, I don’t see any evidence of it actually being error-prone. I have used Elm and Haskell for many years and never had this error, so I’d want to verify that it happens in practice before changing anything in elm-format.

I do not have plans to make changes here though. I think the fact that n : T has a consistent usage in records and documentation is important. So we can save a few chars, but we would pay in beginner questions, more time spent in books explaining things, etc.

If there is an improvement to make, I’d expect it to be in editor tooling. When you type foo : it could add a foo = on the next line or something. (I can imagine such a feature being extremely annoying as well. Cmd-d gives me really precise control, which I think I’d prefer.)

4 Likes

One thing I like to do is write the type annotation first, before writing the function. The name of the function and the type signature inform each other. The original post’s proposed syntax would not be very good for such a process.

As for tooling, the way I decided to handle this in my JetBrains plugin was to provide a quick-fix suggestion on a type annotation with no corresponding implementation. The user presses option-enter and it generates the function declaration, defaulting the parameters by lower-casing each type name. The user can then tab-through the parameters to tweak.

3 Likes

I’m not familiar with Haskell or the decision tree that led to the current syntax in Elm, but one of the things I appreciate most about Elm is that it embraces readability, consistency, and simplicity over special syntax and abstractions. I’m in favor of the current syntax unless there’s a more compelling reason to change it than extra typing or typo-related errors (that are immediately caught by the compiler and quickly fixed).

2 Likes

What’s also quite nice about the current syntax is that you can search for definitions in files quickly by searching for foo :… That (in most cases) finds the definition on the first try, as the first result. This proposal would disable that “usecase”.

2 Likes

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.