在 F# 中命名元组/匿名类型?

Posted

技术标签:

【中文标题】在 F# 中命名元组/匿名类型?【英文标题】:Name Tuples/Anonymous Types in F#? 【发布时间】:2011-12-29 23:02:26 【问题描述】:

在 C# 中,您可以执行以下操作:

var a = new name = "cow", sound = "moooo", omg = "wtfbbq";

在 Python 中你可以做类似的事情

a = t(name = "cow", sound = "moooo", omg = "wtfbbq")

当然不是默认的,但是实现一个类t 让你做到这一点是微不足道的。事实上,当我使用 Python 时,我确实做到了这一点,并发现它对于小型一次性容器非常方便,您希望能够通过名称而不是索引访问组件(这很容易混淆)。

除了这些细节之外,它们基本上与它们所服务的领域中的元组相同。

特别是,我现在正在查看这段 C# 代码:

routes.MapRoute(
            "Default", // Route name
            "controller/action/id", // URL with parameters
            new  controller = "Home", action = "Index", id = UrlParameter.Optional  // Parameter defaults
        );

它是 F# 等价物

type Route =  
    controller : string
    action : string
    id : UrlParameter 

routes.MapRoute(
    "Default", // Route name
    "controller/action/id", // URL with parameters
     controller = "Home"; action = "Index"; id = UrlParameter.Optional  // Parameter defaults
  )

这既冗长又重复,更不用说相当烦人了。在 F# 中,您离这种语法有多近?我现在不介意跳过一些箍(甚至是燃烧的箍!),如果这意味着它会给我一些有用的东西来干掉这样的代码。

【问题讨论】:

只是提到匿名类型不是 c# 中的元组有元组类 F# 4.6 预览:匿名记录github.com/fsharp/fslang-design/blob/master/FSharp-4.6/… 【参考方案1】:

我觉得这样做更容易

let route = routes.MapRoute(
    "Default", // Route name
    "controller/action/id" // URL with parameters
    )
route.Defaults.Add("controller", "Home")
route.Defaults.Add("action", "Index")

[ "controller", "Home"
  "action", "Index" ]
|> List.iter route.Defaults.Add

在 F# 中,我会避​​免调用接受匿名类型的重载,就像我会避免调用从 C# 接受 FSharpList 的 F# 方法一样。这些是特定于语言的功能。通常有一个与语言无关的重载/解决方法可用。

编辑

刚刚查看了文档——这里还有另一种方法

let inline (=>) a b = a, box b

let defaults = dict [
  "controller" => "Home"
  "action"     => "Index" 
]
route.Defaults <- RouteValueDictionary(defaults)

【讨论】:

+1 这绝对更好。我不知道Add 有更合理的重载——我也应该在我的 ASP.NET MVC 示例中使用它! 回复:使用 RouteValueDictionary。这可以编译,但不起作用,因为预期的字典类型是 IDictionary。在运行时它会拆箱“obj”,因此在创建它时必须首先将其装箱。相反,请使用:“dict [("controller", box "Home");("action", box "Index")]"。这也启用了混合类型的默认值,例如。 ("User", box null),("id", box UrlParameter.Optional). @Daniel。不错的作品。我喜欢 (=>) 运算符。使其更具可读性。【参考方案2】:

您不能在 F# 中创建“匿名记录” - 使用类型时,您可以使用匿名但不带标签的元组,也可以使用必须提前声明并带有标签的记录:

// Creating an anonymous tuple
let route = ("Home", "Index", UrlParameter.Optional)

// Declaration and creating of a record with named fields
type Route =  controller : string; action : string; id : UrlParameter  
let route =  controller = "Home"; action = "Index"; id = UrlParameter.Optional  

从技术上讲,匿名记录的问题在于它们必须被定义为实际的类某处(.NET 运行时需要一个类型),但是如果编译器将它们放在每个程序集中,那么如果两个具有相同成员的匿名记录在不同的程序集中定义,则它们可能是不同的类型。

老实说,我认为您发布的示例只是 ASP.NET 中的一个糟糕的设计决策——它滥用了特定的 C# 功能来做一些不是为它设计的事情。它可能没有this那么糟糕,但它仍然很奇怪。该库采用 C# 匿名类型,但将其用作字典(即,它只是将其用作创建键值对的好方法,因为您需要指定的属性是动态的)。

因此,如果您使用 F# 中的 ASP.NET,则使用不必创建记录的替代方法可能更容易 - 如果 ASP.NET API 提供了一些替代方法(如 Daniel 所示,有是一种更好的写法)。

【讨论】:

@Kirk:IDictionary 似乎是一个更好的选择。在 F# 中,这将是 dict ["controller", "Home"; "action", "Index"]——非常自然。 我认为这实际上既不简洁也不清晰:new Dictionary&lt;string, object&gt; "controller", "Home" , "action", "Index" vs. new controller = "Home", action = "Index" (并且专门询问了在 C# 中不会更糟的解决方案) 我不会说IDictionary 在 C# 中更清晰或更短,但鉴于有多种语言以 CLR 为目标,这是一种更好的方法(快乐的媒介)。匿名类型特定于 C#。 重点是,如果 ASP.NET 是一个 .NET 库(而不是一个 C# 库),那么它根本不应该对人们将使用的语言做出任何假设。从纯 .NET 的角度来看,使用具有属性的类来表示字典听起来很愚蠢! @Kirk - 即使在 C# 中,这种方法仍然存在问题,例如可发现性。 defaults 参数的类型为 object,描述为“包含默认路由值的对象。”。作为调用者,这完全是难以理解的,除非我看到了该库使用的大量惯用风格的示例。对我来说,这是 C# 无法支持简洁的集合文字的解决方法的症状,而不是积极使用 F# 缺乏的 C# 功能。【参考方案3】:

OP 没有描述匿名类型的最佳使用。最好在使用 LINQ 映射到任意类时使用它们。例如:

var results = context.Students
              .Where(x => x.CourseID = 12)
              .Select(x => new  
                 StudentID = x.ID, 
                 Name = x.Forename + " " + x.Surname
              );

我知道这可以通过定义一个新的记录类型来完成,但是您有两个地方来维护代码,(1) 记录类型定义 (2) 您使用它的地方。

它可以用元组来完成,但要访问单个字段,您必须始终使用解构语法(studentId, name)。如果元组中有 5 个项目,这将变得笨拙。我宁愿输入x 并点击点,让智能感知告诉我哪些字段可用。

【讨论】:

【参考方案4】:

现在在 F# 4.6(预览版)中,我们有 Anonymous Records

所以我们可以有这样的代码语法:

let AwesomeAnonymous = |  ID = Guid.NewGuid()
                           Name = "F#"
                        |

AwesomeAnonymous.Name |> Debug.WriteLine

Visual Studio Intellisense 也支持它:

所以代码可能是这样的:

routes.MapRoute(
    "Default", // Route name
    "controller/action/id", // URL with parameters
    | controller = "Home"; action = "Index"; id = UrlParameter.Optional | // Parameter defaults
  )

另见:Announcing F# 4.6 Preview

【讨论】:

【参考方案5】:

这是我对默认 Web 项目路由配置的看法:

module RouteConfig =

    open System.Web.Mvc
    open System.Web.Routing

    let registerRoutes (routes: RouteCollection) =

        routes.IgnoreRoute("resource.axd/*pathInfo")

        /// create a pair, boxing the second item
        let inline (=>) a b = a, box b

        /// set the Defaults property from a given dictionary
        let setDefaults defaultDict (route : Route) =  
            route.Defaults <- RouteValueDictionary(defaultDict)

        routes.MapRoute(name="Default", url="controller/action/id")
        |> setDefaults (dict ["controller" => "Home" 
                              "action" => "Index" 
                              "id" => UrlParameter.Optional])

【讨论】:

【参考方案6】:

正如托尼在他的回答中指出的那样,这在 F# 4.6 中并没有好多少。使用 .NET Core SDK 3.0.100-preview4-011158 测试类似示例我能够演示新的 Anonymous Record 功能的使用。至于 RouteMap 方法,我不熟悉这个 API 接受哪些类型的值,但我怀疑下面的示例会起作用。

例如

routes.MapRoute(
    "Default", // Route name
    "controller/action/id", // URL with parameters
    | controller = "Home"; action = "Index"; id = UrlParameter.Optional | // Parameter defaults
  )

注意花括号内部使用了| 字符。这就是现在 F# 中常规记录与匿名记录的区别。

至于您的其他示例,F# 示例现在可能如下所示。

let a = | name = "cow"; sound = "moooo"; omg = "wtfbbq" |

【讨论】:

以上是关于在 F# 中命名元组/匿名类型?的主要内容,如果未能解决你的问题,请参考以下文章

匿名类型

通过 WCF 传递匿名类型的实例

对象方法函数:匿名还是命名? [复制]

匿名属性 anonymous property

将 json 反序列化为匿名类型列表

C#超级有用的一种类型—匿名类型