I have a method which works fine:
member this.GetItems =
let db = dbSchema.GetDataContext()
let getQuery =
query {
for row in db.ItemsTable do
select row
}
getQuery
|> Seq.map (fun e -> new Item(e.ItemId, e.ItemName ))
|> Seq.toArray
It make the select query to database, transforms each record to object and returns an array of such objects.
I want to split this method to two. First one must be more general. It gets query and function to transform record to object.
Here is my code:
member private this.ExecuteSelectQuery(query, transform_function) =
let db = dbSchema.GetDataContext()
query
|> Seq.map transform_function
|> Seq.toArray
member this.GetItems =
let db = dbSchema.GetDataContext()
this.ExecuteSelectQuery
query {
for row in db.ItemsTable do
select row
}
(fun e -> new Item(e.ItemId, e.ItemName ))
But I get the errors:
In this expression required type 'a * ('b -> 'c) but there is a type QueryBuilder
This construction can be used only in the case of the method For
How can I fix it ?
Updated:
member private this.ExecuteSelectQuery query transform_function =
let db = dbSchema.GetDataContext()
query
|> Seq.map transform_function
|> Seq.toArray
member this.GetItems =
let db = dbSchema.GetDataContext()
this.ExecuteSelectQuery
query {
for row in db.ItemsTable do
select row
}
(fun e -> new Item(e.ItemId, e.ItemName ))
The member function ExecuteSelectQuery expects the arguments as a tuple, so you have to use parentheses.
member private this.ExecuteSelectQuery(query, transform_function) =
query
|> Seq.map transform_function
|> Seq.toArray
member this.GetItems =
use db = dbSchema.GetDataContext()
this.ExecuteSelectQuery (
query {
for row in db.ItemsTable do
select row
},
(fun e -> new Item(e.ItemId, e.ItemName )))
Please consider also the use keyword for binding of DataContext instance to correctly implement the disposable pattern. I've done it in my example.
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 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 })
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.
Is it possible to use lambda-style querying of IQueryable objects in F#, instead of query expressions? Something like:
type schema = SqlDataConnection<"Data Source=(local);Initial Catalog=MyDatabase;Integrated Security=true;">
let db = schema.GetDataContext()
let q = db.MyTable |> Seq.filter (fun r -> r.id < 100) |> Seq.take 10
let result = q |> List.ofSeq
When I profile this it is doing select * from MyTable so I assume the filter and take are being executed on IEnumerables not IQueryables?
Or is the only way to fix this to use query {} without lambdas?
The reason is that Seq.toList calls the data struncture GetEnumerator() and there is something like this inside the type (pseudo, not the actual source):
type SqlDataConnection<...>(...) =
interface seq<'T> with
member __.GetEnumerator() = executeSQL()
If you want to operate with IQueryable instead of IEnumerable, there is the query-syntax in F#:
query {
for r in db.MyTable do
where (r.id < 100)
take 10
} |> Seq.toList
More details:
https://learn.microsoft.com/en-us/dotnet/articles/fsharp/language-reference/query-expressions