Trying to do something pretty basic - convert an F# List to a .Net Generic list ( I think )
I am new to F#.
let items =
pricelistItems // List<PriceListItem>
|> Seq.map(fun pli ->
let item =
new Item(
Code = pli.sku,
Description = "",
IsPurchased = true,
IsSold = true,
IsTrackedAsInventory = true,
InventoryAssetAccountCode = "xxxx"
)
item
)
|> Seq.toList
the resulting type of the code above is
Item list
I think this is an F# type? I need to somehow get it it to be a
List<Item>
thanks for your time
First of all, the naming here is a bit confusing, because F# defines its own List type (which is an immutable list) and can be written as List<'T>, 'T List or also list<'T> or 'T list. These all refer to the F# list. To refer to the standard generic .NET type, F# uses ResizeArray<'T> or you can use fully qualified name of System.Collections.Generic.List<'T>
As mentioned in the comments, you can convert a sequence back into ResizeArray by using the constructor. However, the generic .NET list type also supports map operation directly, except that it is an instance method called ConvertAll. You could use that or write a more F#-friendly wrapper:
module ResizeArray =
let map f (l:ResizeArray<_>) = l.ConvertAll(System.Converter(f))
Then you can write:
let items = pricelistItems |> ResizeArray.map (fun pli ->
Item(Code = pli.sku,
Description = "",
IsPurchased = true,
IsSold = true,
IsTrackedAsInventory = true,
InventoryAssetAccountCode = "xxxx") )
To convert an F# list to a System.Collections.Generic.List use either System.Linq.ToList or simply the constructor System.Collections.Generic.List
open System.Linq
open System.Collections.Generic
let l = [1..3] // F# lists implement IEnumerable<'t>, so we can use
l.ToList() // System.Linq.ToList method
l |> List // System.Collections.Generic.List constructor
Your specific code is then
let items =
pricelistItems // List<PriceListItem>
|> Seq.map(fun pli ->
let item =
new Item(
Code = pli.sku,
Description = "",
IsPurchased = true,
IsSold = true,
IsTrackedAsInventory = true,
InventoryAssetAccountCode = "xxxx"
)
item
)
|> System.Linq.Enumerable.ToList
You can also pass the sequence (which is IEnumerable<>) to the System.Collections.Generic.List constructor:
open System.Collections.Generic // List
let items =
pricelistItems // List<PriceListItem>
|> Seq.map(fun pli ->
let item =
new Item(
Code = pli.sku,
Description = "",
IsPurchased = true,
IsSold = true,
IsTrackedAsInventory = true,
InventoryAssetAccountCode = "xxxx"
)
item
)
|> List // constructor accepting IEnumerable = the sequence above
Note that "List" above is actually List you could also type List<_> and let the compiler infer the type or even insert your type. I prefer the shortest way and leave the generic out, which the compiler accepts. These are equivalent:
[1..3] |> List
[1..3] |> List<_>
[1..3] |> List<int>
Related
I am REALLY new to F#, so I might have used the wrong terminology here. Please feel free to correct me if I am wrong, I would really appreciate it! Anyways, on to the question
I have a record that I have defined as so:
type EventSource = {
SourceName: string
Address: string
ParseDocument: HtmlDocument -> Event seq }
And I have created an instance of that record like so:
let lofSource = {
SourceName = "LOF"
Address = "https://lof.dk/syd/kurser"
ParseDocument = fun document ->
document.Descendants ["div"]
|> Seq.filter (fun d -> d.HasClass("item"))
|> Seq.map (
fun e ->
let linkElement
= e.Descendants (fun j -> j.HasClass "item-title")
|> Seq.head
|> (fun y -> y.Descendants ["a"])
|> Seq.map (fun fa -> fa.Attribute "href")
|> Seq.head
{
Title = e.AttributeValue "data-holdnavn"
Link = linkElement.Value()
Status = e.AttributeValue "data-status"
Image = Address //Here!
City = e.AttributeValue "data-bynavn"
Date = System.DateTime.ParseExact(e.AttributeValue("data-datosort"), "yyyyMMdd", null);
PostalCode = e.AttributeValue("data-postnr")})
}
On the line where I am trying to assign a value the Image member, It tells me that the value or constructor 'Address' is not defined.
I have tried using a self-identifier on the instantiation of the record and then trying to access Address like
this.Address
but it tells me that 'this' is not defined. I am guessing I am missing something quite fundamental here, can anyone help me? Is what I am trying to do nonsensical?
You can't do this with records. See: Reference a record from within itself during construction
You can do it with another binding (I couldn't get your code to compile and have simplified it):
type EventSource = {
SourceName: string
Address: string
ParseDocument: string -> string}
let lofSource =
let helloThere = "General Kenobi"
{
SourceName = "LOF"
Address = foo
ParseDocument = fun document ->
foo
}
I have a list of history entries that we retrieve from 3rd party software. This list can either be null or non empty.
In C# I would have written it like this, since list can be null:
List<HistoryEntry>
However I'm struggling in writing it in F#. I've already tried:
* Nullable<HistoryEntry list>
* HistoryEntry list option
* HistoryEntry list?
* HistoryEntry list | null
However none of this works. We use a ListConverter which is pretty much used everywhere, and I dare not change it (since that breaks everything):
type ListConverter() =
inherit JsonConverter()
override __.CanConvert(t : Type) = (t.IsGenericType && t.GetGenericTypeDefinition() = typedefof<list<_>>)
override __.WriteJson(writer, value, serializer) =
let list = value :?> System.Collections.IEnumerable |> Seq.cast
serializer.Serialize(writer, list)
override __.ReadJson(reader, t, _, serializer) =
let itemType = t.GetGenericArguments().[0]
let collectionType = typedefof<IEnumerable<_>>.MakeGenericType(itemType)
let collection = serializer.Deserialize(reader, collectionType) :?> IEnumerable<_>
let listType = typedefof<list<_>>.MakeGenericType(itemType)
let cases = FSharpType.GetUnionCases(listType)
let rec make =
function
| [] -> FSharpValue.MakeUnion(cases.[0], [||])
| head :: tail ->
FSharpValue.MakeUnion(cases.[1],
[| head
(make tail) |])
make (collection |> Seq.toList)
My question is: how to create nullable list that will be understood by this serializer?
I actually think the problem is with the ListConverter, not the type of list you're trying to use. The ListConverter does not account for the entire collection being null, which is perfectly possible in JSON. I think the simplest change would be to use a custom version of Seq.toList that checks for nulls and converts them to an empty list.
let toJsonList s =
if s |> box |> isNull
then []
else s |> Seq.toList
Then just change the last line of the ListConverter to be:
make (collection |> toJsonList)
I am using the CsvTypeProvider to map data from CSV files into my own data structures. This works splendidly, except for that I have to repeat the mapping function every time:
type GamesFile = CsvProvider<"./data/15.csv">
let games15 = GamesFile.Load("./data/15.csv").Rows |> Seq.map ( fun c -> { Division = c.Div; Date = c.Date; HomeScore = c.HomeScore; AwayScore = c.AwayScore })
let games16 = GamesFile.Load("./data/16.csv").Rows |> Seq.map ( fun c -> { Division = c.Div; Date = c.Date; HomeScore = c.HomeScore; AwayScore = c.AwayScore })
When I try moving it to a function, I am told that "Lookup on object of indeterminate type based on information prior to this program point. A type annotation may be needed prior to this program point to constrain the type of the object. This may allow the lookup to be resolved."
This makes sense, but how can I tell a mapping function what type it is when the type is inferred from the contents of the CSV? How is this normally solved?
The type provider generates a type representing the row and it exposes it as a nested type of the main provided type - in your case aliased as GamesFile.
This is not entirely obvious, because the editors will show tooltip with something like
CsvFile<...>.Row so it does not show the name of the alias, but it suggests Row is a nested type. To use the type in your code, you can just write GamesFile.Row, so you need something like this:
type GamesFile = CsvProvider<"./data/15.csv">
let mapRows (rows:seq<GamesFile.Row>) =
rows |> Seq.map (fun c ->
{ Division = c.Div; Date = c.Date; HomeScore = c.HomeScore; AwayScore = c.AwayScore })
let games15 = GamesFile.Load("./data/15.csv").Rows |> mapRows
let games16 = GamesFile.Load("./data/16.csv").Rows |> mapRows
I want to sort items of a class and collect them in Collection-Classes that beside a List-Member also contain further information that are necessary for the sorting process.
The following example is a a very simplified example for my problem. Although it doesn't make sense, I hope it still can help to understand my Question.
type ItemType = Odd|Even //realworld: more than two types possible
type Item(number) =
member this.number = number
member this.Type = if (this.number % 2) = 0 then Even else Odd
type NumberTypeCollection(numberType:ItemType , ?items:List<Item>) =
member this.ItemType = numberType
member val items:List<Item> = defaultArg items List.empty<Item> with get,set
member this.append(item:Item) = this.items <- item::this.items
let addToCollection (collections:List<NumberTypeCollection>) (item:Item) =
let possibleItem =
collections
|> Seq.where (fun c -> c.ItemType = item.Type) //in my realworld code, several groups may be returned
|> Seq.tryFind(fun _ -> true)
match possibleItem with
|Some(f) -> f.append item
collections
|None -> NumberTypeCollection(item.Type, [item]) :: collections
let rec findTypes (collections:List<NumberTypeCollection>) (items:List<Item>) =
match items with
| [] -> collections
| h::t -> let newCollections = ( h|> addToCollection collections)
findTypes newCollections t
let items = [Item(1);Item(2);Item(3);Item(4)]
let finalCollections = findTypes List.empty<NumberTypeCollection> items
I'm unsatisfied with the addToCollection method, since it requires the items in NumberTypeCollection to be mutual. Maybe there are further issues.
What can be a proper functional solution to solve this issue?
Edit: I'm sorry. May code was too simplified. Here is a little more complex example that should hopefully illustrate why I chose the mutual class-member (although this could still be the wrong decision):
open System
type Origin = Afrika|Asia|Australia|Europa|NorthAmerika|SouthAmerica
type Person(income, taxrate, origin:Origin) =
member this.income = income
member this.taxrate = taxrate
member this.origin = origin
type PersonGroup(origin:Origin , ?persons:List<Person>) =
member this.origin = origin
member val persons:List<Person> = defaultArg persons List.empty<Person> with get,set
member this.append(person:Person) = this.persons <- person::this.persons
//just some calculations to group people into some subgroups
let isInGroup (person:Person) (personGroup:PersonGroup) =
let avgIncome =
personGroup.persons
|> Seq.map (fun p -> float(p.income * p.taxrate) / 100.0)
|> Seq.average
Math.Abs ( (avgIncome / float person.income) - 1.0 ) < 0.5
let addToGroup (personGroups:List<PersonGroup>) (person:Person) =
let possibleItem =
personGroups
|> Seq.where (fun p -> p.origin = person.origin)
|> Seq.where (isInGroup person)
|> Seq.tryFind(fun _ -> true)
match possibleItem with
|Some(f) -> f.append person
personGroups
|None -> PersonGroup(person.origin, [person]) :: personGroups
let rec findPersonGroups (persons:List<Person>) (personGroups:List<PersonGroup>) =
match persons with
| [] -> personGroups
| h::t -> let newGroup = ( h|> addToGroup personGroups)
findPersonGroups t newGroup
let persons = [Person(1000,20, Afrika);Person(1300,22,Afrika);Person(500,21,Afrika);Person(400,20,Afrika)]
let c = findPersonGroups persons List.empty<PersonGroup>
What I may need to emphasize: There can be several different groups with the same origin.
Tomas' solution using groupby is the optimal approach if you want to generate your collections only once, it's a simple and concise.
If you want to be able to add/remove items in a functional, referentially transparent style for this type of problem, I suggest you move away from seq and start using Map.
You have a setup which is fundamentally dictionary-like. You have a unique key and a value. The functional F# equivalent to a dictionary is a Map, it is an immutable data structure based on an AVL tree. You can insert, remove and search in O(log n) time. When you append/remove from the Map, the old Map is maintained and you receive a new Map.
Here is your code expressed in this style
type ItemType =
|Odd
|Even
type Item (number) =
member this.Number = number
member this.Type = if (this.Number % 2) = 0 then Even else Odd
type NumTypeCollection = {Items : Map<ItemType, Item list>}
/// Functions on NumTypeCollection
module NumberTypeCollection =
/// Create empty collection
let empty = {Items = Map.empty}
/// Append one item to the collection
let append (item : Item) numTypeCollection =
let key = item.Type
match Map.containsKey key numTypeCollection.Items with
|true ->
let value = numTypeCollection.Items |> Map.find key
let newItems =
numTypeCollection.Items
|> Map.remove key
|> Map.add key (item :: value) // append item
{Items = newItems }
|false -> {Items = numTypeCollection.Items |> Map.add key [item]}
/// Append a list of items to the collections
let appendList (item : Item list) numTypeCollection =
item |> List.fold (fun acc it -> append it acc) numTypeCollection
Then call it using:
let items = [Item(1);Item(2);Item(3);Item(4)]
let finalCollections = NumberTypeCollection.appendList items (NumberTypeCollection.empty)
If I understand your problem correctly, you're trying to group the items by their type. The easiest way to do that is to use the standard library function Seq.groupBy. The following should implement the same logic as your code:
items
|> Seq.groupBy (fun item -> item.Type)
|> Seq.map (fun (key, values) ->
NumberTypeCollection(key, List.ofSeq values))
Maybe there are further issues.
Probably. It's difficult to tell, since it's hard to detect the purpose of the OP code... still:
Why do you even need an Item class? Instead, you could simply have a itemType function:
let itemType i = if i % 2 = 0 then Even else Odd
This function is referentially transparent, which means that you can replace it with its value if you wish. That makes it as good as a property getter method, but now you've already saved yourself from introducing a new type.
Why define a NumberTypeCollection class? Why not a simple record?
type NumberTypeList = { ItemType : ItemType; Numbers : int list }
You can implement addToCollection like something like this:
let addToCollection collections i =
let candidate =
collections
|> Seq.filter (fun c -> c.ItemType = (itemType i))
|> Seq.tryHead
match candidate with
| Some x ->
let x' = { x with Numbers = i :: x.Numbers }
collections |> Seq.filter ((<>) x) |> Seq.append [x']
| None ->
collections |> Seq.append [{ ItemType = (itemType i); Numbers = [i] }]
Being immutable, it doesn't mutate the input collections, but instead returns a new sequence of NumberTypeList.
Also notice the use of Seq.tryHead instead of Seq.tryFind(fun _ -> true).
Still, if you're attempting to group items, then Tomas' suggestion of using Seq.groupBy is more appropriate.
I'm trying to create some kind of interface, but i cannot find how to use custom attributes in F# as MSDN only shows usage of CLR attributes. This is what i want to achieve:
open System
type Command (name : string) =
inherit Attribute()
member this.Name = name
[<Command("something")>]
let doSomething () =
Console.Write("I'm doing something")
[<Command("somethingElse")>]
let doSomethingElse () =
Console.Write("I'm doing something else")
[<EntryPoint>]
let main args =
let command = Console.ReadLine()
// find function where Command.Name = command and call it
Console.Read()
0
To extend on your answer, a more generic approach would be to get all the types and then filter the functions that have the attribute you're looking for (as your approach would break down once your application grows and no longer has everything "packed" into the Program class):
let getCommands () =
let types = Assembly.GetExecutingAssembly().GetTypes()
let commands =
types
|> Array.collect (fun typ -> typ.GetMethods())
|> Array.choose (fun mi ->
mi.CustomAttributes
|> Seq.tryFind (fun attr -> attr.AttributeType = typeof<Command>)
|> Option.map (fun attr -> attr, mi))
let commandsMap =
commands
|> Seq.map (fun (attr, mi) ->
let name =
let arg = attr.ConstructorArguments.[0]
unbox<string> arg.Value
name, mi)
|> Map.ofSeq
commandsMap
This gets all the functions from all the types in the executing assembly, then filters out everything that doesn't have command attribute. Then it builds a map where the key is the attribute argument and the value is the MethodInfo of the function.
Ok, found it.
Reflection.Assembly.GetExecutingAssembly().GetType("Program").GetMethods()
Program typename is not viable in code so it cannot be used in typeof<Program>, but this type exists and can be taken from assembly.