Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prepare for release #6

Merged
merged 2 commits into from
May 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 5 additions & 13 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,11 @@
<DebugType>embedded</DebugType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Nerdbank.GitVersioning" Version="3.5.119" PrivateAssets="all" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
<PackageReference Include="Nerdbank.GitVersioning" Version="3.6.133" PrivateAssets="all" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
<SourceLinkGitHubHost Include="github.com" ContentUrl="https://raw.githubusercontent.com" />
</ItemGroup>
<!--
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SourceLink does support F# now as of v8.0.0, to which we upgrade earlier in this file.

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