管道或组合的类型推断失败,正常的函数调用成功

Abe*_*bel 7 inheritance f# contravariance piping function-composition

我现在很少用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
Run Code Online (Sandbox Code Playgroud)

通常,这是有效的(至少根据我的经验)."失败"抛出的线条:

错误FS0001:类型不匹配.期待一个MyExc - >'a但是给了一个exn - > Ex."MyExc"类型与"exn"类型不匹配

没什么大不了的,不是很难解决,但我碰巧要编写很多代码,其中组合是更简单/更清晰的方法,我不想编写一堆我必须放在任何地方的实用函数.

我看了灵活的类型,因为我猜这是一个逆变问题,但我不知道如何在这里应用它.保持这种惯用的任何想法?

注意,如果我重新排列,即作为Ex << createExc >> Fail或使用管道后向运算符,我最终会在不同的部分上出现相同的错误.

Tom*_*cek 7

在这种情况下,F#编译器的行为有点不规则.在您的示例中,您希望将type的值传递给MyExc期望的构造函数exn.将对象作为其基类的值处理是有效的coersion,但F#编译器仅在非常有限的位置插入此类coersion.

特别是,它在向函数传递参数时插入coersion,但在创建列表或从函数返回结果时不插入它们(例如).

在您的示例中,在将值传递给有区别的联合构造函数时,您需要一个coersion.似乎只有在直接创建union情况时才会发生这种情况,但在将union情况视为函数时不会发生这种情况:

// 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!
Run Code Online (Sandbox Code Playgroud)

因此,F#编译器自动应用coersions的有限位置包括调用函数的各种方式,但只能直接创建DU情况.

这是一个有点滑稽的行为 - 我认为将DU案件作为普通函数处理是有意义的,包括在你使用时自动插入coersions |>,但我不确定是否有任何技术原因使得这很难.