How to manage side-effecting commands when using reactive extensions? - wpf

module CounterApp
open System
open System.Windows
open System.Windows.Controls
open System.Windows.Media
open System.Reactive.Linq
open System.Reactive.Disposables
open FSharp.Control.Reactive
/// Subscribers
let do' f c = f c; Disposable.Empty
let prop s v c = Observable.subscribe (s c) v
let event s f c = (s c : IEvent<_,_>).Subscribe(fun v -> f c v)
let children clear add set (v1 : IObservable<IObservable<IObservable<_>>>) c = // Note: The previous versions of this have bugs.
let v2_disp = new SerialDisposable()
new CompositeDisposable(
v1.Subscribe(fun v2 ->
clear c
v2_disp.Disposable <-
let v3_disp = new CompositeDisposable()
let mutable i = 0
new CompositeDisposable(
v2.Subscribe (fun v3 ->
let i' = i
v3_disp.Add <| v3.Subscribe (fun v -> if i' < i then set c i' v else i <- add c v + 1)
),
v3_disp
)
),
v2_disp
)
:> IDisposable
let ui_element_collection v1 c = children (fun (c : UIElementCollection) -> c.Clear()) (fun c -> c.Add) (fun c i v -> c.RemoveAt i; c.Insert(i,v)) v1 c
/// Transformers
let control'<'a when 'a :> UIElement> (c : unit -> 'a) l =
Observable.Create (fun (sub : IObserver<_>) ->
let c = c()
let d = new CompositeDisposable()
List.iter (fun x -> d.Add(x c)) l
sub.OnNext(c)
d :> IDisposable
)
let control c l = control' c l :?> IObservable<UIElement>
let stack_panel' props childs = control StackPanel (List.append props [fun c -> ui_element_collection childs c.Children])
let stack_panel props childs = stack_panel' props (Observable.ofSeq childs |> Observable.single)
let window props content = control' Window (List.append props [prop (fun t v -> t.Content <- v) content])
/// The example
type Model = {
Count : int
Step : int
TimerOn : bool
}
type Msg =
| Increment
| Decrement
| Reset
| SetStep of int
| TimerToggled of bool
| TimedTick
let init = { Count = 0; Step = 1; TimerOn=false }
let pump = Subject.broadcast
let dispatch msg = pump.OnNext msg
let update =
pump
|> Observable.scanInit init (fun model msg ->
match msg with
| Increment -> { model with Count = model.Count + model.Step }
| Decrement -> { model with Count = model.Count - model.Step }
| Reset -> init
| SetStep n -> { model with Step = n }
| TimerToggled on -> { model with TimerOn = on }
| TimedTick -> if model.TimerOn then { model with Count = model.Count + model.Step } else model
)
|> Observable.startWith [init]
let timerCmd() =
update
|> Observable.map (fun x -> x.TimerOn)
|> Observable.distinctUntilChanged
|> Observable.combineLatest (Observable.interval(TimeSpan.FromSeconds(1.0)))
|> Observable.subscribe (fun (_,timerOn) ->
if timerOn then Application.Current.Dispatcher.Invoke(fun () -> dispatch TimedTick)
)
let view =
window [ do' (fun t -> t.Title <- "Counter App")]
<| control Border [
do' (fun b -> b.Padding <- Thickness 30.0; b.BorderBrush <- Brushes.Black; b.Background <- Brushes.AliceBlue)
prop (fun b v -> b.Child <- v) <|
stack_panel [ do' (fun p -> p.VerticalAlignment <- VerticalAlignment.Center)] [
control Label [
do' (fun l -> l.HorizontalAlignment <- HorizontalAlignment.Center; l.HorizontalContentAlignment <- HorizontalAlignment.Center; l.Width <- 50.0)
prop (fun l v -> l.Content <- v) (update |> Observable.map (fun model -> sprintf "%d" model.Count))
]
control Button [
do' (fun b -> b.Content <- "Increment"; b.HorizontalAlignment <- HorizontalAlignment.Center)
event (fun b -> b.Click) (fun b arg -> dispatch Increment)
]
control Button [
do' (fun b -> b.Content <- "Decrement"; b.HorizontalAlignment <- HorizontalAlignment.Center)
event (fun b -> b.Click) (fun b arg -> dispatch Decrement)
]
control Border [
do' (fun b -> b.Padding <- Thickness 20.0)
prop (fun b v -> b.Child <- v) <|
stack_panel [do' (fun p -> p.Orientation <- Orientation.Horizontal; p.HorizontalAlignment <- HorizontalAlignment.Center)] [
control Label [do' (fun l -> l.Content <- "Timer")]
control CheckBox [
prop (fun c v -> c.IsChecked <- Nullable(v)) (update |> Observable.map (fun model -> model.TimerOn))
event (fun c -> c.Checked) (fun c v -> dispatch (TimerToggled true))
event (fun c -> c.Unchecked) (fun c v -> dispatch (TimerToggled false))
]
]
]
control Slider [
do' (fun s -> s.Minimum <- 0.0; s.Maximum <- 10.0; s.IsSnapToTickEnabled <- true)
prop (fun s v -> s.Value <- v) (update |> Observable.map (fun model -> model.Step |> float))
event (fun s -> s.ValueChanged) (fun c v -> dispatch (SetStep (int v.NewValue)))
]
control Label [
do' (fun l -> l.HorizontalAlignment <- HorizontalAlignment.Center)
prop (fun l v -> l.Content <- v) (update |> Observable.map (fun model -> sprintf "Step size: %d" model.Step))
]
control Button [
do' (fun b -> b.HorizontalAlignment <- HorizontalAlignment.Center; b.Content <- "Reset")
prop (fun b v -> b.IsEnabled <- v) (update |> Observable.map (fun model -> model <> init))
event (fun b -> b.Click) (fun b v -> dispatch Reset)
]
]
]
[<STAThread>]
[<EntryPoint>]
let main _ =
let a = Application()
use __ = view.Subscribe (fun w -> a.MainWindow <- w; w.Show())
use __ = timerCmd()
a.Run()
I am translating the Fabulous counter example to reactive extensions. The above works, but I am not completely satisfied with how the command aspect came out.
let timerCmd() =
update
|> Observable.map (fun x -> x.TimerOn)
|> Observable.distinctUntilChanged
|> Observable.combineLatest (Observable.interval(TimeSpan.FromSeconds(1.0)))
|> Observable.subscribe (fun (_,timerOn) ->
if timerOn then Application.Current.Dispatcher.Invoke(fun () -> dispatch TimedTick)
)
This is how the timer command is defined as a function.
use __ = timerCmd()
I subscribe to it in the main function.
This is not quite expressing what I want.
For one, I do not want this to be a separate subscription in main. I do not want the interval observable to be turned on all the time in the background, only sending messages when allowed.
I want timerCmd to switch on automatically, subscribe to the interval and dispatch TimedTick messages - and switch off and unsubscribe based on the state of x.TimerOn. What would the best way to do this be? Is there a better way to design all of this?

Yes, it's possible to shunt an interval timer in and out of the stream.
We can project onto an observable of either empty notifications or an interval, and then switch to the newest emitted observable.
let model = { Count = 0; Step = 0; TimerOn = false }
let update = Subject.behavior model
update
|> Observable.distinctUntilChangedKey (fun x -> x.TimerOn)
|> Observable.map (fun x ->
if x.TimerOn then
Observable.interval(TimeSpan.FromSeconds(1.0))
else
Observable.empty
)
|> Observable.switch
|> Observable.subscribe (fun i -> printfn "Tick %d" i)
|> ignore
while true do
printfn "Start (y)?"
let switch = Console.ReadLine()
update
|> Subject.onNext( { model with TimerOn = switch = "y" })
|> ignore
Output
Start (y)?
y
Start (y)?
Tick 0
Tick 1
Tick 2
Tick 3
n
Start (y)?
y
Start (y)?
Tick 0
Tick 1
As you can see the timer does restart when you want it to.

Related

How to sort String alphabetically in F#?

I have string "DBCA" and want sort it in F#
let sortedString str =
...
printfn "%A" <| sortedString "DBCA" // "ABCD"
Code sample on C#
String
.Concat(
_str
.OrderBy(ch => ch)
);
open System
let sortedString (str : string) = str |> Seq.sort |> String.Concat
Solution with LINQ
open System
open System.Linq
let orderBy f xs = Enumerable.OrderBy(xs, new Func<_,_>(f))
let sortedString (str:string) =
str
|> List.ofSeq
|> orderBy (fun ch -> ch)
|> String.Concat
I had this solution, but above answer is better

F#: Writing a function that takes any kind of array as input

I am new to programming and F# is my first language.
Here is part of my code:
let splitArrayIntoGroups (inputArray: string[]) (groupSize: int) =
let groups = new LinkedList<string[]>()
let rec splitRecursively currentStartIndex currentEndIndex =
groups.AddLast(inputArray.[currentStartIndex..currentEndIndex]) |> ignore
let newEndIndex = Math.Min((inputArray.Length - 1), (currentEndIndex + groupSize))
if newEndIndex <> currentEndIndex then
splitRecursively (currentStartIndex + groupSize) newEndIndex
splitRecursively 0 (groupSize - 1)
groups
I want this function to be able to accept arrays of any type (including types that I define myself) as input. What changes should I make?
This was already answered but here you have an implementation not using a linked list but just an array of lists
let rec split<'T> (input: 'T array) size =
let rec loopOn (tail : 'T array) grouped =
let lastIndex = Array.length tail - 1
let endindx = min (size - 1) lastIndex
let arrWrapper = (fun e -> [|e|])
let newGroup = tail.[0..endindx]
|> List.ofArray
|> arrWrapper
|> Array.append grouped
match tail with
| [||] -> newGroup
|> Array.filter (fun e -> List.length e > 0)
| _ -> loopOn tail.[endindx + 1..] newGroup
let initialState = [|List.empty<'T>|]
loopOn input initialState
Because this is generic implementation you can call it with different types
type Custom = {Value : int}
let r = split<int> [|1..1000|] 10
let r2 = split<float> [|1.0..1000.0|] 10
let r3 = split<Custom> [|for i in 1..1000 ->
{Value = i}|] 10
replace string[] with _[] in the function signature.

Folding an array of arrays

I'm trying to fold an array of arrays of strings into a single string but I'm not having much luck. Unfortunately it seems Array.reduce expects my lambda to return an array of strings because it is an array of array of strings.
I'm getting :
Line error 37: The type 'string[]' does not match the type 'string'
This is the offending line
(fold state) + (fold item)
Because it's expecting the lambda to return a string[]
Here is the code:
let splitStr (seperator: string[]) (str: string) = str.Split(seperator, StringSplitOptions.None)
let convertFile fileName =
let arrayToTransaction arr =
let rec foldArray index (sb: StringBuilder) (arr:string[]) =
if index > 5 then sb.ToString()
else
let text =
match index with
| 0 -> sb.Append(DateTime.Parse(arr.[1]).ToString("dd/MM/yy", CultureInfo.InvariantCulture))
| 1 -> sb.Append(arr.[0].Substring(0, arr.[0].IndexOf(',')).Trim())
| 2 -> sb.Append("Test")
| 3 -> sb.Append("Test")
| 4 -> sb.Append(Single.Parse(arr.[2].Substring(arr.[2].IndexOf('-') + 1)).ToString("F2", CultureInfo.InvariantCulture))
| _ -> sb.Append(String.Empty)
foldArray (index + 1) (text.Append(",")) arr
arr
|> Array.map (splitStr [|"\n"|])
|> Array.reduce (fun state item -> let fold x = foldArray 0 (new StringBuilder()) x
(fold state) + (fold item))
File.ReadAllText(fileName)
|> splitStr [|"\r\n\r\n"|]
|> arrayToTransaction
Your lambda in the Array.reduce must return a string[] since the signature of the lambda is 'T->'T->'T and the first 'T is already unified as string[] so the result should also be a string[]

Creating insert statements with F#

I use F# once every few months or so and in between it seems I forget everything, so I hope you'll excuse my ignorance. My code below is pulling data from Yahoo. It's a good example that represents what I need to do. The first row returned has the column headings. I need to take the data (the tail of the list) and insert it into the database. What's the best way to generate an insert statement based on the column headings returned (the column headings match the database column names)?
In the example below dataWithHeaders.[0] will contain "Date,Open,High,Low,Close,Volume,Adj Close." Should I just take that string and put brackets around the headings to create the insert? Then in insertData add the value as a parameter? Is there a more elegant solution?
let url = System.String.Format("http://ichart.finance.yahoo.com/table.csv?s={0}&g=d&ignore=.csv", "FB")
let splitLineIntoArray (line : string) =
line.Split(",".ToCharArray())
let insertData (data : string[]) =
// insert data
()
let client = new WebClient()
let dataWithHeaders =
client.DownloadString(url).Split(Environment.NewLine.ToCharArray())
let data =
dataWithHeaders
|> Array.toList
|> List.tail
|> List.map(splitLineIntoArray)
|> List.iter insertData
If you're loading the data into SQL Server you can use this excellent CSV reader (free) and the SqlBulkCopy class. It's simple and efficient.
let loadStockPrices ticker =
use client = new WebClient()
let url = sprintf "http://ichart.finance.yahoo.com/table.csv?s=%s&g=d&ignore=.csv" ticker
use stringReader = new StringReader(client.DownloadString(url))
use csvReader = new CsvReader(stringReader, hasHeaders=true)
use con = new SqlConnection("<connection_string>")
con.Open()
use bulkCopy = new SqlBulkCopy(con, DestinationTableName="<destination_table>")
bulkCopy.WriteToServer(csvReader)
The destination table should have the same columns as the incoming data (OHLC, etc).
EDIT: Type Providers might be a good way to go, but SqlBulkCopy is def. known for its simplicity.
Type Provider Code for Insert: http://msdn.microsoft.com/en-us/library/hh361033(v=vs.110).aspx#BKMK_UpdateDB
type dbSchema = SqlDataConnection<"Data Source=MYSERVER\INSTANCE;Initial Catalog=MyDatabase;Integrated Security=SSPI;">
let db = dbSchema.GetDataContext()
// Enable the logging of database activity to the console.
db.DataContext.Log <- System.Console.Out
let newRecord = new dbSchema.ServiceTypes.Table1(Id = 100,
TestData1 = 35,
TestData2 = 2.0,
Name = "Testing123")
let newValues =
[ for i in [1 .. 10] ->
new dbSchema.ServiceTypes.Table3(Id = 700 + i,
Name = "Testing" + i.ToString(),
Data = i) ]
// Insert the new data into the database.
db.Table1.InsertOnSubmit(newRecord)
db.Table3.InsertAllOnSubmit(newValues)
try
db.DataContext.SubmitChanges()
printfn "Successfully inserted new rows."
with
| exn -> printfn "Exception:\n%s" exn.Message
I did something similar. Actually this code I wrote while watching Luca Bolognese give a presentation on F#. This actually will scrape yahoo's feed and return the standard dev. and variance on stock prices.
Full project here: https://github.com/djohnsonm/Stock-Ticker-App
open System.Net
open System.IO
let internal loadPrices ticker = async {
let url = #"http://ichart.finance.yahoo.com/table.csv?s=" + ticker + "&d=6&e=22&f=2011&g=d&a=2&b=13&c=1986&ignore=.csv"
let req = WebRequest.Create(url)
let resp = req.GetResponse()
let stream = resp.GetResponseStream()
let reader = new StreamReader(stream)
let csv = reader.ReadToEnd()
let prices =
csv.Split([|'\n'|])
|> Seq.skip 1
|> Seq.map (fun line -> line.Split([|','|]))
|> Seq.filter(fun values -> values |> Seq.length = 7)
|> Seq.map(fun values ->
System.DateTime.Parse(values.[0]),
float values.[6])
return prices}
type StockAnalyzer (lprices, days) =
let prices =
lprices
|> Seq.map snd
|> Seq.take days
static member GetAnalyzers (tickers, days) =
tickers
|> Seq.map loadPrices
|> Async.Parallel
|> Async.RunSynchronously
|> Seq.map (fun prices -> new StockAnalyzer(prices, days))
member s.Return =
let lastPrice = prices |> Seq.nth 0
let startPrice = prices |> Seq.nth(days-1)
lastPrice / startPrice - 1.
member s.StdDev =
let logRets =
prices
|> Seq.pairwise
|> Seq.map (fun (x,y) -> log(x/y))
let mean = logRets |> Seq.average
let sqr x = x * x
let var = logRets |> Seq.averageBy (fun r -> sqr (r-mean))
sqrt var

Working with Events in F#

I recently asked this question:
Replay Recorded Data Stream in F# and combined that code with a subset of the functionality I found here:
http://www.mattssoftwareblog.com/?p=271
which combined looks like this:
#r "System.Reactive"
#r "System.CoreEx"
#r "FSharp.PowerPack"
#r "WindowsBase"
#r "PresentationCore"
#r "PresentationFramework"
#r "System.Xaml"
#r "System.Interactive.dll"
open System
open System.Linq
open System.Collections.Generic
open System.Net
open System.IO
open System.Threading
open System.Windows
open System.Windows.Input
open System.Windows.Controls
open System.Windows.Shapes
open System.Windows.Media
open System.Xaml
I need to use the events generated here (which came from my earlier SO question):
let prices = [ (0, 10.0); (1000, 10.5); (500, 9.5); (2500, 8.5); (500, 10.0); (1000, 10.5); (500, 9.5); (2500, 8.5) ]
let evt = new Event<float>()
async { for delay, price in prices do
do! Async.Sleep(delay)
evt.Trigger(price) }
|> Async.StartImmediate
evt.Publish.Add(printfn "Price updated: %A")
to use as the data source for the line which is randomly created here (the code below comes from the blog article I mentioned):
let create f =
Observable.Create<_>(fun x ->
f x
new System.Action((fun () -> ())))
let fromEvent (event:IEvent<_,_>) = create (fun x -> event.Add x.OnNext)
// Random Walker
let rand = Random()
let mutable m = 0.
let randomWalker() =
m <- m + (rand.NextDouble() * 10.) - 5.
m
let timer = new System.Timers.Timer()
timer.Interval <- 100.
let timerObs = (timer.Elapsed |> fromEvent).Select(fun _ -> randomWalker())
let chartWindow = new Window(Height = 600., Width = 600.)
let canvas = new Canvas()
chartWindow.Content <- canvas
chartWindow.Show()
let line xs =
let segs =
seq { for x , y in xs |> List.tail ->
LineSegment(Point(x,y), true) :> PathSegment }
let (sx, sy) = xs |> List.head
PathGeometry([PathFigure(Point(sx,sy), segs, false)])
let plot xs (path:Path) =
let now = DateTime.Now
let timeSpan = TimeSpan(0,1,0)
let width = 600.
let height = 600.
let pts = xs |> List.map (fun (x:Timestamped<float>) ->
(600.-(now - (x.Timestamp.DateTime)).TotalMilliseconds * 600. / timeSpan.TotalMilliseconds),x.Value + 300.)
path.Dispatcher.BeginInvoke(new SendOrPostCallback(fun pts -> path.Data <- line (pts :?> (float*float)list)), pts) |> ignore
let trailing (timespan:TimeSpan) (obs:IObservable<'
a>) =
obs.Timestamp()
.Scan([], fun ys x ->
let now = DateTime.Now
let timespan = timespan
x :: (ys |> List.filter (fun x -> (now - x.Timestamp.DateTime) < timespan)))
.Where(fun xs -> xs |> List.length > 1)
// Main Path
let mainPath = new Path(Stroke=Brushes.Blue, StrokeThickness=1.)
canvas.Children.Add(mainPath)
let trailingRandomsSub = (timerObs |> trailing (TimeSpan.FromSeconds(60.))).Subscribe(fun xs -> plot xs mainPath)
timer.Start()
If you paste this into an interactive session you will see a blue line emerge which is generated randomly and not using my new evt Event. I guess my confusion is not understanding how to make and use an Observable from my evt. Basically, how can I make evt my data source for the blue line?
Thanks in advance,
Bob
In F#, the IEvent<'T> interface inherits from IObservable<'T>. This means that you can use F# events in any place where an observable is expected.
The last bit of your application (that takes the event, adds time stamps, uses Scan to get lists containing the items generated so far and plots the progress) can be written like this:
let trailingRandomsSub =
evt.Publish.Timestamp()
|> Observable.scan (fun l e -> e::l) []
|> Observable.add (fun xs -> plot xs mainPath)
F# provides wrappers for some of the Rx functions, so you can use Observable.scan, which has a bit more F#-friendly syntax. Observable.add is just another syntax for Subscribe.
The key difference between F# events and observables is that observables start when you attach a handler. On the other hand, the F# event that you create using Async.StartImmediate starts immediately when the StartImmediate method is called (this means - to get the sample working, you need to evaluate everything at once, or write a function that starts the event).

Resources