in C# you can do stuff like :
var a = new {name = \"cow\", sound = \"moooo\", omg = \"wtfbbq\"};
and in
You can't create "anonymous records" in F# - when using types, you can either use tuples which are anonymous, but don't carry labels or you can use records which have to be declared in advance and have labels:
// 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 }
Technically, the problem with anonymous records is that they would have to be defined as actual classes somewhere (the .NET runtime needs a type), but if the compiler put them in every assembly, then two anonymous records with same members might be different types if they were defined in different assemblies.
Honestly, I think that the example you posted is just a poor design decision in ASP.NET - it is misusing a particular C# feature to do something for which it wasn't designed. It may not be as bad as this, but it's still odd. The library takes a C# anonymous type, but it uses it as a dictionary (i.e. it uses it just as a nice way to create key-value pairs, because the properties that you need to specify are dynamic).
So, if you're using ASP.NET from F#, it is probably easier to use an alternative approach where you don't have to create records - if the ASP.NET API provides some alternative (As Daniel shows, there is a nicer way to write that).
Here's my take on the default web project route config:
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])
I find it easier to do
let route = routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}" // URL with parameters
)
route.Defaults.Add("controller", "Home")
route.Defaults.Add("action", "Index")
or
[ "controller", "Home"
"action", "Index" ]
|> List.iter route.Defaults.Add
In F#, I would avoid calling overloads that accept anonymous types much as I would avoid calling an F# method accepting FSharpList
from C#. Those are language-specific features. Usually there is a language-agnostic overload/workaround available.
Just looked at the docs--here's yet another way to do it
let inline (=>) a b = a, box b
let defaults = dict [
"controller" => "Home"
"action" => "Index"
]
route.Defaults <- RouteValueDictionary(defaults)
The OP does not describe the best use of anonymous type. They are best used when using LINQ to map to an arbitrary class. For example:
var results = context.Students
.Where(x => x.CourseID = 12)
.Select(x => new {
StudentID = x.ID,
Name = x.Forename + " " + x.Surname
});
I know this can be done by defining a new record type, but then you have two places to maintain code, (1) the record type definition (2) where you've used it.
It could instead be done with a tuple, but to access individual fields you have to use the deconstruction syntax (studentId, name)
all the time. This becomes unwieldy if you have 5 items in the tuple. I would rather type in x
and hit dot and have intellisense tell me what fields are available.
As Tony indicated in his answer this is not much better as of F# 4.6. Testing a similar example using .NET Core SDK 3.0.100-preview4-011158 I was able to demonstrate use of the new Anonymous Record feature. As for the RouteMap
method, I'm unfamiliar with what types of values this API accepts but I would suspect that the example below would work.
Ex.
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
{| controller = "Home"; action = "Index"; id = UrlParameter.Optional |} // Parameter defaults
)
Notice the use of the |
character on the insides of the curly braces. This is what now distinguishes regular records from anonymous records in F#.
As for your other example, perhaps the F# example would now look such as below.
let a = {| name = "cow"; sound = "moooo"; omg = "wtfbbq" |}
Now in F# 4.6 (preview) we have Anonymous Records
So we can have this code syntax:
let AwesomeAnonymous = {| ID = Guid.NewGuid()
Name = "F#"
|}
AwesomeAnonymous.Name |> Debug.WriteLine
It is also supported on the Visual Studio Intellisense:
So that code could be like this:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
{| controller = "Home"; action = "Index"; id = UrlParameter.Optional |} // Parameter defaults
)
See also: Announcing F# 4.6 Preview