4

假设有一个F#定义:

type Either<'a,'b> = | Left of 'a | Right of 'b

let f (i : int) : Either<int, string> =
    if i > 0
        then Left i
        else Right "nothing"

f代码中使用函数C#

var a = Library.f(5);

结果值如何a与数据构造函数进行模式匹配?就像是:

/*
(if a is Left x)
    do something with x
(if a is Right y)
    do something with y
*/
4

3 回答 3

7

从 C# 中使用 F# 可区分联合有点不雅,因为它们是如何编译的。

我认为最好的方法是定义一些成员(在 F# 端),这将简化使用 C# 中的类型。有多种选择,但我更喜欢定义TryLeftTryRight行为类似的方法Int32.TryParse(因此使用 F# API 的 C# 开发人员应该熟悉它们):

open System.Runtime.InteropServices

type Either<'a,'b> = 
  | Left of 'a 
  | Right of 'b
  member x.TryLeft([<Out>] a:byref<'a>) =
    match x with Left v -> a <- v; true | _ -> false
  member x.TryRight([<Out>] b:byref<'b>) =
    match x with Right v -> b <- v; true | _ -> false

然后您可以使用 C# 中的类型,如下所示:

int a;
string s;
if (v.TryLeft(out a)) Console.WriteLine("Number: {0}", a);
else if (v.TryRight(out s)) Console.WriteLine("String: {0}", s);

这样做会失去一些 F# 安全性,但这是在没有模式匹配的语言中所期望的。但好消息是任何熟悉 .NET 的人都应该能够使用在 F# 中实现的 API。

另一种选择是定义成员,该成员使用左/右大小写携带的值Match接受Func<'a>和委托并调用右委托。Func<'b>从函数的角度来看,这更好一些,但对于 C# 调用者来说可能不太明显。

于 2013-09-25T18:29:51.003 回答
6

我会定义一个Match成员,让代表在每个场景中执行。在 F# 中,你会这样做(但如果需要,你可以在 C# 扩展方法中做一些等效的事情):

type Either<'a,'b> = | Left of 'a | Right of 'b
with
    member this.Match<'t>(ifLeft:System.Func<'a,'t>, ifRight:System.Func<'b,'t>) =
        match this with
        | Left a -> ifLeft.Invoke a
        | Right b -> ifRight.Invoke b

现在你应该可以在 C# 中做这样的事情了:

var result = a.Match(ifLeft: x => x + 1, ifRight: y => 2 * y);
于 2013-09-25T21:00:43.473 回答
1

3.0 规范

8.5.4 用于其他 CLI 语言的联合类型的编译形式

编译的联合类型 U 具有:

  1. 每个空联合案例 C 都有一个 CLI 静态 getter 属性 UC。此属性获取一个表示每个此类案例的单例对象。
  2. 每个非空联合案例 C 都有一个 CLI 嵌套类型 UC。此类型具有联合案例的每个字段的实例属性 Item1、Item2....,如果只有一个字段,则具有单个实例属性 Item。但是,只有一种情况的编译联合类型没有嵌套类型。相反,联合类型本身扮演了案例类型的角色。

  3. 每个非空联合案例 C 都有一个 CLI 静态方法 U.NewC。此方法为该案例构造一个对象。

  4. 每个案例 C 都有一个 CLI 实例属性 U.IsC。此属性针对案例返回 true 或 false。
  5. 每个案例 C 都有一个 CLI 实例属性 U.Tag。此属性获取或计算与案例对应的整数标记。
  6. 如果 U 有多个 case,则它有一个 CLI 嵌套类型 U.Tags。U.Tags 类型为每种情况包含一个整数文字,从零开始按递增顺序排列。

  7. 除了任何用户定义的属性或方法之外,已编译的联合类型还具有实现其自动生成的接口所需的方法。

这些方法和属性不能直接从 F# 中使用。但是,这些类型具有面向用户的 List.Empty、List.Cons、Option.None 和 Option.Some 属性和/或方法。

编译的联合类型不能用作另一种 CLI 语言中的基类型,因为它至少有一个程序集私有构造函数而没有公共构造函数。

如果您无法更改 F# api,使用上面的第 2 点和第 4 点,您可以这样做:

C#

class Program
{
    static void Main(string[] args)
    {
        PrintToConsole("5");
        PrintToConsole("test");
    }

    static void PrintToConsole(string value)
    {
        var result = test.getResult(value);
        if (result.IsIntValue) Console.WriteLine("Is Int: " + ((test.DUForCSharp.IntValue)result).Item);
        else Console.WriteLine("Is Not Int: " + ((test.DUForCSharp.StringValue)result).Item);
    }
}

F#

namespace Library1

module test =

    open System

    type DUForCSharp =
    | IntValue of int
    | StringValue of string

    let getResult x =
        match Int32.TryParse x with
        | true, value -> IntValue(value)
        | _ -> StringValue(x)

这个解决方案很方便,因为它还通过为元组中的每个项目创建一个新属性来处理元组 DU 情况。

于 2013-09-25T18:59:01.817 回答