如何在Scala中自动生成一个类型类的可交换实例?

huangapple go评论60阅读模式
英文:

How to Auto Generate Commutative Instances of a Type Class in Scala?

问题

I understand you want a translation, so here it is:

我正在玩一些探索Scala类型系统的有趣的事情我遇到了一个有趣的挑战受到了Scala 3[多元等式](https://docs.scala-lang.org/scala3/reference/contextual/multiversal-equality.html)的启发。我正在编写一个程序,其中有一个表示交换操作的类型类。我想自动生成具有交换参数的此类型类的实例,以便如果我有一个对于`(T1, T2)`的实例,我会自动获得一个对于`(T2, T1)`的实例。

这是我的代码的一个简化版本

...

我不确定Scastie会保留文件多久但这里是它的要点请滚动到底部对其他部分的任何反馈都非常欢迎

---

##### 错误

```plaintext
+ 不是 typst.tpredef.TNone 的成员

尝试了一个扩展方法但无法完全构造

typst.tpredef.+[typst.tpredef.TNone, T2, Out](typst.tpredef.tNone)(
  /* ambiguous: both given instance canAddTNoneTAny in package typst.tpredef and given instance canAddAnyTNone in package typst.tpredef match type typst.tpredef.CommutativeAdd[typst.tpredef.TNone, T2, Out] */
  summon[typst.tpredef.CommutativeAdd[typst.tpredef.TNone, T2, Out]]
)

失败原因

给定的实例存在歧义包 typst.tpredef 中的 canAddTNoneTAny 和包 typst.tpredef 中的 canAddAnyTNone 都与包 typst.tpredef 中的方法 + 中的参数 canAdd 的类型 typst.tpredef.CommutativeAdd[typst.tpredef.TNone, T2, Out] 匹配

我尝试过的方法:

我尝试了几种方法来解决这个问题:

  • 手动交换参数:我最初定义了一个第二个给定实例,其中类型参数已交换。这种方法有效,但需要复制每个操作的代码,这并不理想。
  • 使用交换方法:我尝试通过在 CommutativeAdd 对象中引入一个交换方法来自动化参数交换过程。该方法接受一个类型为 CommutativeAdd[T2, T1, Out] 的实例,并返回类型为 CommutativeAdd[T1, T2, Out] 的实例。然而,这种方法仍然需要手动使用 CommutativeAdd.swap 创建给定的实例。
  • 使用元组:我探索了使用元组来表示添加方法的参数。这使我可以为 (TNone, T)(T, TNone) 定义实例,但它在我的情况下不能提供真正的类型级别的交换性。
  • 使用联合类型:我讨论了使用联合类型来表示添加方法的参数的可能性。然而,联合类型本质上并不提供类型级别的交换性,而且这种方法不会自动生成具有交换参数的实例。

更多细节

在我的程序中,我有诸如 TAnyTNonetNoneTInteger 等类型,它们代表不同类型的值。我可以使用类型类 CommutativeAdd 在这些类型上执行添加等操作。例如,由于在 CommutativeAdd 中定义了可交换性,TInteger(1) + tNonetNone + TInteger(1) 是等价的。顺便说一下,TNone 类似于 UnittNone 类似于 ()TInteger 是包装的 Int,而 TAnyTNoneTInteger 和其他几种类型的联合。


这纯粹是为了好玩和学习,所以我愿意接受创造性和非传统的解决方案。在Scala中是否有一种实现这个的方法?对于任何帮助或见解,我将不胜感激!


<details>
<summary>英文:</summary>

I&#39;m having some fun exploring Scala&#39;s type system and I&#39;ve come across an interesting challenge inspired by Scala 3&#39;s [Multiversal Equality](https://docs.scala-lang.org/scala3/reference/contextual/multiversal-equality.html). I&#39;m working on a program where I have a type class representing a commutative operation. I want to automatically generate instances of this type class with swapped parameters, so that if I have an instance for `(T1, T2)`, I automatically get an instance for `(T2, T1)`.

Here&#39;s a simplified version of my code:

```scala
package typst.tpredef

trait CommutativeAdd[-T1 &lt;: TAny, -T2 &lt;: TAny, +Out &lt;: TAny]:
  def add(t1: T1, t2: T2): Out

object CommutativeAdd:
  def apply[T1 &lt;: TAny, T2 &lt;: TAny, Out &lt;: TAny](using
      instance: CommutativeAdd[T1, T2, Out],
  ): CommutativeAdd[T1, T2, Out] = instance

given canAddTNoneTAny[T &lt;: TAny]: CommutativeAdd[TNone, T, T] with {
  def add(t1: TNone, t2: T): T = t2
}

given canAddTAnyTNone[T &lt;: TAny]: CommutativeAdd[T, TNone, T] with {
  def add(t1: T, t2: TNone): T = t1
}

extension [T1 &lt;: TAny, T2 &lt;: TAny, Out &lt;: TAny](
    t1: T1
)(using canAdd: CommutativeAdd[T1, T2, Out])
  @targetName(&quot;add&quot;)
  inline infix def +(t2: T2): Out = canAdd.add(t1, t2)

val a = TInteger(1) + tNone
val b = tNone + TInteger(1) // &lt;- errors out here

I'm not sure how long Scastie keeps files up, but here's the gist of it, scroll to the bottom. Any feedback on the other parts are more than welcome. Link.


Error
value + is not a member of typst.tpredef.TNone.

An extension method was tried, but could not be fully constructed:

typst.tpredef.+[typst.tpredef.TNone, T2, Out](typst.tpredef.tNone)(
  /* ambiguous: both given instance canAddTNoneTAny in package typst.tpredef and given instance canAddAnyTNone in package typst.tpredef match type typst.tpredef.CommutativeAdd[typst.tpredef.TNone, T2, Out] */
  summon[typst.tpredef.CommutativeAdd[typst.tpredef.TNone, T2, Out]]
)

failed with:

Ambiguous given instances: both given instance canAddTNoneTAny in package typst.tpredef and given instance canAddAnyTNone in package typst.tpredef match type typst.tpredef.CommutativeAdd[typst.tpredef.TNone, T2, Out] of parameter canAdd of method + in package typst.tpredef

What I've tried:

I've tried several approaches to solve this problem:

  • Manually Swapping Parameters: I initially defined a second given instance with the type parameters swapped. This works, but it requires duplicating the code for each operation, which is not ideal.
  • Using a Swap Method: I tried to automate the process of swapping parameters by introducing a swap method in the CommutativeAdd object. This method takes an instance of CommutativeAdd[T2, T1, Out] and returns an instance of CommutativeAdd[T1, T2, Out]. However, this approach still requires manually creating a given instance using CommutativeAdd.swap.
  • Using Tuples: I explored using tuples to represent the parameters of the add method. This allows me to define instances for both (TNone, T) and (T, TNone), but it doesn't provide true commutativity at the type level for my purposes.
  • Using Union Types: I discussed the possibility of using union types to represent the parameters of the add method. However, union types don't inherently provide commutativity at the type level, and this approach wouldn't automatically generate instances with swapped parameters.

Further Details

In my program, I have types like TAny, TNone, tNone, and TInteger representing different kinds of values. I can perform operations like addition on these types using the CommutativeAdd type class. For example, TInteger(1) + tNone and tNone + TInteger(1) are equivalent due to the commutativity defined in CommutativeAdd. By the way TNone is like Unit, tNone is like (), TInteger is a boxed Int, and TAny is a union of TNone, TInteger, and a couple more types.


This is purely for fun and learning, so I'm open to creative and out-of-the-box solutions. Is there a way to achieve this in Scala? Any help or insights would be greatly appreciated!

答案1

得分: 1

我想自动生成这种类型类的实例,参数互换,所以如果我有一个 (T1, T2) 的实例,我会自动获得一个 (T2, T1) 的实例。

这可以通过参数化的 given 实例来实现:

given [T1 <: TAny, T2 <: TAny, Out <: TAny](using canAdd: CommutativeAdd[T1, T2, Out]): CommutativeAdd[T2, T1, Out] with {
  def add(t2: T2, t1: T1): Out = canAdd.add(t1, t2)
}

注意:你的两个 given 实例之一(canAddTAnyTNonecanAddTNoneTAny)可以被删除。

但是,这不是你问题的根本原因。你的问题来自于你的 extension 方法以及处理类型参数的方式。这是修订版本(请注意类型参数):

extension [T1 <: TAny](t1: T1)
  def +[T2 <: TAny, Out <: TAny](t2: T2)(using canAdd: CommutativeAdd[T1, T2, Out]): Out = canAdd.add(t1, t2)

**更进一步:**你的方法是正确的,但有点冗长。你可以尝试找到一种方法来“分组”一些非常相似的操作(例如,TIntegerTFloat 的行为完全相同,只是类型不同)。

我正在玩一些探索 Scala 的类型系统

最后,你可以尝试通过更多地利用 Scala 的类型系统,例如使用不透明类型来完成你的挑战。这里提供了一个线索

英文:

> I want to automatically generate instances of this type class with
> swapped parameters, so that if I have an instance for (T1, T2), I
> automatically get an instance for (T2, T1).

This can be achieved with a parameterized given instance:

given [T1 &lt;: TAny, T2 &lt;: TAny, Out &lt;: TAny](using canAdd: CommutativeAdd[T1, T2, Out]): CommutativeAdd[T2, T1, Out] with {
  def add(t2: T2, t1: T1): Out = canAdd.add(t1, t2)
}

Note: one of your two given instances (canAddTAnyTNone and canAddTNoneTAny) can therefore be deleted.

BUT this is not the source of your issue. Your issue comes from your extension method and the way you handle type parameters. Here is a revised version (pay attention to the type parameters):

extension [T1 &lt;: TAny](t1: T1)
  def +[T2 &lt;: TAny, Out &lt;: TAny](t2: T2)(using canAdd: CommutativeAdd[T1, T2, Out]): Out = canAdd.add(t1, t2)

Going further: your approach is right but a little bit verbose. You could try to find a way to "group" certain operation that are very similar (for example, TInteger and TFloat behave exactly the same, they just have different types).

> I'm having some fun exploring Scala's type system

Finally, you could try to complete your challenge by leveraging Scala's type system even more using opaque type for example. Here is a lead

huangapple
  • 本文由 发表于 2023年6月6日 12:05:30
  • 转载请务必保留本文链接:https://go.coder-hub.com/76411388.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定