在 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您不能在 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<string, object> "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# 中命名元组/匿名类型?的主要内容,如果未能解决你的问题,请参考以下文章