英文:
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)
定义实例,但它在我的情况下不能提供真正的类型级别的交换性。 - 使用联合类型:我讨论了使用联合类型来表示添加方法的参数的可能性。然而,联合类型本质上并不提供类型级别的交换性,而且这种方法不会自动生成具有交换参数的实例。
更多细节
在我的程序中,我有诸如 TAny
、TNone
、tNone
和 TInteger
等类型,它们代表不同类型的值。我可以使用类型类 CommutativeAdd
在这些类型上执行添加等操作。例如,由于在 CommutativeAdd
中定义了可交换性,TInteger(1) + tNone
和 tNone + TInteger(1)
是等价的。顺便说一下,TNone
类似于 Unit
,tNone
类似于 ()
,TInteger
是包装的 Int
,而 TAny
是 TNone
、TInteger
和其他几种类型的联合。
这纯粹是为了好玩和学习,所以我愿意接受创造性和非传统的解决方案。在Scala中是否有一种实现这个的方法?对于任何帮助或见解,我将不胜感激!
<details>
<summary>英文:</summary>
I'm having some fun exploring Scala's type system and I've come across an interesting challenge inspired by Scala 3's [Multiversal Equality](https://docs.scala-lang.org/scala3/reference/contextual/multiversal-equality.html). I'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's a simplified version of my code:
```scala
package typst.tpredef
trait CommutativeAdd[-T1 <: TAny, -T2 <: TAny, +Out <: TAny]:
def add(t1: T1, t2: T2): Out
object CommutativeAdd:
def apply[T1 <: TAny, T2 <: TAny, Out <: TAny](using
instance: CommutativeAdd[T1, T2, Out],
): CommutativeAdd[T1, T2, Out] = instance
given canAddTNoneTAny[T <: TAny]: CommutativeAdd[TNone, T, T] with {
def add(t1: TNone, t2: T): T = t2
}
given canAddTAnyTNone[T <: TAny]: CommutativeAdd[T, TNone, T] with {
def add(t1: T, t2: TNone): T = t1
}
extension [T1 <: TAny, T2 <: TAny, Out <: TAny](
t1: T1
)(using canAdd: CommutativeAdd[T1, T2, Out])
@targetName("add")
inline infix def +(t2: T2): Out = canAdd.add(t1, t2)
val a = TInteger(1) + tNone
val b = tNone + TInteger(1) // <- 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 ofCommutativeAdd[T2, T1, Out]
and returns an instance ofCommutativeAdd[T1, T2, Out]
. However, this approach still requires manually creating a given instance usingCommutativeAdd.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
实例之一(canAddTAnyTNone
和 canAddTNoneTAny
)可以被删除。
但是,这不是你问题的根本原因。你的问题来自于你的 extension
方法以及处理类型参数的方式。这是修订版本(请注意类型参数):
extension [T1 <: TAny](t1: T1)
def +[T2 <: TAny, Out <: TAny](t2: T2)(using canAdd: CommutativeAdd[T1, T2, Out]): Out = canAdd.add(t1, t2)
**更进一步:**你的方法是正确的,但有点冗长。你可以尝试找到一种方法来“分组”一些非常相似的操作(例如,TInteger
和 TFloat
的行为完全相同,只是类型不同)。
我正在玩一些探索 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 <: 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)
}
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 <: TAny](t1: T1)
def +[T2 <: TAny, Out <: 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
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论