我现在很少用F#进行这种斗争,但是再次类型继承与F#相比并不常见,所以也许我只是幸运。或者我错过了显而易见的事实。通常,当编译器抱怨不知道某种类型时,我会颠倒管道或合成操作数的顺序,而且我已经完成了。
基本上,如果函数调用适用于g(f x)
,它也可以作为x |> f |> g
或(f >> g) x
使用。但今天它没有......
这是我的意思的概念验证:
module Exc =
open System
type MyExc(t) = inherit Exception(t)
let createExc t = new MyExc(t)
type Ex = Ex of exn
type Res = Success of string | Fail of Ex with
static member createRes1 t = Ex(createExc(t)) |> Fail // compiled
static member createRes2 t = t |> createExc |> Ex |> Fail // FS0001
static member createRes3 = createExc >> Ex >> Fail // FS0001
通常,这是有效的(至少根据我的经验)。 "失败"扔:
错误FS0001:类型不匹配。期待MyExc - > ' a但是给了一个exn - >防爆。类型' MyExc'不符合' exn'
的类型
没什么大不了的,也不是很难解决,但我碰巧写了很多代码,其中组合是更简单/更清晰的方法,我不想编写一堆实用函数,我有无处不在。
我查看了灵活类型,因为我猜这是一个逆转问题,但我不知道如何在这里应用它。保持这种惯用的任何想法?
注意,如果我重新排列,即作为Ex << createExc >> Fail
或使用管道向后运算符,我最终会在不同的部分出现相同的错误。
答案 0 :(得分:7)
在这种情况下,F#编译器的行为有点不规则。在您的示例中,您希望将类型MyExc
的值传递给期望exn
的构造函数。将对象作为其基类的值处理是一种有效的版本,但F#编译器仅在非常有限的位置插入这些版本。
特别是,它在向函数传递参数时插入coersion,但在创建列表或从函数返回结果时不会插入它们(例如)。
在您的示例中,将值传递给有区别的union构造函数时需要一个coersion。似乎只有在直接创建union情况时才会发生这种情况,但是当将union情况视为函数时, not 会发生:
// foo is a function that takes `obj` and Foo is a DU case that takes `obj`
let foo (o:obj) = o
type Foo = Foo of obj
foo(System.Random()) // Coersion inserted automatically
Foo(System.Random()) // Coersion inserted automatically
System.Random() |> foo // Coersion inserted automatically
System.Random() |> Foo // ..but not here!
因此,F#编译器自动应用coersions的有限位置包括各种调用函数的方法,但只能直接创建DU情况。
这是一个有点冒犯的行为 - 我认为将DU案例视为普通函数是有意义的,包括在使用|>
时自动插入coersions,但我不确定是否有任何技术上的原因造成了这种困难。
答案 1 :(得分:3)
类型推断不适用于子类型(其中继承是一种情况)。 H&amp; M算法在其中没有子类型的概念,并且随着时间的推移使其适应的各种尝试没有产生好的结果。 F#编译器确实尽可能地以特殊情况补丁的形式适应子类型。例如,当实际参数是形式参数的超类型时,它会认为函数“兼容”。但由于某种原因,在将union构造函数转换为函数时,此“补丁”不会转换。
例如:
type U() = inherit exn()
type T = T of exn
let g f x = f x
let e = U()
let a = T e // works
let b = g T e // compile error: `e` was expected to have type `exn`, but here has type `U`
在最后一行,union构造函数T
用作自由函数,因此它丢失了子类型补丁。
奇怪的是,这适用于常规函数(即那些不是作为联合构造函数开始的函数):
let makeT u = T u
let a = makeT e // works
let b = g makeT e // also works!
它甚至可以无点工作:
let makeT = T
let a = makeT e // works
let b = g makeT e // still works!
此详细信息为您提供了一种解决方法:您可以为Ex
构造函数指定另一个名称,并且管道将起作用:
type Ex = Ex of exn
let makeEx = Ex
static member createRes2 t = t |> createExc |> makeEx |> Fail // Should work now
答案 2 :(得分:2)
您可以使用继承约束使类型具有通用性。
open System
type MyExc (t) = inherit Exception (t)
let createExc t = MyExc (t)
type Ex<'t when 't :> exn> = Ex of 't
type Res<'t when 't :> exn> = Success of string | Fail of 't Ex with
static member createRes1 t = Ex (createExc t) |> Fail
static member createRes2 t = t |> createExc |> Ex |> Fail
static member createRes3 = createExc >> Ex >> Fail