I'm trying to write an F# type that wraps an underlying collection of Map<int64, customType> such that I can add items to it:
type public CandleContainer (seedCandles:System.Collections.Generic.IEnumerable<Candle>) =
let candles : Map<int64, Candle> =
seedCandles
|> Seq.map (fun x -> x.Start.Ticks, x)
|> Map.ofSeq
let candleInterval = TimeSpan.FromMinutes(1.0)
member public x.AddCandle (candle:Candle) =
candles = candles.Add(candle.Start.Ticks, candle)
member public x.GetList () : List<Candle> =
candles
|> Map.toSeq
|> Seq.map (fun (key, value) -> value)
|> Seq.toList
The problem that I'm getting is in the AddCandle method. I understand that Map.Add returns a new map with the value added and the compiler isn't complaining that I'm trying to overwrite the value of candles. However, whenever I try adding a new Candle using this method x.GetList returns an empty list. Is there a way to overwrite the old value of candles with the result of the Map.Add? Or do I just make candles a mutable value and overwrite it?
What you're looking for is the mutable keyword and <- operator.
Change let candles ... to be
let mutable candles : Map<int64, Candle>
and candles = ... to be
candles <- candles.Add(candle.Start.Ticks, candle)
. Also note that your previous use of = where you thought replacement was occurring was actually performing an equality check, making the return type of that method bool.
Related
I have a text file that contains the following and I need to retrieve the value assigned to taskId, which in this case is AWc34YBAp0N7ZCmVka2u.
projectKey=ProjectName
serverUrl=http://localhost:9090
serverVersion=10.5.32.3
strong text**interfaceUrl=http://localhost:9090/interface?id=ProjectName
taskId=AWc34YBAp0N7ZCmVka2u
taskUrl=http://localhost:9090/api/ce/task?id=AWc34YBAp0N7ZCmVka2u
I have two different ways of reading the file that I've wrote.
let readLines (filePath:string) = seq {
use sr = new StreamReader (filePath)
while not sr.EndOfStream do
yield sr.ReadLine ()
}
readLines (FindFile currentDirectory "../**/sample.txt")
|> Seq.iter (fun line ->
printfn "%s" line
)
and
let readLines (filePath:string) =
(File.ReadAllLines filePath)
readLines (FindFile currentDirectory "../**/sample.txt")
|> Seq.iter (fun line ->
printfn "%s" line
)
At this point, I don't know how to approach getting the value I need. Options that, I think, are on the table are:
use Contains()
Regex
Record type
Active Pattern
How can I get this value returned and fail if it doesn't exist?
I think all the options would be reasonable - it depends on how complex the file will actually be. If there is no escaping then you can probably just look for = in the line and use that to split the line into a key value pair. If the syntax is more complex, this might not always work though.
My preferred method would be to use Split on string - you can then filter to find values with your required key, map to get the value and use Seq.head to get the value:
["foo=bar"]
|> Seq.map (fun line -> line.Split('='))
|> Seq.filter (fun kvp -> kvp.[0] = "foo")
|> Seq.map (fun kvp -> kvp.[1])
|> Seq.head
Using active patterns, you could define a pattern that takes a string and splits it using = into a list:
let (|Split|) (s:string) = s.Split('=') |> List.ofSeq
This then lets you get the value using Seq.pick with a pattern matching that looks for strings where the substring before = is e.g. foo:
["foo=bar"] |> Seq.pick (function
| Split ["foo"; value] -> Some value
| _ -> None)
The active pattern trick is quite neat, but it might be unnecessarily complicating the code if you only need this in one place.
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 am completely at loss why this code doesn't mutate a member variable in a sequence of types:
for p in prescrs do
p.ATC <- "A"
for c in p.Drug.Components do
for s in c.Substances do
s.DoseTotal.Adjust <- adjustKg
s.DoseTotal.Time <- "DAY"
s.DoseTotal.Unit <- s.DrugConcentration.Unit
s.DoseRate.Adjust <- adjustKg
s.DoseRate.Time <- "DAY"
s.DoseRate.Unit <- s.DrugConcentration.Unit
prescrs is a sequence of Prescriptions which is a very simple 'POCO' defined as a type with member values. I don't have clue why this doesn't work.
I tried a simple test case like:
type IterTest () =
member val Name = "" with get, set
member val IterTests = [] |> List.toSeq : IterTest seq with get, set
let iterseq =
[
new IterTest(Name = "Test1")
new IterTest(Name = "Test2")
]
|> List.toSeq
iterseq |> Seq.iter(fun x -> x.IterTests <- iterseq)
iterseq |> Seq.iter(fun x ->
x.IterTests
|> Seq.iter(fun x' -> x'.Name <- "itered"))
But here the result is as expected. So, can't even quite reproduce my problem???
Found a solution (without really understanding the problem above). When I first convert the prescrs sequence to a list like:
let prescrs = prescrs |> Seq.toList
and then do the imperative looping, properties do get mutated.
Try this sample:
type Mutable() =
member val Iterated = false with get, set
let muts = Seq.init 5 (fun _ -> printfn "init"; Mutable())
let muts2 = muts // try again with let muts2 = muts |> List.ofSeq
printfn "Before iter"
for a in muts2 do
printfn "iter"
a.Iterated <- true
printfn "After iter"
muts2 |> List.ofSeq
and check how iter and init are interleaved.
Seqs are lazy, but are not cached once computed. So even if you imperatively try to mutate some of the elements in your prescrs sequence, it all goes away once you pull prescrs again. If you change prescrs into a concrete collection type like list before doing the mutation, you no longer hit the same problem. Note that things might get even trickier if what you have is a seq inside a seq inside a seq.
The best idea would be to avoid mutation in the first place though.
Ok, I realise this might be a weird question. But I need to ask it anyway. It goes as follows:
Suppose I have something like the following:
type Foo() =
member this.MyFooFun i = 2*i
type Bar() =
inherit Foo()
member this.MyBarFun i = 3*i
type Baz() =
inherit Foo()
member this.MyBazFun i = 5*i
type FooSeq = seq<Foo>
What I want to do, is to filter out all the Foo's from a FooSeq that has the member MyBarFun. Is it possible to do anything like that?
I realise I will probably have to use the :? operator to check whether each element is a Bar, but as I said - I have to ask. The reason I would rather not do this, is that the types corresponding to Foo, Bar and Baz lies in a library developed somewhere else in the company. And there might be added more types containing the MyBarFun-member at any given time.
If you only want to filter on the subtype, it's easy:
let foos = candidates |> Seq.filter (fun x -> not (x :? Bar))
If you explicitly want to filter away types that have a member called "MyBarFun", you'll need to use reflection:
let foos' =
candidates
|> Seq.filter (fun x ->
not (x.GetType().GetMembers() |> Array.exists (fun m ->
m.Name = "MyBarFun")))
I'm writing a quick DB perf test, and chose F# so I can get more practice.
I've created a method, measureSelectTimes, which has the signature Guid list * Guid list -> IDbCommand -> TimeSpan * TimeSpan.
Then, I call it:
let runTests () =
let sqlCeConn : IDbConnection = initSqlCe() :> IDbConnection
let sqlServerConn : IDbConnection = initSqlServer() :> IDbConnection
let dbsToTest = [ sqlCeConn; sqlServerConn ]
let cmds : seq<IDbCommand> = dbsToTest |> Seq.map initSchema
let ids : seq<Guid list * Guid list> = cmds |> Seq.map loadData
let input = Seq.zip ids cmds
let results = input |> Seq.map (fun i -> measureSelectTimes (fst i) (snd i))
// ...
I've annotated explicitly with types to clarify.
What I can't figure out is how to call measureSelectTimes without the lambda. I'd like to partially apply the ids to it like this: ids |> Seq.map measureSelectTimes but then I don't know what to do with the resulting partially applied functions to then map onto the cmds. What's the syntax for this?
You can use Seq.map2:
Seq.map2 measureSelectTimes ids cmds
Or
(ids, cmds) ||> Seq.map2 measureSelectTimes
Your measureSelectTimes function takes two arguments as separate arguments, but you instead need a function that takes them as a tuple. One option is to just change the function to take a tuple (if it is logical for the arguments to be tupled).
Alternative, you can write a cobinator that turns a function taking two arguments into a function taking tuple. This is usually called uncurry and it exists in some functional language:
let uncurry f (a, b) = f a b
Then you can write:
input |> Seq.map (uncurry measureSelectTimes)
This looks okay for a simple use like this, but I think that using combinators too much in F# is not a good idea as it makes code difficult to read for less experienced functional programmers. I would probably write something like this (because I find that more readable):
[ for (time1, time2) in input -> measureSelectTimes time1 time2 ]
One approach is to change the signature of measureSelectTimes to
(Guid list * Guid list) * IDbCommand -> TimeSpan * TimeSpan
Then you can change the map call to
let results = input |> Seq.map measureSelectTimes
// or
let results = Seq.map measureSelectTimes input