├── .gitignore
├── .paket
└── paket.targets
├── README.md
├── build.cmd
├── build.fsx
├── build.sh
├── paket.dependencies
├── paket.lock
└── src
├── FSharpTypeclasses
├── App.config
├── FSharpTypeclasses.fsproj
├── eq.fs
├── eq.fsi
├── functor.fs
├── functor.fsi
├── paket.references
├── to_json.fs
└── to_json.fsi
└── FSharpTypeclassesTest
├── App.config
├── FSharpTypeclassesTest.fsproj
├── paket.references
└── to_json_test.fs
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.gitignore.io/api/f#,linux,windows,macos,vim,emacs,visualstudio,visualstudiocode
2 |
3 | ### F# ###
4 | lib/debug
5 | lib/release
6 | Debug
7 | *.suo
8 | *.user
9 | *.exe
10 |
11 | ### Linux ###
12 | *~
13 |
14 | # temporary files which can be created if a process still has a handle open of a deleted file
15 | .fuse_hidden*
16 |
17 | # KDE directory preferences
18 | .directory
19 |
20 | # Linux trash folder which might appear on any partition or disk
21 | .Trash-*
22 |
23 |
24 | ### Windows ###
25 | # Windows image file caches
26 | Thumbs.db
27 | ehthumbs.db
28 |
29 | # Folder config file
30 | Desktop.ini
31 |
32 | # Recycle Bin used on file shares
33 | $RECYCLE.BIN/
34 |
35 | # Windows Installer files
36 | *.cab
37 | *.msi
38 | *.msm
39 | *.msp
40 |
41 | # Windows shortcuts
42 | *.lnk
43 |
44 |
45 | ### macOS ###
46 | *.DS_Store
47 | .AppleDouble
48 | .LSOverride
49 |
50 | # Icon must end with two \r
51 | Icon
52 |
53 |
54 | # Thumbnails
55 | ._*
56 |
57 | # Files that might appear in the root of a volume
58 | .DocumentRevisions-V100
59 | .fseventsd
60 | .Spotlight-V100
61 | .TemporaryItems
62 | .Trashes
63 | .VolumeIcon.icns
64 | .com.apple.timemachine.donotpresent
65 |
66 | # Directories potentially created on remote AFP share
67 | .AppleDB
68 | .AppleDesktop
69 | Network Trash Folder
70 | Temporary Items
71 | .apdisk
72 |
73 |
74 | ### Vim ###
75 | # swap
76 | [._]*.s[a-w][a-z]
77 | [._]s[a-w][a-z]
78 | # session
79 | Session.vim
80 | # temporary
81 | .netrwhist
82 | *~
83 | # auto-generated tag files
84 | tags
85 |
86 |
87 | ### Emacs ###
88 | # -*- mode: gitignore; -*-
89 | *~
90 | \#*\#
91 | /.emacs.desktop
92 | /.emacs.desktop.lock
93 | *.elc
94 | auto-save-list
95 | tramp
96 | .\#*
97 |
98 | # Org-mode
99 | .org-id-locations
100 | *_archive
101 |
102 | # flymake-mode
103 | *_flymake.*
104 |
105 | # eshell files
106 | /eshell/history
107 | /eshell/lastdir
108 |
109 | # elpa packages
110 | /elpa/
111 |
112 | # reftex files
113 | *.rel
114 |
115 | # AUCTeX auto folder
116 | /auto/
117 |
118 | # cask packages
119 | .cask/
120 | dist/
121 |
122 | # Flycheck
123 | flycheck_*.el
124 |
125 | # server auth directory
126 | /server/
127 |
128 | # projectiles files
129 | .projectile
130 |
131 | ### VisualStudioCode ###
132 | .vscode
133 |
134 |
135 |
136 | ### VisualStudio ###
137 | ## Ignore Visual Studio temporary files, build results, and
138 | ## files generated by popular Visual Studio add-ons.
139 |
140 | # User-specific files
141 | *.userosscache
142 | *.sln.docstates
143 |
144 | # User-specific files (MonoDevelop/Xamarin Studio)
145 | *.userprefs
146 |
147 | # Build results
148 | [Dd]ebug/
149 | [Dd]ebugPublic/
150 | [Rr]elease/
151 | [Rr]eleases/
152 | x64/
153 | x86/
154 | bld/
155 | build/
156 | [Bb]in/
157 | [Oo]bj/
158 | [Ll]og/
159 |
160 | # Visual Studio 2015 cache/options directory
161 | .vs/
162 | # Uncomment if you have tasks that create the project's static files in wwwroot
163 | #wwwroot/
164 |
165 | # MSTest test Results
166 | [Tt]est[Rr]esult*/
167 | [Bb]uild[Ll]og.*
168 |
169 | # NUNIT
170 | *.VisualState.xml
171 | TestResult.xml
172 |
173 | # Build Results of an ATL Project
174 | [Dd]ebugPS/
175 | [Rr]eleasePS/
176 | dlldata.c
177 |
178 | # DNX
179 | project.lock.json
180 | project.fragment.lock.json
181 | artifacts/
182 |
183 | *_i.c
184 | *_p.c
185 | *_i.h
186 | *.ilk
187 | *.meta
188 | *.obj
189 | *.pch
190 | *.pdb
191 | *.pgc
192 | *.pgd
193 | *.rsp
194 | *.sbr
195 | *.tlb
196 | *.tli
197 | *.tlh
198 | *.tmp
199 | *.tmp_proj
200 | *.log
201 | *.vspscc
202 | *.vssscc
203 | .builds
204 | *.pidb
205 | *.svclog
206 | *.scc
207 |
208 | # Chutzpah Test files
209 | _Chutzpah*
210 |
211 | # Visual C++ cache files
212 | ipch/
213 | *.aps
214 | *.ncb
215 | *.opendb
216 | *.opensdf
217 | *.sdf
218 | *.cachefile
219 | *.VC.db
220 | *.VC.VC.opendb
221 |
222 | # Visual Studio profiler
223 | *.psess
224 | *.vsp
225 | *.vspx
226 | *.sap
227 |
228 | # TFS 2012 Local Workspace
229 | $tf/
230 |
231 | # Guidance Automation Toolkit
232 | *.gpState
233 |
234 | # ReSharper is a .NET coding add-in
235 | _ReSharper*/
236 | *.[Rr]e[Ss]harper
237 | *.DotSettings.user
238 |
239 | # JustCode is a .NET coding add-in
240 | .JustCode
241 |
242 | # TeamCity is a build add-in
243 | _TeamCity*
244 |
245 | # DotCover is a Code Coverage Tool
246 | *.dotCover
247 |
248 | # NCrunch
249 | _NCrunch_*
250 | .*crunch*.local.xml
251 | nCrunchTemp_*
252 |
253 | # MightyMoose
254 | *.mm.*
255 | AutoTest.Net/
256 |
257 | # Web workbench (sass)
258 | .sass-cache/
259 |
260 | # Installshield output folder
261 | [Ee]xpress/
262 |
263 | # DocProject is a documentation generator add-in
264 | DocProject/buildhelp/
265 | DocProject/Help/*.HxT
266 | DocProject/Help/*.HxC
267 | DocProject/Help/*.hhc
268 | DocProject/Help/*.hhk
269 | DocProject/Help/*.hhp
270 | DocProject/Help/Html2
271 | DocProject/Help/html
272 |
273 | # Click-Once directory
274 | publish/
275 |
276 | # Publish Web Output
277 | *.[Pp]ublish.xml
278 | *.azurePubxml
279 | # TODO: Comment the next line if you want to checkin your web deploy settings
280 | # but database connection strings (with potential passwords) will be unencrypted
281 | *.pubxml
282 | *.publishproj
283 |
284 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
285 | # checkin your Azure Web App publish settings, but sensitive information contained
286 | # in these scripts will be unencrypted
287 | PublishScripts/
288 |
289 | # NuGet Packages
290 | *.nupkg
291 | # The packages folder can be ignored because of Package Restore
292 | **/packages/*
293 | # except build/, which is used as an MSBuild target.
294 | !**/packages/build/
295 | # Uncomment if necessary however generally it will be regenerated when needed
296 | #!**/packages/repositories.config
297 | # NuGet v3's project.json files produces more ignoreable files
298 | *.nuget.props
299 | *.nuget.targets
300 |
301 | # Microsoft Azure Build Output
302 | csx/
303 | *.build.csdef
304 |
305 | # Microsoft Azure Emulator
306 | ecf/
307 | rcf/
308 |
309 | # Windows Store app package directories and files
310 | AppPackages/
311 | BundleArtifacts/
312 | Package.StoreAssociation.xml
313 | _pkginfo.txt
314 |
315 | # Visual Studio cache files
316 | # files ending in .cache can be ignored
317 | *.[Cc]ache
318 | # but keep track of directories ending in .cache
319 | !*.[Cc]ache/
320 |
321 | # Others
322 | ClientBin/
323 | ~$*
324 | *~
325 | *.dbmdl
326 | *.dbproj.schemaview
327 | *.pfx
328 | *.publishsettings
329 | node_modules/
330 | orleans.codegen.cs
331 |
332 | # Since there are multiple workflows, uncomment next line to ignore bower_components
333 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
334 | #bower_components/
335 |
336 | # RIA/Silverlight projects
337 | Generated_Code/
338 |
339 | # Backup & report files from converting an old project file
340 | # to a newer Visual Studio version. Backup files are not needed,
341 | # because we have git ;-)
342 | _UpgradeReport_Files/
343 | Backup*/
344 | UpgradeLog*.XML
345 | UpgradeLog*.htm
346 |
347 | # SQL Server files
348 | *.mdf
349 | *.ldf
350 |
351 | # Business Intelligence projects
352 | *.rdl.data
353 | *.bim.layout
354 | *.bim_*.settings
355 |
356 | # Microsoft Fakes
357 | FakesAssemblies/
358 |
359 | # GhostDoc plugin setting file
360 | *.GhostDoc.xml
361 |
362 | # Node.js Tools for Visual Studio
363 | .ntvs_analysis.dat
364 |
365 | # Visual Studio 6 build log
366 | *.plg
367 |
368 | # Visual Studio 6 workspace options file
369 | *.opt
370 |
371 | # Visual Studio LightSwitch build output
372 | **/*.HTMLClient/GeneratedArtifacts
373 | **/*.DesktopClient/GeneratedArtifacts
374 | **/*.DesktopClient/ModelManifest.xml
375 | **/*.Server/GeneratedArtifacts
376 | **/*.Server/ModelManifest.xml
377 | _Pvt_Extensions
378 |
379 | # Paket dependency manager
380 | .paket/paket.exe
381 | paket-files/
382 |
383 | # FAKE - F# Make
384 | .fake/
385 |
386 | # JetBrains Rider
387 | .idea/
388 | *.sln.iml
389 |
--------------------------------------------------------------------------------
/.paket/paket.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | true
6 |
7 | true
8 | $(MSBuildThisFileDirectory)
9 | $(MSBuildThisFileDirectory)..\
10 | /Library/Frameworks/Mono.framework/Commands/mono
11 | mono
12 |
13 |
14 |
15 | $(PaketRootPath)paket.exe
16 | $(PaketToolsPath)paket.exe
17 | $(PaketToolsPath)paket.bootstrapper.exe
18 | "$(PaketExePath)"
19 | $(MonoPath) --runtime=v4.0.30319 "$(PaketExePath)"
20 | "$(PaketBootStrapperExePath)" $(PaketBootStrapperCommandArgs)
21 | $(MonoPath) --runtime=v4.0.30319 $(PaketBootStrapperExePath) $(PaketBootStrapperCommandArgs)
22 |
23 | $(MSBuildProjectDirectory)\paket.references
24 | $(MSBuildStartupDirectory)\paket.references
25 | $(MSBuildProjectFullPath).paket.references
26 | $(PaketCommand) restore --references-files "$(PaketReferences)"
27 | $(PaketBootStrapperCommand)
28 |
29 | RestorePackages; $(BuildDependsOn);
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # F# Typeclasses: a Dictionary Encoding Approach
2 |
3 | A lot of people have tried to encode Haskell-style typeclasses or
4 | ML-style modules and functors in F#. So far there have been two major
5 | stumbling blocks: (1) F#'s lack of higher-kinded types, and (2) F#'s
6 | lack of a compile-time mechanism for implicitly selecting
7 | implementations based on types.
8 |
9 | The state of the art in F# seems to be
10 | https://github.com/palladin/Higher , a library for higher-kinded
11 | programming in F#. The problem with Higher and most other F# typeclass
12 | efforts is that they make heavy use of object-oriented techniques,
13 | which--although they work--are clunky and difficult to implement.
14 | Writing new instances--the very thing that gives typeclasses their
15 | extensibility--should be a piece of cake.
16 |
17 | This library, `fsharp-typeclasses`, aims to make that possible by
18 | borrowing ideas heavily from both Haskell and Scala.
19 |
20 | ## Approach
21 |
22 | The basic idea is to encode a typeclass as a record type holding the
23 | methods (the typeclass operations), and encode instances as record
24 | values. This is pretty much how Haskell implements it behind the scenes.
25 | However, we take some code organisation steps to make things easier on
26 | ourselves.
27 |
28 | ### Module Structure
29 |
30 | We structure a typeclass (e.g. `Functor`) as a `module Functor` in a
31 | file `functor.fs` with a corresponding signature file `functor.fsi`. We
32 | use the signature file heavily in our approach, to help us constrain our
33 | types but also keep the implementation clean and clutter-free.
34 |
35 | In the `Functor` module we have a record type `t` which has a field
36 | corresponding to the `Functor` map method, with an appropriate function
37 | type.
38 |
39 | Next, we have the various instances which are appropriate to declare in
40 | the typeclass module, as they're based on standard library types like
41 | `option`, `list` and `array`.
42 |
43 | Next, we have a nested module `Ops` which exposes the typeclass's
44 | methods in an easy-to-use way given any typeclass instance. The
45 | intention is that the user will open this module to get all the
46 | typeclass operations; they won't actually have to open the actual
47 | top-level typeclass module itself. Of course, they'll need to pass in
48 | instances, but they can access the instances safely qualified by the
49 | module name, e.g.
50 |
51 | ```fsharp
52 | open Functor.Ops
53 |
54 | let list = map Functor.list fst [1, 2; 3, 4]
55 | // list = [1; 3]
56 | ```
57 |
58 | Finally, we (optionally) have another nested module `Laws` which encodes
59 | the laws the typeclass is expected to obey in the form of functions
60 | which take the relevant instances and any other inputs they need and
61 | output a `bool` indicating whether the law is obeyed or not for those
62 | instances and values. This practice is also used quite often in Haskell
63 | and Scala to ship typeclasses with their expected invariant behaviours
64 | in the code itself.
65 |
66 | ### Signature Files
67 |
68 | As mentioned, we make heavy use of signature files to assign exact
69 | typing information to all our functions and values. The feedback loop
70 | between implementation and signature files helps to derive
71 | higher-quality code.
72 |
73 | ### Higher-Kinded Types
74 |
75 | We skip over the problem of the lack of higher-kinded types by just
76 | passing in _fully-applied_ types as type parameters. This means that
77 | instead of having a `Functor.t<'f>`, we have a `Functor.t<'a, 'b, 'fa,
78 | 'fb>`. Now, admittedly, there's no guarantee with these type parameters
79 | that they'll actually obey the functor requirements, e.g. if `'fa` is a
80 | `list<'a>` then `'fb` must be a `list<'b>`. There's nothing enforcing
81 | this at compile time. But in my opinion we partially make up for that by
82 | shipping functor laws right beside the functor definition; once we
83 | implement property-based testing of the laws, they will be almost as
84 | solid as in languages with higher-kinded types.
85 |
86 | ## Explicit Dictionary Passing
87 |
88 | For now, we are forced to pass in typeclass instances explicitly as
89 | shown above. F# does not have a general-purpose implicit resolution
90 | technique available. However, we are no worse off here than with ML
91 | modules and having work with them explicitly. Also, there may be some
92 | way to leverage F# code quotations to simulate implicit resolution.
93 |
94 | ## Exploration with JSON Encoding
95 |
96 | So far the most comprehensive exploration of this typeclass technique is
97 | in the `To_json` module. We provide instances for simple types and
98 | combinators to derive instances, ranging in complexity from simple
99 | (deriving a JSON converter of an array of something given a converter
100 | instance for that thing) to complex (deriving a converter instances for
101 | an arbitrary product type given multiple converter instances for its
102 | component types).
103 |
104 | Note in particular the full suite of unit tests of the JSON instances in
105 | the `FsharpTypeclassesTest.To_json_test` module. These tests show a good
106 | cross-section of usages, from building instances out of simpler
107 | instances (much like ML functors, actually) to using the instances for
108 | JSON conversion.
109 |
--------------------------------------------------------------------------------
/build.cmd:
--------------------------------------------------------------------------------
1 | @echo off
2 | cls
3 |
4 | .paket\paket.bootstrapper.exe
5 | if errorlevel 1 (
6 | exit /b %errorlevel%
7 | )
8 |
9 | .paket\paket.exe restore
10 | if errorlevel 1 (
11 | exit /b %errorlevel%
12 | )
13 |
14 | packages\FAKE\tools\FAKE.exe build.fsx %*
15 |
--------------------------------------------------------------------------------
/build.fsx:
--------------------------------------------------------------------------------
1 | // include Fake libs
2 | #r "./packages/FAKE/tools/FakeLib.dll"
3 |
4 | open Fake
5 | open Fake.Testing
6 |
7 | // Directories
8 | let buildDir = "./build/"
9 | let deployDir = "./deploy/"
10 |
11 | // Filesets
12 | let appReferences =
13 | !! "/**/*.csproj"
14 | ++ "/**/*.fsproj"
15 |
16 | // version info
17 | let version = "0.1" // or retrieve from CI server
18 |
19 | // Targets
20 | Target ? Clean <- fun _ -> CleanDirs [buildDir; deployDir]
21 | Target ? Build <- fun _ ->
22 | MSBuildDebug buildDir "Build" appReferences |> Log "AppBuild-Output: "
23 |
24 | Target ? BuildTest <- fun _ ->
25 | !! ("src/FSharpTypeclasses/**/*.fsproj")
26 | |> MSBuildDebug buildDir "Build"
27 | |> Log "TestBuild-Output: "
28 |
29 | Target ? Test <- fun _ ->
30 | !! (buildDir + "FSharpTypeclassesTest.dll")
31 | |> NUnit3 (fun p ->
32 | { p with
33 | ToolPath =
34 | "packages/NUnit.ConsoleRunner/tools/nunit3-console.exe" })
35 |
36 | Target ? Deploy <- fun _ ->
37 | !! (buildDir + "/**/*.*")
38 | -- "*.zip"
39 | |> Zip buildDir (deployDir + "ApplicationName." + version + ".zip")
40 |
41 | // Build order
42 | "Clean"
43 | ==> "Build"
44 | ==> "BuildTest"
45 | ==> "Test"
46 | ==> "Deploy"
47 |
48 | // start build
49 | RunTargetOrDefault ? Build
50 |
--------------------------------------------------------------------------------
/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | if test "$OS" = "Windows_NT"
3 | then
4 | # use .Net
5 |
6 | .paket/paket.bootstrapper.exe
7 | exit_code=$?
8 | if [ $exit_code -ne 0 ]; then
9 | exit $exit_code
10 | fi
11 |
12 | .paket/paket.exe restore
13 | exit_code=$?
14 | if [ $exit_code -ne 0 ]; then
15 | exit $exit_code
16 | fi
17 |
18 | packages/FAKE/tools/FAKE.exe $@ --fsiargs build.fsx
19 | else
20 | # use mono
21 | mono .paket/paket.bootstrapper.exe
22 | exit_code=$?
23 | if [ $exit_code -ne 0 ]; then
24 | exit $exit_code
25 | fi
26 |
27 | mono .paket/paket.exe restore
28 | exit_code=$?
29 | if [ $exit_code -ne 0 ]; then
30 | exit $exit_code
31 | fi
32 | mono packages/FAKE/tools/FAKE.exe $@ --fsiargs -d:MONO build.fsx
33 | fi
34 |
--------------------------------------------------------------------------------
/paket.dependencies:
--------------------------------------------------------------------------------
1 | source https://www.nuget.org/api/v2
2 | nuget FAKE
3 | nuget FSharp.Core
4 | nuget NUnit
5 | nuget NUnit.ConsoleRunner
6 |
--------------------------------------------------------------------------------
/paket.lock:
--------------------------------------------------------------------------------
1 | NUGET
2 | remote: https://www.nuget.org/api/v2
3 | FAKE (4.42)
4 | FSharp.Core (4.0.0.1)
5 | NUnit (3.5)
6 | NUnit.ConsoleRunner (3.5)
7 |
--------------------------------------------------------------------------------
/src/FSharpTypeclasses/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/FSharpTypeclasses/FSharpTypeclasses.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | FSharpTypeclasses
5 | FSharpTypeclasses
6 | FSharpTypeclasses
7 | Debug
8 | AnyCPU
9 | 2.0
10 | 4365901e-7dcc-480e-878d-fe6a087807a7
11 | Library
12 | v4.5
13 | true
14 | 4.4.0.0
15 |
16 |
17 | true
18 | Full
19 | false
20 | false
21 | bin\$(Configuration)\
22 | DEBUG;TRACE
23 | 3
24 | $(Platform)
25 |
26 |
27 | PdbOnly
28 | true
29 | true
30 | bin\$(Configuration)\
31 | TRACE
32 | 3
33 | $(Platform)
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets
55 |
56 |
57 |
58 |
59 | $(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\4.0\Framework\v4.0\Microsoft.FSharp.Targets
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 | ..\..\packages\FSharp.Core\lib\net20\FSharp.Core.dll
69 | True
70 | True
71 |
72 |
73 |
74 |
75 |
76 |
77 | ..\..\packages\FSharp.Core\lib\net40\FSharp.Core.dll
78 | True
79 | True
80 |
81 |
82 |
83 |
84 |
85 |
86 | ..\..\packages\FSharp.Core\lib\portable-net45+monoandroid10+monotouch10+xamarinios10\FSharp.Core.dll
87 | True
88 | True
89 |
90 |
91 |
92 |
93 |
94 |
95 | ..\packages\FSharp.Core\lib\portable-net45+netcore45\FSharp.Core.dll
96 | True
97 | True
98 |
99 |
100 |
101 |
102 |
103 |
104 | ..\..\packages\FSharp.Core\lib\portable-net45+netcore45+wp8\FSharp.Core.dll
105 | True
106 | True
107 |
108 |
109 |
110 |
111 |
112 |
113 | ..\..\packages\FSharp.Core\lib\portable-net45+netcore45+wpa81+wp8\FSharp.Core.dll
114 | True
115 | True
116 |
117 |
118 |
119 |
120 |
121 |
122 | ..\..\packages\FSharp.Core\lib\portable-net45+sl5+netcore45\FSharp.Core.dll
123 | True
124 | True
125 |
126 |
127 |
128 |
129 |
--------------------------------------------------------------------------------
/src/FSharpTypeclasses/eq.fs:
--------------------------------------------------------------------------------
1 | namespace FsharpTypeclasses
2 |
3 | []
4 | module Eq =
5 | type t<'a> = { eq : 'a -> 'a -> bool }
6 |
7 | let equality = { eq = (=) }
8 |
9 | module Ops = let eq t = t.eq
10 | module Laws = let reflexivity t a = Ops.eq t a a
11 |
--------------------------------------------------------------------------------
/src/FSharpTypeclasses/eq.fsi:
--------------------------------------------------------------------------------
1 | namespace FsharpTypeclasses
2 |
3 | module Eq =
4 | type t<'a>
5 |
6 | val equality : t<'a> when 'a : equality
7 |
8 | module Ops = val eq : t<'a> -> ('a -> 'a -> bool)
9 | module Laws = val reflexivity : t<'a> -> 'a -> bool
10 |
--------------------------------------------------------------------------------
/src/FSharpTypeclasses/functor.fs:
--------------------------------------------------------------------------------
1 | namespace FsharpTypeclasses
2 |
3 | []
4 | module Functor =
5 | type t<'a, 'b, 'fa, 'fb> = { map : ('a -> 'b) -> 'fa -> 'fb }
6 |
7 | let option = { map = Option.map }
8 | let list = { map = List.map }
9 | let array = { map = Array.map }
10 | let tuple2 = { map = fun f (a1, a2) -> (f a1, f a2) }
11 |
12 | module Ops = let map t = t.map
13 | module Laws =
14 | open Eq.Ops
15 | open Ops
16 |
17 | let identity t eq_t fa = eq eq_t fa <| map t id fa
18 | let composition t1 t2 t3 eq_t f g fa =
19 | let lhs = map t2 g <| map t1 f fa
20 | let rhs = map t3 (g << f) fa
21 |
22 | eq eq_t lhs rhs
23 |
--------------------------------------------------------------------------------
/src/FSharpTypeclasses/functor.fsi:
--------------------------------------------------------------------------------
1 | namespace FsharpTypeclasses
2 |
3 | module Functor =
4 | type t<'a, 'b, 'fa, 'fb>
5 |
6 | val option : t<'a, 'b, option<'a>, option<'b>>
7 | val list : t<'a, 'b, list<'a>, list<'b>>
8 | val array : t<'a, 'b, array<'a>, array<'b>>
9 | val tuple2 : t<'a, 'b, 'a * 'a, 'b * 'b>
10 |
11 | module Ops =
12 | val map : t<'a, 'b, 'fa, 'fb> -> (('a -> 'b) -> 'fa -> 'fb)
13 |
14 | module Laws =
15 | val identity : t<'a, 'a, 'fa, 'fa> -> Eq.t<'fa> -> 'fa -> bool
16 | val composition :
17 | t<'a, 'b, 'fa, 'fb> ->
18 | t<'b, 'c, 'fb, 'fc> ->
19 | t<'a, 'c, 'fa, 'fc> ->
20 | Eq.t<'fc> ->
21 | ('a -> 'b) ->
22 | ('b -> 'c) ->
23 | 'fa ->
24 | bool
25 |
--------------------------------------------------------------------------------
/src/FSharpTypeclasses/paket.references:
--------------------------------------------------------------------------------
1 | FSharp.Core
2 |
--------------------------------------------------------------------------------
/src/FSharpTypeclasses/to_json.fs:
--------------------------------------------------------------------------------
1 | namespace FsharpTypeclasses
2 |
3 | open System.Collections.Generic
4 |
5 | []
6 | module To_json =
7 | type t<'a> = { apply : 'a -> string }
8 | type key_value<'a> = KeyValuePair
9 |
10 | let commalist = String.concat ","
11 | let enbrace s = "{" + s + "}"
12 |
13 | let int = { apply = sprintf "%d" }
14 | let string = { apply = sprintf "\"%s\"" }
15 | let float : t = { apply = sprintf "%A" }
16 | let double : t = { apply = sprintf "%A" }
17 | let char = { apply = sprintf "\"%c\"" }
18 | let bool = { apply = sprintf "%b" }
19 | let option t = { apply = Option.fold (fun _ -> t.apply) "null" }
20 | let array t =
21 | { apply = fun a ->
22 | "[" + (a |> Array.map t.apply |> commalist) + "]" }
23 |
24 | (**
25 | Returns a To_json instance that can convert a single key-value pair
26 | into a JSON string. Note that by itself this conversion isn't a
27 | well-formed JSON string; you'll need to encapsulate it in one of the
28 | `objectX` instances.
29 |
30 | @param t a JSON converter instance for the value in the key-value
31 | pair.
32 | *)
33 | let key_value t : t> =
34 | { apply = fun a ->
35 | (string.apply a.Key) + ":" + (t.apply a.Value) }
36 |
37 | let object1 (f1, t1) =
38 | { apply = enbrace << (key_value t1).apply << f1 }
39 |
40 | let object2 (f1, t1) (f2, t2) =
41 | { apply = fun a ->
42 | [| a |> f1 |> (key_value t1).apply
43 | a |> f2 |> (key_value t2).apply |]
44 | |> commalist |> enbrace }
45 |
46 | let object3 (f1, t1) (f2, t2) (f3, t3) =
47 | { apply = fun a ->
48 | [| a |> f1 |> (key_value t1).apply
49 | a |> f2 |> (key_value t2).apply
50 | a |> f3 |> (key_value t3).apply |]
51 | |> commalist |> enbrace }
52 |
53 | let object4 (f1, t1) (f2, t2) (f3, t3) (f4, t4) =
54 | { apply = fun a ->
55 | [| a |> f1 |> (key_value t1).apply
56 | a |> f2 |> (key_value t2).apply
57 | a |> f3 |> (key_value t3).apply
58 | a |> f4 |> (key_value t4).apply |]
59 | |> commalist |> enbrace }
60 |
61 | let object5 (f1, t1) (f2, t2) (f3, t3) (f4, t4) (f5, t5) =
62 | { apply = fun a ->
63 | [| a |> f1 |> (key_value t1).apply
64 | a |> f2 |> (key_value t2).apply
65 | a |> f3 |> (key_value t3).apply
66 | a |> f4 |> (key_value t4).apply
67 | a |> f5 |> (key_value t5).apply |]
68 | |> commalist |> enbrace }
69 |
70 | let object6 (f1, t1) (f2, t2) (f3, t3) (f4, t4) (f5, t5) (f6, t6) =
71 | { apply = fun a ->
72 | [| a |> f1 |> (key_value t1).apply
73 | a |> f2 |> (key_value t2).apply
74 | a |> f3 |> (key_value t3).apply
75 | a |> f4 |> (key_value t4).apply
76 | a |> f5 |> (key_value t5).apply
77 | a |> f6 |> (key_value t6).apply |]
78 | |> commalist |> enbrace }
79 |
80 | let object7
81 | (f1, t1) (f2, t2) (f3, t3) (f4, t4) (f5, t5) (f6, t6) (f7, t7) =
82 | { apply = fun a ->
83 | [| a |> f1 |> (key_value t1).apply
84 | a |> f2 |> (key_value t2).apply
85 | a |> f3 |> (key_value t3).apply
86 | a |> f4 |> (key_value t4).apply
87 | a |> f5 |> (key_value t5).apply
88 | a |> f6 |> (key_value t6).apply
89 | a |> f7 |> (key_value t7).apply |]
90 | |> commalist |> enbrace }
91 |
92 | let object8
93 | (f1, t1)
94 | (f2, t2)
95 | (f3, t3)
96 | (f4, t4)
97 | (f5, t5)
98 | (f6, t6)
99 | (f7, t7)
100 | (f8, t8) =
101 | { apply = fun a ->
102 | [| a |> f1 |> (key_value t1).apply
103 | a |> f2 |> (key_value t2).apply
104 | a |> f3 |> (key_value t3).apply
105 | a |> f4 |> (key_value t4).apply
106 | a |> f5 |> (key_value t5).apply
107 | a |> f6 |> (key_value t6).apply
108 | a |> f7 |> (key_value t7).apply
109 | a |> f8 |> (key_value t8).apply |]
110 | |> commalist |> enbrace }
111 |
112 | module Ops =
113 | let key (name : string) value = KeyValuePair(name, value)
114 | let to_json t = t.apply
115 | let make_to_json f = { apply = f }
116 |
--------------------------------------------------------------------------------
/src/FSharpTypeclasses/to_json.fsi:
--------------------------------------------------------------------------------
1 | namespace FsharpTypeclasses
2 |
3 | open System.Collections.Generic
4 |
5 | (**
6 | The To_json.t<'a> typeclass, instances for primitive types, and instance
7 | builders for arbitrary (product) data types.
8 | *)
9 | module To_json =
10 | (**
11 | Essentially this is just a wrapper for a function which can convert a
12 | given type 'a to a JSON string. A good way to think of it is also as a
13 | proposition that a type 'a can be converted into a JSON string. The
14 | typeclass instances of the various types are proofs that those types
15 | can be converted into JSON strings.
16 | *)
17 | type t<'a>
18 | type key_value<'a> = KeyValuePair
19 |
20 | (* JSON converter instances for primitive types. *)
21 |
22 | val int : t
23 | val string : t
24 | val float : t
25 | val double : t
26 | val char : t
27 | val bool : t
28 | val option : t<'a> -> t