FSharpPlus


SeqT<Monad<bool>, 'T>

This is the the Monad Transformer for seq<'T> so it adds sequencing to existing monads by composing them with seq<'T>.

Any monad can be composed, but a very typical usage is when combined with Async or Task, which gives rise to what's called async sequences.

Therefore the AsyncSeq library can be considered a specialization of this monad in Async.

The original post from AsyncSeq can be found here and we can run those examples with SeqT by adapting the code.

In order to do so we need to be aware of the design differences of both implementations.

AsyncSeq

SeqT

Notes

AsyncSeq<'T>

SeqT<Async<bool>, 'T>

asyncSeq { .. }

monad.plus { .. }

At some point it needs to be inferred as SeqT<Async<bool>, 'T>, or it can be specified with type parameters: monad<SeqT<Async<bool>, 'T>>.plus

let! x = y

let! x = SeqT.lift y

No auto lifting. Lifting should be explicit.

do! x

do! SeqT.lift x

''

for x in s

let! x = s

When s: SeqT otherwise for is still ok with regular sequences.

AsyncSeq.[function]

SeqT.[function]

See differences in functions below.

AsyncSeq.[function]Async

SeqT.[function]M

''

AsyncSeq.skip

SeqT.drop

.skip is available but consistently with F# collections, it throws when the sequence doesn't have enough elements.

AsyncSeq.take

SeqT.truncate

.take is available but consistently with F# collections, it throws when the sequence doesn't have enough elements.

AsyncSeq.toBlockingSequence

SeqT.run >> Async.RunSynchronously

Not really the same but semantically equivalent.

AsyncSeq.toListAsync

SeqT.runAsList

AsyncSeq.toArrayAsync

SeqT.runAsArray

AsyncSeq.zipWith

SeqT.map2

Aligned with F# collections.

AsyncSeq.zipWithAsync

SeqT.map2M

''

AsyncSeq.ofObservable

Observable.toAsyncSeq

.toTaskSeq is also available.

AsyncSeq.toObservable

Observable.ofAsyncSeq

.ofTaskSeq is also available.

Examples

#r "nuget: FSharpPlus,1.3.0-CI02744" // still as pre-release

open System
open System.Net
open FSharpPlus
open FSharpPlus.Data

let urls =
  [ "http://bing.com"; "http://yahoo.com";
    "http://google.com"; "http://msn.com"; ]

// Asynchronous sequence that returns URLs and lengths
// of the downloaded HTML. Web pages from a given list
// are downloaded asynchronously in sequence.
let pages: SeqT<_, _> = monad.plus {
    use wc = new WebClient ()
    for url in urls do
        try
            let! html = wc.AsyncDownloadString (Uri url) |> SeqT.lift
            yield url, html.Length
        with _ ->
            yield url, -1 }


// Print URL of pages that are smaller than 100k
let printPages =
    pages
    |> SeqT.filter (fun (_, len) -> len < 100000)
    |> SeqT.map fst
    |> SeqT.iter (printfn "%s")
 
printPages |> Async.Start

These samples above and below come from the original AsyncSeq post and they can be easily switched to task sequeces (taskSeq), simply add |> Async.StartAsTask between wc.AsyncDownloadString (Uri url) and |> SeqT.lift then run eveything but the printPages |> Async.Start.

// A simple webcrawler

#r "nuget: FSharpPlus,1.3.0-CI02744"
#r "nuget: HtmlAgilityPack"

open System
open System.Net
open System.Text.RegularExpressions
open HtmlAgilityPack
open FSharp.Control

open FSharpPlus
open FSharpPlus.Data

// ----------------------------------------------------------------------------
// Helper functions for downloading documents, extracting links etc.

/// Asynchronously download the document and parse the HTML
let downloadDocument url = async {
  try let wc = new WebClient ()
      let! html = wc.AsyncDownloadString (Uri url)
      let doc = new HtmlDocument ()
      doc.LoadHtml html
      return Some doc 
  with _ -> return None }

/// Extract all links from the document that start with "http://"
let extractLinks (doc:HtmlDocument) = 
  try
    [ for a in doc.DocumentNode.SelectNodes ("//a") do
        if a.Attributes.Contains "href" then
          let href = a.Attributes.["href"].Value
          if href.StartsWith "https://" then
            let endl = href.IndexOf '?'
            yield if endl > 0 then href.Substring(0, endl) else href ]
  with _ -> []

/// Extract the <title> of the web page
let getTitle (doc: HtmlDocument) =
  let title = doc.DocumentNode.SelectSingleNode "//title"
  if title <> null then title.InnerText.Trim () else "Untitled"

// ----------------------------------------------------------------------------
// Basic crawling - crawl web pages and follow just one link from every page

/// Crawl the internet starting from the specified page
/// From each page follow the first not-yet-visited page
let rec randomCrawl url = 
  let visited = new System.Collections.Generic.HashSet<_> ()

  // Visits page and then recursively visits all referenced pages
  let rec loop url = monad.plus {
    if visited.Add(url) then
      let! doc = downloadDocument url |> SeqT.lift
      match doc with 
      | Some doc ->
          // Yield url and title as the next element
          yield url, getTitle doc
          // For every link, yield all referenced pages too
          for link in extractLinks doc do
            yield! loop link 
      | _ -> () }
  loop url

// Use SeqT combinators to print the titles of the first 10
// web sites that are from other domains than en.wikipedia.org
randomCrawl "https://en.wikipedia.org/wiki/Main_Page"
|> SeqT.filter (fun (url, title) -> url.Contains "en.wikipedia.org" |> not)
|> SeqT.map snd
|> SeqT.take 10
|> SeqT.iter (printfn "%s")
|> Async.Start
namespace System
namespace System.Net
type WebClient = inherit Component new: unit -> unit member CancelAsync: unit -> unit member DownloadData: address: string -> byte array + 1 overload member DownloadDataAsync: address: Uri -> unit + 1 overload member DownloadDataTaskAsync: address: string -> Task<byte array> + 1 overload member DownloadFile: address: string * fileName: string -> unit + 1 overload member DownloadFileAsync: address: Uri * fileName: string -> unit + 1 overload member DownloadFileTaskAsync: address: string * fileName: string -> Task + 1 overload member DownloadString: address: string -> string + 1 overload ...
<summary>Provides common methods for sending data to and receiving data from a resource identified by a URI.</summary>
val wc: System.Net.WebClient
val uri: System.Uri
Multiple items
type Uri = interface ISpanFormattable interface IFormattable interface ISerializable new: uriString: string -> unit + 6 overloads member Equals: comparand: obj -> bool member GetComponents: components: UriComponents * format: UriFormat -> string member GetHashCode: unit -> int member GetLeftPart: part: UriPartial -> string member IsBaseOf: uri: Uri -> bool member IsWellFormedOriginalString: unit -> bool ...
<summary>Provides an object representation of a uniform resource identifier (URI) and easy access to the parts of the URI.</summary>

--------------------
System.Uri(uriString: string) : System.Uri
System.Uri(uriString: string, creationOptions: inref<System.UriCreationOptions>) : System.Uri
System.Uri(uriString: string, uriKind: System.UriKind) : System.Uri
System.Uri(baseUri: System.Uri, relativeUri: string) : System.Uri
System.Uri(baseUri: System.Uri, relativeUri: System.Uri) : System.Uri
val async: AsyncBuilder
System.Net.WebClient.DownloadString(address: System.Uri) : string
System.Net.WebClient.DownloadString(address: string) : string
namespace FSharpPlus
namespace FSharpPlus.Data
val urls: string list
val pages: SeqT<Async<bool>,(string * int)>
Multiple items
static member SeqTOperations.SeqT: source: 'Monad<seq<'T>> -> SeqT<'Monad<bool>,'T> (requires member (>>=) and member Return)

--------------------
active recognizer SeqT: SeqT<'Monad<bool>,'T> -> 'Monad<'T seq>

--------------------
module SeqT from FSharpPlus.Data.SeqT_V2

--------------------
[<Struct>] type SeqT<'monad<bool>,'t> = | SeqT of IEnumerableM<'monad<bool>,'t> interface IEnumerableM<'monad<bool>,'t> static member ( *> ) : x: SeqT<'Monad<bool>,'T> * y: SeqT<'Monad<bool>,'U> -> SeqT<'Monad<bool>,'U> (requires member (>>=) and member Return and member Delay) static member (<!>) : x: SeqT<'Monad<bool>,'T> * f: ('T -> 'U) -> SeqT<'Monad<bool>,'U> (requires member Delay and member (>>=) and member Return) static member ( <* ) : x: SeqT<'Monad<bool>,'U> * y: SeqT<'Monad<bool>,'T> -> SeqT<'Monad<bool>,'U> (requires member (>>=) and member Return and member Delay) static member (<*>) : f: SeqT<'Monad<bool>,('T -> 'U)> * x: SeqT<'Monad<bool>,'T> -> SeqT<'Monad<bool>,'U> (requires member (>>=) and member Return) static member (<|>) : x: SeqT<'Monad<bool>,'T> * y: SeqT<'Monad<bool>,'T> -> SeqT<'Monad<bool>,'T> (requires member Delay and member Return and member (>>=)) static member (>>=) : x: SeqT<'Monad<bool>,'T> * f: ('T -> SeqT<'Monad<bool>,'U>) -> SeqT<'Monad<bool>,'U> (requires member (>>=) and member Return)
val monad<'monad<'t>> : MonadFxBuilder<'monad<'t>>
<summary> Creates a (lazy) monadic computation expression with side-effects (see http://fsprojects.github.io/FSharpPlus/computation-expressions.html for more information) </summary>
val wc: WebClient
val url: string
val html: string
member WebClient.AsyncDownloadString: address: Uri -> Async<string>
member WebClient.AsyncDownloadString: uri: Uri -> Async<string>
Multiple items
type Uri = interface ISpanFormattable interface IFormattable interface ISerializable new: uriString: string -> unit + 6 overloads member Equals: comparand: obj -> bool member GetComponents: components: UriComponents * format: UriFormat -> string member GetHashCode: unit -> int member GetLeftPart: part: UriPartial -> string member IsBaseOf: uri: Uri -> bool member IsWellFormedOriginalString: unit -> bool ...
<summary>Provides an object representation of a uniform resource identifier (URI) and easy access to the parts of the URI.</summary>

--------------------
Uri(uriString: string) : Uri
Uri(uriString: string, creationOptions: inref<UriCreationOptions>) : Uri
Uri(uriString: string, uriKind: UriKind) : Uri
Uri(baseUri: Uri, relativeUri: string) : Uri
Uri(baseUri: Uri, relativeUri: Uri) : Uri
Multiple items
val lift: source: 'Monad<'T> -> SeqT<'Monad<bool>,'T> (requires member (>>=) and member Map and member Return)
<summary> Lifts the source into the SeqT. </summary>

--------------------
val lift: x: 'Monad<'T> -> SeqT<'Monad<seq<'T>>> (requires member (>>=) and member Map and member Return)
<summary> Embed a Monad&lt;'T&gt; into a SeqT&lt;'Monad&lt;seq&lt;'T&gt;&gt;&gt; </summary>
property String.Length: int with get
val printPages: Async<unit>
val filter: f: ('T -> bool) -> source: SeqT<'Monad<bool>,'T> -> SeqT<'Monad<bool>,'T> (requires member Delay and member (>>=) and member Return)
val len: int
Multiple items
val map: f: ('T -> 'U) -> source: SeqT<'Monad<bool>,'T> -> SeqT<'Monad<bool>,'U> (requires member Delay and member (>>=) and member Return)

--------------------
val map: f: ('T -> 'U) -> SeqT<'Monad<seq<'T>> -> SeqT<'Monad<seq<'U>> (requires member Map)
val fst: tuple: ('T1 * 'T2) -> 'T1
val iter: f: ('T -> unit) -> source: SeqT<'Monad<bool>,'T> -> 'Monad<unit> (requires member (>>=) and member Delay and member Using and member Return and member (>>=))
val printfn: format: Printf.TextWriterFormat<'T> -> 'T
Multiple items
module Async from FSharpPlus
<summary> Additional operations on Async </summary>

--------------------
type Async = static member AsBeginEnd: computation: ('Arg -> Async<'T>) -> ('Arg * AsyncCallback * obj -> IAsyncResult) * (IAsyncResult -> 'T) * (IAsyncResult -> unit) static member AwaitEvent: event: IEvent<'Del,'T> * ?cancelAction: (unit -> unit) -> Async<'T> (requires delegate and 'Del :> Delegate) static member AwaitIAsyncResult: iar: IAsyncResult * ?millisecondsTimeout: int -> Async<bool> static member AwaitTask: task: Task<'T> -> Async<'T> + 1 overload static member AwaitWaitHandle: waitHandle: WaitHandle * ?millisecondsTimeout: int -> Async<bool> static member CancelDefaultToken: unit -> unit static member Catch: computation: Async<'T> -> Async<Choice<'T,exn>> static member Choice: computations: Async<'T option> seq -> Async<'T option> static member FromBeginEnd: beginAction: (AsyncCallback * obj -> IAsyncResult) * endAction: (IAsyncResult -> 'T) * ?cancelAction: (unit -> unit) -> Async<'T> + 3 overloads static member FromContinuations: callback: (('T -> unit) * (exn -> unit) * (OperationCanceledException -> unit) -> unit) -> Async<'T> ...

--------------------
type Async<'T>
static member Async.Start: computation: Async<unit> * ?cancellationToken: Threading.CancellationToken -> unit
namespace System.Text
namespace System.Text.RegularExpressions
namespace HtmlAgilityPack
namespace Microsoft.FSharp
namespace Microsoft.FSharp.Control
val downloadDocument: url: string -> Async<HtmlDocument option>
 Asynchronously download the document and parse the HTML
member WebClient.AsyncDownloadString: uri: Uri -> Async<string>
member WebClient.AsyncDownloadString: address: Uri -> Async<string>
val doc: HtmlDocument
Multiple items
type HtmlDocument = interface IXPathNavigable new: unit -> unit member CreateAttribute: name: string -> HtmlAttribute + 1 overload member CreateComment: unit -> HtmlCommentNode + 1 overload member CreateElement: name: string -> HtmlNode member CreateNavigator: unit -> XPathNavigator member CreateTextNode: unit -> HtmlTextNode + 1 overload member DetectEncoding: stream: Stream -> Encoding + 3 overloads member DetectEncodingAndLoad: path: string -> unit + 1 overload member DetectEncodingHtml: html: string -> Encoding ...
<summary> Represents a complete HTML document. </summary>

--------------------
HtmlDocument() : HtmlDocument
HtmlDocument.LoadHtml(html: string) : unit
union case Option.Some: Value: 'T -> Option<'T>
union case Option.None: Option<'T>
val extractLinks: doc: HtmlDocument -> string list
 Extract all links from the document that start with "http://"
val a: HtmlNode
property HtmlDocument.DocumentNode: HtmlNode with get
<summary> Gets the root node of the document. </summary>
HtmlNode.SelectNodes(xpath: Xml.XPath.XPathExpression) : HtmlNodeCollection
HtmlNode.SelectNodes(xpath: string) : HtmlNodeCollection
property HtmlNode.Attributes: HtmlAttributeCollection with get, set
<summary> Gets the collection of HTML attributes for this node. May not be null. </summary>
HtmlAttributeCollection.Contains(name: string) : bool
HtmlAttributeCollection.Contains(item: HtmlAttribute) : bool
val href: string
String.StartsWith(value: string) : bool
String.StartsWith(value: char) : bool
String.StartsWith(value: string, comparisonType: StringComparison) : bool
String.StartsWith(value: string, ignoreCase: bool, culture: Globalization.CultureInfo) : bool
val endl: int
String.IndexOf(value: string) : int
String.IndexOf(value: char) : int
String.IndexOf(value: string, comparisonType: StringComparison) : int
String.IndexOf(value: string, startIndex: int) : int
String.IndexOf(value: char, comparisonType: StringComparison) : int
String.IndexOf(value: char, startIndex: int) : int
String.IndexOf(value: string, startIndex: int, comparisonType: StringComparison) : int
String.IndexOf(value: string, startIndex: int, count: int) : int
String.IndexOf(value: char, startIndex: int, count: int) : int
String.IndexOf(value: string, startIndex: int, count: int, comparisonType: StringComparison) : int
String.Substring(startIndex: int) : string
String.Substring(startIndex: int, length: int) : string
val getTitle: doc: HtmlDocument -> string
 Extract the <title> of the web page
val title: HtmlNode
HtmlNode.SelectSingleNode(xpath: Xml.XPath.XPathExpression) : HtmlNode
HtmlNode.SelectSingleNode(xpath: string) : HtmlNode
property HtmlNode.InnerText: string with get
<summary> Gets the text between the start and end tags of the object. </summary>
String.Trim() : string
String.Trim([<ParamArray>] trimChars: char array) : string
String.Trim(trimChar: char) : string
val randomCrawl: url: string -> SeqT<Async<bool>,(string * string)>
 Crawl the internet starting from the specified page
 From each page follow the first not-yet-visited page
val visited: Collections.Generic.HashSet<string>
namespace System.Collections
namespace System.Collections.Generic
Multiple items
type HashSet<'T> = interface ICollection<'T> interface IEnumerable<'T> interface IEnumerable interface IReadOnlyCollection<'T> interface ISet<'T> interface IReadOnlySet<'T> interface IDeserializationCallback interface ISerializable new: unit -> unit + 5 overloads member Add: item: 'T -> bool ...
<summary>Represents a set of values.</summary>
<typeparam name="T">The type of elements in the hash set.</typeparam>


--------------------
Collections.Generic.HashSet() : Collections.Generic.HashSet<'T>
Collections.Generic.HashSet(collection: Collections.Generic.IEnumerable<'T>) : Collections.Generic.HashSet<'T>
Collections.Generic.HashSet(comparer: Collections.Generic.IEqualityComparer<'T>) : Collections.Generic.HashSet<'T>
Collections.Generic.HashSet(capacity: int) : Collections.Generic.HashSet<'T>
Collections.Generic.HashSet(collection: Collections.Generic.IEnumerable<'T>, comparer: Collections.Generic.IEqualityComparer<'T>) : Collections.Generic.HashSet<'T>
Collections.Generic.HashSet(capacity: int, comparer: Collections.Generic.IEqualityComparer<'T>) : Collections.Generic.HashSet<'T>
val loop: url: string -> SeqT<Async<bool>,(string * string)>
Collections.Generic.HashSet.Add(item: string) : bool
val doc: HtmlDocument option
val link: string
val title: string
String.Contains(value: string) : bool
String.Contains(value: char) : bool
String.Contains(value: string, comparisonType: StringComparison) : bool
String.Contains(value: char, comparisonType: StringComparison) : bool
val snd: tuple: ('T1 * 'T2) -> 'T2
val take: count: int -> source: SeqT<'Monad<bool>,'T> -> SeqT<'Monad<bool>,'T> (requires member (>>=) and member Return)
<summary>Returns the first N elements of the sequence.</summary>
<remarks>Throws <c>InvalidOperationException</c> if the count exceeds the number of elements in the sequence. <c>SeqT.truncate</c> returns as many items as the sequence contains instead of throwing an exception.</remarks>
<param name="count">The number of items to take.</param>
<param name="source">The input sequence.</param>
<returns>The result sequence.</returns>
<exception cref="T:System.ArgumentNullException">Thrown when count is negative.</exception>
<exception cref="T:System.InvalidOperationException">Thrown when count exceeds the number of elements. in the sequence.</exception>