Pfarah


Introducing Pfarah

This document is a quick overview of the most important features of Pfarah. You can also get this page as an F# script file from GitHub and run the samples interactively. Type annotations are used in examples to ease understanding

There are two ways to parse data. The first way is to parse a specific file, and the second is to parse a given string. The norm should be parsing a file, but for the sake of the tutorial it is shown the second way.

This library takes inspiration from FSharp.Data JSON, Chessie, Chiron, Fleece, Haskell, and json4s.

Quickstart

We're going to start with querying parsed data and as an example, the data will represent a ship named bessie with 22 men onboard.

1: 
2: 
3: 
open Pfarah

let (obj : ParaValue) = ParaValue.Parse "name=bessie strength=22"

The next step is extracting the information. There are many ways to accomplish this and each one will be demonstrated. Which one you choose will be personal preference. As the demonstrations become concise they introduce more advanced concepts, so initial examples will use simple constructs.

ParaValue is a discriminated union and the first example queries it directly for more information.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
// Print the property's keys -- will print "seq ["name"; "strength"]"
// Also demonstrates all the current cases for `ParaValue`
match obj with
| ParaValue.Record (properties : (string * ParaValue)[]) ->
  printfn "%A" (properties |> Seq.map fst)
| ParaValue.Array (arr : ParaValue[]) ->
  failwith "Did not expect `obj` to be an array"
| ParaValue.Bool (x : bool) ->
  failwith "Did not expect `obj` to be a bool"
| ParaValue.Date (x : DateTime) ->
  failwith "Did not expect `obj` to be a DateTime"
| ParaValue.Hsv ((h : float), (s : float), (v : float)) ->
  failwith "Did not expect `obj` to be hsv"
| ParaValue.Number (n : float) ->
  failwith "Did not expect `obj` to be a number"
| ParaValue.Rgb ((r : byte), (g : byte), (b : byte)) ->
  failwith "Did not expect `obj` to be rgb"
| ParaValue.String (s : string) ->
  failwith "Did not expect `obj` to be a string"

Whoa! Those are a lot of data types. To see each of the data types in use, see the Data Format page.

Instead of exhaustively enumerating all the cases every time we query the data, the next example will use F# default case and print the name of the ship

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
// Try to find the first key with "name". If name is found then see if the
// value is a string. If so, print the ship's name. If none of the happy path
// is followed print errors
match obj with
| ParaValue.Record properties ->
  match (Array.tryFind (fst >> (=) "name") properties) with
  | Some(key, value) ->
    match value with
    | ParaValue.String name -> printfn "Ship's name: %s" name
    | _ -> failwith "Expected name to be a string"
  | None -> failwith "Expected name to exist"
| _ -> failwith "Expected `obj` to be a `Record` type"

By now, being flustered with how cumbersome and verbose the examples is ok. This will be fixed by introducing some additional concepts and types. First, the previous example rewritten and an explanation after.

1: 
2: 
3: 
4: 
5: 
let (nameVal : ParaResult<ParaValue>) = ParaValue.get "name" obj
let (name : ParaResult<string>) = ParaResult.bind ParaValue.asString nameVal
match name with
| Ok(nm : string) -> printfn "Ship's name: %s" nm
| Error(err : string) -> printfn "%s" err

Already we've gone from nine lines down to five, and it's all possible because the values returned by the functions are now wrapped in ParaResult<T>, which is a very simple type encapsulating either a value or an error. The ParaValue.get either returns the value of the field with a key of "name" or an error. This error, which is not an exception, could be anything from obj not being a record to there not being a single field with a key of "name"

For similar data types (to name a few) see:

In fact, ParaResult is defined as an alias for Choice<'a,string>, so any libraries or utilities that work with choices like ExtCore can interface seamlessly.

There is one possible question remaining for the unaccustomed and that is ParaResult.bind:

1: 
let (name : ParaResult<string>) = ParaResult.bind ParaValue.asString nameVal

Bind checks to see if the result passed in (nameVal) is an error or a result. If nameVal is an error (get failed earlier) then the error is propogated. Else if there is a value contained, a function is applied to the value (in this case ParaValue.String "bessie"). The function being applied is ParaValue.asString, which unwraps the value so that just "bessie" is exposed.

The implementation of bind is quite concise and may prove illustrative

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
// Can be read as: a function named bind that takes two parameters:
// - fn: a function that when given a value returns a ParaResult
// - m: the value to check if is currently an error
// - returns a new ParaResult
let bind (fn: 'a -> ParaResult<'b>) (m: ParaResult<'a>) : ParaResult<'b> =
  match m with
  | Ok(x) -> fn x
  | Error(x) -> Error(x)

Bind allows Pfarah to define a custom computation expression, which induces syntax sugar using let! and return! so that one doesn't have to deal with ParaResult explicitly

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
let name2 : ParaResult<string> = para {
  let! nameVal = ParaValue.get "name" obj
  return! ParaValue.asString nameVal
}

match name2 with
| Ok(nm) -> printfn "Ship's name: %s" nm
| Error(err) -> printfn "%s" err

While the number of lines of code in the example grew, computation expressions start to shine when the queries become complex. Instead of extracting just the name, extract the number of men on the ship (strength)

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
let data : ParaResult<string * int> = para {
  let! nameVal = ParaValue.get "name" obj
  let! strengthVal = ParaValue.get "strength" obj
  let! name = ParaValue.asString nameVal
  let! strength = ParaValue.asInteger strengthVal
  return name, strength
}

match data with
| Ok(name, strength) -> printf "Ship: %s. Men: %d" name strength
| Error(err) -> printfn "%s" err

The potential is starting to show. Still some cruft is getting in the way, which can be solved by defining custom operators:

  • ? is aliased to ParaValue.get
  • >>= is aliased to ParaResult.bind
 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
open Pfarah.Operators

para {
  let! name = obj?name >>= ParaValue.asString
  let! strength = obj?strength >>= ParaValue.asInteger
  return name, strength
}

// Or even terser
open Pfarah.ParaValue
para {
  let! name = obj?name >>= asString
  let! strength = obj?strength >>= asInteger
  return name, strength
}

Dealing with Multiple Ships

Bessie isn't the only ship in the world. Our data is about to get more complex, but don't worry, Pfarah will be there every step of the way.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
32: 
33: 
34: 
35: 
36: 
37: 
38: 
39: 
40: 
41: 
42: 
43: 
44: 
45: 
46: 
47: 
48: 
let shipsObj = ParaValue.Parse """
  ship={
    name=bessie
    strength=22
  }
  ship={
    name=doris
    strength=40
  }
  ship={
    name=betsy
    strength=10
  }
"""

// Let's define a common function to parse each ship
let parseShip (ship : ParaValue) = para {
  let! name = ship?name >>= asString
  let! strength = ship?strength >>= asInteger
  return name, strength
}

// Collect all the values with a key "ship" into a ParaValue.Array
let (pips : ParaValue) = ParaValue.collect "ship" shipsObj

// Execute `parseShip` on each ship and aggregate the result
// into an array
let (extract : ParaResult<(string * int)[]>) =
  ParaValue.flatMap parseShip pips

// Sort the ships by the most men first
let (sorted : ParaResult<(string * int)[]>) =
  ParaResult.map (Array.sortByDescending snd) extract

match sorted with
| Ok(ships) -> Array.iter (fun (name, strength) -> 
    printfn "Ship: %s. Strength: %d" name strength) ships
| Error(error) -> printfn "%s" error

// In reality the code may be written like:
shipsObj
|> ParaValue.collect "ship"
|> ParaValue.flatMap parseShip
|> ParaResult.map (Array.sortByDescending snd)
|> function
| Ok(ships) -> Array.iter (fun (name, strength) -> 
    printfn "Ship: %s. Strength: %d" name strength) ships
| Error(error) -> printfn "%s" error

Those who like the computation builder don't have to miss out!

1: 
2: 
3: 
4: 
5: 
6: 
para {
  let! (pips : ParaValue[]) = ParaValue.getAll "ship" shipsObj
  let! (ships : (string * int)[]) = ParaValue.reduce parseShip pips
  for (name, strength) in (Array.sortByDescending snd ships) do
    printfn "Ship: %s. Strength: %d" name strength
} |> function Error(err) -> printfn "%s" err | _ -> ()

But why the different functions?

collect vs getAll: Both accept a ParaValue and look for a properties of a certain key, but collect will also work on ParaValue.Array by iterating over each element looking for the key. collect returns a ParaValue.Array, which allows subsequent calls to be chained together:

1: 
2: 
3: 
4: 
// Will return Ok [| "bessie"; "doris" "betsy" |]
ParaValue.collect "ship" obj
|> ParaValue.collect "name"
|> ParaValue.flatMap ParaValue.asString

Defined in the operator module there is the / operator that will delegates to ParaValue.collect. For those that are familiar with xpath, this should appear similar

1: 
2: 
obj / "ship" / "name" |> ParaValue.flatMap ParaValue.asString
obj / "ship" / "name" |> flatMap asString

Going back to the commputation builder vs the pipeline method, another difference is the function that parseShip is passed to. The computation expression only works with arrays of ParaValue whereas like collect, the pipeline method operates on the values of Records, and can map singular values like ParaValue.String, etc.

Knowing which one to use is sometimes only a matter of taste.

Optional Data

Not all objects of a given instance will have the exact same property keys. Some may only have a limited subset of the properties wanted.

In our ship example, we'll have an optional property, patrol, that denotes if a ship is on patrol. If absent, the ship is assumed to not be on patrol.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
32: 
33: 
34: 
35: 
36: 
37: 
38: 
39: 
40: 
41: 
42: 
43: 
let patrolData = """
  ship={
    name=bessie
    strength=22
    patrol=yes
  }
  ship={
    name=doris
    strength=40
  }
  ship={
    name=betsy
    strength=10
    patrol=yes
  }
"""

// Let's define a common function to parse each ship with patrol
let parseShip2 (ship : ParaValue) = para {
  let! name = ship?name >>= asString
  let! strength = ship?strength >>= asInteger
  let! (patrolVal : ParaValue option) = ParaValue.tryGet "patrol" ship

  // Try converting the potential value to a boolean, if not there
  // then assume the ship is not on patrol.
  let! patrol = patrolVal |> ParaResult.defaultOpt asBool false
  return name, strength, patrol
}

para {
  let obj = ParaValue.Parse patrolData
  let! (pips : ParaValue[]) = ParaValue.getAll "ship" obj
  let! (ships : (string * int * bool)[]) = ParaValue.reduce parseShip2 pips

  // Take the ship name and strength of the ships that are on patrol
  let shipsOnPatrol =
    ships
    |> Array.filter (fun (_, _, patrol) -> patrol)
    |> Array.map (fun (key, strength, patrol) -> (key, strength))

  for (name, strength) in (Array.sortByDescending snd shipsOnPatrol) do
    printfn "Ship: %s. Strength: %d" name strength
} |> function Error(err) -> printfn "%s" err | _ -> ()

Finding Optional Data

Knowing the data is the first step to any type of analysis. This is made difficult when there can be thousands of objects, each one having a subset of the properties available. findOptional fixes this problem by dissecting a list of supposedly similar objects and returning the properties that it knows are always present and the ones that are optional.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
// Find all the optional properties on ships. Append a question mark after the
// property name to signify that the property is optional.
para {
  let obj = ParaValue.Parse patrolData
  let! ships = obj / "ship" |> ParaValue.flatMap asRecord
  let required, optional = findOptional ships
  required |> Seq.iter (printfn "%s")
  optional |> Seq.iter (printfn "%s?")
} |> function Error(err) -> printfn "%s" err | _ -> ()

// Will print:
// name
// strength
// patrol?

Deserialization

Working with primitives like string and ints are fine, but programs become much more powerful when compositive data types come into play. While the previous methods allow for manual deserialization Pfarah offers another step of convenience.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
// Let's simplify the data by removing the optional patrol field.
// We'll add it back in later
let multipleShips = """
  ship={
    name=bessie
    strength=22
  }
  ship={
    name=doris
    strength=40
  }
  ship={
    name=betsy
    strength=10
  }
"""

// Define a simple record to store the name and strength of a ship
// with a convenience `Create` constructor along with a specially
// named `FromPara` function
type Ship = { Name: string; Strength: int }
with
  static member inline Create name strength =
    { Ship.Name = name; Strength = strength }
  static member inline FromPara (_:Ship) =
    Ship.Create <!> (!. "name") <*> (!. "ship")

let (ships : Ship[]) = deserialize (ParaValue.Parse multipleShips)

Full stop. There's a lot of magic in the previous example including a couple unseen operators.

First, the type annotation on ships is critical, without it the compiler won't know what to deserialize the type to and raise a compiler error. Pfarah know how to deserialize an array, so it then proceeds to look at the element type. Primitives like string and ints are no problem, but Ship is new. As long as Ship implements a function FromPara, Pfarah can deserialize it. This is known as statically resolved type parameters, and it is a very dark corner of F#.

But let's take a step back because we can use this magic in baby steps

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
// Remember this function? This time we're using `fromPara` which will infer
// what `as*` function to execute based on the type of the variable.
let parseShip3 (ship : ParaValue) : ParaResult<string * int> = para {
  let! name = ship?name >>= fromPara
  let! strength = ship?strength >>= fromPara
  return name, strength
}

// The function pget simplifies things a bit
let parseShip4 (ship : ParaValue) : ParaResult<string * int> = para {
  let! name = pget "name" ship
  let! strength = pget "strength" ship
  return name, strength
}

// There is also an `.@` that is aliased to pget
let parseShip5 (ship : ParaValue) : ParaResult<string * int> = para {
  let! name = ship .@ "name"
  let! strength = ship .@ "strength"
  return name, strength
}

That probably looks and feels a lot better for the uninitiated. We can rewrite the magic parts with our new function.

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
type Ship2 = { Name: string; Strength: int }
with
  static member inline FromPara (_:Ship2) =
    fun ship -> para {
      let! name = ship .@ "name"
      let! strength = ship .@ "strength"
      return { Ship2.Name = name; Strength = strength }
    } |> ApplicativeParaValue.wrap

Pretty neat right? We can throw in our optional patrol pretty easily if you know the right function!

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
type Ship3 = { Name: string; Strength: int; Patrol: bool option }
with
  static member inline FromPara (_:Ship3) =
    fun ship -> para {
      let! name = ship .@ "name"
      let! strength = ship .@ "strength"
      let! patrol = tryPget "patrol" ship
      return { Ship3.Name = name; Strength = strength; Patrol = patrol }
    } |> ApplicativeParaValue.wrap

Binary Data

The examples that we have been working with have been plain text, but Clausewitz files can come compressed and in binary form. To parse these files, we'll need a few things:

  • The file path.
  • The header if it is binary file. For instance, for EU4, the header is "EU4bin".
  • The header if it is plain text file. For EU4, the header is "EU4txt".
  • Since binary files use two byte tokens instead of strings for identifiers, we'll need a dictionary of two byte tokens to strings so that the binary file can be queried exactly like a plain text file. There are many types of tokens that can be encountered, so as not to impose a memory tax unnecessarily if it is a plain text file, the dictionary is lazy

The following code sample will work for a file that is in any format (plain text/binary and compressed/uncompressed)

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
let path = "game.eu4"
let ``binary header`` = "EU4bin"
let ``text header`` = "EU4txt"

// Only if the file is detected to be binary will
// this dictionary be created
let tokens = lazy dict([(0x284ds, "date")])
let game = ParaValue.Load(path, ``binary header``, ``text header``, tokens)
game?date >>= ParaValue.asDate
namespace System
namespace Pfarah
Multiple items
val obj : ParaValue

Full name: Tutorial.obj

--------------------
type obj = Object

Full name: Microsoft.FSharp.Core.obj
Multiple items
module ParaValue

from Pfarah

--------------------
type ParaValue =
  | Bool of bool
  | Number of float
  | Hsv of float * float * float
  | Rgb of byte * byte * byte
  | Date of DateTime
  | String of string
  | Array of elements: ParaValue []
  | Record of properties: (string * ParaValue) []
  override ToString : unit -> string
  static member Load : stream:Stream * binHeader:string * txtHeader:string * lookup:Lazy<IDictionary<int16,string>> -> ParaValue
  static member Load : file:string * binHeader:string * txtHeader:string * lookup:Lazy<IDictionary<int16,string>> -> ParaValue
  static member LoadBinary : stream:Stream * lookup:IDictionary<int16,string> * header:string option -> ParaValue
  static member LoadText : file:string -> ParaValue
  static member LoadText : stream:Stream -> ParaValue
  static member Parse : text:string -> ParaValue
  static member private Prettify : value:ParaValue -> indent:int -> string
  static member Save : stream:Stream * data:ParaValue -> unit
  static member private collect : prop:string -> obj:ParaValue -> ParaValue
  static member ( / ) : obj:ParaValue * propertyName:string -> ParaValue

Full name: Pfarah.ParaValue

--------------------
type ParaValue<'a> = ParaValue -> ParaResult<'a> * ParaValue

Full name: Pfarah.ParaValue<_>
static member ParaValue.Parse : text:string -> ParaValue
union case ParaValue.Record: properties: (string * ParaValue) [] -> ParaValue
val properties : (string * ParaValue) []
Multiple items
val string : value:'T -> string

Full name: Microsoft.FSharp.Core.Operators.string

--------------------
type string = String

Full name: Microsoft.FSharp.Core.string
val printfn : format:Printf.TextWriterFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printfn
module Seq

from Microsoft.FSharp.Collections
val map : mapping:('T -> 'U) -> source:seq<'T> -> seq<'U>

Full name: Microsoft.FSharp.Collections.Seq.map
val fst : tuple:('T1 * 'T2) -> 'T1

Full name: Microsoft.FSharp.Core.Operators.fst
union case ParaValue.Array: elements: ParaValue [] -> ParaValue
val arr : ParaValue []
val failwith : message:string -> 'T

Full name: Microsoft.FSharp.Core.Operators.failwith
union case ParaValue.Bool: bool -> ParaValue
val x : bool
type bool = Boolean

Full name: Microsoft.FSharp.Core.bool
union case ParaValue.Date: DateTime -> ParaValue
val x : DateTime
Multiple items
type DateTime =
  struct
    new : ticks:int64 -> DateTime + 10 overloads
    member Add : value:TimeSpan -> DateTime
    member AddDays : value:float -> DateTime
    member AddHours : value:float -> DateTime
    member AddMilliseconds : value:float -> DateTime
    member AddMinutes : value:float -> DateTime
    member AddMonths : months:int -> DateTime
    member AddSeconds : value:float -> DateTime
    member AddTicks : value:int64 -> DateTime
    member AddYears : value:int -> DateTime
    ...
  end

Full name: System.DateTime

--------------------
DateTime()
   (+0 other overloads)
DateTime(ticks: int64) : unit
   (+0 other overloads)
DateTime(ticks: int64, kind: DateTimeKind) : unit
   (+0 other overloads)
DateTime(year: int, month: int, day: int) : unit
   (+0 other overloads)
DateTime(year: int, month: int, day: int, calendar: Globalization.Calendar) : unit
   (+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int) : unit
   (+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, kind: DateTimeKind) : unit
   (+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, calendar: Globalization.Calendar) : unit
   (+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, millisecond: int) : unit
   (+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, millisecond: int, kind: DateTimeKind) : unit
   (+0 other overloads)
union case ParaValue.Hsv: float * float * float -> ParaValue
val h : float
Multiple items
val float : value:'T -> float (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.float

--------------------
type float = Double

Full name: Microsoft.FSharp.Core.float

--------------------
type float<'Measure> = float

Full name: Microsoft.FSharp.Core.float<_>
val s : float
val v : float
union case ParaValue.Number: float -> ParaValue
val n : float
union case ParaValue.Rgb: byte * byte * byte -> ParaValue
val r : byte
Multiple items
val byte : value:'T -> byte (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.byte

--------------------
type byte = Byte

Full name: Microsoft.FSharp.Core.byte
val g : byte
val b : byte
union case ParaValue.String: string -> ParaValue
val s : string
type Array =
  member Clone : unit -> obj
  member CopyTo : array:Array * index:int -> unit + 1 overload
  member GetEnumerator : unit -> IEnumerator
  member GetLength : dimension:int -> int
  member GetLongLength : dimension:int -> int64
  member GetLowerBound : dimension:int -> int
  member GetUpperBound : dimension:int -> int
  member GetValue : [<ParamArray>] indices:int[] -> obj + 7 overloads
  member Initialize : unit -> unit
  member IsFixedSize : bool
  ...

Full name: System.Array
val tryFind : predicate:('T -> bool) -> array:'T [] -> 'T option

Full name: Microsoft.FSharp.Collections.Array.tryFind
union case Option.Some: Value: 'T -> Option<'T>
val key : string
val value : ParaValue
val name : string
union case Option.None: Option<'T>
val nameVal : ParaResult<ParaValue>

Full name: Tutorial.nameVal
Multiple items
module ParaResult

from Pfarah

--------------------
type ParaResult<'a> = Choice<'a,string>

Full name: Pfarah.ParaResult<_>
val get : key:string -> o:ParaValue -> ParaResult<ParaValue>

Full name: Pfarah.ParaValue.get
val name : ParaResult<string>

Full name: Tutorial.name
val bind : fn:('a -> ParaResult<'b>) -> m:ParaResult<'a> -> ParaResult<'b>

Full name: Pfarah.ParaResult.bind
val asString : _arg1:ParaValue -> ParaResult<string>

Full name: Pfarah.ParaValue.asString
Multiple items
val Ok : x:'a -> ParaResult<'a>

Full name: Pfarah.ParaResultImpl.Ok

--------------------
active recognizer Ok: ParaResult<'Ok> -> ParaResult<'Ok>

Full name: Pfarah.ParaResultImpl.( |Ok|Error| )
val nm : string
Multiple items
val Error : x:string -> ParaResult<'a>

Full name: Pfarah.ParaResultImpl.Error

--------------------
active recognizer Error: ParaResult<'Ok> -> ParaResult<'Ok>

Full name: Pfarah.ParaResultImpl.( |Ok|Error| )
val err : string
val name : obj

Full name: tutorial.name
Multiple items
val string : value:'T -> string

Full name: Microsoft.FSharp.Core.Operators.string

--------------------
type string = System.String

Full name: Microsoft.FSharp.Core.string
val bind : fn:('a -> ParaResult<'b>) -> m:ParaResult<'a> -> ParaResult<'b>

Full name: Tutorial.bind
val fn : ('a -> ParaResult<'b>)
val m : ParaResult<'a>
val x : 'a
val x : string
val name2 : ParaResult<string>

Full name: Tutorial.name2
val para : ParaBuilder

Full name: Pfarah.ParaBuilder.para
val nameVal : ParaValue
val data : ParaResult<string * int>

Full name: Tutorial.data
Multiple items
val int : value:'T -> int (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.int

--------------------
type int = int32

Full name: Microsoft.FSharp.Core.int

--------------------
type int<'Measure> = int

Full name: Microsoft.FSharp.Core.int<_>
val strengthVal : ParaValue
val strength : int
val asInteger : (ParaValue -> ParaResult<int>)

Full name: Pfarah.ParaValue.asInteger
val printf : format:Printf.TextWriterFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printf
module Operators

from Pfarah
Multiple items
module ParaValue

from Pfarah

--------------------
type ParaValue<'a> = ParaValue -> ParaResult<'a> * ParaValue

Full name: Pfarah.ParaValue<_>

--------------------
type ParaValue =
  | Bool of bool
  | Number of float
  | Hsv of float * float * float
  | Rgb of byte * byte * byte
  | Date of DateTime
  | String of string
  | Array of elements: ParaValue []
  | Record of properties: (string * ParaValue) []
  override ToString : unit -> string
  static member Load : stream:Stream * binHeader:string * txtHeader:string * lookup:Lazy<IDictionary<int16,string>> -> ParaValue
  static member Load : file:string * binHeader:string * txtHeader:string * lookup:Lazy<IDictionary<int16,string>> -> ParaValue
  static member LoadBinary : stream:Stream * lookup:IDictionary<int16,string> * header:string option -> ParaValue
  static member LoadText : file:string -> ParaValue
  static member LoadText : stream:Stream -> ParaValue
  static member Parse : text:string -> ParaValue
  static member private Prettify : value:ParaValue -> indent:int -> string
  static member Save : stream:Stream * data:ParaValue -> unit
  static member private collect : prop:string -> obj:ParaValue -> ParaValue
  static member ( / ) : obj:ParaValue * propertyName:string -> ParaValue

Full name: Pfarah.ParaValue
val shipsObj : ParaValue

Full name: Tutorial.shipsObj
val parseShip : ship:ParaValue -> ParaResult<string * int>

Full name: Tutorial.parseShip
val ship : ParaValue
val pips : ParaValue

Full name: Tutorial.pips
val collect : prop:string -> obj:ParaValue -> ParaValue

Full name: Pfarah.ParaValue.collect
val extract : ParaResult<(string * int) []>

Full name: Tutorial.extract
val flatMap : fn:(ParaValue -> ParaResult<'a>) -> o:ParaValue -> ParaResult<'a []>

Full name: Pfarah.ParaValue.flatMap
val sorted : ParaResult<(string * int) []>

Full name: Tutorial.sorted
val map : f:('a -> 'b) -> m:ParaResult<'a> -> ParaResult<'b>

Full name: Pfarah.ParaResult.map
val sortByDescending : projection:('T -> 'Key) -> array:'T [] -> 'T [] (requires comparison)

Full name: Microsoft.FSharp.Collections.Array.sortByDescending
val snd : tuple:('T1 * 'T2) -> 'T2

Full name: Microsoft.FSharp.Core.Operators.snd
val ships : (string * int) []
val iter : action:('T -> unit) -> array:'T [] -> unit

Full name: Microsoft.FSharp.Collections.Array.iter
val error : string
val pips : ParaValue []
val getAll : key:string -> o:ParaValue -> ParaResult<ParaValue []>

Full name: Pfarah.ParaValue.getAll
val reduce : fn:(ParaValue -> ParaResult<'a>) -> arr:ParaValue [] -> ParaResult<'a []>

Full name: Pfarah.ParaValue.reduce
val patrolData : string

Full name: Tutorial.patrolData
val parseShip2 : ship:ParaValue -> ParaResult<string * int * bool>

Full name: Tutorial.parseShip2
val patrolVal : ParaValue option
type 'T option = Option<'T>

Full name: Microsoft.FSharp.Core.option<_>
val tryGet : key:string -> o:ParaValue -> ParaResult<ParaValue option>

Full name: Pfarah.ParaValue.tryGet
val patrol : bool
val defaultOpt : fn:(ParaValue -> ParaResult<'a>) -> def:'a -> opt:ParaValue option -> ParaResult<'a>

Full name: Pfarah.ParaResult.defaultOpt
val asBool : _arg1:ParaValue -> ParaResult<bool>

Full name: Pfarah.ParaValue.asBool
Multiple items
val obj : ParaValue

--------------------
type obj = Object

Full name: Microsoft.FSharp.Core.obj
val ships : (string * int * bool) []
val shipsOnPatrol : (string * int) []
val filter : predicate:('T -> bool) -> array:'T [] -> 'T []

Full name: Microsoft.FSharp.Collections.Array.filter
val map : mapping:('T -> 'U) -> array:'T [] -> 'U []

Full name: Microsoft.FSharp.Collections.Array.map
val ships : (string * ParaValue) [] []
val asRecord : _arg1:ParaValue -> ParaResult<(string * ParaValue) []>

Full name: Pfarah.ParaValue.asRecord
val required : Set<string>
val optional : Set<string>
val findOptional : objs:seq<(string * ParaValue) []> -> Set<string> * Set<string>

Full name: Pfarah.ParaExtensions.findOptional
val iter : action:('T -> unit) -> source:seq<'T> -> unit

Full name: Microsoft.FSharp.Collections.Seq.iter
val multipleShips : string

Full name: Tutorial.multipleShips
type Ship =
  {Name: string;
   Strength: int;}
  static member Create : name:string -> strength:int -> Ship
  static member FromPara : Ship -> ParaValue<Ship>

Full name: Tutorial.Ship
Ship.Name: string
Ship.Strength: int
static member Ship.Create : name:string -> strength:int -> Ship

Full name: Tutorial.Ship.Create
static member Ship.FromPara : Ship -> ParaValue<Ship>

Full name: Tutorial.Ship.FromPara
static member Ship.Create : name:string -> strength:int -> Ship
val ships : Ship []

Full name: Tutorial.ships
val deserialize : paraValue:ParaValue -> 'a (requires member FromPara)

Full name: Pfarah.Functional.deserialize
val parseShip3 : ship:ParaValue -> ParaResult<string * int>

Full name: Tutorial.parseShip3
val fromPara : x:ParaValue -> ParaResult<'a> (requires member FromPara)

Full name: Pfarah.Functional.fromPara
val parseShip4 : ship:ParaValue -> ParaResult<string * int>

Full name: Tutorial.parseShip4
val pget : key:string -> o:ParaValue -> ParaResult<'a> (requires member FromPara)

Full name: Pfarah.Functional.pget
val parseShip5 : ship:ParaValue -> ParaResult<string * int>

Full name: Tutorial.parseShip5
type Ship2 =
  {Name: string;
   Strength: int;}
  static member FromPara : Ship2 -> (ParaValue -> ParaResult<Ship2> * ParaValue)

Full name: Tutorial.Ship2
Ship2.Name: string
Ship2.Strength: int
static member Ship2.FromPara : Ship2 -> (ParaValue -> ParaResult<Ship2> * ParaValue)

Full name: Tutorial.Ship2.FromPara
module ApplicativeParaValue

from Pfarah
val wrap : fn:(ParaValue -> ParaResult<'a>) -> paravalue:ParaValue -> ParaResult<'a> * ParaValue

Full name: Pfarah.ApplicativeParaValue.wrap
type Ship3 =
  {Name: string;
   Strength: int;
   Patrol: bool option;}
  static member FromPara : Ship3 -> (ParaValue -> ParaResult<Ship3> * ParaValue)

Full name: Tutorial.Ship3
Ship3.Name: string
Ship3.Strength: int
Ship3.Patrol: bool option
static member Ship3.FromPara : Ship3 -> (ParaValue -> ParaResult<Ship3> * ParaValue)

Full name: Tutorial.Ship3.FromPara
val patrol : bool option
val tryPget : key:string -> o:ParaValue -> ParaResult<'a option> (requires member FromPara)

Full name: Pfarah.Functional.tryPget
val path : string

Full name: Tutorial.path
val ( binary header ) : string

Full name: Tutorial.( binary header )
val ( text header ) : string

Full name: Tutorial.( text header )
val tokens : Lazy<Collections.Generic.IDictionary<int16,string>>

Full name: Tutorial.tokens
val dict : keyValuePairs:seq<'Key * 'Value> -> Collections.Generic.IDictionary<'Key,'Value> (requires equality)

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.dict
val game : ParaValue

Full name: Tutorial.game
static member ParaValue.Load : stream:IO.Stream * binHeader:string * txtHeader:string * lookup:Lazy<Collections.Generic.IDictionary<int16,string>> -> ParaValue
static member ParaValue.Load : file:string * binHeader:string * txtHeader:string * lookup:Lazy<Collections.Generic.IDictionary<int16,string>> -> ParaValue
val asDate : _arg1:ParaValue -> ParaResult<DateTime>

Full name: Pfarah.ParaValue.asDate
Fork me on GitHub