Skip to content

Commit

Permalink
Prepare for release (#6)
Browse files Browse the repository at this point in the history
  • Loading branch information
Smaug123 authored May 29, 2024
1 parent ef4f5e4 commit 4d41f4f
Show file tree
Hide file tree
Showing 13 changed files with 351 additions and 257 deletions.
14 changes: 3 additions & 11 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,7 @@
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
<SourceLinkGitHubHost Include="github.com" ContentUrl="https://raw.githubusercontent.com" />
</ItemGroup>
<!--
SourceLink doesn't support F# deterministic builds out of the box,
so tell SourceLink that our source root is going to be remapped.
-->
<Target Name="MapSourceRoot" BeforeTargets="_GenerateSourceLinkFile" Condition="'$(SourceRootMappedPathsFeatureSupported)' != 'true'">
<ItemGroup>
<SourceRoot Update="@(SourceRoot)">
<MappedPath>Z:\CheckoutRoot\TeqCrate\</MappedPath>
</SourceRoot>
</ItemGroup>
</Target>
<PropertyGroup Condition="'$(GITHUB_ACTION)' != ''">
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>
</Project>
49 changes: 0 additions & 49 deletions Examples/CsvParser.fsx

This file was deleted.

21 changes: 0 additions & 21 deletions Examples/ExampleParse.fsx

This file was deleted.

163 changes: 0 additions & 163 deletions Examples/SimpleExamples.fsx

This file was deleted.

51 changes: 49 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,56 @@

Type-safe datatype-generic programming for F#.

## Examples
## Getting started

See the [Examples](./Examples) folder for examples demonstrating how to perform type-safe manipulation of various different types.
The most useful place to start is likely with the `tType<'a>` function and corresponding active patterns in the `Patterns` module.
These patterns reflectively determine what type `'a` was, and give you evidence in the form of a `Teq` (see [TypeEquality](https://github.com/G-Research/TypeEquality)).
Where appropriate, you also get a type-safe representation of the type's structure.

Here is a brief example.
Everything written here was forced by the types: once we chose to match on the `Unit` and `Record` active patterns, there was only one way to write this function so that it compiled.

```fsharp
open ShapeSifter
open ShapeSifter.Patterns
let manipulateType<'a> () =
match tType<'a> with
| Unit teq ->
printfn "'a was a unit type, and `teq` witnesses this!"
| Record data ->
{ new RecordConvEvaluator<_> with
member _.Eval (fieldData : RecordTypeField list) (fieldTypes : TypeList<'ts>) (conv : Conv<'a, 'ts HList>) =
failwith "manipulate the type here"
}
|> data.Apply
| _ -> failwith "unrecognised type"
```

Inside the `RecordConvEvaluator`, we have gained access to:

* The list `fieldData` of record fields, telling us the name of each field and any attributes that were on the field (as well as the raw `PropertyInfo` associated with each field).
* The same list of field types, but expressed as a [`HeterogeneousCollections.TypeList`](https://github.com/G-Research/HeterogeneousCollections/blob/main/HeterogeneousCollections/TypeList.fsi).
* A `Conv` (converter) which lets us interchange between an `'a` and a heterogeneous list of its field values.

We have now seen a pattern for a primitive type (`Unit`) and for an arbitrary record.
Using the patterns in ShapeSifter, we can recognise the following types:

* Many primitive types, and `DateTime` and `TimeSpan`
* `Array<_>`, `_ list`, `Seq<_>`, `Set<_>`
* `Option<_>`
* `Map<_, _>`
* `_ * _`, `_ * _ * _`, and arbitrary tuples
* `_ -> _`
* `Dictionary<_,_>`, `ResizeArray<_>`
* `Teq<_, _>`
* Records and unions
* "Sums of products" (that is, unions, but where we give you easier access to the products which make up the union fields).

## More examples

See the [tests](./ShapeSifter.Test) for examples demonstrating how to perform type-safe manipulation of various different types.
There is a [whistlestop tour](./ShapeSifter.Test/TestExamples.fs) and a [specific example of type-safe CSV parsing](./ShapeSifter.Test/CsvExample).

## Credits

Expand Down
48 changes: 48 additions & 0 deletions ShapeSifter.Test/CsvExample/CsvParser.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
namespace CsvParser

open HCollections
open System
open ShapeSifter
open ShapeSifter.Patterns
open TypeEquality

[<RequireQualifiedAccess>]
module CsvParser =
let parseCell<'a> : string -> 'a =
match tType<'a> with
| String (teq : Teq<'a, string>) -> Teq.castFrom teq
| Bool (teq : Teq<'a, bool>) -> Boolean.Parse >> Teq.castFrom teq
| Int (teq : Teq<'a, int>) -> Int32.Parse >> Teq.castFrom teq
| Float (teq : Teq<'a, float>) -> Double.Parse >> Teq.castFrom teq
| DateTime (teq : Teq<'a, DateTime>) -> DateTime.Parse >> Teq.castFrom teq
| _ -> failwithf "Error - the type %s is not supported" typeof<'a>.FullName

let rec parseRow<'ts> (ts : 'ts TypeList) (cells : string list) : 'ts HList =
match TypeList.split ts with
| Choice1Of2 (teq : Teq<'ts, unit>) -> HList.empty |> Teq.castFrom (HList.cong teq)
| Choice2Of2 crate ->

crate.Apply
{ new TypeListConsEvaluator<_, _> with
member _.Eval (us : 'us TypeList) (teq : Teq<'ts, 'u -> 'us>) =
let head = cells |> List.head |> parseCell<'u>
let tail = cells |> List.tail |> parseRow us

HList.cons head tail |> Teq.castFrom (HList.cong teq)
}

let tryParse<'record> (data : string seq) : 'record seq option =
match tType<'record> with
| Record crate ->
crate.Apply
{ new RecordConvEvaluator<_, _> with
member _.Eval
(fields : RecordTypeField list)
(ts : 'ts TypeList)
(conv : Conv<'record, 'ts HList>)
=
data
|> Seq.map (fun row -> row.Split ',' |> List.ofArray |> parseRow ts |> conv.From)
|> Some
}
| _ -> None
Loading

0 comments on commit 4d41f4f

Please sign in to comment.