Proposal: Alternative Syntax for the Built-in Typeclasses

#1

Type signatures like

myfun : comparable -> comparable -> Maybe Bool
myfun2 : number -> number -> number2 -> number2 -> (number, number2)

use built-in timeclasses. These typeclasses exist so you dont need new versions of infix operators like < (for comparable) or ++ (for appendable) for each datatype that uses them, enhancing expressiveness for programmers new and old alike.

The syntax for this is confusing, especially for beginners (or at least me when I was a beginner), because there’s really no indication that these types are special. An alternative is

myfun : Comparable a -> Comparable a -> Maybe Bool
myfun2 : Number a -> Number a -> Number b -> Number b -> (Number a, Number b)

If elm ever lets users extend the predefined typeclasses, then it would definitely be useful for these signatures to carry more information about what these types actually are. Especially if these types could someday be inherited to functions defined under the scope, as described at the bottom of this page

But even if that is never implemented, what is the case for the “magic” syntax of elm typeclasses today? What would you think of a different syntax?

1 Like
#2

What about number, comparable, and appendable using a different text color than other generic types? Would that make it clear to beginners that these have special meaning? If it works, it seems like it would be an easier solution to implement.

#3

I appreciate the reply, but this doesn’t quite address the issue I raised in my original post. Is a beginner supposed to just know what tools to use for the most didactic syntax highlighting? What are they supposed to think when they see number appear as a different color? What if they see code in a gist or a Discourse thread, etc. So, I think a syntactic solution is better than a subtle visual que for the reasons described briefly in my original post.

#4

To reiterate, I’m looking for:

  • A justification of the current syntax, or
  • Ideas about a different syntax which may conform to/deviate from mine, with justification

Cheers

#5

There is a section in the guide that describes these constrained type variables. No magic syntax. Just 4 constrained type variables that make working with core functionality easier than it would be without them.

This is a highly complex topic where changes to the language have dramatic consequences that need to be taken into account. It is such a big change that it turns Elm into another language. Support for type clases is one of the very first issues created for Elm and if you search the old archives you can find a lot of discussions about this topic, not all of them pleasant.

The relationship between current Elm and Elm+typeclasses is similar to the relationship between C and C++ and C++ is not C with Just™ a small added feature.

3 Likes
#6

Thanks for your input!

I am aware that it would be a big change. That is pretty obvious, for sure. I am aware there has a lot of discussion around the “issue” with typeclasses and elm. To clarify, I am not interested in user-defined typeclasses. Rather, I am interested in the design choices surrounding this feature of elm. They are not quite typeclasses, they’re more accurately described as constrained type variables like you say, I use the term loosely. Apologies for the confusion.

No magic syntax

It has been referred to as “magic” elsewhere, I simply use the term to mean there are “hidden” effects of using particular names for types. I don’t think a reference existing in the docs makes the syntax intrinsically intuitive, but I’ll agree the design generally fine once you’ve learned it. But I am interested in previously unexpressable uses for types when type names are scope-inherited. I could write up an example if you’d like. But I am also really interested in what others in the community might think of advantages to other designs of this feature!

Lastly, I don’t think saying “it’s a complex issue” and “it would be hard to implement” adresses the issue. Putting aside the practical ramifications of the implications of changing the syntax, what would be the best design of this feature? I’m really just thinking of ideas that may be used in the long term, or not at all, it’s not important. I’m obviously not in charge of elm development, so I don’t think planning implementation details would be very useful for me! Maybe if Evan swings by this thread and is really curious, we could work something out :slight_smile:.

#7

For a reference, the design philosophy I have in mind when critiquing contrained type variable’s syntax is the gestalt principle Similarity. Read about gestalt here

#8

I have not made a thorough investigation of the advantages and disadvantages of each approach BUT, based on my limited investigation and the needs that I’ve encountered most frequently, I would love something like the protocols in Elixir. It decouples the definition from the implementation and this makes it expansion easy. Someone can add the implementation for a protocol for a specific type (if they so see fit) without having to change the library that defines the type.

Because UIs are full of text, a lot of the time I end up with the need to convert a custom type into a String. I would so like to say text someValue than text (SomeCustomType.toString someValue). This would require implementing some toString protocol for SomeCustomType but I would rather do that in that module once and be able to use the values of that type everywhere else as string.

1 Like
#9

Oh, that’s an interesting response!

Correct me if I’m wrong, but it sounds quite a lot like you’re talking about polymorphism.

#10

Yes, it is a form of polymorphism. A form of static (compile time) polymorphism.

#11

One advantage I see with elixir’s protocols is consistent naming of functions with similar implementations. As in, there’s no guarantee that a datastructure will use the name map for maps or andThen for monadic binds, as is idiomatic within the elm community. Most people tend to favor consistent semantics. It makes swapping between choices of datastructure easier, not to have to remember the domain-specific name for that pattern

But it’s somewhat tangent. The only way polymorphism would apply to these typeclasses is the Java way of doing implicit castes. Say you have a datastructure foo: Foo, with a protocol implementation for toString. Then you could write

foo ++ " is a magnificient datastructure"

instead of

(toString foo) ++ "is a magnificent datastructure"

So the advantage would be that you don’t have to remember what name the Foo module author used for the toString implementation.

But of course, reading the code, you may have no clue what method of Foo specifically turns it into a String. For example, say Foo has the methods toWords and toText. Which one does foo ++ "a string" call? First of all, the module author is sorta dumb for doing this because the users have to just remember what the module author thinks the difference is between "words and “text”. This can get pretty nuanced, sort of like Elixirs distinction between “size” and “length” (I might have preferred “capacity” and “count”, but I digress). Second, the readers and writers have to look up the protocol implementation and remember, on top of these nuances, which the author picked as default. And this can vary from module to module. That’s a lot of cognitive burden!

This is why I, personally, feel differently about polymorphism; I like the explicit rules around type casting, I think explicit castes are the best way to predict how code will behave. It lowers the cognitive burden in cases where a datastructure does different things in different contexts by parameterizing the context, instead of leaving it implicit. Whether or not a language has protocols, it still relies on idiomatic naming to convey appropriate use. Without relying on implicit castes, the author’s previously complex distinction between toWords and toText, although arbirtary, becomes an advantage. The user of the Foo module has the freedom to choose either implementation to best suite the use-case, and is reminded by the compiler if they forget to make the choice.

But CMV! I’m sure you have a lot more first-hand experience with Elixir’s type system since I have none.

1 Like
closed #12

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