I'm trying to get this block of code right, getting it readable by splitting into different lines, can use some help here.
list |> Array.iter ( fun data -> data.Href |> ( regex.Match >> ( fun m ->
let result = {ArticleModule.defaultArticle with publicationId = m.Groups.[1].Value; entityType = m.Groups.[3].Value; entityName = m.Groups.[4].Value; version = m.Groups.[5].Value}
) ) )
Edit
A shortest form perhaps to help further on how to break this in separate lines.
list |> Array.iter ( fun data -> data.Href |> ( regex.Match >> ( fun m -> Console.WriteLine m ) ) )
Attempt 1
list |> Array.iter (fun data -> (data.Href)
|> regex.Match // squiggly lines here
|> (fun m -> Console.WriteLine m))
First, since you ask how to indent this code, I'll point you to https://github.com/dungpa/fantomas/blob/master/docs/FormattingConventions.md, which is an excellent reference that you should probably bookmark. 3/4 of the way down that page you will find https://github.com/dungpa/fantomas/blob/master/docs/FormattingConventions.md#pipeline-operators which suggests that sequences of pipeline operators should be indented with the pipeline directly under the data that is flowing through the pipeline:
let result =
data
|> step1
|> Array.filter (fun x -> x == "something")
|> step3
And so on.
Now, to apply this advice to your situation.
First, the squiggly lines in your attempt 1 are because on the regex.Match line, you're still inside the fun data -> ... lambda. All lines in F# should line up vertically with the thing they belong to (vague language because this is a general rule that applies to many situations). Here, that would look like:
list |> Array.iter (fun data -> data.Href
|> regex.Match
|> (fun m -> Console.WriteLine m))
Now, that looks kind of ugly to me. So I would split out the fun data -> ... lambda into its own function:
let handleOneItem data =
data.Href
|> regex.Match
|> (fun m -> Console.WriteLine m)
list |> Array.iter handleOneItem
Much nicer.
Now, let's look at your original code, where the final lambda in the pipeline was not calling Console.WriteLine, but was creating a record. There's one error in that code, which is that a let statement does nothing in and of itself. What you probably wanted to write was:
fun m ->
let result = {ArticleModule.defaultArticle with publicationId = m.Groups.[1].Value; entityType = m.Groups.[3].Value; entityName = m.Groups.[4].Value; version = m.Groups.[5].Value}
result
Which in turn can simply be turned into:
fun m ->
{ArticleModule.defaultArticle with publicationId = m.Groups.[1].Value; entityType = m.Groups.[3].Value; entityName = m.Groups.[4].Value; version = m.Groups.[5].Value}
And now, I would recommend taking that long record and splitting it across multiple lines (see https://github.com/dungpa/fantomas/blob/master/docs/FormattingConventions.md#records for details), like so:
fun m ->
{ ArticleModule.defaultArticle with
publicationId = m.Groups.[1].Value
entityType = m.Groups.[3].Value
entityName = m.Groups.[4].Value
version = m.Groups.[5].Value }
I'd probably make this its own named function:
let mkRecord m =
{ ArticleModule.defaultArticle with
publicationId = m.Groups.[1].Value
entityType = m.Groups.[3].Value
entityName = m.Groups.[4].Value
version = m.Groups.[5].Value }
Now let's look again at the full code:
let mkRecord m =
{ ArticleModule.defaultArticle with
publicationId = m.Groups.[1].Value
entityType = m.Groups.[3].Value
entityName = m.Groups.[4].Value
version = m.Groups.[5].Value }
let handleOneItem data =
data.Href
|> regex.Match
|> mkRecord
list |> Array.iter handleOneItem
There's just one more mistake here, which is that Array.iter is the wrong type. Array.iter wants you to hand it a function that returns unit (i.e., returns nothing meaningful). Any function that returns nothing meaningful is clearly being called for its side effects, not its return value. Since mkRecord returns a value and has no side effects, you want Array.map instead. So the final version of your code would be:
let mkRecord m =
{ ArticleModule.defaultArticle with
publicationId = m.Groups.[1].Value
entityType = m.Groups.[3].Value
entityName = m.Groups.[4].Value
version = m.Groups.[5].Value }
let handleOneItem data =
data.Href
|> regex.Match
|> mkRecord
list |> Array.map handleOneItem
There is nothing wrong with using a for loop. If what you do in the end is an imperative operation like printing to the console, then using for loop clearly indicates that this is what you do:
for data in list do
let m = regex.Match data.Href
Console.WriteLine m
From your other example, it looks like you are trying to use Array.map to create a new array. The answer from #rmunn covers this nicely, but again, note that you do not need to do everything using |>. It is often easier to use let binding:
list |> Array.map (fun data ->
let m = regex.Match data.Href
{ ArticleModule.defaultArticle with
publicationId = m.Groups.[1].Value
entityType = m.Groups.[3].Value
entityName = m.Groups.[4].Value
version = m.Groups.[5].Value })
Related
I don't want to use this for loop for iterating the JArray. Is there any other method which can replace this for loop?
let tablesInJson = jsonModel.["tables"] :?> JArray //Converting JOject into JArray
for table in tablesInJson do
let TableName = table.["name"] :?> JValue
let columns = table.["columns"] :?> JArray
for col in columns do
let name = col.["name"] :?> JValue
let types = col.["type"] :?> JValue
let length = col.["length"] :?> JValue
let Result_ = sqlTableInfos
|> List.tryFind (fun s -> s.TableName = TableName.ToString() && s.ColumnName = name.ToString())
if Result_ = Unchecked.defaultof<_> then
printfn "is null"
else
printfn "not null"
If you want to iterate over a collection and perform an imperative operation than using for loop is the idiomatic way of doing this in F# and you should just use that. After all, for is an F# language construct! There is a reason why it exists and the reason is that it lets you easily write code that iterates over a collection and does something for each element!
There are cases where for loop is not a good fit. For example, if you wanted to turn a collection of columns into a new collection with information about the tables. Then you could use Seq.map:
let tableInfos = columns |> Seq.map (fun col ->
let name = col.["name"] :?> JValue
let types = col.["type"] :?> JValue
let length = col.["length"] :?> JValue
let result = sqlTableInfos |> List.tryFind (fun s ->
s.TableName = TableName.ToString() && s.ColumnName = name.ToString())
if result = Unchecked.defaultof<_> then None
else Some result)
This looks like something you might be trying to do - but it is difficult to say. Your question does not say what is the problem that you are actually trying to solve.
Your example with printfn is probably misleading, because if you actually just want to print, then for loop is the best way of doing that.
You can use the Seq module to perform sequence-processing operations over the JArray. In your case, I think I would probably do this for the second for loop (over the columns), but not for the outer loop. The reason being, if you factor the code in the inner-loop out to a function, then you can use pipelining and partial application to clean up the code a bit:
open Newtonsoft.Json
open Newtonsoft.Json.Linq
type SqlTableInfo = {TableName: string; ColumnName: string}
let tablesInJson = JArray()
let sqlTableInfo = []
let tryFindColumn (tableName: JValue) (column: JToken) =
let columnName = column.["name"] |> unbox<JValue>
if sqlTableInfo |> List.exists (fun s -> s.TableName = tableName.ToString() && s.ColumnName = columnName.ToString())
then printfn "Table %A, Column %A Found" tableName columnName
else printfn "Table %A, Column %A Found" tableName columnName
for table in tablesInJson do
let tableName = table.["name"] |> unbox<JValue>
table.["columns"]
|> unbox<JArray>
|> Seq.iter (tryFindColumn tableName)
I'm trying to touch some F# language by developing a small "web crawler". I've got a functions declared like this:
let results = HtmlDocument.Load("http://joemonster.org//")
let images =
results.Descendants ["img"]
|> Seq.map (fun x ->
x.TryGetAttribute("src").Value.Value(),
x.TryGetAttribute("alt").Value.Value()
)
which of course should return for me a map of "src" and "alt" attributes for "img" tag. But when I'm encountering a situation when one of those are missing in the tag I'm getting an exception that TryGetAttribute is returning null. I want to change that function to return the attribute value or empty string in case of null.
I've tried out answers from this ticket but with no success.
TryGetAttribute returns an option type, and when it is None you can't get its value—you get an exception instead. You can pattern match against the returned option value and return an empty string for the None case:
let getAttrOrEmptyStr (elem: HtmlNode) attr =
match elem.TryGetAttribute(attr) with
| Some v -> v.Value()
| None -> ""
let images =
results.Descendants ["img"]
|> Seq.map (fun x -> getAttrOrEmptyStr x "src", getAttrOrEmptyStr x "alt")
Or a version using defaultArg and Option.map:
let getAttrOrEmptyStr (elem: HtmlNode) attr =
defaultArg (elem.TryGetAttribute(attr) |> Option.map (fun a -> a.Value())) ""
Or another option now that Option.defaultValue exists, and using HtmlAttribute.value function for a terser Option.map call:
let getAttrOrEmptyStr (elem: HtmlNode) attr =
elem.TryGetAttribute(attr)
|> Option.map HtmlAttribute.value
|> Option.defaultValue ""
I have a function processing a DataTable looking for any row that has a column with a certain value. It looks like this:
let exists =
let mutable e = false
for row in dt.Rows do
if row.["Status"] :?> bool = false
then e <- true
e
I'm wondering if there is a way to do this in a single expression. For example, Python has the "any" function which would do it something like this:
exists = any(row for row in dt.Rows if not row["Status"])
Can I write a similar one-liner in F# for my exists function?
You can use the Seq.exists function, which takes a predicate and returns true if the predicate holds for at least one element of the sequence.
let xs = [1;2;3]
let contains2 = xs |> Seq.exists (fun x -> x = 2)
But in your specific case, it won't work right away, because DataTable.Rows is of type DataRowCollection, which only implements IEnumerable, but not IEnumerable<T>, and so it won't be considered a "sequence" in F# sense, which means that Seq.* functions won't work on it. To make them work, you have to first cast the sequence to the correct type with Seq.cast:
let exists =
dt.Rows |>
Seq.cast<DataRow> |>
Seq.exists (fun r -> not (r.["Status"] :?> bool) )
Something like this (untested):
dt.Rows |> Seq.exists (fun row -> not (row.["Status"] :?> bool))
https://msdn.microsoft.com/visualfsharpdocs/conceptual/seq.exists%5b%27t%5d-function-%5bfsharp%5d
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 am very new to F# here, I encounter the "Collection was modified" problem in F#. I know this problem is common when we are iterating through a Collection while modifying (adding/removing) it at the same time. And previous threads in stackoverflow also point to this.
But in my case, I am working on 2 different sets:
I have 2 collections:
originalCollection the original collection from which I want to remove stuff
colToRemove a collection containing the objects that I want to remove
Below is the code:
Seq.iter ( fun input -> ignore <| originalCollection.Remove(input)) colToRemove
And I got the following runtime error:
+ $exception {System.InvalidOperationException: Collection was modified; enumeration operation may not execute.
at System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource)
at System.Collections.Generic.List1.Enumerator.MoveNextRare()
at System.Collections.Generic.List1.Enumerator.MoveNext()
at Microsoft.FSharp.Collections.IEnumerator.next#174[T](FSharpFunc2 f, IEnumerator1 e, FSharpRef1 started, Unit unitVar0)
at Microsoft.FSharp.Collections.IEnumerator.filter#169.System-Collections-IEnumerator-MoveNext()
at Microsoft.FSharp.Collections.SeqModule.Iterate[T](FSharpFunc2 action, IEnumerable`1 source)
here is the chunk of code:
match newCollection with
| Some(newCollection) ->
// compare newCollection to originalCollection.
// If there are things that exist in the originalCollection that are not in the newCollection, we want to remove them
let colToRemove = Seq.filter (fun input -> Seq.exists (fun i -> i.id = input.id) newCollection) originalCollection
Seq.iter ( fun input -> ignore <| originalCollection.Remove(input)) colToRemove
| None -> ()
Thanks!
Note: Working on a single-threaded environment here, so there are no multi-threading issues that might result in this exception.
The problem here is that colToRemove is not an independent collection but is a projection of the collection originalCollection. So changing originalCollection changes the projection which is not allowed during the iteration. The C# equivalent of the above code is the following
var colToRemove = originalCollection
.Where(input -> newCollection.Any(i -> i.id == input.id));
foreach (var in input in colToRemove) {
originalCollection.Remove(input);
}
You can fix this by making colToRemove an independent collection via the List.ofSeq method.
let colToRemove =
originalCollection
|> Seq.filter (fun input -> Seq.exists (fun i -> i.id = input.id) newCollection) originalCollection
|> List.ofSeq
I would not try to do a remove, since you are modifying a collection, but instead try to create another collection like so:
let foo () =
let orig = [1;2;3;4]
let torem = [1;2]
let find e =
List.tryFind (fun i-> i = e) torem
|> function
| Some _-> true
| None -> false
List.partition (fun e -> find e) orig
//or
List.filter (fun e-> find e) orig
hth