├── src └── Freckle │ ├── paket.references │ ├── ComputationExpression.fs │ ├── AssemblyInfo.fs │ ├── paket.template │ ├── Clock.fs │ ├── Time.fs │ ├── Helpers.fs │ ├── Mailbox.fs │ ├── Sample.fs │ ├── Freckle.fsproj │ └── Feed.fs ├── docs ├── files │ └── img │ │ ├── logo.png │ │ └── logo-template.pdn ├── tools │ ├── templates │ │ └── template.cshtml │ └── generate.fsx └── content │ ├── index.fsx │ └── tutorial.fsx ├── .paket ├── paket.bootstrapper.exe └── paket.targets ├── examples └── AirlockStatemachine │ ├── App.config │ ├── paket.references │ ├── AssemblyInfo.fs │ ├── ExternalAirlock.fs │ ├── Program.fs │ ├── FrpAirlockExample.fs │ └── AirlockStatemachine.fsproj ├── tests └── Freckle.Tests │ ├── paket.references │ ├── App.config │ ├── Tests.fs │ └── Freckle.Tests.fsproj ├── .travis.yml ├── appveyor.yml ├── lib └── README.md ├── README.md ├── paket.dependencies ├── Settings.FSharpLint ├── .gitattributes ├── LICENSE.txt ├── RELEASE_NOTES.md ├── .gitignore ├── Freckle.sln └── paket.lock /src/Freckle/paket.references: -------------------------------------------------------------------------------- 1 | FSPowerPack.Core.Community -------------------------------------------------------------------------------- /docs/files/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jannickj/Freckle/HEAD/docs/files/img/logo.png -------------------------------------------------------------------------------- /.paket/paket.bootstrapper.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jannickj/Freckle/HEAD/.paket/paket.bootstrapper.exe -------------------------------------------------------------------------------- /docs/files/img/logo-template.pdn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jannickj/Freckle/HEAD/docs/files/img/logo-template.pdn -------------------------------------------------------------------------------- /examples/AirlockStatemachine/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /examples/AirlockStatemachine/paket.references: -------------------------------------------------------------------------------- 1 | FSPowerPack.Core.Community 2 | 3 | group Example 4 | Microsoft.Tpl.Dataflow -------------------------------------------------------------------------------- /tests/Freckle.Tests/paket.references: -------------------------------------------------------------------------------- 1 | FSPowerPack.Core.Community 2 | 3 | group Test 4 | xunit 5 | FsUnit.xUnit 6 | FsCheck 7 | FsCheck.Xunit -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: csharp 2 | 3 | sudo: false # use the new container-based Travis infrastructure 4 | 5 | before_install: 6 | - chmod +x build.sh 7 | 8 | script: 9 | - ./build.sh All 10 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | init: 2 | - git config --global core.autocrlf input 3 | 4 | cache: 5 | - packages 6 | 7 | branches: 8 | only: 9 | - master 10 | 11 | build_script: 12 | - cmd: build.cmd 13 | test: off 14 | version: 1.0.0.{build} 15 | artifacts: 16 | - path: 'bin\*.nupkg' 17 | -------------------------------------------------------------------------------- /lib/README.md: -------------------------------------------------------------------------------- 1 | This file is in the `lib` directory. 2 | 3 | Any **libraries** on which your project depends and which are **NOT managed via NuGet** should be kept **in this directory**. 4 | This typically includes custom builds of third-party software, private (i.e. to a company) codebases, and native libraries. 5 | 6 | --- 7 | NOTE: 8 | 9 | This file is a placeholder, used to preserve directory structure in Git. 10 | 11 | This file does not need to be edited. 12 | -------------------------------------------------------------------------------- /tests/Freckle.Tests/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/Freckle/ComputationExpression.fs: -------------------------------------------------------------------------------- 1 | /// Wrapper for all computational expressions for each type. 2 | /// In haskell lingo this is what provides do notation 3 | [] 4 | module Freckle.ComputationExpression 5 | 6 | /// The sample expression 7 | let sample = Sample.ComputationalExpression.sample 8 | 9 | /// The sample returning Async operations expression 10 | let sampleAsync = SampleAsync.ComputationalExpression.sampleAsync 11 | 12 | /// The feed expression 13 | let feed = Feed.ComputationalExpression.feed 14 | -------------------------------------------------------------------------------- /src/Freckle/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace System 2 | open System.Reflection 3 | 4 | [] 5 | [] 6 | [] 7 | [] 8 | [] 9 | do () 10 | 11 | module internal AssemblyVersionInformation = 12 | let [] Version = "0.0.1" 13 | let [] InformationalVersion = "0.0.1" 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Freckle 3 | Simplified monadic FRP, designed for controlling application flows in a event/time based software. 4 | * Base types are: Time, Sample and Feed 5 | * Supports monadic Feed 6 | * Redefines: The obtuse type behavior as the more easily understood Sample type 7 | 8 | Documentation: http://jannickj.github.io/Freckle 9 | 10 | 11 | ## Build Status 12 | 13 | [![Build status](https://ci.appveyor.com/api/projects/status/m03a4k9sj5xlge3h?svg=true)](https://ci.appveyor.com/project/jannickj/freckle) 14 | 15 | ## Maintainer(s) 16 | 17 | - [@jannickj](https://github.com/jannickj) 18 | -------------------------------------------------------------------------------- /paket.dependencies: -------------------------------------------------------------------------------- 1 | source https://nuget.org/api/v2 2 | 3 | nuget FSPowerPack.Core.Community ~> 3 4 | 5 | group Example 6 | source https://nuget.org/api/v2 7 | 8 | nuget Microsoft.Tpl.Dataflow ~> 4.5 9 | 10 | group Build 11 | source https://nuget.org/api/v2 12 | 13 | nuget SourceLink.Fake 14 | nuget FAKE 15 | nuget FSharp.Formatting 16 | 17 | github fsharp/FAKE modules/Octokit/Octokit.fsx 18 | 19 | group Test 20 | source https://nuget.org/api/v2 21 | 22 | nuget xunit ~> 2.1 23 | nuget xunit.runner.console 24 | nuget FsUnit.xUnit 25 | nuget FsCheck 26 | nuget FsCheck.Xunit -------------------------------------------------------------------------------- /Settings.FSharpLint: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | False 8 | 9 | 10 | 11 | 12 | False 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/Freckle/paket.template: -------------------------------------------------------------------------------- 1 | type project 2 | owners 3 | Jannick Johnsen 4 | authors 5 | Jannick Johnsen 6 | projectUrl 7 | http://github.com/jannickj/Freckle 8 | iconUrl 9 | https://raw.githubusercontent.com/jannickj/Freckle/master/docs/files/img/logo.png 10 | licenseUrl 11 | http://github.com/jannickj/Freckle/blob/master/LICENSE.txt 12 | requireLicenseAcceptance 13 | false 14 | copyright 15 | Copyright 2016 16 | tags 17 | FRP reactive event behavior functional freckle 18 | summary 19 | A Functional Reactive Programming Library for FSharp 20 | description 21 | Functional Reactive Programming library, push-pull model, a modern approach to event handling and model sampling. 22 | 23 | 24 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp text=auto eol=lf 6 | *.vb diff=csharp text=auto eol=lf 7 | *.fs diff=csharp text=auto eol=lf 8 | *.fsi diff=csharp text=auto eol=lf 9 | *.fsx diff=csharp text=auto eol=lf 10 | *.sln text eol=crlf merge=union 11 | *.csproj merge=union 12 | *.vbproj merge=union 13 | *.fsproj merge=union 14 | *.dbproj merge=union 15 | 16 | # Standard to msysgit 17 | *.doc diff=astextplain 18 | *.DOC diff=astextplain 19 | *.docx diff=astextplain 20 | *.DOCX diff=astextplain 21 | *.dot diff=astextplain 22 | *.DOT diff=astextplain 23 | *.pdf diff=astextplain 24 | *.PDF diff=astextplain 25 | *.rtf diff=astextplain 26 | *.RTF diff=astextplain 27 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /RELEASE_NOTES.md: -------------------------------------------------------------------------------- 1 | ### 1.0.0-rc4 - Fix bug that stops Mailbox from working correctly 2 | * Mailbox: fix bug that cause TTL on event to be trigger immediately 3 | 4 | ### 1.0.0-rc3 - Update mailbox and sample api 5 | * Mailbox: Replaced createWithExpiration with create and createWithTTL 6 | * Sample: Added function for iterative sampling 7 | 8 | ### 1.0.0-rc2 - Breaking changes to mailbox 9 | * Clock: Optimize and removed the excessive use of async 10 | * Mailbox: Fix bug where future events could be sampled 11 | * Mailbox: Fix bug where events were skipped entirely 12 | * Mailbox: Add functionality for posting a promise 13 | * Feed: Obsoleted take as it makes no sense a function (see debounce) 14 | * Feed: Replaced take with debouncing (the function for removing multi events in one sample) 15 | * Feed: Add chunkBy to group specific count of events 16 | * Feed: Add "then" function (also known as >> in haskell) 17 | * Feed: Add group by time 18 | * Meta: Restructured versioning, from now this library will be SemVar compliant 19 | 20 | ### 1.0.0-rc1 - Support for longer pulses 21 | * Added the Feed.every function 22 | 23 | ### 1.0.0-beta - Initial Release Candidate 24 | * Support for sampling 25 | * Support event messaging (mailbox) 26 | * Monadic event stream (Feed) 27 | 28 | ### 0.0.1-alpha - Initial Project Setup 29 | * Setup the project 30 | -------------------------------------------------------------------------------- /examples/AirlockStatemachine/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace AirlockStatemachine.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /src/Freckle/Clock.fs: -------------------------------------------------------------------------------- 1 | [] 2 | ///Clock is used to generate the current time 3 | module Freckle.Clock 4 | open FSharp.Helpers 5 | 6 | [] 7 | ///The includes the clock type 8 | module Types = 9 | 10 | ///The clock type, representing a clock that can generate the current time. 11 | ///To be used for FRP it must guarantee a that time is changed when time has actually changed (e.g. DateTime.Now does not guarantee this) 12 | type Clock = Clock of (unit -> Time) 13 | 14 | 15 | ///Clock is used to generate the current time 16 | module Clock = 17 | open System 18 | 19 | ///Generates a clock guaranteed to always be sequential. 20 | ///This is accomplished by the fact that if the time generated is the same as last then the time will be incremented by 10μs 21 | let synchronized (Clock ma) = 22 | let last = ref Time.origin 23 | let ma' () = 24 | lock last (fun () -> 25 | let time = ma () 26 | let newT = 27 | if !last >= time 28 | then { Ticks = Time.ticks !last + 1L } 29 | else time 30 | last := newT 31 | newT) 32 | Clock ma' 33 | 34 | ///A system clock, is recommended to just always use this 35 | let systemUtc = Clock (fun _ -> Time.ofDateTime DateTime.UtcNow) 36 | 37 | ///A clock that always return the same time, mainly for testing purposes 38 | let alwaysAt ticks = Clock (fun _ -> ticks ) 39 | 40 | ///Get the current time 41 | let now (Clock m) = async { return m () } 42 | 43 | let inline nowSynced (Clock m) = m () -------------------------------------------------------------------------------- /src/Freckle/Time.fs: -------------------------------------------------------------------------------- 1 | namespace Freckle 2 | open FSharp.Helpers 3 | open System 4 | 5 | ///Time is meassured in ticks a which is a dotNet concept 1 tick = 10μs 6 | type Ticks = int64 7 | 8 | ///Time is the meassurement used for all FRP reasoning 9 | type Time = 10 | { ///Time is meassured in ticks a which is a dotNet concept 1 tick = 10μs 11 | Ticks : Ticks 12 | } 13 | with override x.ToString() = sprintf "%A" x 14 | static member (+) (t1,t2) = { Ticks = t1.Ticks + t2.Ticks } 15 | static member (-) (t1,t2) = { Ticks = t1.Ticks - t2.Ticks } 16 | ///Creates a time 17 | static member time t = { Ticks = t; } 18 | 19 | ///Creates an epoch time 20 | static member origin = Time.time 0L 21 | 22 | ///Get the current tick count 23 | static member ticks t = t.Ticks 24 | 25 | ///Converts time to dotNet's DateTime 26 | static member toDateTime t = DateTime(Time.ticks t) 27 | 28 | ///The max time, tied to DateTime and is the year 9999. 29 | static member maxValue = { Ticks = DateTime.MaxValue.Ticks } 30 | 31 | ///Converts DateTime to Time 32 | static member ofDateTime (d : DateTime) = Time.time d.Ticks 33 | 34 | ///From microseconds get a time 35 | static member ofMicroseconds (microSec : int32) = Time.time <| 10L * int64 microSec 36 | 37 | ///From milliseconds get a time 38 | static member ofMilliseconds (ms : int32) = Time.time <| TimeSpan.TicksPerMillisecond * int64 ms 39 | 40 | ///From seconds get a time 41 | static member ofSeconds (sec : int32) = Time.time <| TimeSpan.TicksPerSecond * int64 sec 42 | 43 | ///From minutes get a time 44 | static member ofMinutes (min : int32) = Time.time <| TimeSpan.TicksPerMinute * int64 min 45 | 46 | ///From hours get a time 47 | static member ofHours (hour : int32) = Time.time <| TimeSpan.TicksPerHour * int64 hour 48 | 49 | ///From days get a time 50 | static member ofDays (days : int32) = Time.time <| TimeSpan.TicksPerDay * int64 days 51 | 52 | ///Gets the time between two times. 53 | ///This is not the same as (-) as this operation is cumulative 54 | static member between a b = 55 | let a' = Time.ticks a 56 | let b' = Time.ticks b 57 | Time.time ((max a' b') - (min a' b')) 58 | 59 | -------------------------------------------------------------------------------- /.paket/paket.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | true 6 | 7 | true 8 | $(MSBuildThisFileDirectory) 9 | $(MSBuildThisFileDirectory)..\ 10 | 11 | 12 | 13 | $(PaketToolsPath)paket.exe 14 | $(PaketToolsPath)paket.bootstrapper.exe 15 | "$(PaketExePath)" 16 | mono --runtime=v4.0.30319 "$(PaketExePath)" 17 | "$(PaketBootStrapperExePath)" 18 | mono --runtime=v4.0.30319 $(PaketBootStrapperExePath) 19 | 20 | $(PaketCommand) restore 21 | $(PaketBootStrapperCommand) 22 | 23 | RestorePackages; $(BuildDependsOn); 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /docs/tools/templates/template.cshtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | @Title 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 21 | 22 | 23 |
24 |
25 | 29 |

@Properties["project-name"]

30 |
31 |
32 |
33 |
34 | @RenderBody() 35 |
36 |
37 | F# Project 38 | 53 |
54 |
55 |
56 | Fork me on GitHub 57 | 58 | 59 | -------------------------------------------------------------------------------- /src/Freckle/Helpers.fs: -------------------------------------------------------------------------------- 1 | ///A helper module used for the library, use at your own discrestion 2 | module FSharp.Helpers 3 | 4 | let inline undefined<'a> : 'a = failwith "undefined" 5 | 6 | let inline swap (a,b) = (b,a) 7 | 8 | let inline flip f a b = f b a 9 | 10 | let inline const' k = fun _ -> k 11 | 12 | 13 | let safeUnbox (o : obj) = 14 | match o with 15 | | :? 'a as a -> Some a 16 | | _ -> None 17 | 18 | let tuple fst snd = (fst, snd) 19 | 20 | 21 | type SortedType(t) = 22 | member x.Type : System.Type = t 23 | override x.Equals(yobj) = 24 | match yobj with 25 | | :? SortedType as y -> (x.Type = y.Type) 26 | | _ -> false 27 | override x.GetHashCode() = hash x.Type 28 | interface System.IComparable with 29 | member x.CompareTo yobj = 30 | match yobj with 31 | | :? SortedType as y -> compare (hash x.Type) (hash y.Type) 32 | | _ -> invalidArg "yobj" "cannot compare values of different types" 33 | module AutoResetEvent = 34 | open System.Threading 35 | 36 | let wait (a : AutoResetEvent) = a.WaitOne() |> ignore 37 | 38 | let release (a : AutoResetEvent) = a.Set() |> ignore 39 | 40 | module Async = 41 | open System.Threading 42 | open System.Threading.Tasks 43 | 44 | let (>>=) ma f = async.Bind(ma, f) 45 | let ( *>>) ma mb = async.Bind(ma, (fun _ -> mb)) 46 | 47 | let startFreeChild a = 48 | a 49 | |> Async.StartChild 50 | |> Async.Ignore 51 | 52 | let doNothing = async.Zero() 53 | 54 | type Signal<'a> = Continue of 'a 55 | | Completed of 'a 56 | 57 | let recursion (f : 's -> Async) (state : 's) : Async<'s> = 58 | async { 59 | let mutable s = state 60 | let mutable sad = true 61 | while sad do 62 | let! (b, a) = Async.TryCancelled(f s, fun _ -> ()) 63 | match b with 64 | | true -> s <- a 65 | | false -> 66 | s <- a 67 | sad <- false 68 | return s 69 | } 70 | 71 | let forever (f : 's -> Async<'s>) (state : 's) : Async<_> = 72 | async { 73 | let mutable s = state 74 | while true do 75 | let! s' = f s 76 | s <- s' 77 | return undefined 78 | } 79 | 80 | let map f ma = 81 | async { 82 | let! a = ma 83 | return f a 84 | } 85 | 86 | let join ma = async.Bind(ma, id) 87 | let bind f ma = async.Bind(ma, f) 88 | 89 | module List = 90 | 91 | let tryHead l = 92 | match l with 93 | | a :: _ -> Some a 94 | | [] -> None 95 | 96 | module Option = 97 | 98 | let pure' x = Some x 99 | 100 | let default' x o = 101 | match o with 102 | | Some a -> a 103 | | None -> x 104 | 105 | let mapDefault x f o = 106 | Option.map f o |> default' x 107 | 108 | let ap mf ma = 109 | match mf, ma with 110 | | Some f, Some a -> Some (f a) 111 | | _ -> None 112 | 113 | let bind fm ma = 114 | match ma with 115 | | Some a -> fm a 116 | | None -> None -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.sln.docstates 8 | 9 | # Xamarin Studio / monodevelop user-specific 10 | *.userprefs 11 | *.dll.mdb 12 | *.exe.mdb 13 | 14 | # Build results 15 | 16 | [Dd]ebug/ 17 | [Rr]elease/ 18 | x64/ 19 | build/ 20 | [Bb]in/ 21 | [Oo]bj/ 22 | 23 | # MSTest test Results 24 | [Tt]est[Rr]esult*/ 25 | [Bb]uild[Ll]og.* 26 | 27 | *_i.c 28 | *_p.c 29 | *.ilk 30 | *.meta 31 | *.obj 32 | *.pch 33 | *.pdb 34 | *.pgc 35 | *.pgd 36 | *.rsp 37 | *.sbr 38 | *.tlb 39 | *.tli 40 | *.tlh 41 | *.tmp 42 | *.tmp_proj 43 | *.log 44 | *.vspscc 45 | *.vssscc 46 | .builds 47 | *.pidb 48 | *.log 49 | *.scc 50 | 51 | # Visual C++ cache files 52 | ipch/ 53 | *.aps 54 | *.ncb 55 | *.opensdf 56 | *.sdf 57 | *.cachefile 58 | 59 | # Visual Studio profiler 60 | *.psess 61 | *.vsp 62 | *.vspx 63 | 64 | # Other Visual Studio data 65 | .vs/ 66 | 67 | # Guidance Automation Toolkit 68 | *.gpState 69 | 70 | # ReSharper is a .NET coding add-in 71 | _ReSharper*/ 72 | *.[Rr]e[Ss]harper 73 | 74 | # TeamCity is a build add-in 75 | _TeamCity* 76 | 77 | # DotCover is a Code Coverage Tool 78 | *.dotCover 79 | 80 | # NCrunch 81 | *.ncrunch* 82 | .*crunch*.local.xml 83 | 84 | # Installshield output folder 85 | [Ee]xpress/ 86 | 87 | # DocProject is a documentation generator add-in 88 | DocProject/buildhelp/ 89 | DocProject/Help/*.HxT 90 | DocProject/Help/*.HxC 91 | DocProject/Help/*.hhc 92 | DocProject/Help/*.hhk 93 | DocProject/Help/*.hhp 94 | DocProject/Help/Html2 95 | DocProject/Help/html 96 | 97 | # Click-Once directory 98 | publish/ 99 | 100 | # Publish Web Output 101 | *.Publish.xml 102 | 103 | # Enable nuget.exe in the .nuget folder (though normally executables are not tracked) 104 | !.nuget/NuGet.exe 105 | 106 | # Windows Azure Build Output 107 | csx 108 | *.build.csdef 109 | 110 | # Windows Store app package directory 111 | AppPackages/ 112 | 113 | # Others 114 | sql/ 115 | *.Cache 116 | ClientBin/ 117 | [Ss]tyle[Cc]op.* 118 | ~$* 119 | *~ 120 | *.dbmdl 121 | *.[Pp]ublish.xml 122 | *.pfx 123 | *.publishsettings 124 | 125 | # RIA/Silverlight projects 126 | Generated_Code/ 127 | 128 | # Backup & report files from converting an old project file to a newer 129 | # Visual Studio version. Backup files are not needed, because we have git ;-) 130 | _UpgradeReport_Files/ 131 | Backup*/ 132 | UpgradeLog*.XML 133 | UpgradeLog*.htm 134 | 135 | # SQL Server files 136 | App_Data/*.mdf 137 | App_Data/*.ldf 138 | 139 | 140 | #LightSwitch generated files 141 | GeneratedArtifacts/ 142 | _Pvt_Extensions/ 143 | ModelManifest.xml 144 | 145 | # ========================= 146 | # Windows detritus 147 | # ========================= 148 | 149 | # Windows image file caches 150 | Thumbs.db 151 | ehthumbs.db 152 | 153 | # Folder config file 154 | Desktop.ini 155 | 156 | # Recycle Bin used on file shares 157 | $RECYCLE.BIN/ 158 | 159 | # Mac desktop service store files 160 | .DS_Store 161 | 162 | # =================================================== 163 | # Exclude F# project specific directories and files 164 | # =================================================== 165 | 166 | # NuGet Packages Directory 167 | packages/ 168 | 169 | # Generated documentation folder 170 | docs/output/ 171 | 172 | # Temp folder used for publishing docs 173 | temp/ 174 | 175 | # Test results produced by build 176 | TestResults.xml 177 | 178 | # Nuget outputs 179 | nuget/*.nupkg 180 | release.cmd 181 | release.sh 182 | localpackages/ 183 | paket-files 184 | *.orig 185 | .paket/paket.exe 186 | docs/content/license.md 187 | docs/content/release-notes.md 188 | .fake 189 | docs/tools/FSharp.Formatting.svclog 190 | -------------------------------------------------------------------------------- /Freckle.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio 14 3 | VisualStudioVersion = 14.0.25123.0 4 | MinimumVisualStudioVersion = 10.0.40219.1 5 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".paket", ".paket", "{63297B98-5CED-492C-A5B7-A5B4F73CF142}" 6 | ProjectSection(SolutionItems) = preProject 7 | paket.dependencies = paket.dependencies 8 | paket.lock = paket.lock 9 | EndProjectSection 10 | EndProject 11 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{A6A6AF7D-D6E3-442D-9B1E-58CC91879BE1}" 12 | EndProject 13 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Freckle", "src\Freckle\Freckle.fsproj", "{ACA0E94A-E608-481F-9B44-44E615FDCBA8}" 14 | EndProject 15 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "project", "project", "{BF60BC93-E09B-4E5F-9D85-95A519479D54}" 16 | ProjectSection(SolutionItems) = preProject 17 | build.fsx = build.fsx 18 | README.md = README.md 19 | RELEASE_NOTES.md = RELEASE_NOTES.md 20 | EndProjectSection 21 | EndProject 22 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{83F16175-43B1-4C90-A1EE-8E351C33435D}" 23 | ProjectSection(SolutionItems) = preProject 24 | docs\tools\generate.fsx = docs\tools\generate.fsx 25 | docs\tools\templates\template.cshtml = docs\tools\templates\template.cshtml 26 | EndProjectSection 27 | EndProject 28 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "content", "content", "{8E6D5255-776D-4B61-85F9-73C37AA1FB9A}" 29 | ProjectSection(SolutionItems) = preProject 30 | docs\content\index.fsx = docs\content\index.fsx 31 | docs\content\tutorial.fsx = docs\content\tutorial.fsx 32 | EndProjectSection 33 | EndProject 34 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{ED8079DD-2B06-4030-9F0F-DC548F98E1C4}" 35 | EndProject 36 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Freckle.Tests", "tests\Freckle.Tests\Freckle.Tests.fsproj", "{D306444C-1CF8-4442-9DE9-E445CF4332EB}" 37 | EndProject 38 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{1226A70E-7641-453C-95DA-77DE0BBA771C}" 39 | EndProject 40 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "AirlockStatemachine", "examples\AirlockStatemachine\AirlockStatemachine.fsproj", "{F473B340-1E3D-47FA-90A6-3F50BA5CA9F9}" 41 | EndProject 42 | Global 43 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 44 | Debug|Any CPU = Debug|Any CPU 45 | Release|Any CPU = Release|Any CPU 46 | EndGlobalSection 47 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 48 | {ACA0E94A-E608-481F-9B44-44E615FDCBA8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 49 | {ACA0E94A-E608-481F-9B44-44E615FDCBA8}.Debug|Any CPU.Build.0 = Debug|Any CPU 50 | {ACA0E94A-E608-481F-9B44-44E615FDCBA8}.Release|Any CPU.ActiveCfg = Release|Any CPU 51 | {ACA0E94A-E608-481F-9B44-44E615FDCBA8}.Release|Any CPU.Build.0 = Release|Any CPU 52 | {D306444C-1CF8-4442-9DE9-E445CF4332EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 53 | {D306444C-1CF8-4442-9DE9-E445CF4332EB}.Debug|Any CPU.Build.0 = Debug|Any CPU 54 | {D306444C-1CF8-4442-9DE9-E445CF4332EB}.Release|Any CPU.ActiveCfg = Release|Any CPU 55 | {D306444C-1CF8-4442-9DE9-E445CF4332EB}.Release|Any CPU.Build.0 = Release|Any CPU 56 | {F473B340-1E3D-47FA-90A6-3F50BA5CA9F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 57 | {F473B340-1E3D-47FA-90A6-3F50BA5CA9F9}.Debug|Any CPU.Build.0 = Debug|Any CPU 58 | {F473B340-1E3D-47FA-90A6-3F50BA5CA9F9}.Release|Any CPU.ActiveCfg = Release|Any CPU 59 | {F473B340-1E3D-47FA-90A6-3F50BA5CA9F9}.Release|Any CPU.Build.0 = Release|Any CPU 60 | EndGlobalSection 61 | GlobalSection(SolutionProperties) = preSolution 62 | HideSolutionNode = FALSE 63 | EndGlobalSection 64 | GlobalSection(NestedProjects) = preSolution 65 | {83F16175-43B1-4C90-A1EE-8E351C33435D} = {A6A6AF7D-D6E3-442D-9B1E-58CC91879BE1} 66 | {8E6D5255-776D-4B61-85F9-73C37AA1FB9A} = {A6A6AF7D-D6E3-442D-9B1E-58CC91879BE1} 67 | {D306444C-1CF8-4442-9DE9-E445CF4332EB} = {ED8079DD-2B06-4030-9F0F-DC548F98E1C4} 68 | {F473B340-1E3D-47FA-90A6-3F50BA5CA9F9} = {1226A70E-7641-453C-95DA-77DE0BBA771C} 69 | EndGlobalSection 70 | EndGlobal 71 | -------------------------------------------------------------------------------- /examples/AirlockStatemachine/ExternalAirlock.fs: -------------------------------------------------------------------------------- 1 | // Implementation meant to simulate outside mutable state 2 | // Warning ugly code inside, this is by design, in order to show the power of freckle 3 | module ExternalAirlock 4 | open System.Collections.Concurrent 5 | open System.Threading.Tasks.Dataflow 6 | 7 | let eventQueue = BufferBlock() 8 | 9 | let consoleLock = new obj() 10 | 11 | let printfn' s = lock consoleLock (fun () -> printfn s) 12 | 13 | type BlockImp = { ObjectInUse : bool ref 14 | Name : string 15 | Status : bool ref 16 | } 17 | 18 | let doorInner = { ObjectInUse = ref false 19 | Name = "DoorInner" 20 | Status = ref false 21 | } 22 | 23 | let doorOuter = { ObjectInUse = ref false 24 | Name = "DoorOuter" 25 | Status = ref true 26 | } 27 | 28 | let vent = { ObjectInUse = ref false 29 | Name = "Vent" 30 | Status = ref false 31 | } 32 | 33 | let enqueue evt = 34 | async { 35 | let! _ = eventQueue.SendAsync(evt) |> Async.AwaitTask 36 | return () 37 | } 38 | 39 | let dequeue = 40 | async { 41 | let! msg = eventQueue.ReceiveAsync() |> Async.AwaitTask 42 | return msg 43 | } 44 | 45 | let press = 46 | async { 47 | do! enqueue "pressbutton" 48 | } 49 | 50 | 51 | let pressurize = 52 | async { 53 | if !vent.Status then 54 | failwith "room was already pressuarized YOU HAVE LOST" 55 | 56 | if !vent.ObjectInUse then 57 | failwith (vent.Name + " already in use YOU HAVE LOST") 58 | 59 | vent.ObjectInUse.Value <- true 60 | printfn' "@@@ Beginning to pressurize" 61 | vent.Status := true 62 | do! Async.Sleep 5000 63 | vent.ObjectInUse.Value <- false 64 | printfn' "@@@ Room has been pressuarized" 65 | do! enqueue "pressuarized" 66 | } 67 | 68 | let depressurize = 69 | async { 70 | if not !vent.Status then 71 | failwith "room was already depressuarized YOU HAVE LOST" 72 | 73 | if !vent.ObjectInUse then 74 | failwith (vent.Name + " already in use YOU HAVE LOST") 75 | 76 | vent.ObjectInUse.Value <- true 77 | vent.Status := false 78 | printfn' "@@@ Beginning to depressurize" 79 | do! Async.Sleep 5000 80 | printfn' "@@@ Room has been depressurized" 81 | vent.ObjectInUse.Value <- false 82 | do! enqueue "depressurized" 83 | 84 | } 85 | 86 | 87 | let open' (door : BlockImp) = 88 | async { 89 | if !door.Status then 90 | failwith "door was already open YOU HAVE LOST" 91 | 92 | if !door.ObjectInUse then 93 | failwith (door.Name + " already in use YOU HAVE LOST") 94 | 95 | door.ObjectInUse.Value <- true 96 | printfn' "@@@ Opening %s" door.Name 97 | door.Status := true 98 | do! Async.Sleep 2000 99 | printfn' "@@@ Door %s has opened" door.Name 100 | door.ObjectInUse.Value <- false 101 | do! enqueue ("opened" + door.Name) 102 | } 103 | 104 | let openInner = open' doorInner 105 | let openOuter = open' doorOuter 106 | 107 | let close (door : BlockImp) = 108 | async { 109 | if not !door.Status then 110 | failwith "door was already closed YOU HAVE LOST" 111 | 112 | if !door.ObjectInUse then 113 | failwith (door.Name + " already in use YOU HAVE LOST") 114 | 115 | door.ObjectInUse.Value <- true 116 | printfn' "@@@ Closing %s" door.Name 117 | door.Status := false 118 | do! Async.Sleep 2000 119 | printfn' "@@@ Door %s has closed" door.Name 120 | door.ObjectInUse.Value <- false 121 | do! enqueue ("closed" + door.Name) 122 | } 123 | 124 | let closeInner = close doorInner 125 | let closeOuter = close doorOuter 126 | -------------------------------------------------------------------------------- /examples/AirlockStatemachine/Program.fs: -------------------------------------------------------------------------------- 1 | open FrpAirlockExample 2 | open System 3 | open FSharp.Helpers 4 | open Freckle 5 | open System.Threading.Tasks 6 | open System.Threading 7 | 8 | [] 9 | let main argv = 10 | 11 | let writeConsole s = 12 | async { 13 | return ignore <| ExternalAirlock.printfn' "%s" s 14 | } 15 | 16 | let writeStatus s = 17 | async { 18 | let crazy () = 19 | let pos = System.Console.CursorTop 20 | let posLeft = System.Console.CursorLeft 21 | System.Console.SetCursorPosition(40, 3) 22 | ignore <| printf "STATUS: %s" s 23 | System.Console.SetCursorPosition(posLeft, pos) 24 | return lock ExternalAirlock.consoleLock crazy 25 | } 26 | 27 | let writeTime time = 28 | async { 29 | let crazy () = 30 | let pos = System.Console.CursorTop 31 | let posLeft = System.Console.CursorLeft 32 | System.Console.SetCursorPosition(40, 1) 33 | ignore <| printf "Current Time: %s" ((Time.toDateTime time).ToString("yyyy-MM-dd HH:mm:ss.fff", System.Globalization.CultureInfo.InvariantCulture)) 34 | System.Console.SetCursorPosition(posLeft, pos) 35 | return lock ExternalAirlock.consoleLock crazy 36 | } 37 | 38 | let writeFps p = 39 | async { 40 | let crazy () = 41 | let pos = System.Console.CursorTop 42 | let posLeft = System.Console.CursorLeft 43 | System.Console.SetCursorPosition(40, 2) 44 | let ticks = ((Period.finish p |> Time.toDateTime) - (Period.beginning p |> Time.toDateTime)) 45 | let fps = 1.0 / ticks.TotalSeconds 46 | ignore <| printfn "Fps: %8.5f | Sampling Delay: %.2f ms" fps ticks.TotalMilliseconds 47 | System.Console.SetCursorPosition(posLeft, pos) 48 | 49 | return lock ExternalAirlock.consoleLock crazy 50 | } 51 | 52 | let readConsole = 53 | async { 54 | while true do 55 | let s = System.Console.ReadLine() 56 | lock ExternalAirlock.consoleLock (fun () -> System.Console.SetCursorPosition(0, (System.Console.CursorTop - 1))) 57 | do! ExternalAirlock.press 58 | } 59 | 60 | let eventMap s = 61 | match s with 62 | | "depressurized" -> Depressurized 63 | | "pressuarized" -> Pressurized 64 | | "openedDoorInner" -> DoorOpened InnerDoor 65 | | "openedDoorOuter" -> DoorOpened OuterDoor 66 | | "closedDoorInner" -> DoorClosed InnerDoor 67 | | "closedDoorOuter" -> DoorClosed OuterDoor 68 | | "pressbutton" -> PressButton 69 | | evt -> failwith <| sprintf "unknown event %s" evt 70 | 71 | 72 | let events = Async.map eventMap ExternalAirlock.dequeue 73 | 74 | let openDoor door = 75 | match door with 76 | | InnerDoor -> ExternalAirlock.openInner 77 | | OuterDoor -> ExternalAirlock.openOuter 78 | 79 | let closeDoor door = 80 | match door with 81 | | InnerDoor -> ExternalAirlock.closeInner 82 | | OuterDoor -> ExternalAirlock.closeOuter 83 | 84 | let airlock : Airlock = 85 | { Open = openDoor 86 | Close = closeDoor 87 | Pressurize = ExternalAirlock.pressurize 88 | Depressurize = ExternalAirlock.depressurize 89 | ShowTerminal = writeConsole >> Async.startFreeChild 90 | ShowStatus = writeStatus >> Async.startFreeChild 91 | SetClock = writeTime >> Async.startFreeChild 92 | SetFps = writeFps >> Async.startFreeChild 93 | } 94 | async { 95 | printfn "Hi! and welcome to the Airlock example, to start double press enter." 96 | let! _ = readConsole |> Async.StartChild 97 | let syncUtcClock = Clock.synchronized Clock.systemUtc 98 | let mb = Mailbox.create syncUtcClock 99 | do! Mailbox.listenTo events mb 100 | let state = { Airlock = AirLockState.IsDepressurized; ActionAt = None; LastDoubleClick = Time.origin } 101 | 102 | let resolution = 103 | Async.awaitAny [ Async.pulseMax 104 | Mailbox.awaitMail mb 105 | ] 106 | let runner = 107 | setup mb airlock 108 | >> SampleAsync.doAsync resolution 109 | do! Sample.sampleForever syncUtcClock runner state 110 | } |> Async.RunSynchronously 111 | 0 112 | -------------------------------------------------------------------------------- /docs/content/index.fsx: -------------------------------------------------------------------------------- 1 | (*** hide ***) 2 | // This block of code is omitted in the generated HTML documentation. Use 3 | // it to define helpers that you do not want to show in the documentation. 4 | #I __SOURCE_DIRECTORY__ 5 | #r "../../packages/FSPowerPack.Core.Community/Lib/Net40/FSharp.PowerPack.dll" 6 | #r "System.Core.dll" 7 | #r "System.dll" 8 | #r "System.Numerics.dll" 9 | #r "../../bin/Freckle/Freckle.dll" 10 | #r "../../packages/example/Microsoft.Tpl.Dataflow/lib/portable-net45+win8+wpa81/System.Threading.Tasks.Dataflow.dll" 11 | open System.Threading.Tasks.Dataflow 12 | 13 | (** 14 | Freckle 15 | ====================== 16 | 17 | Simple monadic Functional Reactive Programming for F# 18 | 19 |
20 |
21 |
22 |
23 | The Freckle library can be installed from NuGet: 24 |
PM> Install-Package Freckle
25 |
26 |
27 |
28 |
29 | 30 | *) 31 | 32 | (** 33 | 34 | Quick Introduction 35 | ----------------------- 36 | 37 | ### Namespace and referecing 38 | 39 | *) 40 | open Freckle 41 | 42 | (** 43 | ### Setting up a sampler and running 44 | Create a sampler that prints the timespan it samples over 45 | *) 46 | 47 | let sampler count = 48 | sample { 49 | let! currentSpan = Sample.period 50 | let startedAt = (Time.toDateTime currentSpan.Beginning) 51 | let endedAt = (Time.toDateTime currentSpan.Finish) 52 | printfn "%d: Sampling %A -> %A" count startedAt endedAt 53 | return count + 1 54 | } |> SampleAsync.ofSample 55 | 56 | Sample.sampleForever Clock.systemUtc sampler 0 57 | |> Async.RunSynchronously 58 | 59 | 60 | (** 61 | ### Guaranteed Fixed interval updates 62 | Create a sampler that counts every second from 0 and is resistentent to computer lagspike 63 | *) 64 | 65 | let countSecondsSampler count = 66 | sample { 67 | let! pulses = Feed.pulse 1 68 | return! Feed.foldPast (fun c _ -> c + 1) count pulses 69 | } |> SampleAsync.ofSample 70 | 71 | Sample.sampleForever Clock.systemUtc countSecondsSampler 0 72 | |> Async.RunSynchronously 73 | 74 | (** 75 | ### Best attempt Fixed interval updates 76 | Creates a sampler that does something every second, but doesn't care if a step is skipped due to a lagspike 77 | *) 78 | let render = 79 | async { 80 | return printfn "Pretend like this is graphic rendering" 81 | } 82 | 83 | let renderingSampler () = 84 | sample { 85 | let! pulses = Feed.pulseUpto 1 //Notice it's pulseUpto 86 | return! Feed.transition (fun _ _ -> render) () pulses 87 | } 88 | 89 | Sample.sampleForever Clock.systemUtc renderingSampler () 90 | |> Async.RunSynchronously 91 | 92 | 93 | (** 94 | ### Listening to events 95 | Listen to external events and print their content to the console 96 | 97 | *) 98 | 99 | async { 100 | let syncClock = Clock.synchronized Clock.systemUtc 101 | let! mb = Mailbox.createWithExpiration (Expire.After (Time.ofSeconds 30)) syncClock 102 | 103 | let renderingSampler mb () = 104 | sampleAsync { 105 | let! events = Mailbox.read mb |> SampleAsync.ofAsync 106 | do! Mailbox.clear |> SampleAsync.ofAsync 107 | return! Feed.foldPast (fun _ msg -> printfn "revieved %s" msg) () events 108 | |> SampleAsync.ofSample 109 | } 110 | 111 | do! Sample.sampleForever syncClock (renderingSampler mb) () 112 | } 113 | |> Async.RunSynchronously 114 | 115 | (** 116 | 117 | Further Reading 118 | --------------- 119 | 120 | * [Tutorial](tutorial.html) contains a further explanation of this sample library. 121 | 122 | * [API Reference](reference/index.html) contains automatically generated documentation for all types, modules 123 | and functions in the library. This includes additional brief samples on using most of the 124 | functions. 125 | 126 | Contributing and copyright 127 | -------------------------- 128 | 129 | The project is hosted on [GitHub][gh] where you can [report issues][issues], fork 130 | the project and submit pull requests. If you're adding a new public API, please also 131 | consider adding [samples][content] that can be turned into a documentation. You might 132 | also want to read the [library design notes][readme] to understand how it works. 133 | 134 | The library is available under Public Domain license, which allows modification and 135 | redistribution for both commercial and non-commercial purposes. For more information see the 136 | [License file][license] in the GitHub repository. 137 | 138 | [content]: https://github.com/fsprojects/Freckle/tree/master/docs/content 139 | [gh]: https://github.com/fsprojects/Freckle 140 | [issues]: https://github.com/fsprojects/Freckle/issues 141 | [readme]: https://github.com/fsprojects/Freckle/blob/master/README.md 142 | [license]: https://github.com/fsprojects/Freckle/blob/master/LICENSE.txt 143 | *) 144 | -------------------------------------------------------------------------------- /examples/AirlockStatemachine/FrpAirlockExample.fs: -------------------------------------------------------------------------------- 1 | module FrpAirlockExample 2 | open FSharp 3 | open System 4 | open FSharp.Helpers 5 | open Freckle 6 | 7 | 8 | type Door = InnerDoor 9 | | OuterDoor 10 | 11 | type DoorStatus = Opened 12 | | Closed 13 | 14 | type AirLockEvent = PressButton 15 | | DoublePressButton 16 | | DoorOpened of Door 17 | | DoorClosed of Door 18 | | Pressurized 19 | | Depressurized 20 | 21 | type AirLockState = 22 | | Pressurizing 23 | | Depressurizing 24 | | IsPressurized 25 | | IsDepressurized 26 | 27 | type Airlock = 28 | { Open : Door -> Async 29 | Close : Door -> Async 30 | Pressurize : Async 31 | Depressurize : Async 32 | ShowTerminal : string -> Async 33 | ShowStatus : string -> Async 34 | SetClock : Time -> Async 35 | SetFps : Period -> Async 36 | } 37 | 38 | type State = 39 | { Airlock : AirLockState 40 | ActionAt : Ticks option 41 | LastDoubleClick : Time 42 | } 43 | 44 | 45 | let stm (airlock : Airlock) state event = 46 | match state, event with 47 | | IsPressurized , DoublePressButton -> (Depressurizing , true, airlock.Close InnerDoor) 48 | | Depressurizing , DoorClosed InnerDoor -> (Depressurizing , false, airlock.Depressurize) 49 | | Depressurizing , Depressurized -> (Depressurizing , false, airlock.Open OuterDoor) 50 | | Depressurizing , DoorOpened OuterDoor -> (IsDepressurized, false, airlock.ShowTerminal "Depressurized room") 51 | 52 | | IsDepressurized, DoublePressButton -> (Pressurizing , true, airlock.Close OuterDoor) 53 | | Pressurizing , DoorClosed OuterDoor -> (Pressurizing , false, airlock.Pressurize) 54 | | Pressurizing , Pressurized -> (Pressurizing , false, airlock.Open InnerDoor) 55 | | Pressurizing , DoorOpened InnerDoor -> (IsPressurized , false, airlock.ShowTerminal "Pressurized room") 56 | 57 | | _ -> (state , false, Async.doNothing) 58 | 59 | 60 | let doubleClickTime = Time.ofMilliseconds 500 61 | 62 | let clicks clickFeed = 63 | feed { 64 | let! c1 = clickFeed 65 | |> Feed.groupBy (fun t1 t2 -> Time.between t1 t2 < doubleClickTime) 66 | let len = Feed.testLength 2 c1 67 | if len = 2 68 | then return DoublePressButton 69 | else return PressButton 70 | } 71 | 72 | let doublePress' last evts = 73 | let (buttonEvts, others) = Feed.partition ((=) PressButton) evts 74 | buttonEvts 75 | |> Feed.discardOlderIncl last 76 | |> Feed.delay doubleClickTime 77 | |> Feed.discardFuture 78 | |> Sample.map (Feed.time >> clicks >> Feed.combine others) 79 | 80 | let airlockProg (airlock : Airlock) now s e = 81 | async { 82 | let (airlock, beganActions, ma) = stm airlock s.Airlock e 83 | let! _ = ma |> Async.StartChild 84 | return { s with Airlock = airlock; ActionAt = if beganActions then Some now else s.ActionAt } 85 | } 86 | 87 | let status airlock s = 88 | sampleAsync { 89 | match s.ActionAt with 90 | | Some ticks when s.Airlock = Pressurizing || s.Airlock = Depressurizing -> 91 | let pctDone t = min 100.0 <| (float (t - ticks) * 100.0 ) / float (TimeSpan.TicksPerSecond * 9L) 92 | do! Feed.pulseUpto 30 93 | |> Sample.map (Feed.map (fun t -> airlock.ShowStatus <| sprintf "%A %.2f%% " s.Airlock (pctDone (Time.ticks t)))) 94 | |> Sample.bind Feed.plan_ 95 | | _ -> return () 96 | } 97 | 98 | let setup mb airlock s = 99 | sampleAsync { 100 | let! p = Sample.period |> SampleAsync.ofSample 101 | do! status airlock s 102 | do! Feed.pulseUpto 10 103 | |> Sample.map (Feed.map (const' <| airlock.SetClock p.Finish)) 104 | |> Sample.bind Feed.plan_ 105 | do! Feed.pulseUpto 10 106 | |> Sample.map (Feed.map (const' <| airlock.SetFps p)) 107 | |> Sample.bind Feed.plan_ 108 | 109 | let! evts = Mailbox.read mb 110 | let! evts' = doublePress' s.LastDoubleClick evts |> SampleAsync.ofSample 111 | let! last' = Feed.foldPast (fun s (t,a) -> if a = DoublePressButton then t else s) s.LastDoubleClick (Feed.timeStamp evts') |> SampleAsync.ofSample 112 | return! Feed.transition (airlockProg airlock p.Finish.Ticks) { s with LastDoubleClick = last' } evts' 113 | } -------------------------------------------------------------------------------- /src/Freckle/Mailbox.fs: -------------------------------------------------------------------------------- 1 | namespace Freckle 2 | 3 | open System 4 | 5 | ///The mailbox is the concept of messages passed from external parts of the system 6 | ///A mailbox should preferably should only have one consumer and one or more producers 7 | type Mailbox<'e> = 8 | { Post : 'e -> unit 9 | Read : Sample> 10 | Clear : unit -> unit 11 | AwaitMail : Async 12 | } 13 | 14 | ///The Mailbox module contains all functions to work with external events, such as post, read and suc. 15 | module Mailbox = 16 | open FSharp.Helpers 17 | open System.Threading 18 | open SampleAsync.ComputationalExpression 19 | open Sample.ComputationalExpression 20 | 21 | ///This module contains internally used functions, these are suceptible to change even with minor updates. 22 | ///Is not recommended for use in a production environment. 23 | module Internal = 24 | 25 | ///Automatically delete events from a mailbox by setting an expiration 26 | type Expire = Never 27 | | After of Time 28 | 29 | type MailTrigger = delegate of obj * EventArgs -> unit 30 | 31 | let inline read' mLock (m : Ref * Feed<'e>>) = 32 | let (inc, out) = lock mLock (fun _ -> !m) 33 | Feed.combine inc out 34 | 35 | let inline push' time evt (trigger : Event) (expire : Expire) (m : Ref * Feed<'e>>) : unit = 36 | let (inc, out) = !m 37 | match expire, Feed.tryHead (Feed.time out) with 38 | | After _, None -> 39 | m := (Feed.empty, Feed.Internal.unsafePush time evt inc) 40 | | After t, Some outTime when t.Ticks < time.Ticks - outTime.Ticks -> 41 | m := (Feed.Internal.unsafePush time evt inc, Feed.empty) 42 | | _ -> m := (Feed.Internal.unsafePush time evt inc, out) 43 | trigger.Trigger(null, null) 44 | 45 | let post mLock clock trigger expire evts evt = 46 | lock mLock (fun () -> let time = Clock.nowSynced clock in push' time evt trigger expire evts) 47 | 48 | let dropAll mLock (evts : Ref * Feed<'e>>) () = 49 | lock mLock (fun () -> evts := (Feed.empty, Feed.empty)) 50 | 51 | let awaitMail (evtTrigger : Event) = 52 | Async.AwaitEvent evtTrigger.Publish 53 | |> Async.Ignore 54 | 55 | let create expire clock = 56 | let evts = ref (Feed.empty, Feed.empty) 57 | let mlock = new obj() 58 | let evtTrigger = new Event<_,_>() 59 | { Post = post mlock clock evtTrigger expire evts 60 | Read = sample { return read' mlock evts } 61 | Clear = dropAll mlock evts 62 | AwaitMail = awaitMail evtTrigger 63 | } 64 | 65 | /// Creates a mailbox used for sending and recieving events between different samplers and/or outside state, events has a time they live before they are cleaned 66 | let createWithTTL time clock = 67 | Internal.create (Internal.Expire.After time) clock 68 | 69 | /// Creates a mailbox used for sending and recieving events between different samplers and/or outside state, NB: has to be cleared manually or timeleaks will occur, otherwise use createWithTTL 70 | let create clock = 71 | Internal.create (Internal.Expire.Never) clock 72 | 73 | // Reads from an mailbox synchronized 74 | let inline readSync (mb : Mailbox<_>) = mb.Read |> Sample.bind Feed.discardFuture 75 | 76 | /// Post an event to a mailbox 77 | let inline postSync evt (mb : Mailbox<_>) = mb.Post evt 78 | 79 | let inline post evt (mb : Mailbox<_>) = async { return mb.Post evt } 80 | 81 | // Post an event not yet realized 82 | let inline postPromise (ma : Async<_>) mb = 83 | async { 84 | let! a = ma 85 | do! post a mb 86 | } |> Async.StartChild |> Async.Ignore 87 | 88 | // Post an event not yet realized, but start immidately 89 | let inline postPromiseSilent (ma : Async<_>) mb = 90 | postPromise ma mb |> Async.StartImmediate 91 | 92 | /// Post an event to many mailboxes 93 | let inline postMany evt mbs = 94 | async { 95 | for mb in mbs do 96 | do! post evt mb 97 | } 98 | 99 | /// Read all events from a mailbox 100 | let inline read (mb : Mailbox<_>) = 101 | sampleAsync { 102 | return! mb.Read 103 | |> Sample.bind Feed.discardFuture 104 | |> SampleAsync.ofSample 105 | } 106 | 107 | /// Discard all events currently in mailbox 108 | let inline clear (mb : Mailbox<_>) = mb.Clear 109 | 110 | /// wait until a mailbox recieves an event 111 | let inline awaitMail (mb : Mailbox<_>) = mb.AwaitMail 112 | 113 | /// setup a mailbox for listening to an external event stream 114 | let inline listenTo eventStream (mb : Mailbox<_>) = 115 | async { 116 | while true do 117 | let! evt = eventStream 118 | do! post evt mb 119 | } |> Async.StartChild 120 | |> Async.map ignore 121 | 122 | /// setup a mailbox for listening to an external event stream 123 | let inline listenTo' (mb : Mailbox<_>) eventStream = listenTo eventStream mb 124 | 125 | /// setup a mailbox for listening to an external dotnet event 126 | let inline listenToEvent event (mb : Mailbox<_>) = 127 | listenTo (event |> Async.AwaitEvent) mb 128 | 129 | /// etup a mailbox for listening to an dotnet event and perform a map before being posted to the mailbox 130 | let inline ListenToMappedEvent f event (mb : Mailbox<_>) = 131 | listenTo (event |> Async.AwaitEvent |> Async.map f) mb 132 | 133 | -------------------------------------------------------------------------------- /docs/tools/generate.fsx: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------- 2 | // Builds the documentation from `.fsx` and `.md` files in the 'docs/content' directory 3 | // (the generated documentation is stored in the 'docs/output' directory) 4 | // -------------------------------------------------------------------------------------- 5 | 6 | // Binaries that have XML documentation (in a corresponding generated XML file) 7 | // Any binary output / copied to bin/projectName/projectName.dll will 8 | // automatically be added as a binary to generate API docs for. 9 | // for binaries output to root bin folder please add the filename only to the 10 | // referenceBinaries list below in order to generate documentation for the binaries. 11 | // (This is the original behaviour of ProjectScaffold prior to multi project support) 12 | let referenceBinaries = [] 13 | // Web site location for the generated documentation 14 | let website = "/Freckle" 15 | 16 | let githubLink = "http://github.com/jannickj/Freckle" 17 | 18 | // Specify more information about your project 19 | let info = 20 | [ "project-name", "Freckle" 21 | "project-author", "Jannick Johnsen" 22 | "project-summary", "A Functional Reactive Programming Library for FSharp" 23 | "project-github", githubLink 24 | "project-nuget", "http://nuget.org/packages/Freckle" ] 25 | 26 | // -------------------------------------------------------------------------------------- 27 | // For typical project, no changes are needed below 28 | // -------------------------------------------------------------------------------------- 29 | 30 | #load "../../packages/build/FSharp.Formatting/FSharp.Formatting.fsx" 31 | #I "../../packages/build/FAKE/tools/" 32 | #r "FakeLib.dll" 33 | open Fake 34 | open System.IO 35 | open Fake.FileHelper 36 | open FSharp.Literate 37 | open FSharp.MetadataFormat 38 | 39 | // When called from 'build.fsx', use the public project URL as 40 | // otherwise, use the current 'output' directory. 41 | #if RELEASE 42 | let root = website 43 | #else 44 | let root = "file://" + (__SOURCE_DIRECTORY__ @@ "../output") 45 | #endif 46 | 47 | // Paths with template/source/output locations 48 | let bin = __SOURCE_DIRECTORY__ @@ "../../bin" 49 | let content = __SOURCE_DIRECTORY__ @@ "../content" 50 | let output = __SOURCE_DIRECTORY__ @@ "../output" 51 | let files = __SOURCE_DIRECTORY__ @@ "../files" 52 | let templates = __SOURCE_DIRECTORY__ @@ "templates" 53 | let formatting = __SOURCE_DIRECTORY__ @@ "../../packages/build/FSharp.Formatting/" 54 | let docTemplate = "docpage.cshtml" 55 | 56 | // Where to look for *.csproj templates (in this order) 57 | let layoutRootsAll = new System.Collections.Generic.Dictionary() 58 | layoutRootsAll.Add("en",[ templates; formatting @@ "templates" 59 | formatting @@ "templates/reference" ]) 60 | subDirectories (directoryInfo templates) 61 | |> Seq.iter (fun d -> 62 | let name = d.Name 63 | if name.Length = 2 || name.Length = 3 then 64 | layoutRootsAll.Add( 65 | name, [templates @@ name 66 | formatting @@ "templates" 67 | formatting @@ "templates/reference" ])) 68 | 69 | // Copy static files and CSS + JS from F# Formatting 70 | let copyFiles () = 71 | CopyRecursive files output true |> Log "Copying file: " 72 | ensureDirectory (output @@ "content") 73 | CopyRecursive (formatting @@ "styles") (output @@ "content") true 74 | |> Log "Copying styles and scripts: " 75 | 76 | let binaries = 77 | let manuallyAdded = 78 | referenceBinaries 79 | |> List.map (fun b -> bin @@ b) 80 | 81 | let conventionBased = 82 | directoryInfo bin 83 | |> subDirectories 84 | |> Array.map (fun d -> d.FullName @@ (sprintf "%s.dll" d.Name)) 85 | |> List.ofArray 86 | 87 | conventionBased @ manuallyAdded 88 | 89 | let libDirs = 90 | let conventionBasedbinDirs = 91 | directoryInfo bin 92 | |> subDirectories 93 | |> Array.map (fun d -> d.FullName) 94 | |> List.ofArray 95 | 96 | conventionBasedbinDirs @ [bin] 97 | 98 | // Build API reference from XML comments 99 | let buildReference () = 100 | CleanDir (output @@ "reference") 101 | MetadataFormat.Generate 102 | ( binaries, output @@ "reference", layoutRootsAll.["en"], 103 | parameters = ("root", root)::info, 104 | sourceRepo = githubLink @@ "tree/master", 105 | sourceFolder = __SOURCE_DIRECTORY__ @@ ".." @@ "..", 106 | publicOnly = true,libDirs = libDirs ) 107 | 108 | // Build documentation from `fsx` and `md` files in `docs/content` 109 | let buildDocumentation () = 110 | 111 | // First, process files which are placed in the content root directory. 112 | 113 | Literate.ProcessDirectory 114 | ( content, docTemplate, output, replacements = ("root", root)::info, 115 | layoutRoots = layoutRootsAll.["en"], 116 | generateAnchors = true, 117 | processRecursive = false) 118 | 119 | // And then process files which are placed in the sub directories 120 | // (some sub directories might be for specific language). 121 | 122 | let subdirs = Directory.EnumerateDirectories(content, "*", SearchOption.TopDirectoryOnly) 123 | for dir in subdirs do 124 | let dirname = (new DirectoryInfo(dir)).Name 125 | let layoutRoots = 126 | // Check whether this directory name is for specific language 127 | let key = layoutRootsAll.Keys 128 | |> Seq.tryFind (fun i -> i = dirname) 129 | match key with 130 | | Some lang -> layoutRootsAll.[lang] 131 | | None -> layoutRootsAll.["en"] // "en" is the default language 132 | 133 | Literate.ProcessDirectory 134 | ( dir, docTemplate, output @@ dirname, replacements = ("root", root)::info, 135 | layoutRoots = layoutRoots, 136 | generateAnchors = true ) 137 | 138 | // Generate 139 | copyFiles() 140 | #if HELP 141 | buildDocumentation() 142 | #endif 143 | #if REFERENCE 144 | buildReference() 145 | #endif 146 | -------------------------------------------------------------------------------- /src/Freckle/Sample.fs: -------------------------------------------------------------------------------- 1 | namespace Freckle 2 | 3 | ///A period is two times, a beginning time and a finish time. 4 | ///Beginning time will always be considered as non-inclusive and finish will always be considered as inclusive 5 | type Period = 6 | { ///Defines when a period has ended (incl) 7 | Finish : Time 8 | ///Defines when a period has started (excl) 9 | Beginning : Time 10 | } 11 | with ///get the beginning time 12 | static member beginning (p : Period) = p.Beginning 13 | ///get the finish time 14 | static member finish (p : Period) = p.Finish 15 | ///Creates a period from two times, the order is irrelevant as the younger of the two is picked as finish time and vice verse 16 | static member period t1 t2 = if t1 > t2 then { Finish = t1; Beginning = t2 } else { Finish = t2; Beginning = t1 } 17 | 18 | ///Sample is a function from Period to some value. 19 | ///you can think of Sample as (Time -> Behavior) 20 | type Sample<'a> = Period -> 'a 21 | 22 | ///Based on Behavior, Sample generates values from a span of time rather than a single time 23 | module Sample = 24 | open FSharp.Helpers 25 | 26 | ///Get a sample from a value (it's just the const function) 27 | let inline pure' (a : 'a) : Sample<'a> = const' a 28 | 29 | ///Maps a sample from one result to another 30 | let inline map (f : 'a -> 'b) (sample : Sample<'a>) : Sample<'b> = sample >> f 31 | 32 | ///Joins nested samples 33 | let inline join (sample : Sample>) : Sample<'a> = fun p -> sample p p 34 | 35 | ///Binds a sample function into another sample 36 | let inline bind (f : 'a -> Sample<'b>) (m : Sample<'a> ) : Sample<'b> = 37 | join ((map f) m) 38 | 39 | ///Get the period of sample (it's just the id function) 40 | let period : Sample = id 41 | 42 | ///Get the finish of sample 43 | let finish : Sample