I am trying to emulate a system of type classes in F#; I would like to create pair printer which automatically instantiates the right series of calls to the printing functions. My latest try, which is pasted here, fails miserably since F# cannot identify the right overload and gives up immediately:
我正在尝试模拟f#中的类型类系统;我想创建对打印机,它自动地实例化对打印函数的正确调用系列。我的最新尝试,在这里粘贴,失败了,因为f#不能识别正确的超载,并立即放弃:
type PrintableInt(x:int) =
member this.Print() = printfn "%d" x
let (!) x = PrintableInt(x)
type Printer() =
static member inline Print< ^a when ^a : (member Print : Unit -> Unit)>(x : ^a) =
(^a : (member Print : Unit -> Unit) x)
static member inline Print((x,y) : 'a * 'b) =
Printer.Print(x)
Printer.Print(y)
let x = (!1,!2),(!3,!4)
Printer.Print(x)
Is there any way to do so? I am doing this in the context of game development, so I cannot afford the runtime overhead of reflection, retyping and dynamic casting: either I do this statically through inlining or I don't do it at all :(
有什么办法吗?我在游戏开发的环境中这样做,所以我无法负担反射、重分和动态转换的运行时开销:要么我通过内联进行静态的操作,要么我根本就不做。
2 个解决方案
#1
7
What you're trying to do is possible. You can emulate typeclasses in F#, as Tomas said maybe is not as idiomatic as in Haskell. I think in your example you are mixing typeclasses with duck-typing, if you want to go for the typeclasses approach don't use members, use functions and static members instead.
你想做的事是可能的。您可以在f#中模拟类型化,就像Tomas说的那样,可能不像Haskell中那么惯用。我认为在您的示例中,您将类型转换与duck类型混合在一起,如果您想要进行类型转换,请不要使用成员,而是使用函数和静态成员。
So your code could be something like this:
你的代码可能是这样的:
type Print = Print with
static member ($) (_Printable:Print, x:string) = printfn "%s" x
static member ($) (_Printable:Print, x:int ) = printfn "%d" x
// more overloads for existing types
let inline print p = Print $ p
type Print with
static member inline ($) (_Printable:Print, (a,b) ) = print a; print b
print 5
print ((10,"hi"))
print (("hello",20), (2,"world"))
// A wrapper for Int (from your sample code)
type PrintableInt = PrintableInt of int with
static member ($) (_Printable:Print, (PrintableInt (x:int))) = printfn "%d" x
let (!) x = PrintableInt(x)
let x = (!1,!2),(!3,!4)
print x
// Create a type
type Person = {fstName : string ; lstName : string } with
// Make it member of _Printable
static member ($) (_Printable:Print, p:Person) = printfn "%s, %s" p.lstName p.fstName
print {fstName = "John"; lstName = "Doe" }
print (1 ,{fstName = "John"; lstName = "Doe" })
Note: I used an operator to avoid writing the constraints by hand, but in this case is also possible to use a named static member. More about this technique here.
注意:我使用了一个操作符来避免手工编写约束,但是在这种情况下也可以使用命名的静态成员。更多关于这个技巧。
#2
4
What you're trying to do is not possible
(edit: apparently, it can be done - but it might not be idiomatic F#), because the constraint language cannot capture the constraints you need for the second Print
operation. Basically, there is no way to write recursive constraints saying that:
您想要做的事情是不可能的(编辑:显然,它是可以完成的——但是它可能不是惯用的f#),因为约束语言无法捕获您需要的第二次打印操作所需要的约束。基本上,没有办法写递归约束说:
Let C be a constraint specifying that the type either provides
让C成为一个约束,指定该类型提供Print,或者它是一个二元数组,其中每个元素都满足C。
F# does not support type-classes and so most of the attempts to emulate them will (probably) be limited in some way or will look very unnatural. In practice, instead of trying to emulate solutions that work in other languages, it is better to look for an idiomatic F# solution to the problem.
f#不支持类型类,因此大多数试图模仿它们的尝试(可能)会以某种方式受到限制,或者看起来很不自然。在实践中,与其尝试使用其他语言的解决方案,不如寻找一个惯用的f#解决方案。
The pretty printing that you're using as a sample would be probably implemented using Reflection or by wrapping not just integers, but also tuples.
作为一个示例,您使用的漂亮的打印可能是通过反射实现的,或者通过包装不只是整数,也可以是元组。
#1
7
What you're trying to do is possible. You can emulate typeclasses in F#, as Tomas said maybe is not as idiomatic as in Haskell. I think in your example you are mixing typeclasses with duck-typing, if you want to go for the typeclasses approach don't use members, use functions and static members instead.
你想做的事是可能的。您可以在f#中模拟类型化,就像Tomas说的那样,可能不像Haskell中那么惯用。我认为在您的示例中,您将类型转换与duck类型混合在一起,如果您想要进行类型转换,请不要使用成员,而是使用函数和静态成员。
So your code could be something like this:
你的代码可能是这样的:
type Print = Print with
static member ($) (_Printable:Print, x:string) = printfn "%s" x
static member ($) (_Printable:Print, x:int ) = printfn "%d" x
// more overloads for existing types
let inline print p = Print $ p
type Print with
static member inline ($) (_Printable:Print, (a,b) ) = print a; print b
print 5
print ((10,"hi"))
print (("hello",20), (2,"world"))
// A wrapper for Int (from your sample code)
type PrintableInt = PrintableInt of int with
static member ($) (_Printable:Print, (PrintableInt (x:int))) = printfn "%d" x
let (!) x = PrintableInt(x)
let x = (!1,!2),(!3,!4)
print x
// Create a type
type Person = {fstName : string ; lstName : string } with
// Make it member of _Printable
static member ($) (_Printable:Print, p:Person) = printfn "%s, %s" p.lstName p.fstName
print {fstName = "John"; lstName = "Doe" }
print (1 ,{fstName = "John"; lstName = "Doe" })
Note: I used an operator to avoid writing the constraints by hand, but in this case is also possible to use a named static member. More about this technique here.
注意:我使用了一个操作符来避免手工编写约束,但是在这种情况下也可以使用命名的静态成员。更多关于这个技巧。
#2
4
What you're trying to do is not possible
(edit: apparently, it can be done - but it might not be idiomatic F#), because the constraint language cannot capture the constraints you need for the second Print
operation. Basically, there is no way to write recursive constraints saying that:
您想要做的事情是不可能的(编辑:显然,它是可以完成的——但是它可能不是惯用的f#),因为约束语言无法捕获您需要的第二次打印操作所需要的约束。基本上,没有办法写递归约束说:
Let C be a constraint specifying that the type either provides
让C成为一个约束,指定该类型提供Print,或者它是一个二元数组,其中每个元素都满足C。
F# does not support type-classes and so most of the attempts to emulate them will (probably) be limited in some way or will look very unnatural. In practice, instead of trying to emulate solutions that work in other languages, it is better to look for an idiomatic F# solution to the problem.
f#不支持类型类,因此大多数试图模仿它们的尝试(可能)会以某种方式受到限制,或者看起来很不自然。在实践中,与其尝试使用其他语言的解决方案,不如寻找一个惯用的f#解决方案。
The pretty printing that you're using as a sample would be probably implemented using Reflection or by wrapping not just integers, but also tuples.
作为一个示例,您使用的漂亮的打印可能是通过反射实现的,或者通过包装不只是整数,也可以是元组。