<p>I implemented a basic generics-free, parameterless trait (type class) system. And I want to parameterize my trait system. The alternative paths I know of are <strong>Generic Associated Types (GAT)</strong> and <strong>Higher-Kinded Types (HKT)</strong>. In my opinion HKT seems to be more expressive.</p>
<p>For the following GAT usage,</p>
<pre class="lang-rust prettyprint-override"><code>trait MonadGAT {
    type Unplug;
    type Plug&lt;B&gt;: MonadGAT;
    fn pure(a: Self::Unplug) -&gt; Self::Plug&lt;Self::Unplug&gt;;
    fn bind&lt;B, F&gt;(self, f: F) -&gt; Self::Plug&lt;B&gt;
    where
        F: Fn(Self::Unplug) -&gt; Self::Plug&lt;B&gt;;
}

impl&lt;A&gt; MonadGAT for Option&lt;A&gt; {
    type Unplug = A;
    type Plug&lt;B&gt; = Option&lt;B&gt;;
    fn pure(a: Self::Unplug) -&gt; Self::Plug&lt;Self::Unplug&gt; {
        Some(a)
    }

    fn bind&lt;B, F&gt;(self, f: F) -&gt; Option&lt;B&gt;
    where
        F: Fn(A) -&gt; Option&lt;B&gt;,
    {
        self.and_then(f)
    }
}
</code></pre>
<p>The equivalent HKT usage would be something like:</p>
<pre class="lang-rust prettyprint-override"><code>trait MonadHKT&lt;M&lt;~&gt;&gt; {
    fn pure&lt;A&gt;(a: A)-&gt; M&lt;A&gt;;
    fn bind&lt;A, B&gt;(self: M&lt;A&gt;, f: A =&gt; M&lt;B&gt;) -&gt; M&lt;B&gt;;
}

impl MonadHKT&lt;M&lt;~&gt;&gt; for Option {
    fn pure&lt;A&gt;(a: A) -&gt; Option&lt;A&gt; {
        Some(a)
    }
    fn bind&lt;A, B&gt;(self, f: A =&gt; Option&lt;B&gt;) -&gt; Option&lt;B&gt; {
        self.and_then(f)
    }
}
</code></pre>
<p>I think HKT is more readable and concise, so I tend to implement HKT.</p>
<hr />
<p>But I still have some questions:</p>
<ol>
<li><p>Are the upper limit of expression ability of the two at the same level?</p>
<ul>
<li>Is there a situation where GAT can express something but HKT cannot, or vice versa?</li>
<li>Is there any reason why Rust has to use GAT, since GAT is more complicated?</li>
</ul>
</li>
<li><p>Are there other ways to parameterize type classes?</p>
</li>
</ol>

<types><rust><type-systems>
What is the difference between GAT and HKT?
<p>The two type system features are related, but still quite distinct. For the specific example you’ve chosen, it is possible to encode the same rough idea using either feature, but this is a very narrow view of what the respective features are. To make it clearer why that is, let’s start by considering each of them independently.</p>
<h2>Higher-kinded types</h2>
<p>Higher-kinded types allow type constructors to accept other type constructors as arguments, rather than only accepting types. For example, in a type system that supports higher-kinded types, it is possible to write a type that represents the sum of two functors. In Haskell, this definition is provided in <a href="https://hackage.haskell.org/package/base-4.19.0.0/docs/Data-Functor-Sum.html" rel="noreferrer"><code>Data.Functor.Sum</code></a>:</p>
<pre class="lang-hs prettyprint-override"><code>data Sum f g a = InL (f a) | InR (g a)
</code></pre>
<p>In Rust syntax, the above definition would look like this:</p>
<pre class="lang-rust prettyprint-override"><code>enum Sum&lt;F,G,A&gt; {
  InL(F&lt;A&gt;),
  InR(G&lt;A&gt;)
}
</code></pre>
<p>This definition is not currently legal Rust, as Rust does not permit higher-kinded types, but it should hopefully get the idea across. The key detail is that <code>F</code> and <code>G</code> are not <em>types</em>, but <em>type constructors</em>, which allows them to each be applied to the type parameter <code>A</code> in the fields of <code>InL</code> and <code>InR</code>.</p>
<p>Using these definitions, it is possible to write the type <code>Sum Maybe Vector Int</code> (in Haskell syntax) or <code>Sum&lt;Option, Vec, i32&gt;</code> (in Rust syntax) to mean “either an optional integer or a vector of integers”. As you can see, this is quite unrelated to GATs, as there are not any traits involved in this example at all.</p>
<p>Another way of thinking about higher-kinded types is that <strong>HKTs allow type constructors to be used without being fully-applied.</strong> In a language that does not support HKTs, a bare type like <code>Option</code> is essentially a <em>syntax error</em>; all type constructors <em>must</em> appear applied to the appropriate number of arguments. This is not entirely dissimilar from languages that do not (directly) support higher-order functions: in Java, it is illegal to refer to a bare method name without applying it to arguments. Higher-kinded types allow type constructors to be passed around as if they were types in much the same way that higher-order functions allow functions to be passed around as if they were values.</p>
<h3>Higher-kinded typeclasses/traits</h3>
<p>In a language that supports HKTs, it is natural to also support higher-kinded typeclasses (traits in Rust nomenclature). For example, in Haskell, a commonly used typeclass has the following definition:</p>
<pre class="lang-hs prettyprint-override"><code>class Functor f where
  fmap :: (a -&gt; b) -&gt; f a -&gt; f b
</code></pre>
<p>Again, in hypothetical Rust syntax, this might look like this:</p>
<pre class="lang-rust prettyprint-override"><code>trait Functor {
  fn fmap&lt;F,A,B&gt;(self: Self&lt;A&gt;, f: F) -&gt; Self&lt;B&gt;
  where
    F: Fn(A) -&gt; B;
}
</code></pre>
<p>This translation is a little bit more confusing than the one above due to the fact that the first type parameter to all Rust traits is the implicit <code>Self</code> parameter, whereas all type parameters to Haskell typeclasses are written explicitly. Regardless, it should once again hopefully get the idea across: implementations of the <code>Functor</code> trait are not <em>types</em> but <em>type constructors</em>, which is what allows <code>Self</code> to be applied to the type parameters <code>A</code> and <code>B</code> in the type signature for <code>fmap</code>.</p>
<p>Here is an example implementation of <code>Functor</code> in both Haskell and (hypothetical) Rust syntax:</p>
<pre class="lang-hs prettyprint-override"><code>instance Functor Maybe where
  fmap :: (a -&gt; b) -&gt; Maybe a -&gt; Maybe b
  fmap f x = case x of
    Just v  -&gt; Just (f v)
    Nothing -&gt; Nothing
</code></pre>
<pre class="lang-rust prettyprint-override"><code>impl Functor for Option {
  fn fmap&lt;F,A,B&gt;(self: Option&lt;A&gt;, f: F) -&gt; Option&lt;B&gt;
  where
    F: Fn(A) -&gt; B
  {
    match self {
      Some(v) =&gt; Some(f(v)),
      None    =&gt; None,
    }
  }
}
</code></pre>
<p>Note that this uses the syntax <code>impl Functor for Option</code>, <em>not</em> <code>impl Functor for Option&lt;T&gt;</code>, as the whole idea is that <code>Self</code> is the <em>unapplied</em> type constructor.</p>
<h2>Associated types</h2>
<p>Associated types are an extension of typeclasses that allows type declarations to appear in the body of a class declaration. For example, we might define a class that provides conversions between signed and unsigned versions of an integer type (once again in both Haskell and Rust syntax):</p>
<pre class="lang-hs prettyprint-override"><code>class Signed a where
  type U a
  toUnsigned :: a -&gt; U a
  fromUnsigned :: U a -&gt; a
</code></pre>
<pre class="lang-rust prettyprint-override"><code>trait Signed {
  type U;
  fn to_unsigned(self) -&gt; Self::U;
  fn from_unsigned(Self::U) -&gt; Self;
}
</code></pre>
<p>Again, the specialness of the implicit <code>Self</code> parameter (and the use of method dot notation) makes the correspondence a little less direct, but the concept is the same. Here is an example instance for 8-bit integers:</p>
<pre class="lang-hs prettyprint-override"><code>instance Signed Int8 where
  type U Int8 = Word8
  asUnsigned = fromIntegral
  fromUnsigned = fromIntegral
</code></pre>
<pre class="lang-rust prettyprint-override"><code>impl Signed for i8 {
  type U = u8;
  fn to_unsigned(self) -&gt; u8 { self as u8 }
  fn from_unsigned(x: u8) -&gt; i8 { x as i8 }
}
</code></pre>
<p>One way to view associated types is as <em>type-level functions</em>. The Haskell syntax makes this particularly clear: <code>U</code> is a <em>function</em> that accepts a type and returns some other type. For example, <code>U Int8</code> applies <code>U</code> to <code>Int8</code> to get <code>Word8</code>, while <code>U Int16</code> applies <code>U</code> to <code>Int16</code> to get <code>Word16</code>. The Rust syntax makes this interpretation a little less clear, but Rust’s associated types still work the same way.</p>
<p>What distinguishes these “type-level functions” from type constructors is the same thing that distinguishes term-level functions from datatype constructors. That is, just as the expression <code>f x</code> can be <em>reduced</em> to another term by applying the function, the type <code>U Int8</code> can be <em>reduced</em> to another type. This is unlike the expression <code>Just 5</code> or the type <code>Maybe Int</code>, each of which is already fully reduced and can’t be reduced any further.</p>
<h3>Generic associated types</h3>
<p>Generic associated types are an extension of associated types, and they are conceptually quite simple: they are associated types that happen to be generic, i.e. they accept type parameters.</p>
<p>In a language that has both associated types and higher-kinded types, there is essentially nothing special about this idea: it simply allows the type-level functions defined by associated types to return <em>type constructors</em> in addition to types. For this reason, the term “generic associated type” is not in use in Haskell; the concept is included in the phrase “associated type”.</p>
<p>On the other hand, Rust does not support higher-kinded types, and as I noted in the previous section, this effectively means that its type system lacks the ability to talk about type constructors that are not immediately applied to arguments. This restriction means that generic associated types must be a special type system feature rather than merely being a special case of the associated type concept.</p>
<p>Since GATs are essentially type-level functions that return type constructors, one way to view them is as a limited and highly restricted form of HKTs. It is therefore unsurprising that some idioms that are usually encoded using HKTs can also be encoded using GATs, but these encodings are usually not exactly the same. For example, let’s consider a GAT encoding of the <code>Functor</code> class from above (which is quite similar to the definition of <code>MonadGAT</code> from your question):</p>
<pre class="lang-hs prettyprint-override"><code>class Functor (Unplug a) =&gt; Functor a where
  type Plug a :: Type
  type Unplug a :: Type -&gt; Type
  fmap :: (Plug a -&gt; b) -&gt; a -&gt; Unplug a b
</code></pre>
<pre class="lang-rust prettyprint-override"><code>trait Functor {
  type Plug;
  type Unplug&lt;B&gt;: Functor;
  fn fmap&lt;F,B&gt;(self, f: F) -&gt; Self::Unplug&lt;B&gt;
  where
    F: Fn(Self::Plug) -&gt; B;
}
</code></pre>
<p>If you consider that <code>Unplug</code> is essentially a function from a type to a type constructor, you may be able to figure out why this definition of <code>Functor</code> is not precisely the same as the one from earlier. Here is an instance of the GAT version of <code>Functor</code> that illustrates the difference:</p>
<pre class="lang-hs prettyprint-override"><code>instance Functor (Maybe a) where
  type Plug (Maybe a) = a
  type Unplug (Maybe a) = List

  fmap :: (a -&gt; b) -&gt; Maybe a -&gt; List b
  fmap f x = case x of
    Just v  -&gt; [f v]
    Nothing -&gt; []
</code></pre>
<pre class="lang-rust prettyprint-override"><code>impl&lt;A&gt; Functor for Option&lt;A&gt; {
  type Plug = A;
  type Unplug&lt;B&gt; = Vec&lt;B&gt;;

  fn fmap&lt;F,B&gt;(self, f: F) -&gt; Vec&lt;B&gt;
  where
    F: Fn(A) -&gt; B
  {
    match self {
      Some(v) =&gt; vec![f(v)],
      None    =&gt; vec![],
    }
  }
}
</code></pre>
<p>This instance is completely legal and is accepted by <code>rustc</code> today (given an implementation of <code>Functor</code> for <code>Vec&lt;A&gt;</code>), though it is probably not the instance that is desired: it results in <code>Some(false).fmap(|x| !x)</code> evaluating to <code>[true]</code>! This should not be completely shocking—the <code>Unplug</code> associated type is an arbitrary function, and the only restriction placed on it is that it must have an implementation of <code>Functor</code>. This is quite different from the more obvious encoding using HKTs, where type functions are not involved at all and the constructor is referred to directly.</p>
<h2>Returning to your questions</h2>
<p>With the above out of the way, allow me to finally answer your questions.</p>
<blockquote>
<p>Is there a situation where GAT can express something but HKT cannot, or vice versa?</p>
</blockquote>
<p>Yes.</p>
<p>As illustrated above, even attempting to encode the same thing using these features yields different results. Comparing the two directly is sort of a category error, as GATs are a special case of associated types, while HKTs are a pervasive type system feature. The <code>Sum</code> functor from the section on HKTs cannot be encoded using GATs (no traits are even involved, so GATs are simply not relevant). Likewise, HKTs alone certainly do not provide the ability to define type-level functions.</p>
<blockquote>
<p>Is there any reason why Rust has to use GAT, since GAT is more complicated?</p>
</blockquote>
<p>Again, this is sort of asking the wrong question, because it makes it sound as though these are two alternatives, and that Rust has chosen GATs over HKTs. This is almost backwards: Rust needs GATs precisely because it does not support HKTs! As mentioned above, if the type system natively supports both HKTs and associated types, support for GATs comes more or less for free.</p>
<p>This shifts the question to why Rust does not support HKTs more generally. The answer to that is nuanced and would likely be better suited to a separate question, but one reason is that HKTs pair particularly nicely with <em>partial application</em>, something that Haskell pervasively supports but Rust does not. In Haskell, a multi-argument type constructor like <code>Either</code> has kind <code>Type -&gt; Type -&gt; Type</code>, and that constructor can be partially applied as <code>Either e</code> to yield a type constructor of kind <code>Type -&gt; Type</code> (which is the right kind to support a <code>Functor</code> instance).</p>
<p>Haskell programmers generally accept this design choice, along with all the tradeoffs it entails, such as the inability to partially apply the <em>second</em> argument but not the first. This has substantial ramifications on library design, so that approach is unlikely to work in Rust, but alternatives are substantially more complicated.</p>
<blockquote>
<p>Are there other ways to parameterize type classes?</p>
</blockquote>
<p>I’m not sure what you mean (and this question seems potentially very broad). Regardless, it’s usually best to only ask one thing per question, so I will decline to answer it here, but feel free to open a separate question (with more details) about this.</p>

CC BY-SA 4.0
2023-11-24T01:26:27.300Z
2023-11-24T01:26:27.300Z
Alexis King
# What is the difference between GAT and HKT?
Posted by: username_0 on 2023-11-23
I implemented a basic generics-free, parameterless trait (type class) system. And I want to parameterize my trait system. The alternative paths I know of are Generic Associated Types (GAT) and Higher-Kinded Types (HKT). In my opinion HKT seems to be more expressive.
For the following GAT usage,
trait MonadGAT {
    type Unplug;
    type Plug: MonadGAT;
    fn pure(a: Self::Unplug) -> Self::Plug;
    fn bind(self, f: F) -> Self::Plug
    where
        F: Fn(Self::Unplug) -> Self::Plug;
}

impl MonadGAT for Option {
    type Unplug = A;
    type Plug = Option;
    fn pure(a: Self::Unplug) -> Self::Plug {
        Some(a)
    }

    fn bind(self, f: F) -> Option
    where
        F: Fn(A) -> Option,
    {
        self.and_then(f)
    }
}

The equivalent HKT usage would be something like:
trait MonadHKT> {
    fn pure(a: A)-> M;
    fn bind(self: M, f: A => M) -> M;
}

impl MonadHKT> for Option {
    fn pure(a: A) -> Option {
        Some(a)
    }
    fn bind(self, f: A => Option) -> Option {
        self.and_then(f)
    }
}

I think HKT is more readable and concise, so I tend to implement HKT.

But I still have some questions:

Are the upper limit of expression ability of the two at the same level?

Is there a situation where GAT can express something but HKT cannot, or vice versa?
Is there any reason why Rust has to use GAT, since GAT is more complicated?


Are there other ways to parameterize type classes?
**Tags:** types rust type-systems
---
## 1 answers
### Answer by username_1 on 2023-11-24. Score: 12 | Accepted answer
The two type system features are related, but still quite distinct. For the specific example you’ve chosen, it is possible to encode the same rough idea using either feature, but this is a very narrow view of what the respective features are. To make it clearer why that is, let’s start by considering each of them independently.
Higher-kinded types
Higher-kinded types allow type constructors to accept other type constructors as arguments, rather than only accepting types. For example, in a type system that supports higher-kinded types, it is possible to write a type that represents the sum of two functors. In Haskell, this definition is provided in Data.Functor.Sum:
data Sum f g a = InL (f a) | InR (g a)

In Rust syntax, the above definition would look like this:
enum Sum {
  InL(F),
  InR(G)
}

This definition is not currently legal Rust, as Rust does not permit higher-kinded types, but it should hopefully get the idea across. The key detail is that F and G are not types, but type constructors, which allows them to each be applied to the type parameter A in the fields of InL and InR.
Using these definitions, it is possible to write the type Sum Maybe Vector Int (in Haskell syntax) or Sum (in Rust syntax) to mean “either an optional integer or a vector of integers”. As you can see, this is quite unrelated to GATs, as there are not any traits involved in this example at all.
Another way of thinking about higher-kinded types is that HKTs allow type constructors to be used without being fully-applied. In a language that does not support HKTs, a bare type like Option is essentially a syntax error; all type constructors must appear applied to the appropriate number of arguments. This is not entirely dissimilar from languages that do not (directly) support higher-order functions: in Java, it is illegal to refer to a bare method name without applying it to arguments. Higher-kinded types allow type constructors to be passed around as if they were types in much the same way that higher-order functions allow functions to be passed around as if they were values.
Higher-kinded typeclasses/traits
In a language that supports HKTs, it is natural to also support higher-kinded typeclasses (traits in Rust nomenclature). For example, in Haskell, a commonly used typeclass has the following definition:
class Functor f where
  fmap :: (a -> b) -> f a -> f b

Again, in hypothetical Rust syntax, this might look like this:
trait Functor {
  fn fmap(self: Self, f: F) -> Self
  where
    F: Fn(A) -> B;
}

This translation is a little bit more confusing than the one above due to the fact that the first type parameter to all Rust traits is the implicit Self parameter, whereas all type parameters to Haskell typeclasses are written explicitly. Regardless, it should once again hopefully get the idea across: implementations of the Functor trait are not types but type constructors, which is what allows Self to be applied to the type parameters A and B in the type signature for fmap.
Here is an example implementation of Functor in both Haskell and (hypothetical) Rust syntax:
instance Functor Maybe where
  fmap :: (a -> b) -> Maybe a -> Maybe b
  fmap f x = case x of
    Just v  -> Just (f v)
    Nothing -> Nothing

impl Functor for Option {
  fn fmap(self: Option, f: F) -> Option
  where
    F: Fn(A) -> B
  {
    match self {
      Some(v) => Some(f(v)),
      None    => None,
    }
  }
}

Note that this uses the syntax impl Functor for Option, not impl Functor for Option, as the whole idea is that Self is the unapplied type constructor.
Associated types
Associated types are an extension of typeclasses that allows type declarations to appear in the body of a class declaration. For example, we might define a class that provides conversions between signed and unsigned versions of an integer type (once again in both Haskell and Rust syntax):
class Signed a where
  type U a
  toUnsigned :: a -> U a
  fromUnsigned :: U a -> a

trait Signed {
  type U;
  fn to_unsigned(self) -> Self::U;
  fn from_unsigned(Self::U) -> Self;
}

Again, the specialness of the implicit Self parameter (and the use of method dot notation) makes the correspondence a little less direct, but the concept is the same. Here is an example instance for 8-bit integers:
instance Signed Int8 where
  type U Int8 = Word8
  asUnsigned = fromIntegral
  fromUnsigned = fromIntegral

impl Signed for i8 {
  type U = u8;
  fn to_unsigned(self) -> u8 { self as u8 }
  fn from_unsigned(x: u8) -> i8 { x as i8 }
}

One way to view associated types is as type-level functions. The Haskell syntax makes this particularly clear: U is a function that accepts a type and returns some other type. For example, U Int8 applies U to Int8 to get Word8, while U Int16 applies U to Int16 to get Word16. The Rust syntax makes this interpretation a little less clear, but Rust’s associated types still work the same way.
What distinguishes these “type-level functions” from type constructors is the same thing that distinguishes term-level functions from datatype constructors. That is, just as the expression f x can be reduced to another term by applying the function, the type U Int8 can be reduced to another type. This is unlike the expression Just 5 or the type Maybe Int, each of which is already fully reduced and can’t be reduced any further.
Generic associated types
Generic associated types are an extension of associated types, and they are conceptually quite simple: they are associated types that happen to be generic, i.e. they accept type parameters.
In a language that has both associated types and higher-kinded types, there is essentially nothing special about this idea: it simply allows the type-level functions defined by associated types to return type constructors in addition to types. For this reason, the term “generic associated type” is not in use in Haskell; the concept is included in the phrase “associated type”.
On the other hand, Rust does not support higher-kinded types, and as I noted in the previous section, this effectively means that its type system lacks the ability to talk about type constructors that are not immediately applied to arguments. This restriction means that generic associated types must be a special type system feature rather than merely being a special case of the associated type concept.
Since GATs are essentially type-level functions that return type constructors, one way to view them is as a limited and highly restricted form of HKTs. It is therefore unsurprising that some idioms that are usually encoded using HKTs can also be encoded using GATs, but these encodings are usually not exactly the same. For example, let’s consider a GAT encoding of the Functor class from above (which is quite similar to the definition of MonadGAT from your question):
class Functor (Unplug a) => Functor a where
  type Plug a :: Type
  type Unplug a :: Type -> Type
  fmap :: (Plug a -> b) -> a -> Unplug a b

trait Functor {
  type Plug;
  type Unplug: Functor;
  fn fmap(self, f: F) -> Self::Unplug
  where
    F: Fn(Self::Plug) -> B;
}

If you consider that Unplug is essentially a function from a type to a type constructor, you may be able to figure out why this definition of Functor is not precisely the same as the one from earlier. Here is an instance of the GAT version of Functor that illustrates the difference:
instance Functor (Maybe a) where
  type Plug (Maybe a) = a
  type Unplug (Maybe a) = List

  fmap :: (a -> b) -> Maybe a -> List b
  fmap f x = case x of
    Just v  -> [f v]
    Nothing -> []

impl Functor for Option {
  type Plug = A;
  type Unplug = Vec;

  fn fmap(self, f: F) -> Vec
  where
    F: Fn(A) -> B
  {
    match self {
      Some(v) => vec![f(v)],
      None    => vec![],
    }
  }
}

This instance is completely legal and is accepted by rustc today (given an implementation of Functor for Vec), though it is probably not the instance that is desired: it results in Some(false).fmap(|x| !x) evaluating to [true]! This should not be completely shocking—the Unplug associated type is an arbitrary function, and the only restriction placed on it is that it must have an implementation of Functor. This is quite different from the more obvious encoding using HKTs, where type functions are not involved at all and the constructor is referred to directly.
Returning to your questions
With the above out of the way, allow me to finally answer your questions.

Is there a situation where GAT can express something but HKT cannot, or vice versa?

Yes.
As illustrated above, even attempting to encode the same thing using these features yields different results. Comparing the two directly is sort of a category error, as GATs are a special case of associated types, while HKTs are a pervasive type system feature. The Sum functor from the section on HKTs cannot be encoded using GATs (no traits are even involved, so GATs are simply not relevant). Likewise, HKTs alone certainly do not provide the ability to define type-level functions.

Is there any reason why Rust has to use GAT, since GAT is more complicated?

Again, this is sort of asking the wrong question, because it makes it sound as though these are two alternatives, and that Rust has chosen GATs over HKTs. This is almost backwards: Rust needs GATs precisely because it does not support HKTs! As mentioned above, if the type system natively supports both HKTs and associated types, support for GATs comes more or less for free.
This shifts the question to why Rust does not support HKTs more generally. The answer to that is nuanced and would likely be better suited to a separate question, but one reason is that HKTs pair particularly nicely with partial application, something that Haskell pervasively supports but Rust does not. In Haskell, a multi-argument type constructor like Either has kind Type -> Type -> Type, and that constructor can be partially applied as Either e to yield a type constructor of kind Type -> Type (which is the right kind to support a Functor instance).
Haskell programmers generally accept this design choice, along with all the tradeoffs it entails, such as the inability to partially apply the second argument but not the first. This has substantial ramifications on library design, so that approach is unlikely to work in Rust, but alternatives are substantially more complicated.

Are there other ways to parameterize type classes?

I’m not sure what you mean (and this question seems potentially very broad). Regardless, it’s usually best to only ask one thing per question, so I will decline to answer it here, but feel free to open a separate question (with more details) about this.
---
