<p>I'm playing with creating a language after doing the <a href="https://llvm.org/docs/tutorial/" rel="nofollow noreferrer">LLVM Kaleidoscope tutorial</a>. I've added some basic type checking, and now I want to add some functions that write things to standard output, as must languages do.</p>
<p>Let's say I create functions like (pseudocode):</p>
<pre><code>func print(x: Int) { ... }
func print(x: String) { ... }
func print(x: Double) { ... }
</code></pre>
<p>Which I can call like:</p>
<pre><code>print(123)
print(&quot;Hello world&quot;)
==&gt;
123
Hello world
</code></pre>
<p>In languages like C++ that have &quot;overloading&quot;, I can simply define the different functions with the same name and different argument types, and it's as if the symbol <code>print</code> is bound to to a function group – an &quot;overload set&quot;. And you have to decide during type checking which exact function to call based on the types of the arguments.</p>
<p>The other style of solution seems to be to create a central generic function and then specify in some way the implementations of it, like:</p>
<pre><code>func print(x: T) where T is Printable
Int is Printable { ... }
String is Printable { ... }
</code></pre>
<p>There are different strategies here but basically you're saying the type <code>T</code> can be anything that's a member of this defined group of types.</p>
<p>Is this six of one, half dozen of the other? Or are there some advantages to one way or the other?</p>
<p>It seems like the second way is more modern, and I see it in Rust and Haskell, and others. In C++ too, in a way, with templates and concepts.</p>

<generics><polymorphism><overloading>
Pros and cons of overloading vs type classes or similar polymorphism
<p>These do not need to be exclusive options: Scala has both typeclasses and overloads. Here are two ways to implement <code>println</code>:</p>
<pre class="lang-scala prettyprint-override"><code>def println(x: Int) = ???
def println(x: String) = ???
</code></pre>
<pre class="lang-scala prettyprint-override"><code>trait Printable[+A]:
  def println(a: A): Unit
given Printable[Int] with
  def println(i: Int) = ???
given Printable[String] with
  def println(s: String) = ???
def println[A](a: A)(using p: Printable[A]) = p.println(a)
// or:
def println[A: Printable](a: A) = summon[Printable[A]].println(a)
</code></pre>
<p>There are advantages and disadvantages to both options.</p>
<h2>Competing standards</h2>
<p>Every type class which is not in the standard library will be subject to competing standards. <code>foo[A: mylib.Foo]</code> and <code>foo[A: otherlib.Foo]</code> can't exist in the same place unless your language does also have overloads and type resolution strong enough to decide which to use, <em>and</em> provided no type exists that has both <code>mylib.Foo[T]</code> and <code>otherlib.Foo[T]</code> instances. This is somewhat unlikely, assuming those <code>Foo</code>s do the same things and some third library might want to provide users of either library the instances for their types.</p>
<h2>Locality</h2>
<p>Many languages with overloads do not allow third party code to add overloads to existing code. This might mean that library code needs to use different names than the standard library for doing similar things. Typeclasses, by design, avoid such problems.</p>

CC BY-SA 4.0
2023-05-28T11:53:12.660Z
2023-05-28T11:53:12.660Z
RubenVerg
CC BY-SA 4.0
2023-05-28T16:54:44.987Z
This is helpful, but I'm having trouble understanding the paragraph "Competing Standards", because I'm not sure what the code `aaa[B: C]` means. I think a little elaboration with a more concrete example of a conflict would help.
Rob N
CC BY-SA 4.0
2023-05-28T19:15:27.027Z
right, sorry, that's the same syntax as the last line of the second Scala example, it basically means that `aaa` is generic in `B`, which is required to also have a `C` instance
RubenVerg
CC BY-SA 4.0
2023-05-29T19:57:15.137Z
@RobN `def printable[A: Printable](a: A) = ...` is sugar for `def printable[A](a: A)(using p: Printable) = p.println(a)`, where `p` is basically an instance of the typeclass `Printable`. The `A: Printable` thing is a context bound. It's kinda like saying `(Printable a) =>` in Haskell.
user
CC BY-SA 4.0
2023-05-30T14:30:35.353Z
I think you meant `(using p: Printable[A])` but I get the drift. Thank you to both of you.
Rob N
# Pros and cons of overloading vs type classes or similar polymorphism
Posted by: username_0 on 2023-05-27
I'm playing with creating a language after doing the LLVM Kaleidoscope tutorial. I've added some basic type checking, and now I want to add some functions that write things to standard output, as must languages do.
Let's say I create functions like (pseudocode):
func print(x: Int) { ... }
func print(x: String) { ... }
func print(x: Double) { ... }

Which I can call like:
print(123)
print("Hello world")
==>
123
Hello world

In languages like C++ that have "overloading", I can simply define the different functions with the same name and different argument types, and it's as if the symbol print is bound to to a function group – an "overload set". And you have to decide during type checking which exact function to call based on the types of the arguments.
The other style of solution seems to be to create a central generic function and then specify in some way the implementations of it, like:
func print(x: T) where T is Printable
Int is Printable { ... }
String is Printable { ... }

There are different strategies here but basically you're saying the type T can be anything that's a member of this defined group of types.
Is this six of one, half dozen of the other? Or are there some advantages to one way or the other?
It seems like the second way is more modern, and I see it in Rust and Haskell, and others. In C++ too, in a way, with templates and concepts.
**Tags:** generics polymorphism overloading
---
## 1 answers
### Answer by username_1 on 2023-05-28. Score: 9 | Accepted answer
These do not need to be exclusive options: Scala has both typeclasses and overloads. Here are two ways to implement println:
def println(x: Int) = ???
def println(x: String) = ???

trait Printable[+A]:
  def println(a: A): Unit
given Printable[Int] with
  def println(i: Int) = ???
given Printable[String] with
  def println(s: String) = ???
def println[A](a: A)(using p: Printable[A]) = p.println(a)
// or:
def println[A: Printable](a: A) = summon[Printable[A]].println(a)

There are advantages and disadvantages to both options.
Competing standards
Every type class which is not in the standard library will be subject to competing standards. foo[A: mylib.Foo] and foo[A: otherlib.Foo] can't exist in the same place unless your language does also have overloads and type resolution strong enough to decide which to use, and provided no type exists that has both mylib.Foo[T] and otherlib.Foo[T] instances. This is somewhat unlikely, assuming those Foos do the same things and some third library might want to provide users of either library the instances for their types.
Locality
Many languages with overloads do not allow third party code to add overloads to existing code. This might mean that library code needs to use different names than the standard library for doing similar things. Typeclasses, by design, avoid such problems.
> **Comments:**
> **username_0** (2023-05-28): This is helpful, but I'm having trouble understanding the paragraph "Competing Standards", because I'm not sure what the code `aaa[B: C]` means. I think a little elaboration with a more concrete example of a conflict would help.  
> **username_1** (2023-05-28): right, sorry, that's the same syntax as the last line of the second Scala example, it basically means that `aaa` is generic in `B`, which is required to also have a `C` instance  
> **username_2** (2023-05-29): @RobN `def printable[A: Printable](a: A) = ...` is sugar for `def printable[A](a: A)(using p: Printable) = p.println(a)`, where `p` is basically an instance of the typeclass `Printable`. The `A: Printable` thing is a context bound. It's kinda like saying `(Printable a) =>` in Haskell.  
> **username_0** (2023-05-30): I think you meant `(using p: Printable[A])` but I get the drift. Thank you to both of you.  
---
