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<'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.
|
#r "nuget: FSharpPlus,1.3.0-CI02744" // still as pre-release
#r @"../../src/FSharpPlus/bin/Release/netstandard2.0/FSharpPlus.dll"
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 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
member MakeRelative: toUri: Uri -> string
member MakeRelativeUri: uri: Uri -> Uri
...
<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,'t> =
| SeqT of IEnumerableM<'monad,'t>
interface IEnumerableM<'monad,'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 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
member MakeRelative: toUri: Uri -> string
member MakeRelativeUri: uri: Uri -> Uri
...
<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<'T> into a SeqT<'Monad<seq<'T>>>
</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>