├── .github └── workflows │ ├── ci.yml │ └── clean.yml ├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── COPYING ├── README.md ├── benchmark ├── README.md └── src │ └── main │ └── scala │ └── algebra │ └── ExampleBenchmark.scala ├── build.sbt ├── core └── src │ ├── main │ └── scala │ │ └── algebra │ │ ├── Priority.scala │ │ ├── instances │ │ ├── StaticMethods.scala │ │ ├── all.scala │ │ ├── array.scala │ │ ├── bigDecimal.scala │ │ ├── bigInt.scala │ │ ├── bitSet.scala │ │ ├── boolean.scala │ │ ├── byte.scala │ │ ├── char.scala │ │ ├── double.scala │ │ ├── float.scala │ │ ├── int.scala │ │ ├── list.scala │ │ ├── long.scala │ │ ├── map.scala │ │ ├── option.scala │ │ ├── set.scala │ │ ├── short.scala │ │ ├── string.scala │ │ ├── tuple.scala │ │ └── unit.scala │ │ ├── lattice │ │ ├── Bool.scala │ │ ├── BoundedDistributiveLattice.scala │ │ ├── BoundedJoinSemilattice.scala │ │ ├── BoundedLattice.scala │ │ ├── BoundedMeetSemilattice.scala │ │ ├── DeMorgan.scala │ │ ├── DistributiveLattice.scala │ │ ├── GenBool.scala │ │ ├── Heyting.scala │ │ ├── JoinSemilattice.scala │ │ ├── Lattice.scala │ │ ├── Logic.scala │ │ └── MeetSemilattice.scala │ │ ├── package.scala │ │ └── ring │ │ ├── Additive.scala │ │ ├── BoolRing.scala │ │ ├── BoolRng.scala │ │ ├── CommutativeRig.scala │ │ ├── CommutativeRing.scala │ │ ├── CommutativeRng.scala │ │ ├── CommutativeSemiring.scala │ │ ├── Field.scala │ │ ├── Multiplicative.scala │ │ ├── Rig.scala │ │ ├── Ring.scala │ │ ├── Rng.scala │ │ └── Semiring.scala │ └── test │ └── scala │ └── algebra │ ├── Instances.scala │ └── ring │ └── RingTest.scala ├── docs └── docs │ └── main │ ├── resources │ └── microsite │ │ └── data │ │ └── menu.yml │ └── tut │ ├── cookbook.md │ ├── cookbook │ └── cookbook.md │ ├── faq.md │ ├── index.md │ ├── typeclasses.md │ └── typeclasses │ └── overview.md ├── laws ├── js │ └── src │ │ └── main │ │ └── scala │ │ └── algebra │ │ └── laws │ │ └── platform │ │ └── Platform.scala ├── jvm │ └── src │ │ └── main │ │ └── scala │ │ └── algebra │ │ └── laws │ │ └── platform │ │ └── Platform.scala ├── native │ └── src │ │ └── main │ │ └── scala │ │ └── algebra │ │ └── laws │ │ └── platform │ │ └── Platform.scala └── shared │ └── src │ ├── main │ └── scala │ │ └── algebra │ │ └── laws │ │ ├── BaseLaws.scala │ │ ├── CheckSupport.scala │ │ ├── DeMorganLaws.scala │ │ ├── GroupLaws.scala │ │ ├── IsSerializable.scala │ │ ├── LatticeLaws.scala │ │ ├── LatticePartialOrderLaws.scala │ │ ├── LogicLaws.scala │ │ ├── OrderLaws.scala │ │ ├── RingLaws.scala │ │ ├── Rules.scala │ │ └── package.scala │ └── test │ └── scala │ └── algebra │ └── laws │ ├── FPApprox.scala │ ├── LawTests.scala │ ├── Rat.scala │ ├── SimpleDeMorgan.scala │ └── SimpleHeyting.scala ├── project ├── Boilerplate.scala ├── build.properties └── plugins.sbt ├── sbt └── version.sbt /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by sbt-github-actions using the 2 | # githubWorkflowGenerate task. You should add and commit this file to 3 | # your git repository. It goes without saying that you shouldn't edit 4 | # this file by hand! Instead, if you wish to make changes, you should 5 | # change your sbt build configuration to revise the workflow description 6 | # to meet your needs, then regenerate this file. 7 | 8 | name: Continuous Integration 9 | 10 | on: 11 | pull_request: 12 | branches: ['*'] 13 | push: 14 | branches: ['*'] 15 | 16 | env: 17 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 18 | 19 | jobs: 20 | build: 21 | name: Build and Test 22 | strategy: 23 | fail-fast: false 24 | matrix: 25 | os: [ubuntu-latest] 26 | scala: [2.12.13, 2.13.6, 3.0.0] 27 | java: [adopt@1.8] 28 | platform: [jvm, js, native] 29 | exclude: 30 | - platform: native 31 | scala: 3.0.0 32 | runs-on: ${{ matrix.os }} 33 | steps: 34 | - name: Checkout current branch (full) 35 | uses: actions/checkout@v2 36 | with: 37 | fetch-depth: 0 38 | 39 | - name: Setup Java and Scala 40 | uses: olafurpg/setup-scala@v10 41 | with: 42 | java-version: ${{ matrix.java }} 43 | 44 | - name: Cache sbt 45 | uses: actions/cache@v2 46 | with: 47 | path: | 48 | ~/.sbt 49 | ~/.ivy2/cache 50 | ~/.coursier/cache/v1 51 | ~/.cache/coursier/v1 52 | ~/AppData/Local/Coursier/Cache/v1 53 | ~/Library/Caches/Coursier/v1 54 | key: ${{ runner.os }}-sbt-cache-v2-${{ hashFiles('**/*.sbt') }}-${{ hashFiles('project/build.properties') }} 55 | 56 | - name: Check that workflows are up to date 57 | run: sbt ++${{ matrix.scala }} githubWorkflowCheck 58 | 59 | - name: Validate Scala JS 60 | if: matrix.platform == 'js' 61 | run: sbt ++${{ matrix.scala }} coreJS/test lawsJS/test 62 | 63 | - name: Validate Scala Native 64 | if: matrix.platform == 'native' 65 | run: sbt ++${{ matrix.scala }} coreNative/test lawsNative/test 66 | 67 | - name: Validate Scala JVM 68 | if: matrix.platform == 'jvm' 69 | run: sbt ++${{ matrix.scala }} coreJVM/test lawsJVM/test benchmark/test 70 | 71 | microsite: 72 | name: Microsite 73 | strategy: 74 | matrix: 75 | os: [ubuntu-latest] 76 | scala: [2.12.13] 77 | java: [adopt@1.8] 78 | runs-on: ${{ matrix.os }} 79 | steps: 80 | - name: Checkout current branch (full) 81 | uses: actions/checkout@v2 82 | with: 83 | fetch-depth: 0 84 | 85 | - name: Setup Java and Scala 86 | uses: olafurpg/setup-scala@v10 87 | with: 88 | java-version: ${{ matrix.java }} 89 | 90 | - name: Cache sbt 91 | uses: actions/cache@v2 92 | with: 93 | path: | 94 | ~/.sbt 95 | ~/.ivy2/cache 96 | ~/.coursier/cache/v1 97 | ~/.cache/coursier/v1 98 | ~/AppData/Local/Coursier/Cache/v1 99 | ~/Library/Caches/Coursier/v1 100 | key: ${{ runner.os }}-sbt-cache-v2-${{ hashFiles('**/*.sbt') }}-${{ hashFiles('project/build.properties') }} 101 | 102 | - name: Setup Ruby 103 | uses: ruby/setup-ruby@v1 104 | with: 105 | ruby-version: 2.7 106 | 107 | - name: Setup Jekyll 108 | run: gem install jekyll -v 4.0.0 109 | 110 | - name: Build the microsite 111 | run: sbt ++${{ matrix.scala }} docs/makeMicrosite 112 | 113 | mima: 114 | name: MiMa 115 | strategy: 116 | matrix: 117 | os: [ubuntu-latest] 118 | scala: [2.12.13] 119 | java: [adopt@1.8] 120 | runs-on: ${{ matrix.os }} 121 | steps: 122 | - name: Checkout current branch (full) 123 | uses: actions/checkout@v2 124 | with: 125 | fetch-depth: 0 126 | 127 | - name: Setup Java and Scala 128 | uses: olafurpg/setup-scala@v10 129 | with: 130 | java-version: ${{ matrix.java }} 131 | 132 | - name: Cache sbt 133 | uses: actions/cache@v2 134 | with: 135 | path: | 136 | ~/.sbt 137 | ~/.ivy2/cache 138 | ~/.coursier/cache/v1 139 | ~/.cache/coursier/v1 140 | ~/AppData/Local/Coursier/Cache/v1 141 | ~/Library/Caches/Coursier/v1 142 | key: ${{ runner.os }}-sbt-cache-v2-${{ hashFiles('**/*.sbt') }}-${{ hashFiles('project/build.properties') }} 143 | 144 | - name: Run MiMa 145 | run: sbt ++${{ matrix.scala }} mimaReportBinaryIssues -------------------------------------------------------------------------------- /.github/workflows/clean.yml: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by sbt-github-actions using the 2 | # githubWorkflowGenerate task. You should add and commit this file to 3 | # your git repository. It goes without saying that you shouldn't edit 4 | # this file by hand! Instead, if you wish to make changes, you should 5 | # change your sbt build configuration to revise the workflow description 6 | # to meet your needs, then regenerate this file. 7 | 8 | name: Clean 9 | 10 | on: push 11 | 12 | jobs: 13 | delete-artifacts: 14 | name: Delete Artifacts 15 | runs-on: ubuntu-latest 16 | env: 17 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 18 | steps: 19 | - name: Delete artifacts 20 | run: | 21 | # Customize those three lines with your repository and credentials: 22 | REPO=${GITHUB_API_URL}/repos/${{ github.repository }} 23 | 24 | # A shortcut to call GitHub API. 25 | ghapi() { curl --silent --location --user _:$GITHUB_TOKEN "$@"; } 26 | 27 | # A temporary file which receives HTTP response headers. 28 | TMPFILE=/tmp/tmp.$$ 29 | 30 | # An associative array, key: artifact name, value: number of artifacts of that name. 31 | declare -A ARTCOUNT 32 | 33 | # Process all artifacts on this repository, loop on returned "pages". 34 | URL=$REPO/actions/artifacts 35 | while [[ -n "$URL" ]]; do 36 | 37 | # Get current page, get response headers in a temporary file. 38 | JSON=$(ghapi --dump-header $TMPFILE "$URL") 39 | 40 | # Get URL of next page. Will be empty if we are at the last page. 41 | URL=$(grep '^Link:' "$TMPFILE" | tr ',' '\n' | grep 'rel="next"' | head -1 | sed -e 's/.*.*//') 42 | rm -f $TMPFILE 43 | 44 | # Number of artifacts on this page: 45 | COUNT=$(( $(jq <<<$JSON -r '.artifacts | length') )) 46 | 47 | # Loop on all artifacts on this page. 48 | for ((i=0; $i < $COUNT; i++)); do 49 | 50 | # Get name of artifact and count instances of this name. 51 | name=$(jq <<<$JSON -r ".artifacts[$i].name?") 52 | ARTCOUNT[$name]=$(( $(( ${ARTCOUNT[$name]} )) + 1)) 53 | 54 | id=$(jq <<<$JSON -r ".artifacts[$i].id?") 55 | size=$(( $(jq <<<$JSON -r ".artifacts[$i].size_in_bytes?") )) 56 | printf "Deleting '%s' #%d, %'d bytes\n" $name ${ARTCOUNT[$name]} $size 57 | ghapi -X DELETE $REPO/actions/artifacts/$id 58 | done 59 | done -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | project/boot 2 | target 3 | .ensime 4 | .ensime_lucene 5 | TAGS 6 | \#*# 7 | *~ 8 | .#* 9 | .lib 10 | .history 11 | .*.swp 12 | .idea/ 13 | 14 | # Auto-copied by sbt-microsites 15 | docs/src/main/tut/contributing.md 16 | .bloop/ 17 | .metals/ 18 | .bsp/ 19 | project/metals.sbt 20 | project/project/ 21 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Algebra 2 | 3 | ### Version 0.6.1 (Not yet released) ### 4 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | We are committed to providing a friendly, safe and welcoming 4 | environment for all, regardless of level of experience, gender, gender 5 | identity and expression, sexual orientation, disability, personal 6 | appearance, body size, race, ethnicity, age, religion, nationality, or 7 | other such characteristics. 8 | 9 | Everyone is expected to follow the [Scala Code of Conduct] when 10 | discussing the project on the available communication channels. If you 11 | are being harassed, please contact us immediately so that we can 12 | support you. 13 | 14 | ## Moderation 15 | 16 | For any questions, concerns, or moderation requests please contact a 17 | member of the project. 18 | 19 | [Scala Code of Conduct]: https://typelevel.org/code-of-conduct.html 20 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: "Contributing" 4 | section: "contributing" 5 | position: 2 6 | --- 7 | 8 | # Contributing to Algebra 9 | 10 | This document is a guide to how to get started contributing to Algebra. 11 | 12 | ## Contributing Documentation 13 | 14 | Often the biggest issue facing open-source projects is a lack of good documentation, and Algebra is no exception here. If you have ideas for specific pieces of documentation which are absent, feel free to open a specific issue for that. 15 | 16 | The documentation for Algebra's website is stored in the `docs/src/main/tut` directory of the [docs subproject](https://github.com/typelevel/algebra/tree/master/docs). 17 | 18 | Algebra's documentation is powered by [sbt-microsites](https://47deg.github.io/sbt-microsites/) and [tut](https://github.com/tpolecat/tut). `tut` compiles any code that appears in the documentation, ensuring that snippets and examples won't go out of date. 19 | 20 | We also gladly accept patches for documentation. Each page has a link that will allow you to edit and submit a PR for a documentation change, right from the Github UI. Anything from fixing a typo to writing a full tutorial is a great way to help the project. 21 | 22 | ### Generating the Site 23 | 24 | run `sbt docs/makeMicrosite` to generate a local copy of the microsite. 25 | 26 | ### Previewing the site 27 | 28 | 1. Install jekyll locally, depending on your platform, you might do this with any of the following commands: 29 | 30 | ``` 31 | yum install jekyll 32 | apt-get install jekyll 33 | gem install jekyll 34 | ``` 35 | 36 | 2. In a shell, navigate to the generated site directory in `docs/target/site` 37 | 3. Start jekyll with `jekyll serve --incremental` 38 | 4. Navigate to http://127.0.0.1:4000/algebird/ in your browser 39 | 5. Make changes to your site, and run `sbt docs/makeMicrosite` to regenerate the site. The changes should be reflected as soon as `sbt docs/makeMicrosite` completes. 40 | 41 | ## Post-release 42 | 43 | After the release occurs, you will need to update the documentation. Here is a list of the places that will definitely need to be updated: 44 | 45 | * `README.md`: update version numbers 46 | * `CHANGES.md`: summarize changes since last release 47 | 48 | (Other changes may be necessary, especially for large releases.) 49 | 50 | You can get a list of changes between release tags `v0.6.0` and `v0.6.1` via `git log v0.6.0..v0.6.1`. Scanning this list of commit messages is a good way to get a summary of what happened, although it does not account for conversations that occured on Github. (You can see the same view on the Github UI by navigating to .) 51 | 52 | Once the relevant documentation changes have been committed, you should add new [release notes](https://github.com/typelevel/algebra/releases). You can add a release by clicking the "Draft a new release" button on that page, or if the relevant release already exists, you can click "Edit release". 53 | 54 | Finally, update the website via `sbt docs/publishMicrosite`. 55 | 56 | ## Reporting bugs, issues, or unexpected behavior 57 | 58 | If you encounter anything that is broken, confusing, or could be better, you should [open an issue](https://github.com/typelevel/algebra/issues). You don't have to know *why* the error is occuring, or even that an error happens at all. 59 | 60 | If you are trying to do something with Algebra, and are having a hard time, it could be any of the following issues: 61 | 62 | * an actual bug or error 63 | * an omission or problem with the API 64 | * a confusing edge case 65 | * a documentation problem 66 | 67 | Feel free to open a bug before you're sure which of these is happening. You can also ask questions on the [Gitter channel](https://gitter.im/non/algebra?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) to get other people's opinions. 68 | 69 | ## Creating or improving tests 70 | 71 | Algebra uses [ScalaTest](www.scalatest.org) and [ScalaCheck](http://scalacheck.org/) to test our code. The tests fulfill a number of important functions: 72 | 73 | * ensure our algorithms return correct results 74 | * check the visibility of our type class instances 75 | * confirm that the API works as we expect 76 | * test edge cases which might otherwise be missed 77 | 78 | If you find a bug you are also encouraged to submit a test case (the code you tried that failed). Adding these failing cases to Algebra's tests provides a good way to ensure the bug is really fixed, and is also a good opportunity to start contributing. 79 | 80 | ALso, when you notice places that lack tests (or where the tests are sparse, incomplete, or just ugly) feel free to submit a pull request with improvements! 81 | 82 | ### What need testing the most? 83 | 84 | The best way to figure out where we need tests is to look at our [Codecov coverage report](https://codecov.io/github/typelevel/algebra). Codecov has an incredible [browser extension](http://docs.codecov.io/docs/browser-extension) that will embed coverage information right into the GitHub file browsing UI. 85 | 86 | Once you've got the extension installed, navigate to the any of the folders in the [algebra package index](https://github.com/typelevel/algebra/tree/master/core/src/main/scala/algebra) to see file-by-file coverage percentages. Inside each file, lines that aren't covered by tests will be highlighted red. 87 | 88 | ## Submitting patches or code 89 | 90 | Algebra is on Github to make it easy to fork the code and change it. There are very few requirements but here are some suggestions for what makes a good pull request. 91 | 92 | Please make pull requests against the `master` branch. If you're writing a small amount of code to fix a bug, feel free to just open a pull request immediately. You can even attach some code snippets to the issue if that's easier. 93 | 94 | For adding new code to Algebra, it's often smart to create a topic branch that can be used to collaborate on the design. Features that require a lot of code, or which represent a big change to Algebra, tend not to get merged to master as quickly. For this kind of work, you should submit a pull request from your branch, but we will probably leave the PR open for awhile while commenting on it. 95 | 96 | You can always email the list, or visit the [Gitter channel](https://gitter.im/non/algebra?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) to get a second opinion on your idea or design. 97 | 98 | ## Ask questions and make suggestions 99 | 100 | Algebra strives to be an excellent part of the Scala ecosystem. We welcome your questions about how Algebra works now, and your ideas for how to make it even better! 101 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Algebra Copyright (c) 2016. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://api.travis-ci.org/typelevel/algebra.png)](https://travis-ci.org/typelevel/algebra/) 2 | [![Coverage status](https://img.shields.io/codecov/c/github/typelevel/algebra/master.svg)](https://codecov.io/github/typelevel/algebra) 3 | [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/non/algebra?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 4 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/org.typelevel/algebra_2.11/badge.svg)](https://maven-badges.herokuapp.com/maven-central/org.typelevel/algebra_2.11) 5 | 6 | # algebra 7 | 8 | ## current status 9 | 10 | The code in this repository was moved into Cats in [cats#3918](https://github.com/typelevel/cats/pull/3918). 11 | All future development will occur there. 12 | Algebra will continue to release under the same artifact name and maintain binary compatibility with the 2.x series. 13 | 14 | ## the vision 15 | 16 | This repo represents an attempt to unify the basic algebraic type 17 | classes from [Spire](http://github.com/non/spire) and 18 | [Algebird](http://github.com/twitter/algebird). By targeting just 19 | these type classes, we will distribute an `algebra` package with no 20 | dependencies (except for cats-kernel) that works with Scala 2.11 and 21 | 2.12, which can be shared by all Scala libraries interested in abstract 22 | algebra. 23 | 24 | Since the creation of Algebra, we have also decided to interoperate 25 | with the [Cats](http://github.com/typelevel/cats) project. Algebra and 26 | Cats interoperate using the *cats-kernel* module. 27 | 28 | See the [Algebra website](https://typelevel.org/algebra) for more information. The latest API docs are hosted at Algebra's [ScalaDoc index](https://typelevel.org/algebra/api/). 29 | 30 | ## getting algebra 31 | 32 | Algebra supports Scala 2.11, 2.12 and 2.13 and is available from 33 | Sonatype (and Maven Central). In addition to the JVM, Algebra also 34 | supports Scala.js. 35 | 36 | To use algebra in your own projects, include this snippet in your 37 | `build.sbt` file: 38 | 39 | ```scala 40 | libraryDependencies += "org.typelevel" %% "algebra" % "2.0.0" 41 | ``` 42 | 43 | If you want to use Algebra's laws, you can include those as well with 44 | this snippet: 45 | 46 | ```scala 47 | libraryDependencies += "org.typelevel" %% "algebra-laws" % "2.0.0" 48 | ``` 49 | 50 | ## what we have so far 51 | 52 | This repo has been seeded with most of the contents of 53 | `spire.algebra`, and has been modified to fit an 54 | initially-conservative and shared vision for algebraic type classes. 55 | Discussions over removals and additions should take place on the issue 56 | tracker or on relevant pull requests. 57 | 58 | ## Projects using Algebra 59 | 60 | - [Algebird](http://github.com/twitter/algebird) 61 | - [Spire](https://github.com/non/spire) 62 | 63 | ## participants 64 | 65 | Anyone who wants to participate should feel free to open issues or 66 | send pull requests to this repo. 67 | 68 | The following people are Algebra maintainers (i.e. have push access): 69 | 70 | * [Oscar Boykin](https://github.com/johnynek) 71 | * [Avi Bryant](https://github.com/avibryant) 72 | * [Lars Hupel](https://github.com/larsrh) 73 | * [Rüdiger Klaehn](https://github.com/rklaehn) 74 | * [Tomas Mikula](https://github.com/tomasmikula) 75 | * [Erik Osheim](https://github.com/non) 76 | * [Tom Switzer](https://github.com/tixxit) 77 | 78 | ## development process 79 | 80 | Please make a pull request against the `master` branch. For those who 81 | can merge pull requests, we follow these rules: 82 | 83 | 1. Do not merge your own PR unless *N* people have signed-off on the 84 | PR (e.g. given a thumbs-up, +1, shipit, etc) and Travis is green. 85 | 86 | 2. If you are not the author, and you see *(N - 1)* sign-offs and 87 | Travis is green, just click merge and delete the branch. 88 | 89 | 3. Currently, *N* = *2*. 90 | 91 | ## algebra overview 92 | 93 | Algebra uses type classes to represent algebraic structures. You can 94 | use these type classes to represent the abstract capabilities (and 95 | requirements) you want generic parameters to possess. 96 | 97 | For a tour of the Algebraic structures that `algebra` offers, see the 98 | extensive 99 | [overview on the Algebra website](https://typelevel.org/algebra/typeclasses/overview.html). 100 | 101 | ### Code of Conduct 102 | 103 | See the [Code of Conduct](CODE_OF_CONDUCT.md) 104 | 105 | ### Copyright and License 106 | 107 | All code is available to you under the MIT license, available at 108 | http://opensource.org/licenses/mit-license.php and also in the 109 | [COPYING](COPYING) file. 110 | 111 | Copyright the maintainers, 2015-2016. 112 | -------------------------------------------------------------------------------- /benchmark/README.md: -------------------------------------------------------------------------------- 1 | # algebra-benchmark 2 | 3 | [jmh](http://openjdk.java.net/projects/code-tools/jmh/)-based Benchmarks for Algebra typeclasses and data structures. 4 | 5 | ## Usage 6 | 7 | Run the following commands from the top-level Algebra directory: 8 | 9 | $ ./sbt # <<< enter sbt REPL 10 | > project benchmark 11 | 12 | Now you can run the following commands from within the sbt REPL: 13 | 14 | # List available benchmarks 15 | > jmh:run -l 16 | 17 | # Run a particular benchmark 18 | > jmh:run -t1 -f1 -wi 2 -i 3 .*ExampleBenchmark.* 19 | 20 | # Run all benchmarks 21 | > jmh:run .* 22 | 23 | These options tell JMH to run the benchmark with 1 thread (`-t1`), 1 fork (`-f1`), 2 warmup iterations and 3 real iterations. You can find further details in the [sbt-jmh](https://github.com/ktoso/sbt-jmh) documentation. 24 | 25 | Example output from `jmh:run -t1 -f1 -wi 2 -i 3 .*ExampleBenchmark.*`: 26 | 27 | ``` 28 | [info] # Run complete. Total time: 00:00:11 29 | [info] 30 | [info] Benchmark (numElements) Mode Cnt Score Error Units 31 | [info] ExampleBenchmark.timeFoldLeft 10000 thrpt 3 20730.607 ± 4294.652 ops/s 32 | [info] ExampleBenchmark.timeReduce 10000 thrpt 3 20032.976 ± 45474.075 ops/s 33 | ``` 34 | 35 | ## Writing Benchmarks 36 | 37 | Please use `ExampleBenchmark` as a template! 38 | -------------------------------------------------------------------------------- /benchmark/src/main/scala/algebra/ExampleBenchmark.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | package benchmark 3 | 4 | import scala.util.Random 5 | import org.openjdk.jmh.annotations._ 6 | import org.openjdk.jmh.infra.Blackhole 7 | 8 | object ExampleBenchmark { 9 | @State(Scope.Benchmark) 10 | class ExampleState { 11 | @Param(Array("10000")) 12 | var numElements: Int = 0 13 | 14 | var inputData: Seq[Long] = _ 15 | 16 | @Setup(Level.Trial) 17 | def setup(): Unit = { 18 | inputData = Seq.fill(numElements)(Random.nextInt(1000).toLong) 19 | } 20 | } 21 | } 22 | 23 | class ExampleBenchmark { 24 | import ExampleBenchmark._ 25 | 26 | @Benchmark 27 | def timeReduce(state: ExampleState, bh: Blackhole) = 28 | bh.consume(state.inputData.reduce(_ + _)) 29 | 30 | @Benchmark 31 | def timeFoldLeft(state: ExampleState, bh: Blackhole) = 32 | bh.consume(state.inputData.foldLeft(0L)(_ + _)) 33 | } 34 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | import sbtrelease.Utilities._ 2 | import ReleaseTransformations._ 3 | import microsites.ExtraMdFileConfig 4 | import sbtcrossproject.CrossPlugin.autoImport.{crossProject, CrossType} 5 | import com.typesafe.tools.mima.core._ 6 | 7 | lazy val catsVersion = "2.6.1" 8 | lazy val mUnit = "0.7.26" 9 | lazy val disciplineMUnit = "1.0.9" 10 | 11 | val Scala212 = "2.12.13" 12 | val Scala213 = "2.13.6" 13 | val Scala300 = "3.0.0" 14 | 15 | ThisBuild / crossScalaVersions := Seq(Scala212, Scala213, Scala300) 16 | ThisBuild / scalaVersion := Scala213 17 | 18 | ThisBuild / githubWorkflowBuildMatrixAdditions += 19 | "platform" -> List("jvm", "js", "native") 20 | 21 | ThisBuild / githubWorkflowBuildMatrixExclusions += 22 | MatrixExclude(Map("platform" -> "native", "scala" -> Scala300)) 23 | 24 | val JvmCond = s"matrix.platform == 'jvm'" 25 | val JsCond = s"matrix.platform == 'js'" 26 | val NativeCond = s"matrix.platform == 'native'" 27 | 28 | ThisBuild / githubWorkflowBuildMatrixFailFast := Some(false) 29 | ThisBuild / githubWorkflowArtifactUpload := false 30 | 31 | ThisBuild / githubWorkflowPublishTargetBranches := Seq() 32 | 33 | ThisBuild / githubWorkflowBuild := Seq( 34 | WorkflowStep.Sbt(List("coreJS/test", "lawsJS/test"), name = Some("Validate Scala JS"), cond = Some(JsCond)), 35 | WorkflowStep.Sbt(List("coreNative/test", "lawsNative/test"), name = Some("Validate Scala Native"), cond = Some(NativeCond)), 36 | WorkflowStep.Sbt(List("coreJVM/test", "lawsJVM/test", "benchmark/test"), name = Some("Validate Scala JVM"), cond = Some(JvmCond)) 37 | ) 38 | 39 | ThisBuild / githubWorkflowAddedJobs ++= Seq( 40 | WorkflowJob( 41 | "microsite", 42 | "Microsite", 43 | githubWorkflowJobSetup.value.toList ::: List( 44 | WorkflowStep.Use(UseRef.Public("ruby", "setup-ruby", "v1"), Map("ruby-version" -> "2.7"), name = Some("Setup Ruby")), 45 | WorkflowStep.Run(List("gem install jekyll -v 4.0.0"), name = Some("Setup Jekyll")), 46 | WorkflowStep.Sbt(List("docs/makeMicrosite"), name = Some("Build the microsite")) 47 | ), 48 | scalas = List(Scala212) 49 | ), 50 | WorkflowJob( 51 | "mima", 52 | "MiMa", 53 | githubWorkflowJobSetup.value.toList ::: List( 54 | WorkflowStep.Sbt(List("mimaReportBinaryIssues"), name = Some("Run MiMa")) 55 | ), 56 | scalas = List(Scala212) 57 | ) 58 | ) 59 | 60 | 61 | lazy val buildSettings = Seq( 62 | organization := "org.typelevel" 63 | ) 64 | 65 | lazy val commonSettings = Seq( 66 | scalacOptions ++= Seq( 67 | "-deprecation", 68 | "-encoding", "UTF-8", 69 | "-feature", 70 | "-language:existentials", 71 | "-language:higherKinds", 72 | "-language:implicitConversions", 73 | "-unchecked", 74 | "-Xlint", 75 | "-Ywarn-dead-code", 76 | "-Ywarn-numeric-widen", 77 | //"-Ywarn-value-discard", // fails with @sp on Unit 78 | ) ++ (CrossVersion.partialVersion(scalaVersion.value) match { 79 | case Some((2, v)) if v <= 12 => Seq("-Ywarn-unused-import") 80 | case _ => Seq.empty 81 | }), 82 | scalacOptions ++= { 83 | CrossVersion.partialVersion(scalaVersion.value) match { 84 | case Some((2, v)) if v <= 12 => 85 | Seq( 86 | "-Xfuture", 87 | "-Xfatal-warnings", 88 | "-Yno-adapted-args" 89 | ) 90 | case _ => 91 | Nil 92 | } 93 | }, 94 | resolvers += Resolver.sonatypeRepo("public"), 95 | Compile / console / scalacOptions ~= (_ filterNot (_ == "-Ywarn-unused-import")), 96 | Test / console / scalacOptions := (Compile / console / scalacOptions).value, 97 | Global / scalaJSStage := FastOptStage, 98 | jsEnv := new org.scalajs.jsenv.nodejs.NodeJSEnv(), 99 | fork := false, 100 | Test / parallelExecution := false 101 | ) 102 | 103 | lazy val algebraSettings = buildSettings ++ commonSettings ++ publishSettings 104 | 105 | lazy val nativeSettings = Seq( 106 | crossScalaVersions ~= (_.filterNot(Scala300.contains)), 107 | publishConfiguration := publishConfiguration.value.withOverwrite(true) // needed since we double-publish on release 108 | ) 109 | 110 | lazy val docsMappingsAPIDir = 111 | settingKey[String]("Name of subdirectory in site target directory for api docs") 112 | 113 | lazy val docSettings = Seq( 114 | micrositeName := "Algebra", 115 | micrositeDescription := "Algebraic Typeclasses for Scala.", 116 | micrositeAuthor := "Algebra's contributors", 117 | micrositeHighlightTheme := "atom-one-light", 118 | micrositeHomepage := "https://typelevel.org/algebra/", 119 | micrositeBaseUrl := "algebra", 120 | micrositeDocumentationUrl := "api/", 121 | micrositeGithubOwner := "typelevel", 122 | micrositeExtraMdFiles := Map(file("CONTRIBUTING.md") -> ExtraMdFileConfig("contributing.md", "page")), // TODO check layout 123 | micrositeGithubRepo := "algebra", 124 | autoAPIMappings := true, 125 | ScalaUnidoc / unidoc / unidocProjectFilter := inProjects(coreJVM, lawsJVM), 126 | docsMappingsAPIDir := "api", 127 | addMappingsToSiteDir(ScalaUnidoc / packageDoc / mappings, docsMappingsAPIDir), 128 | git.remoteRepo := "git@github.com:typelevel/algebra.git", 129 | ghpagesNoJekyll := false, 130 | mdoc / fork := true, 131 | ScalaUnidoc / unidoc / fork := true, 132 | ScalaUnidoc / unidoc / scalacOptions ++= Seq( 133 | "-doc-source-url", "https://github.com/typelevel/algebra/tree/master€{FILE_PATH}.scala", 134 | "-sourcepath", (LocalRootProject / baseDirectory).value.getAbsolutePath, 135 | "-diagrams" 136 | ), 137 | makeSite / includeFilter := "*.html" | "*.css" | "*.png" | "*.jpg" | "*.gif" | "*.js" | "*.swf" | "*.yml" | "*.md" 138 | ) 139 | 140 | lazy val docs = project.in(file("docs")) 141 | .enablePlugins(MdocPlugin) 142 | .enablePlugins(GhpagesPlugin) 143 | .enablePlugins(MicrositesPlugin) 144 | .enablePlugins(ScalaUnidocPlugin) 145 | .settings(moduleName := "algebra-docs") 146 | .settings(algebraSettings: _*) 147 | .settings(noPublishSettings: _*) 148 | .settings(docSettings) 149 | .dependsOn(coreJVM, lawsJVM) 150 | 151 | lazy val aggregate = project.in(file(".")) 152 | .enablePlugins(GhpagesPlugin) 153 | .enablePlugins(ScalaUnidocPlugin) 154 | .disablePlugins(MimaPlugin) 155 | .settings(algebraSettings: _*) 156 | .settings(noPublishSettings: _*) 157 | .settings(docSettings: _*) 158 | .aggregate(coreJVM, lawsJVM, benchmark) 159 | .dependsOn(coreJVM, lawsJVM) 160 | .aggregate(coreJS, lawsJS) 161 | .dependsOn(coreJS, lawsJS) 162 | .aggregate(coreNative, lawsNative) 163 | .dependsOn(coreNative, lawsNative) 164 | 165 | val binaryCompatibleVersions = Set( 166 | "1.0.0", 167 | "1.0.1", 168 | "2.0.0", 169 | "2.0.1", 170 | "2.1.0", 171 | "2.1.1" 172 | ) 173 | 174 | /** 175 | * Empty this each time we publish a new version (and bump the minor number) 176 | */ 177 | val ignoredABIProblems = { 178 | import com.typesafe.tools.mima.core._ 179 | import com.typesafe.tools.mima.core.ProblemFilters._ 180 | Seq( 181 | exclude[ReversedMissingMethodProblem]("algebra.ring.RingFunctions.defaultFromDouble"), 182 | exclude[IncompatibleSignatureProblem]("algebra.instances.all.package.catsKernelStdOrderForChar"), 183 | exclude[IncompatibleSignatureProblem]("algebra.instances.char.package.catsKernelStdOrderForChar") 184 | ) 185 | } 186 | 187 | lazy val core = crossProject(JSPlatform, NativePlatform, JVMPlatform) 188 | .crossType(CrossType.Pure) 189 | .enablePlugins(MimaPlugin) 190 | .settings(moduleName := "algebra") 191 | .settings( 192 | mimaPreviousArtifacts := binaryCompatibleVersions.map(v => "org.typelevel" %% "algebra" % v), 193 | mimaBinaryIssueFilters ++= ignoredABIProblems, 194 | testFrameworks += new TestFramework("munit.Framework"), 195 | libraryDependencies ++= Seq( 196 | "org.typelevel" %%% "cats-kernel" % catsVersion, 197 | "org.typelevel" %%% "discipline-munit" % disciplineMUnit % Test, 198 | "org.scalameta" %%% "munit" % mUnit % Test 199 | ) 200 | ) 201 | .settings(algebraSettings: _*) 202 | .jsSettings(scalaJSLinkerConfig ~= (_.withModuleKind(ModuleKind.CommonJSModule))) 203 | .settings(Compile / sourceGenerators += (Compile / sourceManaged).map(Boilerplate.gen).taskValue) 204 | .nativeSettings(nativeSettings) 205 | 206 | lazy val coreJVM = core.jvm 207 | lazy val coreJS = core.js 208 | lazy val coreNative = core.native 209 | 210 | lazy val laws = crossProject(JSPlatform, NativePlatform, JVMPlatform) 211 | .crossType(CrossType.Full) 212 | .enablePlugins(MimaPlugin) 213 | .dependsOn(core) 214 | .settings( 215 | moduleName := "algebra-laws", 216 | mimaPreviousArtifacts := binaryCompatibleVersions.map(v => "org.typelevel" %% "algebra-laws" % v), 217 | testFrameworks += new TestFramework("munit.Framework"), 218 | libraryDependencies ++= Seq( 219 | "org.typelevel" %%% "cats-kernel-laws" % catsVersion, 220 | "org.typelevel" %%% "discipline-munit" % disciplineMUnit % Test, 221 | "org.scalameta" %%% "munit" % mUnit % Test 222 | ) 223 | ) 224 | .jsSettings(scalaJSLinkerConfig ~= (_.withModuleKind(ModuleKind.CommonJSModule))) 225 | .nativeSettings(nativeSettings) 226 | .settings(algebraSettings: _*) 227 | 228 | lazy val lawsJVM = laws.jvm 229 | lazy val lawsJS = laws.js 230 | lazy val lawsNative = laws.native 231 | 232 | lazy val benchmark = project.in(file("benchmark")) 233 | .settings( 234 | moduleName := "algebra-benchmark" 235 | ) 236 | .enablePlugins(JmhPlugin) 237 | .disablePlugins(MimaPlugin) 238 | .settings(JmhPlugin.projectSettings:_*) 239 | .settings(noPublishSettings: _*) 240 | .settings(algebraSettings: _*) 241 | .dependsOn(coreJVM) 242 | 243 | lazy val publishSettings = Seq( 244 | homepage := Some(url("http://typelevel.org/algebra")), 245 | licenses := Seq("MIT" -> url("http://opensource.org/licenses/MIT")), 246 | autoAPIMappings := true, 247 | apiURL := Some(url("https://typelevel.org/algebra/api/")), 248 | 249 | releaseCrossBuild := true, 250 | releasePublishArtifactsAction := PgpKeys.publishSigned.value, 251 | publishMavenStyle := true, 252 | Test / publishArtifact := false, 253 | pomIncludeRepository := Function.const(false), 254 | publishTo := { 255 | val nexus = "https://oss.sonatype.org/" 256 | if (isSnapshot.value) 257 | Some("Snapshots" at nexus + "content/repositories/snapshots") 258 | else 259 | Some("Releases" at nexus + "service/local/staging/deploy/maven2") 260 | }, 261 | pomExtra := ( 262 | 263 | 264 | johnynek 265 | P. Oscar Boykin 266 | https://github.com/johnynek/ 267 | 268 | 269 | avibryant 270 | Avi Bryant 271 | https://github.com/avibryant/ 272 | 273 | 274 | non 275 | Erik Osheim 276 | http://github.com/non/ 277 | 278 | 279 | tixxit 280 | Tom Switzer 281 | http://github.com/tixxit/ 282 | 283 | 284 | ), 285 | releaseProcess := Seq[ReleaseStep]( 286 | inquireVersions, 287 | runTest, 288 | setReleaseVersion, 289 | commitReleaseVersion, 290 | tagRelease, 291 | releaseStepCommandAndRemaining("+publishSigned"), 292 | setNextVersion, 293 | commitNextVersion, 294 | releaseStepCommand("sonatypeReleaseAll"), 295 | pushChanges 296 | ) 297 | ) 298 | 299 | addCommandAlias("validate", ";compile;test;unidoc") 300 | 301 | // Base Build Settings 302 | 303 | lazy val noPublishSettings = Seq( 304 | publish := {}, 305 | publishLocal := {}, 306 | publishArtifact := false 307 | ) 308 | 309 | def crossVersionSharedSources() = 310 | Seq(Compile, Test).map { sc => 311 | (sc / unmanagedSourceDirectories) ++= { 312 | (sc / unmanagedSourceDirectories).value.map { 313 | dir:File => new File(dir.getPath + "_" + scalaBinaryVersion.value) 314 | } 315 | } 316 | } 317 | 318 | addCommandAlias("gitSnapshots", ";set version in ThisBuild := git.gitDescribedVersion.value.get + \"-SNAPSHOT\"") 319 | 320 | // For Travis CI - see http://www.cakesolutions.net/teamblogs/publishing-artefacts-to-oss-sonatype-nexus-using-sbt-and-travis-ci 321 | credentials ++= (for { 322 | username <- Option(System.getenv().get("SONATYPE_USERNAME")) 323 | password <- Option(System.getenv().get("SONATYPE_PASSWORD")) 324 | } yield Credentials("Sonatype Nexus Repository Manager", "oss.sonatype.org", username, password)).toSeq 325 | 326 | credentials += Credentials( 327 | Option(System.getProperty("build.publish.credentials")) map (new File(_)) getOrElse (Path.userHome / ".ivy2" / ".credentials") 328 | ) 329 | -------------------------------------------------------------------------------- /core/src/main/scala/algebra/Priority.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | 3 | /** 4 | * Priority is a type class for prioritized implicit search. 5 | * 6 | * This type class will attempt to provide an implicit instance of `P` 7 | * (the preferred type). If that type is not available it will 8 | * fallback to `F` (the fallback type). If neither type is available 9 | * then a `Priority[P, F]` instance will not be available. 10 | * 11 | * This type can be useful for problems where multiple algorithms can 12 | * be used, depending on the type classes available. 13 | */ 14 | sealed trait Priority[+P, +F] { 15 | 16 | import Priority.{Preferred, Fallback} 17 | 18 | def fold[B](f1: P => B)(f2: F => B): B = 19 | this match { 20 | case Preferred(x) => f1(x) 21 | case Fallback(y) => f2(y) 22 | } 23 | 24 | def join[U >: P with F]: U = 25 | fold(_.asInstanceOf[U])(_.asInstanceOf[U]) 26 | 27 | def bimap[P2, F2](f1: P => P2)(f2: F => F2): Priority[P2, F2] = 28 | this match { 29 | case Preferred(x) => Preferred(f1(x)) 30 | case Fallback(y) => Fallback(f2(y)) 31 | } 32 | 33 | def toEither: Either[P, F] = 34 | fold[Either[P, F]](p => Left(p))(f => Right(f)) 35 | 36 | def isPreferred: Boolean = 37 | fold(_ => true)(_ => false) 38 | 39 | def isFallback: Boolean = 40 | fold(_ => false)(_ => true) 41 | 42 | def getPreferred: Option[P] = 43 | fold[Option[P]](p => Some(p))(_ => None) 44 | 45 | def getFallback: Option[F] = 46 | fold[Option[F]](_ => None)(f => Some(f)) 47 | } 48 | 49 | object Priority extends FindPreferred { 50 | 51 | case class Preferred[P](get: P) extends Priority[P, Nothing] 52 | case class Fallback[F](get: F) extends Priority[Nothing, F] 53 | 54 | def apply[P, F](implicit ev: Priority[P, F]): Priority[P, F] = ev 55 | } 56 | 57 | private[algebra] trait FindPreferred extends FindFallback { 58 | implicit def preferred[P](implicit ev: P): Priority[P, Nothing] = 59 | Priority.Preferred(ev) 60 | } 61 | 62 | private[algebra] trait FindFallback { 63 | implicit def fallback[F](implicit ev: F): Priority[Nothing, F] = 64 | Priority.Fallback(ev) 65 | } 66 | -------------------------------------------------------------------------------- /core/src/main/scala/algebra/instances/StaticMethods.scala: -------------------------------------------------------------------------------- 1 | package algebra.instances 2 | 3 | import scala.annotation.tailrec 4 | 5 | object StaticMethods { 6 | 7 | /** 8 | * Exponentiation function, e.g. x^y 9 | * 10 | * If base^ex doesn't fit in a Long, the result will overflow (unlike 11 | * Math.pow which will return +/- Infinity). 12 | */ 13 | final def pow(base: Long, exponent: Long): Long = { 14 | @tailrec def loop(t: Long, b: Long, e: Long): Long = 15 | if (e == 0L) t 16 | else if ((e & 1) == 1) loop(t * b, b * b, e >>> 1L) 17 | else loop(t, b * b, e >>> 1L) 18 | 19 | if (exponent >= 0L) loop(1L, base, exponent) else { 20 | if(base == 0L) throw new ArithmeticException("zero can't be raised to negative power") 21 | else if (base == 1L) 1L 22 | else if (base == -1L) if ((exponent & 1L) == 0L) -1L else 1L 23 | else 0L 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /core/src/main/scala/algebra/instances/all.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | package instances 3 | 4 | package object all extends AllInstances 5 | 6 | trait AllInstances 7 | extends ArrayInstances 8 | with BigDecimalInstances 9 | with BigIntInstances 10 | with BitSetInstances 11 | with BooleanInstances 12 | with ByteInstances 13 | with CharInstances 14 | with DoubleInstances 15 | with FloatInstances 16 | with IntInstances 17 | with ListInstances 18 | with LongInstances 19 | with MapInstances 20 | with OptionInstances 21 | with SetInstances 22 | with ShortInstances 23 | with StringInstances 24 | with TupleInstances 25 | with UnitInstances 26 | -------------------------------------------------------------------------------- /core/src/main/scala/algebra/instances/array.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | package instances 3 | 4 | import scala.{ specialized => sp } 5 | 6 | package object array extends ArrayInstances 7 | 8 | trait ArrayInstances { 9 | implicit def arrayEq[@sp A: Eq]: Eq[Array[A]] = 10 | new ArrayEq[A] 11 | implicit def arrayOrder[@sp A: Order]: Order[Array[A]] = 12 | new ArrayOrder[A] 13 | implicit def arrayPartialOrder[@sp A: PartialOrder]: PartialOrder[Array[A]] = 14 | new ArrayPartialOrder[A] 15 | } 16 | 17 | private object ArraySupport { 18 | 19 | private def signum(x: Int): Int = 20 | if(x < 0) -1 21 | else if(x > 0) 1 22 | else 0 23 | 24 | def eqv[@sp A](x: Array[A], y: Array[A])(implicit ev: Eq[A]): Boolean = { 25 | var i = 0 26 | if (x.length != y.length) return false 27 | while (i < x.length && i < y.length && ev.eqv(x(i), y(i))) i += 1 28 | i == x.length 29 | } 30 | 31 | def compare[@sp A](x: Array[A], y: Array[A])(implicit ev: Order[A]): Int = { 32 | var i = 0 33 | while (i < x.length && i < y.length) { 34 | val cmp = ev.compare(x(i), y(i)) 35 | if (cmp != 0) return cmp 36 | i += 1 37 | } 38 | signum(x.length - y.length) 39 | } 40 | 41 | def partialCompare[@sp A](x: Array[A], y: Array[A])(implicit ev: PartialOrder[A]): Double = { 42 | var i = 0 43 | while (i < x.length && i < y.length) { 44 | val cmp = ev.partialCompare(x(i), y(i)) 45 | // Double.NaN is also != 0.0 46 | if (cmp != 0.0) return cmp 47 | i += 1 48 | } 49 | signum(x.length - y.length).toDouble 50 | } 51 | } 52 | 53 | private final class ArrayEq[@sp A: Eq] extends Eq[Array[A]] with Serializable { 54 | def eqv(x: Array[A], y: Array[A]): Boolean = 55 | ArraySupport.eqv(x, y) 56 | } 57 | 58 | private final class ArrayOrder[@sp A: Order] 59 | extends Order[Array[A]] with Serializable { 60 | override def eqv(x: Array[A], y: Array[A]): Boolean = 61 | ArraySupport.eqv(x, y) 62 | def compare(x: Array[A], y: Array[A]): Int = 63 | ArraySupport.compare(x, y) 64 | } 65 | 66 | private final class ArrayPartialOrder[@sp A: PartialOrder] 67 | extends PartialOrder[Array[A]] with Serializable { 68 | override def eqv(x: Array[A], y: Array[A]): Boolean = 69 | ArraySupport.eqv(x, y) 70 | override def partialCompare(x: Array[A], y: Array[A]): Double = 71 | ArraySupport.partialCompare(x, y) 72 | } 73 | -------------------------------------------------------------------------------- /core/src/main/scala/algebra/instances/bigDecimal.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | package instances 3 | 4 | import java.math.MathContext 5 | 6 | import algebra.ring._ 7 | 8 | package object bigDecimal extends BigDecimalInstances 9 | 10 | trait BigDecimalInstances extends cats.kernel.instances.BigDecimalInstances { 11 | implicit val bigDecimalAlgebra: BigDecimalAlgebra = new BigDecimalAlgebra() 12 | } 13 | 14 | class BigDecimalAlgebra(mc: MathContext) extends Field[BigDecimal] with Serializable { 15 | def this() = this(MathContext.UNLIMITED) 16 | 17 | val zero: BigDecimal = BigDecimal(0, mc) 18 | val one: BigDecimal = BigDecimal(1, mc) 19 | 20 | def plus(a: BigDecimal, b: BigDecimal): BigDecimal = a + b 21 | def negate(a: BigDecimal): BigDecimal = -a 22 | override def minus(a: BigDecimal, b: BigDecimal): BigDecimal = a - b 23 | 24 | def times(a: BigDecimal, b: BigDecimal): BigDecimal = a * b 25 | def div(a: BigDecimal, b: BigDecimal): BigDecimal = a / b 26 | 27 | override def pow(a: BigDecimal, k: Int): BigDecimal = a pow k 28 | 29 | override def fromInt(n: Int): BigDecimal = BigDecimal(n, mc) 30 | override def fromBigInt(n: BigInt): BigDecimal = BigDecimal(n, mc) 31 | } 32 | -------------------------------------------------------------------------------- /core/src/main/scala/algebra/instances/bigInt.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | package instances 3 | 4 | import algebra.ring._ 5 | 6 | package object bigInt extends BigIntInstances 7 | 8 | trait BigIntInstances extends cats.kernel.instances.BigIntInstances { 9 | implicit val bigIntAlgebra: BigIntAlgebra = 10 | new BigIntAlgebra 11 | } 12 | 13 | class BigIntAlgebra extends CommutativeRing[BigInt] with Serializable { 14 | 15 | val zero: BigInt = BigInt(0) 16 | val one: BigInt = BigInt(1) 17 | 18 | def plus(a: BigInt, b: BigInt): BigInt = a + b 19 | def negate(a: BigInt): BigInt = -a 20 | override def minus(a: BigInt, b: BigInt): BigInt = a - b 21 | 22 | def times(a: BigInt, b: BigInt): BigInt = a * b 23 | 24 | override def pow(a: BigInt, k: Int): BigInt = a pow k 25 | 26 | override def fromInt(n: Int): BigInt = BigInt(n) 27 | override def fromBigInt(n: BigInt): BigInt = n 28 | } 29 | -------------------------------------------------------------------------------- /core/src/main/scala/algebra/instances/bitSet.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | package instances 3 | 4 | import scala.collection.immutable.BitSet 5 | 6 | import algebra.lattice._ 7 | 8 | package object bitSet extends BitSetInstances 9 | 10 | trait BitSetInstances extends cats.kernel.instances.BitSetInstances { 11 | implicit val bitSetAlgebra: BitSetAlgebra = 12 | new BitSetAlgebra 13 | } 14 | 15 | class BitSetAlgebra extends GenBool[BitSet] with Serializable { 16 | val zero: BitSet = BitSet.empty 17 | def and(a: BitSet, b: BitSet): BitSet = a & b 18 | def or(a: BitSet, b: BitSet): BitSet = a | b 19 | def without(a: BitSet, b: BitSet): BitSet = a -- b 20 | override def xor(a: BitSet, b: BitSet): BitSet = a ^ b 21 | } 22 | -------------------------------------------------------------------------------- /core/src/main/scala/algebra/instances/boolean.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | package instances 3 | 4 | import algebra.lattice.Bool 5 | import algebra.ring.BoolRing 6 | import algebra.ring.CommutativeRig 7 | 8 | package object boolean extends BooleanInstances 9 | 10 | trait BooleanInstances extends cats.kernel.instances.BooleanInstances { 11 | implicit val booleanAlgebra: BooleanAlgebra = 12 | new BooleanAlgebra 13 | 14 | val booleanRing = new BoolRing[Boolean] { 15 | def zero: Boolean = false 16 | def one: Boolean = true 17 | def plus(x: Boolean, y: Boolean): Boolean = x ^ y 18 | def times(x: Boolean, y: Boolean): Boolean = x && y 19 | } 20 | } 21 | 22 | /** 23 | * This commutative rig is different than the one obtained from GF(2). 24 | * 25 | * It uses || for plus, and && for times. 26 | */ 27 | class BooleanAlgebra extends Bool[Boolean] with CommutativeRig[Boolean] { 28 | 29 | def zero: Boolean = false 30 | def one: Boolean = true 31 | 32 | override def isZero(x: Boolean)(implicit ev: Eq[Boolean]): Boolean = !x 33 | override def isOne(x: Boolean)(implicit ev: Eq[Boolean]): Boolean = x 34 | 35 | def and(x: Boolean, y: Boolean): Boolean = x && y 36 | def or(x: Boolean, y: Boolean): Boolean = x || y 37 | def complement(x: Boolean): Boolean = !x 38 | 39 | def plus(a:Boolean, b:Boolean): Boolean = a || b 40 | override def pow(a:Boolean, b:Int): Boolean = a 41 | override def times(a:Boolean, b:Boolean): Boolean = a && b 42 | } 43 | -------------------------------------------------------------------------------- /core/src/main/scala/algebra/instances/byte.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | package instances 3 | 4 | import algebra.lattice._ 5 | import algebra.ring._ 6 | 7 | package object byte extends ByteInstances 8 | 9 | trait ByteInstances extends cats.kernel.instances.ByteInstances { 10 | implicit val byteAlgebra: ByteAlgebra = new ByteAlgebra 11 | 12 | val ByteMinMaxLattice: BoundedDistributiveLattice[Byte] = 13 | BoundedDistributiveLattice.minMax[Byte](Byte.MinValue, Byte.MaxValue) 14 | } 15 | 16 | class ByteAlgebra extends CommutativeRing[Byte] with Serializable { 17 | 18 | def zero: Byte = 0 19 | def one: Byte = 1 20 | 21 | def plus(x: Byte, y: Byte): Byte = (x + y).toByte 22 | def negate(x: Byte): Byte = (-x).toByte 23 | override def minus(x: Byte, y: Byte): Byte = (x - y).toByte 24 | 25 | def times(x: Byte, y: Byte): Byte = (x * y).toByte 26 | 27 | override def pow(x: Byte, y: Int): Byte = 28 | Math.pow(x.toDouble, y.toDouble).toByte 29 | 30 | override def fromInt(n: Int): Byte = n.toByte 31 | override def fromBigInt(n: BigInt): Byte = n.toByte 32 | } 33 | -------------------------------------------------------------------------------- /core/src/main/scala/algebra/instances/char.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | package instances 3 | 4 | package object char extends CharInstances 5 | 6 | trait CharInstances extends cats.kernel.instances.CharInstances 7 | -------------------------------------------------------------------------------- /core/src/main/scala/algebra/instances/double.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | package instances 3 | 4 | import algebra.lattice.DistributiveLattice 5 | import algebra.ring.Field 6 | 7 | import java.lang.Math 8 | 9 | trait DoubleInstances extends cats.kernel.instances.DoubleInstances { 10 | implicit val doubleAlgebra: Field[Double] = 11 | new DoubleAlgebra 12 | 13 | // This is not Bounded due to the presence of NaN 14 | val DoubleMinMaxLattice: DistributiveLattice[Double] = 15 | DistributiveLattice.minMax[Double] 16 | } 17 | 18 | /** 19 | * Due to the way floating-point equality works, this instance is not 20 | * lawful under equality, but is correct when taken as an 21 | * approximation of an exact value. 22 | * 23 | * If you would prefer an absolutely lawful fractional value, you'll 24 | * need to investigate rational numbers or more exotic types. 25 | */ 26 | class DoubleAlgebra extends Field[Double] with Serializable { 27 | 28 | def zero: Double = 0.0 29 | def one: Double = 1.0 30 | 31 | def plus(x: Double, y: Double): Double = x + y 32 | def negate(x: Double): Double = -x 33 | override def minus(x: Double, y: Double): Double = x - y 34 | 35 | def times(x: Double, y: Double): Double = x * y 36 | def div(x: Double, y: Double): Double = x / y 37 | override def reciprocal(x: Double): Double = 1.0 / x 38 | override def pow(x: Double, y: Int): Double = Math.pow(x, y.toDouble) 39 | 40 | override def fromInt(x: Int): Double = x.toDouble 41 | override def fromBigInt(n: BigInt): Double = n.toDouble 42 | override def fromDouble(x: Double): Double = x 43 | } 44 | -------------------------------------------------------------------------------- /core/src/main/scala/algebra/instances/float.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | package instances 3 | 4 | import algebra.lattice.DistributiveLattice 5 | import algebra.ring.Field 6 | import java.lang.Math 7 | 8 | trait FloatInstances extends cats.kernel.instances.FloatInstances { 9 | implicit val floatAlgebra: Field[Float] = 10 | new FloatAlgebra 11 | 12 | // Not bounded due to the presence of NaN 13 | val FloatMinMaxLattice: DistributiveLattice[Float] = 14 | DistributiveLattice.minMax[Float] 15 | } 16 | 17 | /** 18 | * Due to the way floating-point equality works, this instance is not 19 | * lawful under equality, but is correct when taken as an 20 | * approximation of an exact value. 21 | * 22 | * If you would prefer an absolutely lawful fractional value, you'll 23 | * need to investigate rational numbers or more exotic types. 24 | */ 25 | class FloatAlgebra extends Field[Float] with Serializable { 26 | 27 | def zero: Float = 0.0F 28 | def one: Float = 1.0F 29 | 30 | def plus(x: Float, y: Float): Float = x + y 31 | def negate(x: Float): Float = -x 32 | override def minus(x: Float, y: Float): Float = x - y 33 | 34 | def times(x: Float, y: Float): Float = x * y 35 | def div(x: Float, y: Float): Float = x / y 36 | override def reciprocal(x: Float): Float = 1.0F / x 37 | 38 | override def pow(x: Float, y: Int): Float = 39 | Math.pow(x.toDouble, y.toDouble).toFloat 40 | 41 | override def fromInt(x: Int): Float = x.toFloat 42 | override def fromBigInt(n: BigInt): Float = n.toFloat 43 | override def fromDouble(x: Double): Float = x.toFloat 44 | } 45 | -------------------------------------------------------------------------------- /core/src/main/scala/algebra/instances/int.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | package instances 3 | 4 | import algebra.lattice._ 5 | import algebra.ring._ 6 | 7 | package object int extends IntInstances 8 | 9 | trait IntInstances extends cats.kernel.instances.IntInstances { 10 | implicit val intAlgebra: IntAlgebra = 11 | new IntAlgebra 12 | 13 | val IntMinMaxLattice: BoundedDistributiveLattice[Int] = 14 | BoundedDistributiveLattice.minMax[Int](Int.MinValue, Int.MaxValue) 15 | } 16 | 17 | class IntAlgebra extends CommutativeRing[Int] with Serializable { 18 | 19 | def zero: Int = 0 20 | def one: Int = 1 21 | 22 | def plus(x: Int, y: Int): Int = x + y 23 | def negate(x: Int): Int = -x 24 | override def minus(x: Int, y: Int): Int = x - y 25 | 26 | def times(x: Int, y: Int): Int = x * y 27 | 28 | override def pow(x: Int, y: Int): Int = 29 | StaticMethods.pow(x.toLong, y.toLong).toInt 30 | 31 | override def fromInt(n: Int): Int = n 32 | override def fromBigInt(n: BigInt): Int = n.toInt 33 | } 34 | -------------------------------------------------------------------------------- /core/src/main/scala/algebra/instances/list.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | package instances 3 | 4 | package object list extends ListInstances 5 | 6 | trait ListInstances extends cats.kernel.instances.ListInstances 7 | -------------------------------------------------------------------------------- /core/src/main/scala/algebra/instances/long.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | package instances 3 | 4 | import algebra.lattice._ 5 | import algebra.ring._ 6 | 7 | package object long extends LongInstances 8 | 9 | trait LongInstances extends cats.kernel.instances.LongInstances { 10 | implicit val longAlgebra: LongAlgebra = 11 | new LongAlgebra 12 | 13 | val LongMinMaxLattice: BoundedDistributiveLattice[Long] = 14 | BoundedDistributiveLattice.minMax[Long](Long.MinValue, Long.MaxValue) 15 | } 16 | 17 | class LongAlgebra extends CommutativeRing[Long] with Serializable { 18 | 19 | def zero: Long = 0 20 | def one: Long = 1 21 | 22 | def plus(x: Long, y: Long): Long = x + y 23 | def negate(x: Long): Long = -x 24 | override def minus(x: Long, y: Long): Long = x - y 25 | 26 | def times(x: Long, y: Long): Long = x * y 27 | 28 | override def pow(x: Long, y: Int): Long = StaticMethods.pow(x, y.toLong) 29 | 30 | override def fromInt(n: Int): Long = n.toLong 31 | override def fromBigInt(n: BigInt): Long = n.toLong 32 | } 33 | -------------------------------------------------------------------------------- /core/src/main/scala/algebra/instances/map.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | package instances 3 | 4 | import scala.collection.mutable 5 | 6 | import algebra.ring._ 7 | 8 | package object map extends MapInstances 9 | 10 | trait MapInstances extends cats.kernel.instances.MapInstances with MapInstances3 11 | 12 | trait MapInstances3 extends MapInstances2 { 13 | } 14 | 15 | trait MapInstances2 extends MapInstances1 { 16 | implicit def mapSemiring[K, V: Semiring]: MapSemiring[K, V] = 17 | new MapSemiring[K, V] 18 | } 19 | 20 | trait MapInstances1 extends MapInstances0 { 21 | } 22 | 23 | trait MapInstances0 { 24 | implicit def mapAdditiveMonoid[K, V: AdditiveSemigroup]: MapAdditiveMonoid[K, V] = 25 | new MapAdditiveMonoid[K, V] 26 | } 27 | 28 | class MapAdditiveMonoid[K, V](implicit V: AdditiveSemigroup[V]) extends AdditiveMonoid[Map[K, V]] { 29 | def zero: Map[K, V] = Map.empty 30 | 31 | def plus(xs: Map[K, V], ys: Map[K, V]): Map[K, V] = 32 | if (xs.size <= ys.size) { 33 | xs.foldLeft(ys) { case (my, (k, x)) => 34 | my.updated(k, my.get(k) match { 35 | case Some(y) => V.plus(x, y) 36 | case None => x 37 | }) 38 | } 39 | } else { 40 | ys.foldLeft(xs) { case (mx, (k, y)) => 41 | mx.updated(k, mx.get(k) match { 42 | case Some(x) => V.plus(x, y) 43 | case None => y 44 | }) 45 | } 46 | } 47 | 48 | override def sumN(a: Map[K, V], n: Int): Map[K, V] = 49 | if (n > 0) a.map { case (k, v) => (k, V.sumN(v, n)) } 50 | else if (n == 0) zero 51 | else throw new IllegalArgumentException("Illegal negative exponent to sumN: %s" format n) 52 | 53 | override def sum(as: TraversableOnce[Map[K, V]]): Map[K, V] = { 54 | val acc = mutable.Map.empty[K, V] 55 | as.foreach { m => 56 | val it = m.iterator 57 | while (it.hasNext) { 58 | val (k, y) = it.next() 59 | acc.get(k) match { 60 | case None => acc(k) = y 61 | case Some(x) => acc(k) = V.plus(x, y) 62 | } 63 | } 64 | } 65 | cats.kernel.instances.StaticMethods.wrapMutableMap(acc) 66 | } 67 | } 68 | 69 | class MapSemiring[K, V](implicit V: Semiring[V]) extends MapAdditiveMonoid[K, V] with Semiring[Map[K, V]] { 70 | 71 | def times(xs: Map[K, V], ys: Map[K, V]): Map[K, V] = 72 | // we figure out which of our maps is smaller, iterate over its 73 | // keys, see which of those are in the larger map, and add the 74 | // resulting product to our result map. 75 | if (xs.size <= ys.size) { 76 | xs.foldLeft(Map.empty[K, V]) { case (m, (k, x)) => 77 | ys.get(k) match { 78 | case Some(y) => m.updated(k, V.times(x, y)) 79 | case None => m 80 | } 81 | } 82 | } else { 83 | ys.foldLeft(Map.empty[K, V]) { case (m, (k, y)) => 84 | xs.get(k) match { 85 | case Some(x) => m.updated(k, V.times(x, y)) 86 | case None => m 87 | } 88 | } 89 | } 90 | 91 | override def pow(x: Map[K, V], n: Int): Map[K, V] = 92 | if (n < 1) throw new IllegalArgumentException(s"non-positive exponent: $n") 93 | else if (n == 1) x 94 | else x.map { case (k, v) => (k, V.pow(v, n)) } 95 | 96 | override def tryProduct(as: TraversableOnce[Map[K, V]]): Option[Map[K, V]] = 97 | if (as.isEmpty) { 98 | None 99 | } else { 100 | val acc = mutable.Map.empty[K, V] 101 | var ready: Boolean = false 102 | as.foreach { m => 103 | if (ready) { 104 | // at this point all we can do is modify or remove 105 | // keys. since any "missing" key is effectively zero, and 106 | // since 0 * x = 0, we ignore any keys not already in our 107 | // accumulator. 108 | val it = acc.iterator 109 | while (it.hasNext) { 110 | val (k, x) = it.next() 111 | m.get(k) match { 112 | case None => acc -= k 113 | case Some(y) => acc(k) = V.times(x, y) 114 | } 115 | } 116 | } else { 117 | // we have to initialize our accumulator (`acc`) with the 118 | // very first element of `as`. if there is only one map in 119 | // our collection we want to return exactly those values. 120 | val it = m.iterator 121 | while (it.hasNext) acc += it.next() 122 | ready = true 123 | } 124 | } 125 | Some(cats.kernel.instances.StaticMethods.wrapMutableMap(acc)) 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /core/src/main/scala/algebra/instances/option.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | package instances 3 | 4 | package object option extends OptionInstances 5 | 6 | trait OptionInstances extends cats.kernel.instances.OptionInstances 7 | -------------------------------------------------------------------------------- /core/src/main/scala/algebra/instances/set.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | package instances 3 | 4 | import algebra.lattice.GenBool 5 | import algebra.ring.{BoolRng, Semiring} 6 | 7 | package object set extends SetInstances 8 | 9 | trait SetInstances extends cats.kernel.instances.SetInstances { 10 | 11 | implicit def setLattice[A]: GenBool[Set[A]] = new SetLattice[A] 12 | implicit def setSemiring[A]: Semiring[Set[A]] = new SetSemiring[A] 13 | 14 | // this instance is not compatible with setSemiring, so it is not 15 | // marked as implicit to avoid an ambiguity. 16 | def setBoolRng[A]: BoolRng[Set[A]] = new SetBoolRng[A] 17 | } 18 | 19 | class SetLattice[A] extends GenBool[Set[A]] { 20 | def zero: Set[A] = Set.empty 21 | def or(lhs: Set[A], rhs: Set[A]): Set[A] = lhs | rhs 22 | def and(lhs: Set[A], rhs: Set[A]): Set[A] = lhs & rhs 23 | def without(lhs: Set[A], rhs: Set[A]): Set[A] = lhs -- rhs 24 | } 25 | 26 | class SetSemiring[A] extends Semiring[Set[A]] { 27 | def zero: Set[A] = Set.empty 28 | def plus(x: Set[A], y: Set[A]): Set[A] = x | y 29 | def times(x: Set[A], y: Set[A]): Set[A] = x & y 30 | } 31 | 32 | class SetBoolRng[A] extends BoolRng[Set[A]] { 33 | def zero: Set[A] = Set.empty 34 | def plus(x: Set[A], y: Set[A]): Set[A] = (x -- y) | (y -- x) // this is xor 35 | def times(x: Set[A], y: Set[A]): Set[A] = x & y 36 | } 37 | -------------------------------------------------------------------------------- /core/src/main/scala/algebra/instances/short.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | package instances 3 | 4 | import algebra.lattice._ 5 | import algebra.ring._ 6 | 7 | package object short extends ShortInstances 8 | 9 | trait ShortInstances extends cats.kernel.instances.ShortInstances { 10 | implicit val shortAlgebra: ShortAlgebra = 11 | new ShortAlgebra 12 | 13 | val ShortMinMaxLattice: BoundedDistributiveLattice[Short] = 14 | BoundedDistributiveLattice.minMax[Short](Short.MinValue, Short.MaxValue) 15 | } 16 | 17 | class ShortAlgebra extends CommutativeRing[Short] with Serializable { 18 | 19 | def zero: Short = 0 20 | def one: Short = 1 21 | 22 | def plus(x: Short, y: Short): Short = (x + y).toShort 23 | def negate(x: Short): Short = (-x).toShort 24 | override def minus(x: Short, y: Short): Short = (x - y).toShort 25 | 26 | def times(x: Short, y: Short): Short = (x * y).toShort 27 | 28 | override def pow(x: Short, y: Int): Short = 29 | Math.pow(x.toDouble, y.toDouble).toShort 30 | 31 | override def fromInt(n: Int): Short = n.toShort 32 | override def fromBigInt(n: BigInt): Short = n.toShort 33 | } 34 | -------------------------------------------------------------------------------- /core/src/main/scala/algebra/instances/string.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | package instances 3 | 4 | package object string extends StringInstances 5 | 6 | trait StringInstances extends cats.kernel.instances.StringInstances 7 | -------------------------------------------------------------------------------- /core/src/main/scala/algebra/instances/tuple.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | package instances 3 | 4 | package object tuple extends TupleInstances 5 | -------------------------------------------------------------------------------- /core/src/main/scala/algebra/instances/unit.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | package instances 3 | 4 | import algebra.ring.CommutativeRing 5 | 6 | package object unit extends UnitInstances 7 | 8 | trait UnitInstances extends cats.kernel.instances.UnitInstances { 9 | implicit val unitRing: CommutativeRing[Unit] = 10 | new UnitAlgebra 11 | } 12 | 13 | class UnitAlgebra extends CommutativeRing[Unit] { 14 | 15 | def zero: Unit = () 16 | def one: Unit = () 17 | 18 | override def isZero(x: Unit)(implicit ev: Eq[Unit]): Boolean = true 19 | override def isOne(x: Unit)(implicit ev: Eq[Unit]): Boolean = true 20 | 21 | def plus(a: Unit, b: Unit): Unit = () 22 | def negate(x: Unit): Unit = () 23 | def times(a: Unit, b: Unit): Unit = () 24 | override def pow(a: Unit, b: Int): Unit = () 25 | } 26 | -------------------------------------------------------------------------------- /core/src/main/scala/algebra/lattice/Bool.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | package lattice 3 | 4 | import ring.BoolRing 5 | import scala.{specialized => sp} 6 | 7 | /** 8 | * Boolean algebras are Heyting algebras with the additional 9 | * constraint that the law of the excluded middle is true 10 | * (equivalently, double-negation is true). 11 | * 12 | * This means that in addition to the laws Heyting algebras obey, 13 | * boolean algebras also obey the following: 14 | * 15 | * - (a ∨ ¬a) = 1 16 | * - ¬¬a = a 17 | * 18 | * Boolean algebras generalize classical logic: one is equivalent to 19 | * "true" and zero is equivalent to "false". Boolean algebras provide 20 | * additional logical operators such as `xor`, `nand`, `nor`, and 21 | * `nxor` which are commonly used. 22 | * 23 | * Every boolean algebras has a dual algebra, which involves reversing 24 | * true/false as well as and/or. 25 | */ 26 | trait Bool[@sp(Int, Long) A] extends Any with Heyting[A] with GenBool[A] { self => 27 | def imp(a: A, b: A): A = or(complement(a), b) 28 | def without(a: A, b: A): A = and(a, complement(b)) 29 | 30 | // xor is already defined in both Heyting and GenBool. 31 | // In Bool, the definitions coincide, so we just use one of them. 32 | override def xor(a: A, b: A): A = 33 | or(without(a, b), without(b, a)) 34 | 35 | override def dual: Bool[A] = new DualBool(this) 36 | 37 | /** 38 | * Every Boolean algebra is a BoolRing, with multiplication defined as 39 | * `and` and addition defined as `xor`. Bool does not extend BoolRing 40 | * because, e.g. we might want a Bool[Int] and CommutativeRing[Int] to 41 | * refer to different structures, by default. 42 | * 43 | * Note that the ring returned by this method is not an extension of 44 | * the `Rig` returned from `BoundedDistributiveLattice.asCommutativeRig`. 45 | */ 46 | override def asBoolRing: BoolRing[A] = new BoolRingFromBool(self) 47 | } 48 | 49 | class DualBool[@sp(Int, Long) A](orig: Bool[A]) extends Bool[A] { 50 | def one: A = orig.zero 51 | def zero: A = orig.one 52 | def and(a: A, b: A): A = orig.or(a, b) 53 | def or(a: A, b: A): A = orig.and(a, b) 54 | def complement(a: A): A = orig.complement(a) 55 | override def xor(a: A, b: A): A = orig.complement(orig.xor(a, b)) 56 | 57 | override def imp(a: A, b: A): A = orig.and(orig.complement(a), b) 58 | override def nand(a: A, b: A): A = orig.nor(a, b) 59 | override def nor(a: A, b: A): A = orig.nand(a, b) 60 | override def nxor(a: A, b: A): A = orig.xor(a, b) 61 | 62 | override def dual: Bool[A] = orig 63 | } 64 | 65 | private[lattice] class BoolRingFromBool[A](orig: Bool[A]) extends BoolRngFromGenBool(orig) with BoolRing[A] { 66 | def one: A = orig.one 67 | } 68 | 69 | /** 70 | * Every Boolean ring gives rise to a Boolean algebra: 71 | * - 0 and 1 are preserved; 72 | * - ring multiplication (`times`) corresponds to `and`; 73 | * - ring addition (`plus`) corresponds to `xor`; 74 | * - `a or b` is then defined as `a xor b xor (a and b)`; 75 | * - complement (`¬a`) is defined as `a xor 1`. 76 | */ 77 | class BoolFromBoolRing[A](orig: BoolRing[A]) extends GenBoolFromBoolRng(orig) with Bool[A] { 78 | def one: A = orig.one 79 | def complement(a: A): A = orig.plus(orig.one, a) 80 | override def without(a: A, b: A): A = super[GenBoolFromBoolRng].without(a, b) 81 | override def asBoolRing: BoolRing[A] = orig 82 | 83 | override def meet(a: A, b: A): A = super[GenBoolFromBoolRng].meet(a, b) 84 | override def join(a: A, b: A): A = super[GenBoolFromBoolRng].join(a, b) 85 | } 86 | 87 | object Bool extends HeytingFunctions[Bool] with GenBoolFunctions[Bool] { 88 | 89 | /** 90 | * Access an implicit `Bool[A]`. 91 | */ 92 | @inline final def apply[@sp(Int, Long) A](implicit ev: Bool[A]): Bool[A] = ev 93 | } 94 | -------------------------------------------------------------------------------- /core/src/main/scala/algebra/lattice/BoundedDistributiveLattice.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | package lattice 3 | 4 | import scala.{specialized => sp} 5 | import algebra.ring.CommutativeRig 6 | 7 | /** 8 | * A bounded distributive lattice is a lattice that both bounded and distributive 9 | */ 10 | trait BoundedDistributiveLattice[@sp(Int, Long, Float, Double) A] extends Any with BoundedLattice[A] with DistributiveLattice[A] { self => 11 | /** 12 | * Return a CommutativeRig using join and meet. Note this must obey the commutative rig laws since 13 | * meet(a, one) = a, and meet and join are associative, commutative and distributive. 14 | */ 15 | def asCommutativeRig: CommutativeRig[A] = 16 | new CommutativeRig[A] { 17 | def zero: A = self.zero 18 | def one: A = self.one 19 | def plus(x: A, y: A): A = self.join(x, y) 20 | def times(x: A, y: A): A = self.meet(x, y) 21 | } 22 | 23 | override def dual: BoundedDistributiveLattice[A] = new BoundedDistributiveLattice[A] { 24 | def meet(a: A, b: A) = self.join(a, b) 25 | def join(a: A, b: A) = self.meet(a, b) 26 | def one = self.zero 27 | def zero = self.one 28 | override def dual = self 29 | } 30 | } 31 | 32 | object BoundedDistributiveLattice extends 33 | BoundedMeetSemilatticeFunctions[BoundedDistributiveLattice] 34 | with BoundedJoinSemilatticeFunctions[BoundedDistributiveLattice] { 35 | 36 | /** 37 | * Access an implicit `BoundedDistributiveLattice[A]`. 38 | */ 39 | @inline final def apply[@sp(Int, Long, Float, Double) A](implicit ev: BoundedDistributiveLattice[A]): BoundedDistributiveLattice[A] = ev 40 | 41 | def minMax[@sp(Int, Long, Float, Double)A](min: A, max: A)(implicit ord: Order[A]): BoundedDistributiveLattice[A] = 42 | new MinMaxBoundedDistributiveLattice(min, max) 43 | } 44 | 45 | class MinMaxBoundedDistributiveLattice[A](min: A, max: A)(implicit o: Order[A]) extends MinMaxLattice[A] 46 | with BoundedDistributiveLattice[A] { 47 | def zero = min 48 | def one = max 49 | } 50 | -------------------------------------------------------------------------------- /core/src/main/scala/algebra/lattice/BoundedJoinSemilattice.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | package lattice 3 | 4 | import scala.{specialized => sp} 5 | 6 | trait BoundedJoinSemilattice[@sp(Int, Long, Float, Double) A] extends Any with JoinSemilattice[A] { self => 7 | def zero: A 8 | def isZero(a: A)(implicit ev: Eq[A]): Boolean = ev.eqv(a, zero) 9 | 10 | override def joinSemilattice: BoundedSemilattice[A] = 11 | new BoundedSemilattice[A] { 12 | def empty: A = self.zero 13 | def combine(x: A, y: A): A = join(x, y) 14 | } 15 | } 16 | 17 | trait BoundedJoinSemilatticeFunctions[B[A] <: BoundedJoinSemilattice[A]] extends JoinSemilatticeFunctions[B] { 18 | def zero[@sp(Int, Long, Float, Double) A](implicit ev: B[A]): A = ev.zero 19 | } 20 | 21 | object BoundedJoinSemilattice extends JoinSemilatticeFunctions[BoundedJoinSemilattice] 22 | with BoundedJoinSemilatticeFunctions[BoundedJoinSemilattice] { 23 | 24 | /** 25 | * Access an implicit `BoundedJoinSemilattice[A]`. 26 | */ 27 | @inline final def apply[@sp(Int, Long, Float, Double) A](implicit ev: BoundedJoinSemilattice[A]): BoundedJoinSemilattice[A] = ev 28 | } 29 | -------------------------------------------------------------------------------- /core/src/main/scala/algebra/lattice/BoundedLattice.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | package lattice 3 | 4 | import scala.{specialized => sp} 5 | 6 | /** 7 | * A bounded lattice is a lattice that additionally has one element 8 | * that is the bottom (zero, also written as ⊥), and one element that 9 | * is the top (one, also written as ⊤). 10 | * 11 | * This means that for any a in A: 12 | * 13 | * join(zero, a) = a = meet(one, a) 14 | * 15 | * Or written using traditional notation: 16 | * 17 | * (0 ∨ a) = a = (1 ∧ a) 18 | */ 19 | trait BoundedLattice[@sp(Int, Long, Float, Double) A] extends Any with Lattice[A] with BoundedMeetSemilattice[A] with BoundedJoinSemilattice[A] { self => 20 | override def dual: BoundedLattice[A] = new BoundedLattice[A] { 21 | def meet(a: A, b: A) = self.join(a, b) 22 | def join(a: A, b: A) = self.meet(a, b) 23 | def one = self.zero 24 | def zero = self.one 25 | override def dual = self 26 | } 27 | } 28 | 29 | object BoundedLattice extends 30 | BoundedMeetSemilatticeFunctions[BoundedLattice] 31 | with BoundedJoinSemilatticeFunctions[BoundedLattice] { 32 | 33 | /** 34 | * Access an implicit `BoundedLattice[A]`. 35 | */ 36 | @inline final def apply[@sp(Int, Long, Float, Double) A](implicit ev: BoundedLattice[A]): BoundedLattice[A] = ev 37 | } 38 | -------------------------------------------------------------------------------- /core/src/main/scala/algebra/lattice/BoundedMeetSemilattice.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | package lattice 3 | 4 | import scala.{specialized => sp} 5 | 6 | trait BoundedMeetSemilattice[@sp(Int, Long, Float, Double) A] extends Any with MeetSemilattice[A] { self => 7 | def one: A 8 | def isOne(a: A)(implicit ev: Eq[A]): Boolean = ev.eqv(a, one) 9 | 10 | override def meetSemilattice: BoundedSemilattice[A] = 11 | new BoundedSemilattice[A] { 12 | def empty: A = self.one 13 | def combine(x: A, y: A): A = meet(x, y) 14 | } 15 | } 16 | 17 | trait BoundedMeetSemilatticeFunctions[B[A] <: BoundedMeetSemilattice[A]] extends MeetSemilatticeFunctions[B] { 18 | def one[@sp(Int, Long, Float, Double) A](implicit ev: B[A]): A = 19 | ev.one 20 | } 21 | 22 | object BoundedMeetSemilattice extends BoundedMeetSemilatticeFunctions[BoundedMeetSemilattice] { 23 | 24 | /** 25 | * Access an implicit `BoundedMeetSemilattice[A]`. 26 | */ 27 | @inline final def apply[@sp(Int, Long, Float, Double) A](implicit ev: BoundedMeetSemilattice[A]): BoundedMeetSemilattice[A] = ev 28 | } 29 | -------------------------------------------------------------------------------- /core/src/main/scala/algebra/lattice/DeMorgan.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | package lattice 3 | 4 | import scala.{specialized => sp} 5 | 6 | /** 7 | * De Morgan algebras are bounded lattices that are also equipped with 8 | * a De Morgan involution. 9 | * 10 | * De Morgan involution obeys the following laws: 11 | * 12 | * - ¬¬a = a 13 | * - ¬(x∧y) = ¬x∨¬y 14 | * 15 | * However, in De Morgan algebras this involution does not necessarily 16 | * provide the law of the excluded middle. This means that there is no 17 | * guarantee that (a ∨ ¬a) = 1. De Morgan algebra do not not necessarily 18 | * provide the law of non contradiction either. This means that there is 19 | * no guarantee that (a ∧ ¬a) = 0. 20 | * 21 | * De Morgan algebras are useful to model fuzzy logic. For a model of 22 | * classical logic, see the boolean algebra type class implemented as 23 | * [[Bool]]. 24 | */ 25 | trait DeMorgan[@sp(Int, Long) A] extends Any with Logic[A] { self => 26 | def meet(a: A, b: A): A = and(a, b) 27 | 28 | def join(a: A, b: A): A = or(a, b) 29 | 30 | def imp(a: A, b: A): A = or(not(a), b) 31 | } 32 | 33 | trait DeMorganFunctions[H[A] <: DeMorgan[A]] extends 34 | BoundedMeetSemilatticeFunctions[H] 35 | with BoundedJoinSemilatticeFunctions[H] 36 | with LogicFunctions[H] 37 | 38 | 39 | object DeMorgan extends DeMorganFunctions[DeMorgan] { 40 | 41 | /** 42 | * Access an implicit `DeMorgan[A]`. 43 | */ 44 | @inline final def apply[@sp(Int, Long) A](implicit ev: DeMorgan[A]): DeMorgan[A] = ev 45 | 46 | 47 | /** 48 | * Turn a [[Bool]] into a `DeMorgan` 49 | * Used for binary compatibility. 50 | */ 51 | final def fromBool[@sp(Int, Long) A](bool: Bool[A]): DeMorgan[A] = 52 | new DeMorgan[A] { 53 | def and(a: A, b: A): A = bool.and(a, b) 54 | 55 | def or(a: A, b: A): A = bool.or(a, b) 56 | 57 | def not(a: A): A = bool.complement(a) 58 | 59 | def one: A = bool.one 60 | 61 | def zero: A = bool.zero 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /core/src/main/scala/algebra/lattice/DistributiveLattice.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | package lattice 3 | 4 | import scala.{specialized => sp} 5 | 6 | /** 7 | * A distributive lattice a lattice where join and meet distribute: 8 | * 9 | * - a ∨ (b ∧ c) = (a ∨ b) ∧ (a ∨ c) 10 | * - a ∧ (b ∨ c) = (a ∧ b) ∨ (a ∧ c) 11 | */ 12 | trait DistributiveLattice[@sp(Int, Long, Float, Double) A] extends Any with Lattice[A] 13 | 14 | object DistributiveLattice extends JoinSemilatticeFunctions[DistributiveLattice] with MeetSemilatticeFunctions[DistributiveLattice] { 15 | 16 | /** 17 | * Access an implicit `Lattice[A]`. 18 | */ 19 | @inline final def apply[@sp(Int, Long, Float, Double) A](implicit ev: DistributiveLattice[A]): DistributiveLattice[A] = ev 20 | 21 | def minMax[@sp(Int, Long, Float, Double) A: Order]: DistributiveLattice[A] = 22 | new MinMaxLattice[A] 23 | } 24 | 25 | class MinMaxLattice[@sp(Int, Long, Float, Double) A](implicit order: Order[A]) extends DistributiveLattice[A] { 26 | def join(x: A, y: A): A = order.max(x, y) 27 | def meet(x: A, y: A): A = order.min(x, y) 28 | } 29 | -------------------------------------------------------------------------------- /core/src/main/scala/algebra/lattice/GenBool.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | package lattice 3 | 4 | import ring.BoolRng 5 | import scala.{specialized => sp} 6 | 7 | /** 8 | * Generalized Boolean algebra, that is, a Boolean algebra without 9 | * the top element. Generalized Boolean algebras do not (in general) 10 | * have (absolute) complements, but they have ''relative complements'' 11 | * (see [[GenBool.without]]). 12 | */ 13 | trait GenBool[@sp(Int, Long) A] extends Any with DistributiveLattice[A] with BoundedJoinSemilattice[A] { self => 14 | def and(a: A, b: A): A 15 | override def meet(a: A, b: A): A = and(a, b) 16 | 17 | def or(a: A, b: A): A 18 | override def join(a: A, b: A): A = or(a, b) 19 | 20 | /** 21 | * The operation of ''relative complement'', symbolically often denoted 22 | * `a\b` (the symbol for set-theoretic difference, which is the 23 | * meaning of relative complement in the lattice of sets). 24 | */ 25 | def without(a: A, b: A): A 26 | 27 | /** 28 | * Logical exclusive or, set-theoretic symmetric difference. 29 | * Defined as `a\b ∨ b\a`. 30 | */ 31 | def xor(a: A, b: A): A = or(without(a, b), without(b, a)) 32 | 33 | /** 34 | * Every generalized Boolean algebra is also a `BoolRng`, with 35 | * multiplication defined as `and` and addition defined as `xor`. 36 | */ 37 | def asBoolRing: BoolRng[A] = new BoolRngFromGenBool(self) 38 | } 39 | 40 | /** 41 | * Every Boolean rng gives rise to a Boolean algebra without top: 42 | * - 0 is preserved; 43 | * - ring multiplication (`times`) corresponds to `and`; 44 | * - ring addition (`plus`) corresponds to `xor`; 45 | * - `a or b` is then defined as `a xor b xor (a and b)`; 46 | * - relative complement `a\b` is defined as `a xor (a and b)`. 47 | * 48 | * `BoolRng.asBool.asBoolRing` gives back the original `BoolRng`. 49 | * 50 | * @see [[algebra.lattice.GenBool.asBoolRing]] 51 | */ 52 | class GenBoolFromBoolRng[A](orig: BoolRng[A]) extends GenBool[A] { 53 | def zero: A = orig.zero 54 | def and(a: A, b: A): A = orig.times(a, b) 55 | def or(a: A, b: A): A = orig.plus(orig.plus(a, b), orig.times(a, b)) 56 | def without(a: A, b: A): A = orig.plus(a, orig.times(a, b)) 57 | override def asBoolRing: BoolRng[A] = orig 58 | } 59 | 60 | private[lattice] class BoolRngFromGenBool[@sp(Int, Long) A](orig: GenBool[A]) extends BoolRng[A] { 61 | def zero: A = orig.zero 62 | def plus(x: A, y: A): A = orig.xor(x, y) 63 | def times(x: A, y: A): A = orig.and(x, y) 64 | } 65 | 66 | trait GenBoolFunctions[G[A] <: GenBool[A]] extends BoundedJoinSemilatticeFunctions[G] with MeetSemilatticeFunctions[G] { 67 | def and[@sp(Int, Long) A](x: A, y: A)(implicit ev: G[A]): A = ev.and(x, y) 68 | def or[@sp(Int, Long) A](x: A, y: A)(implicit ev: G[A]): A = ev.or(x, y) 69 | def without[@sp(Int, Long) A](x: A, y: A)(implicit ev: G[A]): A = ev.without(x, y) 70 | def xor[@sp(Int, Long) A](x: A, y: A)(implicit ev: G[A]): A = ev.xor(x, y) 71 | } 72 | 73 | object GenBool extends GenBoolFunctions[GenBool] { 74 | @inline final def apply[@sp(Int, Long) A](implicit ev: GenBool[A]): GenBool[A] = ev 75 | } 76 | -------------------------------------------------------------------------------- /core/src/main/scala/algebra/lattice/Heyting.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | package lattice 3 | 4 | import scala.{specialized => sp} 5 | 6 | /** 7 | * Heyting algebras are bounded lattices that are also equipped with 8 | * an additional binary operation `imp` (for implication, also 9 | * written as →). 10 | * 11 | * Implication obeys the following laws: 12 | * 13 | * - a → a = 1 14 | * - a ∧ (a → b) = a ∧ b 15 | * - b ∧ (a → b) = b 16 | * - a → (b ∧ c) = (a → b) ∧ (a → c) 17 | * 18 | * In heyting algebras, `and` is equivalent to `meet` and `or` is 19 | * equivalent to `join`; both methods are available. 20 | * 21 | * Heyting algebra also define `complement` operation (sometimes 22 | * written as ¬a). The complement of `a` is equivalent to `(a → 0)`, 23 | * and the following laws hold: 24 | * 25 | * - a ∧ ¬a = 0 26 | * 27 | * However, in Heyting algebras this operation is only a 28 | * pseudo-complement, since Heyting algebras do not necessarily 29 | * provide the law of the excluded middle. This means that there is no 30 | * guarantee that (a ∨ ¬a) = 1. 31 | * 32 | * Heyting algebras model intuitionistic logic. For a model of 33 | * classical logic, see the boolean algebra type class implemented as 34 | * `Bool`. 35 | */ 36 | trait Heyting[@sp(Int, Long) A] extends Any with BoundedDistributiveLattice[A] { self => 37 | def and(a: A, b: A): A 38 | def meet(a: A, b: A): A = and(a, b) 39 | 40 | def or(a: A, b: A): A 41 | def join(a: A, b: A): A = or(a, b) 42 | 43 | def imp(a: A, b: A): A 44 | def complement(a: A): A 45 | 46 | def xor(a: A, b: A): A = or(and(a, complement(b)), and(complement(a), b)) 47 | def nand(a: A, b: A): A = complement(and(a, b)) 48 | def nor(a: A, b: A): A = complement(or(a, b)) 49 | def nxor(a: A, b: A): A = complement(xor(a, b)) 50 | } 51 | 52 | trait HeytingGenBoolOverlap[H[A] <: Heyting[A]] { 53 | def and[@sp(Int, Long) A](x: A, y: A)(implicit ev: H[A]): A = 54 | ev.and(x, y) 55 | def or[@sp(Int, Long) A](x: A, y: A)(implicit ev: H[A]): A = 56 | ev.or(x, y) 57 | def xor[@sp(Int, Long) A](x: A, y: A)(implicit ev: H[A]): A = 58 | ev.xor(x, y) 59 | } 60 | 61 | trait HeytingFunctions[H[A] <: Heyting[A]] extends 62 | BoundedMeetSemilatticeFunctions[H] 63 | with BoundedJoinSemilatticeFunctions[H] { 64 | 65 | def complement[@sp(Int, Long) A](x: A)(implicit ev: H[A]): A = 66 | ev.complement(x) 67 | 68 | def imp[@sp(Int, Long) A](x: A, y: A)(implicit ev: H[A]): A = 69 | ev.imp(x, y) 70 | def nor[@sp(Int, Long) A](x: A, y: A)(implicit ev: H[A]): A = 71 | ev.nor(x, y) 72 | def nxor[@sp(Int, Long) A](x: A, y: A)(implicit ev: H[A]): A = 73 | ev.nxor(x, y) 74 | def nand[@sp(Int, Long) A](x: A, y: A)(implicit ev: H[A]): A = 75 | ev.nand(x, y) 76 | } 77 | 78 | 79 | object Heyting extends HeytingFunctions[Heyting] with HeytingGenBoolOverlap[Heyting] { 80 | 81 | /** 82 | * Access an implicit `Heyting[A]`. 83 | */ 84 | @inline final def apply[@sp(Int, Long) A](implicit ev: Heyting[A]): Heyting[A] = ev 85 | } 86 | -------------------------------------------------------------------------------- /core/src/main/scala/algebra/lattice/JoinSemilattice.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | package lattice 3 | 4 | import scala.{specialized => sp} 5 | 6 | /** 7 | * A join-semilattice (or upper semilattice) is a semilattice whose 8 | * operation is called "join", and which can be thought of as a least 9 | * upper bound. 10 | */ 11 | trait JoinSemilattice[@sp(Int, Long, Float, Double) A] extends Any with Serializable { self => 12 | def join(lhs: A, rhs: A): A 13 | 14 | def joinSemilattice: Semilattice[A] = 15 | new Semilattice[A] { 16 | def combine(x: A, y: A): A = self.join(x, y) 17 | } 18 | 19 | def joinPartialOrder(implicit ev: Eq[A]): PartialOrder[A] = 20 | joinSemilattice.asJoinPartialOrder 21 | } 22 | 23 | trait JoinSemilatticeFunctions[J[A] <: JoinSemilattice[A]] { 24 | def join[@sp(Int, Long, Float, Double) A](x: A, y: A)(implicit ev: J[A]): A = 25 | ev.join(x, y) 26 | } 27 | 28 | object JoinSemilattice extends JoinSemilatticeFunctions[JoinSemilattice] { 29 | 30 | /** 31 | * Access an implicit `JoinSemilattice[A]`. 32 | */ 33 | @inline final def apply[@sp(Int, Long, Float, Double) A](implicit ev: JoinSemilattice[A]): JoinSemilattice[A] = ev 34 | 35 | } 36 | -------------------------------------------------------------------------------- /core/src/main/scala/algebra/lattice/Lattice.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | package lattice 3 | 4 | import scala.{specialized => sp} 5 | 6 | /** 7 | * A lattice is a set `A` together with two operations (meet and 8 | * join). Both operations individually constitute semilattices (join- 9 | * and meet-semilattices respectively): each operation is commutative, 10 | * associative, and idempotent. 11 | * 12 | * Join can be thought of as finding a least upper bound (supremum), 13 | * and meet can be thought of as finding a greatest lower bound 14 | * (infimum). 15 | * 16 | * The join and meet operations are also linked by absorption laws: 17 | * 18 | * meet(a, join(a, b)) = join(a, meet(a, b)) = a 19 | */ 20 | trait Lattice[@sp(Int, Long, Float, Double) A] extends Any with JoinSemilattice[A] with MeetSemilattice[A] { self => 21 | /** 22 | * This is the lattice with meet and join swapped 23 | */ 24 | def dual: Lattice[A] = new Lattice[A] { 25 | def meet(a: A, b: A) = self.join(a, b) 26 | def join(a: A, b: A) = self.meet(a, b) 27 | override def dual = self 28 | } 29 | } 30 | 31 | object Lattice extends JoinSemilatticeFunctions[Lattice] with MeetSemilatticeFunctions[Lattice] { 32 | /** 33 | * Access an implicit `Lattice[A]`. 34 | */ 35 | @inline final def apply[@sp(Int, Long, Float, Double) A](implicit ev: Lattice[A]): Lattice[A] = ev 36 | } 37 | -------------------------------------------------------------------------------- /core/src/main/scala/algebra/lattice/Logic.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | package lattice 3 | 4 | import scala.{specialized => sp} 5 | 6 | /** 7 | * Logic models a logic generally. It is a bounded distributive 8 | * lattice with an extra negation operator. 9 | * 10 | * The negation operator obeys the weak De Morgan laws: 11 | * - ¬(x∨y) = ¬x∧¬y 12 | * - ¬(x∧y) = ¬¬(¬x∨¬y) 13 | * 14 | * For intuitionistic logic see [[Heyting]] 15 | * For fuzzy logic see [[DeMorgan]] 16 | */ 17 | trait Logic[@sp(Int, Long) A] extends Any with BoundedDistributiveLattice[A] { self => 18 | def and(a: A, b: A): A 19 | 20 | def or(a: A, b: A): A 21 | 22 | def not(a: A): A 23 | 24 | def xor(a: A, b: A): A = or(and(a, not(b)), and(not(a), b)) 25 | def nand(a: A, b: A): A = not(and(a, b)) 26 | def nor(a: A, b: A): A = not(or(a, b)) 27 | def nxor(a: A, b: A): A = not(xor(a, b)) 28 | } 29 | 30 | trait LogicFunctions[H[A] <: Logic[A]] { 31 | def complement[@sp(Int, Long) A](x: A)(implicit ev: H[A]): A = 32 | ev.not(x) 33 | 34 | def nor[@sp(Int, Long) A](x: A, y: A)(implicit ev: H[A]): A = 35 | ev.nor(x, y) 36 | def nxor[@sp(Int, Long) A](x: A, y: A)(implicit ev: H[A]): A = 37 | ev.nxor(x, y) 38 | def nand[@sp(Int, Long) A](x: A, y: A)(implicit ev: H[A]): A = 39 | ev.nand(x, y) 40 | } 41 | 42 | 43 | object Logic extends LogicFunctions[Logic] { 44 | 45 | /** 46 | * Access an implicit `Logic[A]`. 47 | */ 48 | @inline final def apply[@sp(Int, Long) A](implicit ev: Logic[A]): Logic[A] = ev 49 | 50 | /** 51 | * Turn a [[Heyting]] into a `Logic`. 52 | * Used for binary compatibility. 53 | */ 54 | final def fromHeyting[@sp(Int, Long) A](h: Heyting[A]): Logic[A] = 55 | new Logic[A] { 56 | def and(a: A, b: A): A = h.and(a, b) 57 | 58 | def or(a: A, b: A): A = h.or(a, b) 59 | 60 | def not(a: A): A = h.complement(a) 61 | 62 | def zero: A = h.zero 63 | 64 | def one: A = h.one 65 | 66 | def meet(lhs: A, rhs: A): A = h.meet(lhs, rhs) 67 | 68 | def join(lhs: A, rhs: A): A = h.join(lhs, rhs) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /core/src/main/scala/algebra/lattice/MeetSemilattice.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | package lattice 3 | 4 | import scala.{specialized => sp} 5 | 6 | /** 7 | * A meet-semilattice (or lower semilattice) is a semilattice whose 8 | * operation is called "meet", and which can be thought of as a 9 | * greatest lower bound. 10 | */ 11 | trait MeetSemilattice[@sp(Int, Long, Float, Double) A] extends Any with Serializable { self => 12 | def meet(lhs: A, rhs: A): A 13 | 14 | def meetSemilattice: Semilattice[A] = 15 | new Semilattice[A] { 16 | def combine(x: A, y: A): A = self.meet(x, y) 17 | } 18 | 19 | def meetPartialOrder(implicit ev: Eq[A]): PartialOrder[A] = 20 | meetSemilattice.asMeetPartialOrder 21 | } 22 | 23 | trait MeetSemilatticeFunctions[M[A] <: MeetSemilattice[A]] { 24 | def meet[@sp(Int, Long, Float, Double) A](x: A, y: A)(implicit ev: M[A]): A = 25 | ev.meet(x, y) 26 | } 27 | 28 | object MeetSemilattice extends MeetSemilatticeFunctions[MeetSemilattice] { 29 | 30 | /** 31 | * Access an implicit `MeetSemilattice[A]`. 32 | */ 33 | @inline final def apply[@sp(Int, Long, Float, Double) A](implicit ev: MeetSemilattice[A]): MeetSemilattice[A] = ev 34 | } 35 | -------------------------------------------------------------------------------- /core/src/main/scala/algebra/package.scala: -------------------------------------------------------------------------------- 1 | package object algebra { 2 | 3 | type Band[A] = cats.kernel.Band[A] 4 | val Band = cats.kernel.Band 5 | 6 | type BoundedSemilattice[A] = cats.kernel.BoundedSemilattice[A] 7 | val BoundedSemilattice = cats.kernel.BoundedSemilattice 8 | 9 | type CommutativeGroup[A] = cats.kernel.CommutativeGroup[A] 10 | val CommutativeGroup = cats.kernel.CommutativeGroup 11 | 12 | type CommutativeMonoid[A] = cats.kernel.CommutativeMonoid[A] 13 | val CommutativeMonoid = cats.kernel.CommutativeMonoid 14 | 15 | type CommutativeSemigroup[A] = cats.kernel.CommutativeSemigroup[A] 16 | val CommutativeSemigroup = cats.kernel.CommutativeSemigroup 17 | 18 | type Eq[A] = cats.kernel.Eq[A] 19 | val Eq = cats.kernel.Eq 20 | 21 | type Group[A] = cats.kernel.Group[A] 22 | val Group = cats.kernel.Group 23 | 24 | type Monoid[A] = cats.kernel.Monoid[A] 25 | val Monoid = cats.kernel.Monoid 26 | 27 | type Order[A] = cats.kernel.Order[A] 28 | val Order = cats.kernel.Order 29 | 30 | type PartialOrder[A] = cats.kernel.PartialOrder[A] 31 | val PartialOrder = cats.kernel.PartialOrder 32 | 33 | type Semigroup[A] = cats.kernel.Semigroup[A] 34 | val Semigroup = cats.kernel.Semigroup 35 | 36 | type Semilattice[A] = cats.kernel.Semilattice[A] 37 | val Semilattice = cats.kernel.Semilattice 38 | } 39 | -------------------------------------------------------------------------------- /core/src/main/scala/algebra/ring/Additive.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | package ring 3 | 4 | import scala.{ specialized => sp } 5 | import scala.annotation.tailrec 6 | 7 | trait AdditiveSemigroup[@sp(Int, Long, Float, Double) A] extends Any with Serializable { 8 | def additive: Semigroup[A] = new Semigroup[A] { 9 | def combine(x: A, y: A): A = plus(x, y) 10 | override def combineAllOption(as: TraversableOnce[A]): Option[A] = trySum(as) 11 | } 12 | 13 | def plus(x: A, y: A): A 14 | 15 | def sumN(a: A, n: Int): A = 16 | if (n > 0) positiveSumN(a, n) 17 | else throw new IllegalArgumentException("Illegal non-positive exponent to sumN: %s" format n) 18 | 19 | protected[this] def positiveSumN(a: A, n: Int): A = { 20 | @tailrec def loop(b: A, k: Int, extra: A): A = 21 | if (k == 1) plus(b, extra) else { 22 | val x = if ((k & 1) == 1) plus(b, extra) else extra 23 | loop(plus(b, b), k >>> 1, x) 24 | } 25 | if (n == 1) a else loop(a, n - 1, a) 26 | } 27 | 28 | /** 29 | * Given a sequence of `as`, combine them and return the total. 30 | * 31 | * If the sequence is empty, returns None. Otherwise, returns Some(total). 32 | */ 33 | def trySum(as: TraversableOnce[A]): Option[A] = 34 | as.toIterator.reduceOption(plus) 35 | } 36 | 37 | trait AdditiveCommutativeSemigroup[@sp(Int, Long, Float, Double) A] extends Any with AdditiveSemigroup[A] { 38 | override def additive: CommutativeSemigroup[A] = new CommutativeSemigroup[A] { 39 | def combine(x: A, y: A): A = plus(x, y) 40 | override def combineAllOption(as: TraversableOnce[A]): Option[A] = trySum(as) 41 | } 42 | } 43 | 44 | trait AdditiveMonoid[@sp(Int, Long, Float, Double) A] extends Any with AdditiveSemigroup[A] { 45 | override def additive: Monoid[A] = new Monoid[A] { 46 | def empty = zero 47 | def combine(x: A, y: A): A = plus(x, y) 48 | override def combineAllOption(as: TraversableOnce[A]): Option[A] = trySum(as) 49 | override def combineAll(as: TraversableOnce[A]): A = sum(as) 50 | } 51 | 52 | def zero: A 53 | 54 | /** 55 | * Tests if `a` is zero. 56 | */ 57 | def isZero(a: A)(implicit ev: Eq[A]): Boolean = ev.eqv(a, zero) 58 | 59 | override def sumN(a: A, n: Int): A = 60 | if (n > 0) positiveSumN(a, n) 61 | else if (n == 0) zero 62 | else throw new IllegalArgumentException("Illegal negative exponent to sumN: %s" format n) 63 | 64 | /** 65 | * Given a sequence of `as`, compute the sum. 66 | */ 67 | def sum(as: TraversableOnce[A]): A = 68 | as.foldLeft(zero)(plus) 69 | 70 | override def trySum(as: TraversableOnce[A]): Option[A] = 71 | if (as.isEmpty) None else Some(sum(as)) 72 | } 73 | 74 | trait AdditiveCommutativeMonoid[@sp(Int, Long, Float, Double) A] extends Any with AdditiveMonoid[A] with AdditiveCommutativeSemigroup[A] { 75 | override def additive: CommutativeMonoid[A] = new CommutativeMonoid[A] { 76 | def empty = zero 77 | def combine(x: A, y: A): A = plus(x, y) 78 | override def combineAllOption(as: TraversableOnce[A]): Option[A] = trySum(as) 79 | override def combineAll(as: TraversableOnce[A]): A = sum(as) 80 | } 81 | } 82 | 83 | trait AdditiveGroup[@sp(Int, Long, Float, Double) A] extends Any with AdditiveMonoid[A] { 84 | override def additive: Group[A] = new Group[A] { 85 | def empty = zero 86 | def combine(x: A, y: A): A = plus(x, y) 87 | override def remove(x: A, y: A): A = minus(x, y) 88 | def inverse(x: A): A = negate(x) 89 | override def combineAllOption(as: TraversableOnce[A]): Option[A] = trySum(as) 90 | override def combineAll(as: TraversableOnce[A]): A = sum(as) 91 | } 92 | 93 | def negate(x: A): A 94 | def minus(x: A, y: A): A = plus(x, negate(y)) 95 | 96 | override def sumN(a: A, n: Int): A = 97 | if (n > 0) positiveSumN(a, n) 98 | else if (n == 0) zero 99 | else if (n == Int.MinValue) positiveSumN(negate(plus(a, a)), 1073741824) 100 | else positiveSumN(negate(a), -n) 101 | } 102 | 103 | trait AdditiveCommutativeGroup[@sp(Int, Long, Float, Double) A] extends Any with AdditiveGroup[A] with AdditiveCommutativeMonoid[A] { 104 | override def additive: CommutativeGroup[A] = new CommutativeGroup[A] { 105 | def empty = zero 106 | def combine(x: A, y: A): A = plus(x, y) 107 | override def remove(x: A, y: A): A = minus(x, y) 108 | def inverse(x: A): A = negate(x) 109 | override def combineAllOption(as: TraversableOnce[A]): Option[A] = trySum(as) 110 | override def combineAll(as: TraversableOnce[A]): A = sum(as) 111 | } 112 | } 113 | 114 | trait AdditiveSemigroupFunctions[S[T] <: AdditiveSemigroup[T]] { 115 | 116 | def isAdditiveCommutative[A](implicit ev: S[A]): Boolean = 117 | ev.isInstanceOf[AdditiveCommutativeSemigroup[_]] 118 | 119 | def plus[@sp(Int, Long, Float, Double) A](x: A, y: A)(implicit ev: S[A]): A = 120 | ev.plus(x, y) 121 | 122 | def sumN[@sp(Int, Long, Float, Double) A](a: A, n: Int)(implicit ev: S[A]): A = 123 | ev.sumN(a, n) 124 | 125 | def trySum[A](as: TraversableOnce[A])(implicit ev: S[A]): Option[A] = 126 | ev.trySum(as) 127 | } 128 | 129 | trait AdditiveMonoidFunctions[M[T] <: AdditiveMonoid[T]] extends AdditiveSemigroupFunctions[M] { 130 | def zero[@sp(Int, Long, Float, Double) A](implicit ev: M[A]): A = 131 | ev.zero 132 | 133 | def isZero[@sp(Int, Long, Float, Double) A](a: A)(implicit ev0: M[A], ev1: Eq[A]): Boolean = 134 | ev0.isZero(a) 135 | 136 | def sum[@sp(Int, Long, Float, Double) A](as: TraversableOnce[A])(implicit ev: M[A]): A = 137 | ev.sum(as) 138 | } 139 | 140 | trait AdditiveGroupFunctions[G[T] <: AdditiveGroup[T]] extends AdditiveMonoidFunctions[G] { 141 | def negate[@sp(Int, Long, Float, Double) A](x: A)(implicit ev: G[A]): A = 142 | ev.negate(x) 143 | def minus[@sp(Int, Long, Float, Double) A](x: A, y: A)(implicit ev: G[A]): A = 144 | ev.minus(x, y) 145 | } 146 | 147 | object AdditiveSemigroup extends AdditiveSemigroupFunctions[AdditiveSemigroup] { 148 | @inline final def apply[A](implicit ev: AdditiveSemigroup[A]): AdditiveSemigroup[A] = ev 149 | /** 150 | * This method converts an additive instance into a generic 151 | * instance. 152 | * 153 | * Given an implicit `AdditiveSemigroup[A]`, this method returns a 154 | * `Semigroup[A]`. 155 | */ 156 | @inline final def additive[A](implicit ev: AdditiveSemigroup[A]): Semigroup[A] = 157 | ev.additive 158 | } 159 | 160 | object AdditiveCommutativeSemigroup extends AdditiveSemigroupFunctions[AdditiveCommutativeSemigroup] { 161 | @inline final def apply[A](implicit ev: AdditiveCommutativeSemigroup[A]): AdditiveCommutativeSemigroup[A] = ev 162 | /** 163 | * This method converts an additive instance into a generic 164 | * instance. 165 | * 166 | * Given an implicit `AdditiveCommutativeSemigroup[A]`, this method returns a 167 | * `CommutativeSemigroup[A]`. 168 | */ 169 | @inline final def additive[A](implicit ev: AdditiveCommutativeSemigroup[A]): CommutativeSemigroup[A] = 170 | ev.additive 171 | } 172 | 173 | object AdditiveMonoid extends AdditiveMonoidFunctions[AdditiveMonoid] { 174 | @inline final def apply[A](implicit ev: AdditiveMonoid[A]): AdditiveMonoid[A] = ev 175 | /** 176 | * This method converts an additive instance into a generic 177 | * instance. 178 | * 179 | * Given an implicit `AdditiveMonoid[A]`, this method returns a 180 | * `Monoid[A]`. 181 | */ 182 | @inline final def additive[A](implicit ev: AdditiveMonoid[A]): Monoid[A] = 183 | ev.additive 184 | } 185 | 186 | object AdditiveCommutativeMonoid extends AdditiveMonoidFunctions[AdditiveCommutativeMonoid] { 187 | @inline final def apply[A](implicit ev: AdditiveCommutativeMonoid[A]): AdditiveCommutativeMonoid[A] = ev 188 | /** 189 | * This method converts an additive instance into a generic 190 | * instance. 191 | * 192 | * Given an implicit `AdditiveCommutativeMonoid[A]`, this method returns a 193 | * `CommutativeMonoid[A]`. 194 | */ 195 | @inline final def additive[A](implicit ev: AdditiveCommutativeMonoid[A]): CommutativeMonoid[A] = 196 | ev.additive 197 | } 198 | 199 | object AdditiveGroup extends AdditiveGroupFunctions[AdditiveGroup] { 200 | @inline final def apply[A](implicit ev: AdditiveGroup[A]): AdditiveGroup[A] = ev 201 | /** 202 | * This method converts an additive instance into a generic 203 | * instance. 204 | * 205 | * Given an implicit `AdditiveGroup[A]`, this method returns a 206 | * `Group[A]`. 207 | */ 208 | @inline final def additive[A](implicit ev: AdditiveGroup[A]): Group[A] = 209 | ev.additive 210 | } 211 | 212 | object AdditiveCommutativeGroup extends AdditiveGroupFunctions[AdditiveCommutativeGroup] { 213 | @inline final def apply[A](implicit ev: AdditiveCommutativeGroup[A]): AdditiveCommutativeGroup[A] = ev 214 | /** 215 | * This method converts an additive instance into a generic 216 | * instance. 217 | * 218 | * Given an implicit `AdditiveCommutativeGroup[A]`, this method returns a 219 | * `CommutativeGroup[A]`. 220 | */ 221 | @inline final def additive[A](implicit ev: AdditiveCommutativeGroup[A]): CommutativeGroup[A] = 222 | ev.additive 223 | } 224 | -------------------------------------------------------------------------------- /core/src/main/scala/algebra/ring/BoolRing.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | package ring 3 | 4 | /** 5 | * A Boolean ring is a ring whose multiplication is idempotent, that is 6 | * `a⋅a = a` for all elements ''a''. This property also implies `a+a = 0` 7 | * for all ''a'', and `a⋅b = b⋅a` (commutativity of multiplication). 8 | * 9 | * Every Boolean ring is equivalent to a Boolean algebra. 10 | * See `algebra.lattice.BoolFromBoolRing` for details. 11 | */ 12 | trait BoolRing[A] extends Any with BoolRng[A] with CommutativeRing[A] 13 | 14 | object BoolRing extends RingFunctions[BoolRing] { 15 | @inline final def apply[A](implicit r: BoolRing[A]): BoolRing[A] = r 16 | } 17 | -------------------------------------------------------------------------------- /core/src/main/scala/algebra/ring/BoolRng.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | package ring 3 | 4 | /** 5 | * A Boolean rng is a rng whose multiplication is idempotent, that is 6 | * `a⋅a = a` for all elements ''a''. This property also implies `a+a = 0` 7 | * for all ''a'', and `a⋅b = b⋅a` (commutativity of multiplication). 8 | * 9 | * Every `BoolRng` is equivalent to `algebra.lattice.GenBool`. 10 | * See `algebra.lattice.GenBoolFromBoolRng` for details. 11 | */ 12 | trait BoolRng[A] extends Any with CommutativeRng[A] { self => 13 | override final def negate(x: A): A = x 14 | } 15 | 16 | object BoolRng extends AdditiveGroupFunctions[BoolRng] with MultiplicativeSemigroupFunctions[BoolRng] { 17 | @inline final def apply[A](implicit r: BoolRng[A]): BoolRng[A] = r 18 | } 19 | -------------------------------------------------------------------------------- /core/src/main/scala/algebra/ring/CommutativeRig.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | package ring 3 | 4 | import scala.{specialized => sp} 5 | 6 | /** 7 | * CommutativeRig is a Rig that is commutative under multiplication. 8 | */ 9 | trait CommutativeRig[@sp(Int, Long, Float, Double) A] extends Any with Rig[A] with CommutativeSemiring[A] with MultiplicativeCommutativeMonoid[A] 10 | 11 | object CommutativeRig extends AdditiveMonoidFunctions[CommutativeRig] with MultiplicativeMonoidFunctions[CommutativeRig] { 12 | @inline final def apply[A](implicit r: CommutativeRig[A]): CommutativeRig[A] = r 13 | } 14 | -------------------------------------------------------------------------------- /core/src/main/scala/algebra/ring/CommutativeRing.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | package ring 3 | 4 | import scala.{specialized => sp} 5 | 6 | /** 7 | * CommutativeRing is a Ring that is commutative under multiplication. 8 | */ 9 | trait CommutativeRing[@sp(Int, Long, Float, Double) A] extends Any with Ring[A] with CommutativeRig[A] with CommutativeRng[A] 10 | 11 | object CommutativeRing extends RingFunctions[CommutativeRing] { 12 | @inline final def apply[A](implicit r: CommutativeRing[A]): CommutativeRing[A] = r 13 | } 14 | -------------------------------------------------------------------------------- /core/src/main/scala/algebra/ring/CommutativeRng.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | package ring 3 | 4 | import scala.{specialized => sp} 5 | 6 | /** 7 | * CommutativeRng is a Rng that is commutative under multiplication. 8 | */ 9 | trait CommutativeRng[@sp(Int, Long, Float, Double) A] extends Any with Rng[A] with CommutativeSemiring[A] 10 | 11 | object CommutativeRng extends AdditiveGroupFunctions[CommutativeRng] with MultiplicativeSemigroupFunctions[CommutativeRng] { 12 | @inline final def apply[A](implicit r: CommutativeRng[A]): CommutativeRng[A] = r 13 | } 14 | -------------------------------------------------------------------------------- /core/src/main/scala/algebra/ring/CommutativeSemiring.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | package ring 3 | 4 | import scala.{specialized => sp} 5 | 6 | /** 7 | * CommutativeSemiring is a Semiring that is commutative under multiplication. 8 | */ 9 | trait CommutativeSemiring[@sp(Int, Long, Float, Double) A] extends Any with Semiring[A] with MultiplicativeCommutativeSemigroup[A] 10 | 11 | object CommutativeSemiring extends AdditiveMonoidFunctions[CommutativeSemiring] with MultiplicativeSemigroupFunctions[CommutativeSemiring] { 12 | @inline final def apply[A](implicit r: CommutativeSemiring[A]): CommutativeSemiring[A] = r 13 | } 14 | -------------------------------------------------------------------------------- /core/src/main/scala/algebra/ring/Field.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | package ring 3 | 4 | import scala.{ specialized => sp } 5 | 6 | trait Field[@sp(Int, Long, Float, Double) A] extends Any with CommutativeRing[A] with MultiplicativeCommutativeGroup[A] { self => 7 | 8 | /** 9 | * This is implemented in terms of basic Field ops. However, this is 10 | * probably significantly less efficient than can be done with a 11 | * specific type. So, it is recommended that this method be 12 | * overriden. 13 | * 14 | * This is possible because a Double is a rational number. 15 | */ 16 | def fromDouble(a: Double): A = Field.defaultFromDouble(a)(self, self) 17 | 18 | } 19 | 20 | trait FieldFunctions[F[T] <: Field[T]] extends RingFunctions[F] with MultiplicativeGroupFunctions[F] { 21 | def fromDouble[@sp(Int, Long, Float, Double) A](n: Double)(implicit ev: F[A]): A = 22 | ev.fromDouble(n) 23 | } 24 | 25 | object Field extends FieldFunctions[Field] { 26 | @inline final def apply[A](implicit ev: Field[A]): Field[A] = ev 27 | } 28 | -------------------------------------------------------------------------------- /core/src/main/scala/algebra/ring/Multiplicative.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | package ring 3 | 4 | import scala.{ specialized => sp } 5 | import scala.annotation.tailrec 6 | 7 | trait MultiplicativeSemigroup[@sp(Int, Long, Float, Double) A] extends Any with Serializable { 8 | def multiplicative: Semigroup[A] = 9 | new Semigroup[A] { 10 | def combine(x: A, y: A): A = times(x, y) 11 | } 12 | 13 | def times(x: A, y: A): A 14 | 15 | def pow(a: A, n: Int): A = 16 | if (n > 0) positivePow(a, n) 17 | else throw new IllegalArgumentException("Illegal non-positive exponent to pow: %s" format n) 18 | 19 | protected[this] def positivePow(a: A, n: Int): A = { 20 | @tailrec def loop(b: A, k: Int, extra: A): A = 21 | if (k == 1) times(b, extra) else { 22 | val x = if ((k & 1) == 1) times(b, extra) else extra 23 | loop(times(b, b), k >>> 1, x) 24 | } 25 | if (n == 1) a else loop(a, n - 1, a) 26 | } 27 | 28 | /** 29 | * Given a sequence of `as`, combine them and return the total. 30 | * 31 | * If the sequence is empty, returns None. Otherwise, returns Some(total). 32 | */ 33 | def tryProduct(as: TraversableOnce[A]): Option[A] = 34 | as.toIterator.reduceOption(times) 35 | } 36 | 37 | trait MultiplicativeCommutativeSemigroup[@sp(Int, Long, Float, Double) A] extends Any with MultiplicativeSemigroup[A] { 38 | override def multiplicative: CommutativeSemigroup[A] = new CommutativeSemigroup[A] { 39 | def combine(x: A, y: A): A = times(x, y) 40 | } 41 | } 42 | 43 | trait MultiplicativeMonoid[@sp(Int, Long, Float, Double) A] extends Any with MultiplicativeSemigroup[A] { 44 | override def multiplicative: Monoid[A] = new Monoid[A] { 45 | def empty = one 46 | def combine(x: A, y: A): A = times(x, y) 47 | } 48 | 49 | def one: A 50 | 51 | /** 52 | * Tests if `a` is one. 53 | */ 54 | def isOne(a: A)(implicit ev: Eq[A]): Boolean = ev.eqv(a, one) 55 | 56 | override def pow(a: A, n: Int): A = 57 | if (n > 0) positivePow(a, n) 58 | else if (n == 0) one 59 | else throw new IllegalArgumentException("Illegal negative exponent to pow: %s" format n) 60 | 61 | /** 62 | * Given a sequence of `as`, compute the product. 63 | */ 64 | def product(as: TraversableOnce[A]): A = 65 | as.foldLeft(one)(times) 66 | 67 | override def tryProduct(as: TraversableOnce[A]): Option[A] = 68 | if (as.isEmpty) None else Some(product(as)) 69 | } 70 | 71 | trait MultiplicativeCommutativeMonoid[@sp(Int, Long, Float, Double) A] extends Any with MultiplicativeMonoid[A] with MultiplicativeCommutativeSemigroup[A] { 72 | override def multiplicative: CommutativeMonoid[A] = new CommutativeMonoid[A] { 73 | def empty = one 74 | def combine(x: A, y: A): A = times(x, y) 75 | } 76 | } 77 | 78 | trait MultiplicativeGroup[@sp(Int, Long, Float, Double) A] extends Any with MultiplicativeMonoid[A] { 79 | override def multiplicative: Group[A] = new Group[A] { 80 | def empty = one 81 | def combine(x: A, y: A): A = times(x, y) 82 | override def remove(x: A, y: A): A = div(x, y) 83 | def inverse(x: A): A = reciprocal(x) 84 | } 85 | 86 | def reciprocal(x: A): A = div(one, x) 87 | def div(x: A, y: A): A 88 | 89 | override def pow(a: A, n: Int): A = 90 | if (n > 0) positivePow(a, n) 91 | else if (n == 0) one 92 | else if (n == Int.MinValue) positivePow(reciprocal(times(a, a)), 1073741824) 93 | else positivePow(reciprocal(a), -n) 94 | } 95 | 96 | trait MultiplicativeCommutativeGroup[@sp(Int, Long, Float, Double) A] extends Any with MultiplicativeGroup[A] with MultiplicativeCommutativeMonoid[A] { 97 | override def multiplicative: CommutativeGroup[A] = new CommutativeGroup[A] { 98 | def empty = one 99 | def combine(x: A, y: A): A = times(x, y) 100 | override def remove(x: A, y: A): A = div(x, y) 101 | def inverse(x: A): A = reciprocal(x) 102 | } 103 | } 104 | 105 | trait MultiplicativeSemigroupFunctions[S[T] <: MultiplicativeSemigroup[T]] { 106 | def isMultiplicativeCommutative[A](implicit ev: S[A]): Boolean = 107 | ev.isInstanceOf[MultiplicativeCommutativeSemigroup[A]] 108 | 109 | def times[@sp(Int, Long, Float, Double) A](x: A, y: A)(implicit ev: S[A]): A = 110 | ev.times(x, y) 111 | def pow[@sp(Int, Long, Float, Double) A](a: A, n: Int)(implicit ev: S[A]): A = 112 | ev.pow(a, n) 113 | 114 | def tryProduct[A](as: TraversableOnce[A])(implicit ev: S[A]): Option[A] = 115 | ev.tryProduct(as) 116 | } 117 | 118 | trait MultiplicativeMonoidFunctions[M[T] <: MultiplicativeMonoid[T]] extends MultiplicativeSemigroupFunctions[M] { 119 | def one[@sp(Int, Long, Float, Double) A](implicit ev: M[A]): A = 120 | ev.one 121 | 122 | def isOne[@sp(Int, Long, Float, Double) A](a: A)(implicit ev0: M[A], ev1: Eq[A]): Boolean = 123 | ev0.isOne(a) 124 | 125 | def product[@sp(Int, Long, Float, Double) A](as: TraversableOnce[A])(implicit ev: M[A]): A = 126 | ev.product(as) 127 | } 128 | 129 | trait MultiplicativeGroupFunctions[G[T] <: MultiplicativeGroup[T]] extends MultiplicativeMonoidFunctions[G] { 130 | def reciprocal[@sp(Int, Long, Float, Double) A](x: A)(implicit ev: G[A]): A = 131 | ev.reciprocal(x) 132 | def div[@sp(Int, Long, Float, Double) A](x: A, y: A)(implicit ev: G[A]): A = 133 | ev.div(x, y) 134 | } 135 | 136 | object MultiplicativeSemigroup extends MultiplicativeSemigroupFunctions[MultiplicativeSemigroup] { 137 | @inline final def apply[A](implicit ev: MultiplicativeSemigroup[A]): MultiplicativeSemigroup[A] = ev 138 | /** 139 | * This method converts a multiplicative instance into a generic 140 | * instance. 141 | * 142 | * Given an implicit `MultiplicativeSemigroup[A]`, this method returns 143 | * a `Semigroup[A]`. 144 | */ 145 | @inline final def multiplicative[A](implicit ev: MultiplicativeSemigroup[A]): Semigroup[A] = 146 | ev.multiplicative 147 | } 148 | 149 | object MultiplicativeCommutativeSemigroup extends MultiplicativeSemigroupFunctions[MultiplicativeCommutativeSemigroup] { 150 | @inline final def apply[A](implicit ev: MultiplicativeCommutativeSemigroup[A]): MultiplicativeCommutativeSemigroup[A] = ev 151 | /** 152 | * This method converts a multiplicative instance into a generic 153 | * instance. 154 | * 155 | * Given an implicit `MultiplicativeCommutativeSemigroup[A]`, this method returns 156 | * a `CommutativeSemigroup[A]`. 157 | */ 158 | @inline final def multiplicative[A](implicit ev: MultiplicativeCommutativeSemigroup[A]): CommutativeSemigroup[A] = 159 | ev.multiplicative 160 | } 161 | 162 | object MultiplicativeMonoid extends MultiplicativeMonoidFunctions[MultiplicativeMonoid] { 163 | @inline final def apply[A](implicit ev: MultiplicativeMonoid[A]): MultiplicativeMonoid[A] = ev 164 | /** 165 | * This method converts a multiplicative instance into a generic 166 | * instance. 167 | * 168 | * Given an implicit `MultiplicativeMonoid[A]`, this method returns 169 | * a `Monoid[A]`. 170 | */ 171 | @inline final def multiplicative[A](implicit ev: MultiplicativeMonoid[A]): Monoid[A] = 172 | ev.multiplicative 173 | } 174 | 175 | object MultiplicativeCommutativeMonoid extends MultiplicativeMonoidFunctions[MultiplicativeCommutativeMonoid] { 176 | @inline final def apply[A](implicit ev: MultiplicativeCommutativeMonoid[A]): MultiplicativeCommutativeMonoid[A] = ev 177 | /** 178 | * This method converts a multiplicative instance into a generic 179 | * instance. 180 | * 181 | * Given an implicit `MultiplicativeCommutativeMonoid[A]`, this method returns 182 | * a `CommutativeMonoid[A]`. 183 | */ 184 | @inline final def multiplicative[A](implicit ev: MultiplicativeCommutativeMonoid[A]): CommutativeMonoid[A] = 185 | ev.multiplicative 186 | } 187 | 188 | object MultiplicativeGroup extends MultiplicativeGroupFunctions[MultiplicativeGroup] { 189 | @inline final def apply[A](implicit ev: MultiplicativeGroup[A]): MultiplicativeGroup[A] = ev 190 | /** 191 | * This method converts a multiplicative instance into a generic 192 | * instance. 193 | * 194 | * Given an implicit `MultiplicativeGroup[A]`, this method returns 195 | * a `Group[A]`. 196 | */ 197 | @inline final def multiplicative[A](implicit ev: MultiplicativeGroup[A]): Group[A] = 198 | ev.multiplicative 199 | } 200 | 201 | object MultiplicativeCommutativeGroup extends MultiplicativeGroupFunctions[MultiplicativeCommutativeGroup] { 202 | @inline final def apply[A](implicit ev: MultiplicativeCommutativeGroup[A]): MultiplicativeCommutativeGroup[A] = ev 203 | /** 204 | * This method converts a multiplicative instance into a generic 205 | * instance. 206 | * 207 | * Given an implicit `MultiplicativeCommutativeGroup[A]`, this method returns 208 | * a `CommutativeGroup[A]`. 209 | */ 210 | @inline final def multiplicative[A](implicit ev: MultiplicativeCommutativeGroup[A]): CommutativeGroup[A] = 211 | ev.multiplicative 212 | } 213 | -------------------------------------------------------------------------------- /core/src/main/scala/algebra/ring/Rig.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | package ring 3 | 4 | import scala.{specialized => sp} 5 | 6 | /** 7 | * Rig consists of: 8 | * 9 | * - a commutative monoid for addition (+) 10 | * - a monoid for multiplication (*) 11 | * 12 | * Alternately, a Rig can be thought of as a ring without 13 | * multiplicative or additive inverses (or as a semiring with a 14 | * multiplicative identity). 15 | 16 | * Mnemonic: "Rig is a Ring without 'N'egation." 17 | */ 18 | trait Rig[@sp(Int, Long, Float, Double) A] extends Any with Semiring[A] with MultiplicativeMonoid[A] 19 | 20 | object Rig extends AdditiveMonoidFunctions[Rig] with MultiplicativeMonoidFunctions[Rig] { 21 | @inline final def apply[A](implicit ev: Rig[A]): Rig[A] = ev 22 | } 23 | -------------------------------------------------------------------------------- /core/src/main/scala/algebra/ring/Ring.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | package ring 3 | 4 | import scala.{specialized => sp} 5 | import scala.annotation.tailrec 6 | 7 | /** 8 | * Ring consists of: 9 | * 10 | * - a commutative group for addition (+) 11 | * - a monoid for multiplication (*) 12 | * 13 | * Additionally, multiplication must distribute over addition. 14 | * 15 | * Ring implements some methods (for example fromInt) in terms of 16 | * other more fundamental methods (zero, one and plus). Where 17 | * possible, these methods should be overridden by more efficient 18 | * implementations. 19 | */ 20 | trait Ring[@sp(Int, Long, Float, Double) A] extends Any with Rig[A] with Rng[A] { 21 | 22 | /** 23 | * Convert the given integer to an instance of A. 24 | * 25 | * Defined to be equivalent to `sumN(one, n)`. 26 | * 27 | * That is, `n` repeated summations of this ring's `one`, or `-n` 28 | * summations of `-one` if `n` is negative. 29 | * 30 | * Most type class instances should consider overriding this method 31 | * for performance reasons. 32 | */ 33 | def fromInt(n: Int): A = sumN(one, n) 34 | 35 | /** 36 | * Convert the given BigInt to an instance of A. 37 | * 38 | * This is equivalent to `n` repeated summations of this ring's `one`, or 39 | * `-n` summations of `-one` if `n` is negative. 40 | * 41 | * Most type class instances should consider overriding this method for 42 | * performance reasons. 43 | */ 44 | def fromBigInt(n: BigInt): A = Ring.defaultFromBigInt(n)(this) 45 | } 46 | 47 | trait RingFunctions[R[T] <: Ring[T]] extends AdditiveGroupFunctions[R] with MultiplicativeMonoidFunctions[R] { 48 | def fromInt[@sp(Int, Long, Float, Double) A](n: Int)(implicit ev: R[A]): A = 49 | ev.fromInt(n) 50 | 51 | def fromBigInt[@sp(Int, Long, Float, Double) A](n: BigInt)(implicit ev: R[A]): A = 52 | ev.fromBigInt(n) 53 | 54 | final def defaultFromBigInt[@sp(Int, Long, Float, Double) A](n: BigInt)(implicit ev: R[A]): A = { 55 | if (n.isValidInt) { 56 | ev.fromInt(n.toInt) 57 | } else { 58 | val d = ev.fromInt(1 << 30) 59 | val mask = (1L << 30) - 1 60 | @tailrec def loop(k: A, x: BigInt, acc: A): A = 61 | if (x.isValidInt) { 62 | ev.plus(ev.times(k, ev.fromInt(x.toInt)), acc) 63 | } else { 64 | val y = x >> 30 65 | val r = ev.fromInt((x & mask).toInt) 66 | loop(ev.times(d, k), y, ev.plus(ev.times(k, r), acc)) 67 | } 68 | 69 | val absValue = loop(one, n.abs, zero) 70 | if (n.signum < 0) ev.negate(absValue) else absValue 71 | } 72 | } 73 | 74 | /** Returns the given Double, understood as a rational number, in the provided 75 | * (division) ring. 76 | * 77 | * This is implemented in terms of basic ops. However, this is 78 | * probably significantly less efficient than can be done with a specific 79 | * type. So, it is recommended to specialize this general method. 80 | */ 81 | final def defaultFromDouble[A](a: Double)(implicit ringA: Ring[A], mgA: MultiplicativeGroup[A]): A = 82 | if (a == 0.0) ringA.zero 83 | else if (a.isValidInt) ringA.fromInt(a.toInt) 84 | else { 85 | import java.lang.Double.{ isInfinite, isNaN, doubleToLongBits } 86 | import java.lang.Long.numberOfTrailingZeros 87 | require(!isInfinite(a) && !isNaN(a), "Double must be representable as a fraction.") 88 | val bits = doubleToLongBits(a) 89 | val expBits = ((bits >> 52) & 0x7FF).toInt 90 | val mBits = bits & 0x000FFFFFFFFFFFFFL 91 | // If expBits is 0, then this is a subnormal and we drop the implicit 92 | // 1 bit. 93 | val m = if (expBits > 0) mBits | 0x0010000000000000L else mBits 94 | val zeros = numberOfTrailingZeros(m) 95 | val value = m >>> zeros 96 | // If expBits is 0, then this is a subnormal with expBits = 1. 97 | val exp = math.max(1, expBits) - 1075 + zeros // 1023 + 52 98 | 99 | val high = ringA.times(ringA.fromInt((value >>> 30).toInt), ringA.fromInt(1 << 30)) 100 | val low = ringA.fromInt((value & 0x3FFFFFFF).toInt) 101 | val num = ringA.plus(high, low) 102 | val unsigned = if (exp > 0) { 103 | ringA.times(num, ringA.pow(ringA.fromInt(2), exp)) 104 | } else if (exp < 0) { 105 | mgA.div(num, ringA.pow(ringA.fromInt(2), -exp)) 106 | } else { 107 | num 108 | } 109 | 110 | if (a < 0) ringA.negate(unsigned) else unsigned 111 | } 112 | 113 | } 114 | 115 | object Ring extends RingFunctions[Ring] { 116 | @inline final def apply[A](implicit ev: Ring[A]): Ring[A] = ev 117 | } 118 | -------------------------------------------------------------------------------- /core/src/main/scala/algebra/ring/Rng.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | package ring 3 | 4 | import scala.{specialized => sp} 5 | 6 | /** 7 | * Rng (pronounced "Rung") consists of: 8 | * 9 | * - a commutative group for addition (+) 10 | * - a semigroup for multiplication (*) 11 | * 12 | * Alternately, a Rng can be thought of as a ring without a 13 | * multiplicative identity (or as a semiring with an additive 14 | * inverse). 15 | * 16 | * Mnemonic: "Rng is a Ring without multiplicative 'I'dentity." 17 | */ 18 | trait Rng[@sp(Int, Long, Float, Double) A] extends Any with Semiring[A] with AdditiveCommutativeGroup[A] 19 | 20 | object Rng extends AdditiveGroupFunctions[Rng] with MultiplicativeSemigroupFunctions[Rng] { 21 | @inline final def apply[A](implicit ev: Rng[A]): Rng[A] = ev 22 | } 23 | -------------------------------------------------------------------------------- /core/src/main/scala/algebra/ring/Semiring.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | package ring 3 | 4 | import scala.{specialized => sp} 5 | 6 | /** 7 | * Semiring consists of: 8 | * 9 | * - a commutative monoid for addition (+) 10 | * - a semigroup for multiplication (*) 11 | * 12 | * Alternately, a Semiring can be thought of as a ring without a 13 | * multiplicative identity or an additive inverse. 14 | * 15 | * A Semiring with an additive inverse (-) is a Rng. 16 | * A Semiring with a multiplicative identity (1) is a Rig. 17 | * A Semiring with both of those is a Ring. 18 | */ 19 | trait Semiring[@sp(Int, Long, Float, Double) A] extends Any with AdditiveCommutativeMonoid[A] with MultiplicativeSemigroup[A] 20 | 21 | object Semiring extends AdditiveMonoidFunctions[Semiring] with MultiplicativeSemigroupFunctions[Semiring] { 22 | @inline final def apply[A](implicit ev: Semiring[A]): Semiring[A] = ev 23 | } 24 | -------------------------------------------------------------------------------- /core/src/test/scala/algebra/Instances.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | 3 | object Instances { 4 | 5 | def t2HasSemigroup[A, B](implicit eva: Semigroup[A], evb: Semigroup[B]) = 6 | new Semigroup[(A, B)] { 7 | def combine(x: (A, B), y: (A, B)): (A, B) = 8 | (eva.combine(x._1, y._1), evb.combine(x._2, y._2)) 9 | } 10 | 11 | val stringHasMonoid = 12 | new Monoid[String] { 13 | def empty: String = "" 14 | def combine(x: String, y: String): String = x + y 15 | } 16 | 17 | def f1ComposeMonoid[A] = 18 | new Monoid[A => A] { 19 | def empty: A => A = 20 | a => a 21 | def combine(x: A => A, y: A => A): A => A = 22 | a => y(x(a)) 23 | } 24 | 25 | def f1HomomorphismMonoid[A, B](implicit ev: Monoid[B]) = 26 | new Monoid[A => B] { 27 | def empty: A => B = 28 | _ => ev.empty 29 | def combine(x: A => B, y: A => B): A => B = 30 | a => ev.combine(x(a), y(a)) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /core/src/test/scala/algebra/ring/RingTest.scala: -------------------------------------------------------------------------------- 1 | package algebra.ring 2 | 3 | import algebra.instances.bigInt._ 4 | 5 | import org.scalacheck.Prop._ 6 | 7 | class RingTest extends munit.DisciplineSuite { 8 | test("Ring.defaultFromBigInt") { 9 | forAll { (n: BigInt) => 10 | assertEquals(Ring.defaultFromBigInt[BigInt](n), n) 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /docs/docs/main/resources/microsite/data/menu.yml: -------------------------------------------------------------------------------- 1 | options: 2 | ############################# 3 | # Type Classes Menu Options # 4 | ############################# 5 | 6 | - title: Type Classes 7 | url: typeclasses.html 8 | menu_type: typeclasses 9 | menu_section: typeclasses 10 | 11 | - title: Algebra Overview 12 | url: typeclasses/overview.html 13 | menu_type: typeclasses 14 | menu_section: abstractalgebra 15 | 16 | # nested_options: 17 | # - title: Semigroup (or 18 | # url: typeclasses/semigroup.html 19 | # menu_section: abstractalgebra 20 | 21 | ########################### 22 | # Cookbook Menu Options # 23 | ########################### 24 | 25 | - title: Cookbook 26 | url: cookbook.html 27 | menu_type: cookbook 28 | -------------------------------------------------------------------------------- /docs/docs/main/tut/cookbook.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: docs 3 | title: "Cookbook" 4 | section: "cookbook" 5 | position: 6 6 | --- 7 | 8 | {% include_relative cookbook/cookbook.md %} 9 | 10 | ## Index 11 | 12 | {% for x in site.pages %} 13 | {% if x.section == 'cookbook' %} 14 | - [{{x.title}}]({{site.baseurl}}{{x.url}}) 15 | {% endif %} 16 | {% endfor %} 17 | -------------------------------------------------------------------------------- /docs/docs/main/tut/cookbook/cookbook.md: -------------------------------------------------------------------------------- 1 | # Cookbook 2 | 3 | In Progress - a cookbook of things you might like to do with Algebra. 4 | 5 | ### Documentation Help 6 | 7 | We'd love your help with this documentation! You can edit this page in your browser by clicking [this link](https://github.com/typelevel/algebra/edit/master/docs/src/main/tut/cookbook/cookbook.md). 8 | -------------------------------------------------------------------------------- /docs/docs/main/tut/faq.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: "FAQ" 4 | section: "faq" 5 | position: 3 6 | --- 7 | 8 | ## Frequently Asked Questions 9 | 10 | * [How can I help?](#contributing) 11 | 12 | ### How can I help? 13 | 14 | The Algebra community welcomes and encourages contributions! See the [contributing guide]({{ site.baseurl }}/contributing.html) for more information on the best ways to help. 15 | 16 | ### Documentation Help 17 | 18 | We'd love your help with this documentation! You can edit this page in your browser by clicking [this link](https://github.com/typelevel/algebra/edit/master/docs/src/main/tut/faq.md). 19 | -------------------------------------------------------------------------------- /docs/docs/main/tut/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: home 3 | title: "Home" 4 | section: "home" 5 | --- 6 | 7 | Algebra unifies the basic algebraic type classes from [Spire](http://github.com/non/spire) and [Algebird](http://github.com/twitter/algebird). By targeting just these type classes `algebra` offers a package with no dependencies (except for cats-kernel) that works with Scala 2.11 and 2.12, which can be shared by all Scala libraries interested in abstract algebra. 8 | 9 | Algebra also interoperates with the [Cats](http://github.com/typelevel/cats) project. Algebra and Cats interoperate using the *cats-kernel* module. 10 | 11 | ## getting algebra 12 | 13 | Algebra supports Scala 2.11 and 2.12 and is available from Sonatype (and Maven Central). In addition to the JVM, Algebra also supports Scala.js. 14 | 15 | To use algebra in your own projects, include this snippet in your `build.sbt` file: 16 | 17 | ```scala 18 | libraryDependencies += "org.typelevel" %% "algebra" % "1.0.1" 19 | ``` 20 | 21 | If you want to use Algebra's laws, you can include those as well with this snippet: 22 | 23 | ```scala 24 | libraryDependencies += "org.typelevel" %% "algebra-laws" % "1.0.1" 25 | ``` 26 | 27 | ## what we have so far 28 | 29 | This repo has been seeded with most of the contents of `spire.algebra`, and has been modified to fit an initially-conservative and shared vision for algebraic type classes. Discussions over removals and additions should take place on the issue tracker or on relevant pull requests. 30 | 31 | ## Copyright and License 32 | 33 | All code is available to you under the [MIT license](http://opensource.org/licenses/mit-license.php). 34 | 35 | Copyright the maintainers, 2015-2016. 36 | -------------------------------------------------------------------------------- /docs/docs/main/tut/typeclasses.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: docs 3 | title: "Type Classes" 4 | section: "typeclasses" 5 | position: 1 6 | --- 7 | 8 | # Type Classes 9 | 10 | Algebra uses type classes to represent algebraic structures. You can use these type classes to represent the abstract capabilities (and requirements) you want generic parameters to possess. 11 | 12 | ## Index 13 | 14 | {% for x in site.pages %} 15 | {% if x.section == 'typeclasses' %} 16 | - [{{x.title}}]({{site.baseurl}}{{x.url}}) 17 | {% endif %} 18 | {% endfor %} 19 | 20 | ### Documentation Help 21 | 22 | We'd love your help with this documentation! You can edit this page in your browser by clicking [this link](https://github.com/typelevel/algebra/edit/master/docs/src/main/tut/typeclasses.md). 23 | -------------------------------------------------------------------------------- /docs/docs/main/tut/typeclasses/overview.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: docs 3 | title: "Algebra Overview" 4 | section: "typeclasses" 5 | --- 6 | 7 | # Algebra Overview 8 | 9 | Algebra uses type classes to represent algebraic structures. You can use these type classes to represent the abstract capabilities (and requirements) you want generic parameters to possess. 10 | 11 | This section will explain the structures available. 12 | 13 | ## algebraic properties and terminology 14 | 15 | We will be talking about properties like *associativity* and *commutativity*. Here is a quick explanation of what those properties mean: 16 | 17 | |Name |Description | 18 | |-------------|--------------------------------------------------------------------------------| 19 | |Associative | If `⊕` is associative, then `a ⊕ (b ⊕ c)` = `(a ⊕ b) ⊕ c`. | 20 | |Commutative | If `⊕` is commutative, then `a ⊕ b` = `b ⊕ a`. | 21 | |Identity | If `id` is an identity for `⊕`, then `a ⊕ id` = `id ⊕ a` = `a`. | 22 | |Inverse | If `¬` is an inverse for `⊕` and `id`, then `a ⊕ ¬a` = `¬a ⊕ a` = `id`. | 23 | |Distributive | If `⊕` and `⊙` distribute, then `a ⊙ (b ⊕ c)` = `(a ⊙ b) ⊕ (a ⊙ c)` and `(a ⊕ b) ⊙ c` = `(a ⊙ c) ⊕ (b ⊙ c)`. | 24 | |Idempotent | If `⊕` is idempotent, then `a ⊕ a` = `a`. If `f` is idempotent, then `f(f(a))` = `f(a)` | 25 | 26 | Though these properties are illustrated with symbolic operators, they work equally-well with functions. When you see `a ⊕ b` that is equivalent to `f(a, b)`: `⊕` is an infix representation of the binary function `f`, and `a` and `b` are values (of some type `A`). 27 | 28 | Similarly, when you see `¬a` that is equivalent to `g(a)`: `¬` is a prefix representation of the unary function `g`, and `a` is a value (of some type `A`). 29 | 30 | ## basic algebraic structures 31 | 32 | The most basic structures can be found in the `algebra` package. They all implement a method called `combine`, which is associative. The identity element (if present) will be called `empty`, and the inverse method (if present) will be called `inverse`. 33 | 34 | |Name |Associative?|Commutative?|Identity?|Inverse?|Idempotent?| 35 | |--------------------|------------|------------|---------|--------|-----------| 36 | |Semigroup | ✓| | | | | 37 | |CommutativeSemigroup| ✓| ✓| | | | 38 | |Monoid | ✓| | ✓| | | 39 | |Band | ✓| | | | ✓| 40 | |Semilattice | ✓| ✓| | | ✓| 41 | |Group | ✓| | ✓| ✓| | 42 | |CommutativeMonoid | ✓| ✓| ✓| | | 43 | |CommutativeGroup | ✓| ✓| ✓| ✓| | 44 | |BoundedSemilattice | ✓| ✓| ✓| | ✓| 45 | 46 | (For a description of what each column means, see [§algebraic properties and terminology](#algebraic-properties-and-terminology).) 47 | 48 | ## ring-like structures 49 | 50 | The `algebra.ring` package contains more sophisticated structures which combine an *additive* operation (called `plus`) and a *multiplicative* operation (called `times`). Additive identity and inverses will be called `zero` and `negate` (respectively); multiplicative identity and inverses will be called `one` and `reciprocal` (respectively). 51 | 52 | All ring-like structures are associative for both `+` and `*`, have commutative `+`, and have a `zero` element (an identity for `+`). 53 | 54 | |Name |Has `negate`?|Has `1`?|Has `reciprocal`?|Commutative `*`?| 55 | |--------------------|-------------|--------|-----------------|----------------| 56 | |Semiring | | | | | 57 | |Rng | ✓| | | | 58 | |Rig | | ✓| | | 59 | |CommutativeRig | | ✓| | ✓| 60 | |Ring | ✓| ✓| | | 61 | |CommutativeRing | ✓| ✓| | ✓| 62 | |Field | ✓| ✓| ✓| ✓| 63 | 64 | With the exception of `CommutativeRig` and `Rng`, every lower structure is also an instance of the structures above it. For example, every `Ring` is a `Rig`, every `Field` is a `CommutativeRing`, and so on. 65 | 66 | (For a description of what the terminology in each column means, see [§algebraic properties and terminology](#algebraic-properties-and-terminology).) 67 | 68 | ## lattice-like structures 69 | 70 | The `algebra.lattice` package contains more structures that can be somewhat ring-like. Rather than `plus` and `times` we have `meet` and `join` both of which are always associative, commutative and idempotent, and as such each can be viewed as a semilattice. Meet can be thought of as the greatest lower bound of two items while join can be thought of as the least upper bound between two items. 71 | 72 | When `zero` is present, `join(a, zero)` = `a`. When `one` is present `meet(a, one)` = `a`. 73 | 74 | When `meet` and `join` are both present, they obey the absorption law: 75 | 76 | - `meet(a, join(a, b))` = `join(a, meet(a, b)) = a` 77 | 78 | Sometimes meet and join distribute, we say it is distributive in this case: 79 | 80 | - `meet(a, join(b, c))` = `join(meet(a, b), meet(a, c))` 81 | - `join(a, meet(b, c))` = `meet(join(a, b), join(a, c))` 82 | 83 | Sometimes an additional binary operation `imp` (for impliciation, also written as →, meet written as ∧) is present. Implication obeys the following laws: 84 | 85 | - `a → a` = `1` 86 | - `a ∧ (a → b)` = `a ∧ b` 87 | - `b ∧ (a → b)` = `b` 88 | - `a → (b ∧ c)` = `(a → b) ∧ (a → c)` 89 | 90 | The law of the excluded middle can be expressed as: 91 | 92 | - `(a ∨ (a → 0))` = `1` 93 | 94 | |Name |Has `join`?|Has `meet`?|Has `zero`?|Has `one`?|Distributive|Has `imp`?|Excludes middle?| 95 | |--------------------------|-----------|-----------|-----------|----------|------------|----------|----------------| 96 | |JoinSemilattice | ✓| | | | | | | 97 | |MeetSemilattice | | ✓| | | | | | 98 | |BoundedJoinSemilattice | ✓| | ✓| | | | | 99 | |BoundedMeetSemilattice | | ✓| | ✓| | | | 100 | |Lattice | ✓| ✓| | | | | | 101 | |DistributiveLattice | ✓| ✓| | | ✓| | | 102 | |BoundedLattice | ✓| ✓| ✓| ✓| | | | 103 | |BoundedDistributiveLattice| ✓| ✓| ✓| ✓| ✓| | | 104 | |Heyting | ✓| ✓| ✓| ✓| ✓| ✓| | 105 | |Bool | ✓| ✓| ✓| ✓| ✓| ✓| ✓| 106 | 107 | Note that a `BoundedDistributiveLattice` gives you a `CommutativeRig`, but not the other way around: rigs aren't distributive with `a + (b * c) = (a + b) * (a + c)`. 108 | 109 | Also, a `Bool` gives rise to a `BoolRing`, since each element can be defined as its own negation. Note, Bool's `.asBoolRing` is not an extension of the `.asCommutativeRig` method as the `plus` operations are defined differently. 110 | 111 | ### Documentation Help 112 | 113 | We'd love your help with this documentation! You can edit this page in your browser by clicking [this link](https://github.com/typelevel/algebra/edit/master/docs/src/main/tut/typeclasses/overview.md). 114 | -------------------------------------------------------------------------------- /laws/js/src/main/scala/algebra/laws/platform/Platform.scala: -------------------------------------------------------------------------------- 1 | package algebra.laws.platform 2 | 3 | private[laws] object Platform { 4 | // using `final val` makes compiler constant-fold any use of these values, dropping dead code automatically 5 | // $COVERAGE-OFF$ 6 | final val isJvm = false 7 | final val isJs = true 8 | final val isNative = false 9 | // $COVERAGE-ON$ 10 | } 11 | -------------------------------------------------------------------------------- /laws/jvm/src/main/scala/algebra/laws/platform/Platform.scala: -------------------------------------------------------------------------------- 1 | package algebra.laws.platform 2 | 3 | private[laws] object Platform { 4 | // using `final val` makes compiler constant-fold any use of these values, dropping dead code automatically 5 | // $COVERAGE-OFF$ 6 | final val isJvm = true 7 | final val isJs = false 8 | final val isNative = false 9 | // $COVERAGE-ON$ 10 | } 11 | -------------------------------------------------------------------------------- /laws/native/src/main/scala/algebra/laws/platform/Platform.scala: -------------------------------------------------------------------------------- 1 | package algebra.laws.platform 2 | 3 | private[laws] object Platform { 4 | // using `final val` makes compiler constant-fold any use of these values, dropping dead code automatically 5 | // $COVERAGE-OFF$ 6 | final val isJvm = false 7 | final val isJs = false 8 | final val isNative = true 9 | // $COVERAGE-ON$ 10 | } 11 | -------------------------------------------------------------------------------- /laws/shared/src/main/scala/algebra/laws/BaseLaws.scala: -------------------------------------------------------------------------------- 1 | package algebra.laws 2 | 3 | import cats.kernel._ 4 | 5 | import org.typelevel.discipline.Laws 6 | 7 | import org.scalacheck.{Arbitrary, Prop} 8 | 9 | object BaseLaws { 10 | def apply[A : Eq : Arbitrary]: BaseLaws[A] = new BaseLaws[A] { 11 | def Equ = Eq[A] 12 | def Arb = implicitly[Arbitrary[A]] 13 | } 14 | } 15 | 16 | trait BaseLaws[A] extends Laws { 17 | 18 | implicit def Equ: Eq[A] 19 | implicit def Arb: Arbitrary[A] 20 | 21 | class BaseRuleSet( 22 | val name: String, 23 | val parent: Option[RuleSet], 24 | val bases: Seq[(String, Laws#RuleSet)], 25 | val props: (String, Prop)* 26 | ) extends RuleSet with HasOneParent 27 | } 28 | -------------------------------------------------------------------------------- /laws/shared/src/main/scala/algebra/laws/CheckSupport.scala: -------------------------------------------------------------------------------- 1 | package algebra.laws 2 | 3 | /** 4 | * This object contains Arbitrary instances for types defined in 5 | * algebra.instances, as well as anything else we'd like to import to assist 6 | * in running ScalaCheck tests. 7 | * 8 | * (Since algebra-instances has no dependencies, its types can't 9 | * define Arbitrary instances in companions.) 10 | */ 11 | object CheckSupport { 12 | } 13 | -------------------------------------------------------------------------------- /laws/shared/src/main/scala/algebra/laws/DeMorganLaws.scala: -------------------------------------------------------------------------------- 1 | package algebra.laws 2 | 3 | import algebra._ 4 | import algebra.lattice._ 5 | import org.scalacheck.{Arbitrary, Prop} 6 | import org.scalacheck.Prop._ 7 | import org.typelevel.discipline.Laws 8 | 9 | object DeMorganLaws { 10 | def apply[A: Eq : Arbitrary : LatticeLaws] = new DeMorganLaws[A] { 11 | def Equ = Eq[A] 12 | def Arb = implicitly[Arbitrary[A]] 13 | def LL = implicitly[LatticeLaws[A]] 14 | } 15 | } 16 | /* TODO: 17 | * This is separated for LogicLaws for binary compatibility reasons. 18 | * Merge with LogicLaws when possible. 19 | */ 20 | trait DeMorganLaws[A] extends Laws { 21 | 22 | implicit def Equ: Eq[A] 23 | implicit def Arb: Arbitrary[A] 24 | def LL: LatticeLaws[A] 25 | 26 | def logic(implicit A: Logic[A]) = new DeMorganProperties( 27 | name = "logic", 28 | parents = Seq(), 29 | ll = LL.boundedDistributiveLattice, 30 | 31 | Rules.distributive(A.or)(A.and), 32 | 33 | "¬(x∨y) = ¬x∧¬y" -> forAll { (x: A, y: A) => A.not(A.or(x, y)) ?== A.and(A.not(x), A.not(y)) }, 34 | "¬(x∧y) = ¬¬(¬x∨¬y)" -> forAll { (x: A, y: A) => A.not(A.and(x, y)) ?== A.not(A.not(A.or(A.not(x), A.not(y)))) } 35 | ) 36 | 37 | def deMorgan(implicit A: DeMorgan[A]) = new DeMorganProperties( 38 | name = "deMorgan", 39 | parents = Seq(logic), 40 | ll = LL.boundedDistributiveLattice, 41 | 42 | Rules.distributive(A.or)(A.and), 43 | 44 | "involutive" -> forAll { (x: A) => A.not(A.not(x)) ?== x } 45 | ) 46 | 47 | 48 | class DeMorganProperties( 49 | val name: String, 50 | val parents: Seq[DeMorganProperties], 51 | val ll: LatticeLaws[A]#LatticeProperties, 52 | val props: (String, Prop)* 53 | ) extends RuleSet { 54 | val bases = Seq("lattice" -> ll) 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /laws/shared/src/main/scala/algebra/laws/GroupLaws.scala: -------------------------------------------------------------------------------- 1 | package algebra.laws 2 | 3 | import cats.kernel._ 4 | import cats.kernel.instances.option._ 5 | 6 | import org.typelevel.discipline.Laws 7 | 8 | import org.scalacheck.{Arbitrary, Prop} 9 | import org.scalacheck.Prop._ 10 | 11 | object GroupLaws { 12 | def apply[A : Eq : Arbitrary]: GroupLaws[A] = new GroupLaws[A] { 13 | def Equ = Eq[A] 14 | def Arb = implicitly[Arbitrary[A]] 15 | } 16 | } 17 | 18 | trait GroupLaws[A] extends Laws { 19 | 20 | implicit def Equ: Eq[A] 21 | implicit def Arb: Arbitrary[A] 22 | 23 | // groups 24 | 25 | def semigroup(implicit A: Semigroup[A]): GroupProperties = new GroupProperties( 26 | name = "semigroup", 27 | parents = Nil, 28 | Rules.serializable(A), 29 | Rules.associativity(A.combine), 30 | Rules.repeat1("combineN")(A.combineN), 31 | Rules.repeat2("combineN", "|+|")(A.combineN)(A.combine), 32 | "combineAllOption" -> forAll { (xs: Vector[A]) => 33 | A.combineAllOption(xs) ?== xs.reduceOption(A.combine) 34 | } 35 | ) 36 | 37 | def band(implicit A: Band[A]): GroupProperties = new GroupProperties( 38 | name = "band", 39 | parents = List(semigroup), 40 | Rules.idempotence(A.combine), 41 | "isIdempotent" -> Semigroup.isIdempotent[A] 42 | ) 43 | 44 | def commutativeSemigroup(implicit A: CommutativeSemigroup[A]): GroupProperties = new GroupProperties( 45 | name = "commutative semigroup", 46 | parents = List(semigroup), 47 | Rules.commutative(A.combine) 48 | ) 49 | 50 | def semilattice(implicit A: Semilattice[A]): GroupProperties = new GroupProperties( 51 | name = "semilattice", 52 | parents = List(band, commutativeSemigroup) 53 | ) 54 | 55 | def monoid(implicit A: Monoid[A]): GroupProperties = new GroupProperties( 56 | name = "monoid", 57 | parents = List(semigroup), 58 | Rules.leftIdentity(A.empty)(A.combine), 59 | Rules.rightIdentity(A.empty)(A.combine), 60 | Rules.repeat0("combineN", "id", A.empty)(A.combineN), 61 | Rules.collect0("combineAll", "id", A.empty)(A.combineAll), 62 | Rules.isId("isEmpty", A.empty)(A.isEmpty), 63 | "combineAll" -> forAll { (xs: Vector[A]) => 64 | A.combineAll(xs) ?== (A.empty +: xs).reduce(A.combine) 65 | } 66 | ) 67 | 68 | def commutativeMonoid(implicit A: CommutativeMonoid[A]): GroupProperties = new GroupProperties( 69 | name = "commutative monoid", 70 | parents = List(monoid, commutativeSemigroup) 71 | ) 72 | 73 | def boundedSemilattice(implicit A: BoundedSemilattice[A]): GroupProperties = new GroupProperties( 74 | name = "boundedSemilattice", 75 | parents = List(commutativeMonoid, semilattice) 76 | ) 77 | 78 | def group(implicit A: Group[A]): GroupProperties = new GroupProperties( 79 | name = "group", 80 | parents = List(monoid), 81 | Rules.leftInverse(A.empty)(A.combine)(A.inverse), 82 | Rules.rightInverse(A.empty)(A.combine)(A.inverse), 83 | Rules.consistentInverse("remove")(A.remove)(A.combine)(A.inverse) 84 | ) 85 | 86 | def commutativeGroup(implicit A: CommutativeGroup[A]): GroupProperties = new GroupProperties( 87 | name = "commutative group", 88 | parents = List(group, commutativeMonoid) 89 | ) 90 | 91 | // property classes 92 | 93 | class GroupProperties( 94 | val name: String, 95 | val parents: Seq[GroupProperties], 96 | val props: (String, Prop)* 97 | ) extends RuleSet { 98 | val bases = Nil 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /laws/shared/src/main/scala/algebra/laws/IsSerializable.scala: -------------------------------------------------------------------------------- 1 | package algebra.laws 2 | 3 | import algebra.laws.platform.Platform 4 | import org.scalacheck.Prop 5 | import org.scalacheck.Prop._ 6 | import scala.util.control.NonFatal 7 | import scala.util.DynamicVariable 8 | 9 | /** 10 | * Object with a dynamic variable that allows users to skip the 11 | * serialization tests for certain instances. 12 | */ 13 | private[laws] object IsSerializable { 14 | val runTests = new DynamicVariable[Boolean](true) 15 | def apply(): Boolean = Platform.isJvm && runTests.value 16 | 17 | def testSerialization[M](m: M): Prop.Result = 18 | if (Platform.isJvm) { 19 | import java.io._ 20 | val baos = new ByteArrayOutputStream() 21 | val oos = new ObjectOutputStream(baos) 22 | var ois: ObjectInputStream = null // scalastyle:ignore null 23 | try { 24 | oos.writeObject(m) 25 | oos.close() 26 | val bais = new ByteArrayInputStream(baos.toByteArray()) 27 | ois = new ObjectInputStream(bais) 28 | ois.readObject() // just ensure we can read it back 29 | ois.close() 30 | Result(status = Proof) 31 | } catch { 32 | case NonFatal(t) => 33 | Result(status = Exception(t)) 34 | } finally { 35 | oos.close() 36 | if (ois != null) ois.close() // scalastyle:ignore null 37 | } 38 | } else { 39 | Result(status = Proof) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /laws/shared/src/main/scala/algebra/laws/LatticeLaws.scala: -------------------------------------------------------------------------------- 1 | package algebra.laws 2 | 3 | import algebra._ 4 | import algebra.lattice._ 5 | 6 | import org.scalacheck.{Arbitrary, Prop} 7 | import org.scalacheck.Prop._ 8 | 9 | object LatticeLaws { 10 | def apply[A: Eq: Arbitrary] = new LatticeLaws[A] { 11 | def Equ = Eq[A] 12 | def Arb = implicitly[Arbitrary[A]] 13 | } 14 | } 15 | 16 | trait LatticeLaws[A] extends GroupLaws[A] { 17 | 18 | implicit def Equ: Eq[A] 19 | implicit def Arb: Arbitrary[A] 20 | 21 | def joinSemilattice(implicit A: JoinSemilattice[A]) = new LatticeProperties( 22 | name = "joinSemilattice", 23 | parents = Nil, 24 | join = Some(semilattice(A.joinSemilattice)), 25 | meet = None, 26 | Rules.serializable(A) 27 | ) 28 | 29 | def meetSemilattice(implicit A: MeetSemilattice[A]) = new LatticeProperties( 30 | name = "meetSemilattice", 31 | parents = Nil, 32 | join = None, 33 | meet = Some(semilattice(A.meetSemilattice)), 34 | Rules.serializable(A) 35 | ) 36 | 37 | def lattice(implicit A: Lattice[A]) = new LatticeProperties( 38 | name = "lattice", 39 | parents = Seq(joinSemilattice, meetSemilattice), 40 | join = Some(semilattice(A.joinSemilattice)), 41 | meet = Some(semilattice(A.meetSemilattice)), 42 | "absorption" -> forAll { (x: A, y: A) => 43 | (A.join(x, A.meet(x, y)) ?== x) && (A.meet(x, A.join(x, y)) ?== x) 44 | } 45 | ) 46 | 47 | def distributiveLattice(implicit A: DistributiveLattice[A]) = new LatticeProperties( 48 | name = "distributiveLattice", 49 | parents = Seq(lattice), 50 | join = Some(semilattice(A.joinSemilattice)), 51 | meet = Some(semilattice(A.meetSemilattice)), 52 | "distributive" -> forAll { (x: A, y: A, z: A) => 53 | (A.join(x, A.meet(y, z)) ?== A.meet(A.join(x, y), A.join(x, z))) && 54 | (A.meet(x, A.join(y, z)) ?== A.join(A.meet(x, y), A.meet(x, z))) 55 | } 56 | ) 57 | 58 | def boundedJoinSemilattice(implicit A: BoundedJoinSemilattice[A]) = new LatticeProperties( 59 | name = "boundedJoinSemilattice", 60 | parents = Seq(joinSemilattice), 61 | join = Some(boundedSemilattice(A.joinSemilattice)), 62 | meet = None 63 | ) 64 | 65 | def boundedMeetSemilattice(implicit A: BoundedMeetSemilattice[A]) = new LatticeProperties( 66 | name = "boundedMeetSemilattice", 67 | parents = Seq(meetSemilattice), 68 | join = None, 69 | meet = Some(boundedSemilattice(A.meetSemilattice)) 70 | ) 71 | 72 | def boundedJoinLattice(implicit A: Lattice[A] with BoundedJoinSemilattice[A]) = new LatticeProperties( 73 | name = "boundedJoinLattice", 74 | parents = Seq(boundedJoinSemilattice, lattice), 75 | join = Some(boundedSemilattice(A.joinSemilattice)), 76 | meet = Some(semilattice(A.meetSemilattice)) 77 | ) 78 | 79 | def boundedMeetLattice(implicit A: Lattice[A] with BoundedMeetSemilattice[A]) = new LatticeProperties( 80 | name = "boundedMeetLattice", 81 | parents = Seq(boundedMeetSemilattice, lattice), 82 | join = Some(semilattice(A.joinSemilattice)), 83 | meet = Some(boundedSemilattice(A.meetSemilattice)) 84 | ) 85 | 86 | def boundedLattice(implicit A: BoundedLattice[A]) = new LatticeProperties( 87 | name = "boundedLattice", 88 | parents = Seq(boundedJoinSemilattice, boundedMeetSemilattice, lattice), 89 | join = Some(boundedSemilattice(A.joinSemilattice)), 90 | meet = Some(boundedSemilattice(A.meetSemilattice)) 91 | ) 92 | 93 | def boundedDistributiveLattice(implicit A: BoundedDistributiveLattice[A]) = new LatticeProperties( 94 | name = "boundedLattice", 95 | parents = Seq(boundedLattice, distributiveLattice), 96 | join = Some(boundedSemilattice(A.joinSemilattice)), 97 | meet = Some(boundedSemilattice(A.meetSemilattice)) 98 | ) 99 | 100 | class LatticeProperties( 101 | val name: String, 102 | val parents: Seq[LatticeProperties], 103 | val join: Option[GroupProperties], 104 | val meet: Option[GroupProperties], 105 | val props: (String, Prop)* 106 | ) extends RuleSet { 107 | private val _m = meet map { "meet" -> _ } 108 | private val _j = join map { "join" -> _ } 109 | 110 | val bases = _m.toList ::: _j.toList 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /laws/shared/src/main/scala/algebra/laws/LatticePartialOrderLaws.scala: -------------------------------------------------------------------------------- 1 | package algebra.laws 2 | 3 | import algebra._ 4 | import algebra.lattice._ 5 | 6 | import org.typelevel.discipline.Laws 7 | 8 | import org.scalacheck.{Arbitrary, Prop} 9 | import org.scalacheck.Prop._ 10 | 11 | import algebra.instances.boolean._ 12 | 13 | object LatticePartialOrderLaws { 14 | def apply[A: Eq: Arbitrary] = new LatticePartialOrderLaws[A] { 15 | def Equ = Eq[A] 16 | def Arb = implicitly[Arbitrary[A]] 17 | } 18 | } 19 | 20 | trait LatticePartialOrderLaws[A] extends Laws { 21 | 22 | implicit def Equ: Eq[A] 23 | implicit def Arb: Arbitrary[A] 24 | 25 | def joinSemilatticePartialOrder(implicit A: JoinSemilattice[A], P: PartialOrder[A]) = new LatticePartialOrderProperties( 26 | name = "joinSemilatticePartialOrder", 27 | parents = Seq.empty, 28 | "join+lteqv" -> forAll { (x: A, y: A) => 29 | P.lteqv(x, y) ?== P.eqv(y, A.join(x, y)) 30 | } 31 | ) 32 | 33 | def meetSemilatticePartialOrder(implicit A: MeetSemilattice[A], P: PartialOrder[A]) = new LatticePartialOrderProperties( 34 | name = "meetSemilatticePartialOrder", 35 | parents = Seq.empty, 36 | "meet+lteqv" -> forAll { (x: A, y: A) => 37 | P.lteqv(x, y) ?== P.eqv(x, A.meet(x, y)) 38 | } 39 | ) 40 | 41 | def latticePartialOrder(implicit A: Lattice[A], P: PartialOrder[A]) = new LatticePartialOrderProperties( 42 | name = "latticePartialOrder", 43 | parents = Seq(joinSemilatticePartialOrder, meetSemilatticePartialOrder) 44 | ) 45 | 46 | def boundedJoinSemilatticePartialOrder(implicit A: BoundedJoinSemilattice[A], P: PartialOrder[A]) = new LatticePartialOrderProperties( 47 | name = "boundedJoinSemilatticePartialOrder", 48 | parents = Seq(joinSemilatticePartialOrder), 49 | "lteqv+zero" -> forAll { (x: A) => A.zero ?<= x } 50 | ) 51 | 52 | def boundedMeetSemilatticePartialOrder(implicit A: BoundedMeetSemilattice[A], P: PartialOrder[A]) = new LatticePartialOrderProperties( 53 | name = "boundedMeetSemilatticePartialOrder", 54 | parents = Seq(meetSemilatticePartialOrder), 55 | "lteqv+one" -> forAll { (x: A) => x ?<= A.one } 56 | ) 57 | 58 | def boundedBelowLatticePartialOrder(implicit A: Lattice[A] with BoundedJoinSemilattice[A], P: PartialOrder[A]) = new LatticePartialOrderProperties( 59 | name = "boundedBelowLatticePartialOrder", 60 | parents = Seq(boundedJoinSemilatticePartialOrder, latticePartialOrder) 61 | ) 62 | 63 | def boundedAboveLatticePartialOrder(implicit A: Lattice[A] with BoundedMeetSemilattice[A], P: PartialOrder[A]) = new LatticePartialOrderProperties( 64 | name = "boundedAboveLatticePartialOrder", 65 | parents = Seq(boundedMeetSemilatticePartialOrder, latticePartialOrder) 66 | ) 67 | 68 | def boundedLatticePartialOrder(implicit A: BoundedLattice[A], P: PartialOrder[A]) = new LatticePartialOrderProperties( 69 | name = "boundedLatticePartialOrder", 70 | parents = Seq(boundedJoinSemilatticePartialOrder, boundedMeetSemilatticePartialOrder) 71 | ) 72 | 73 | class LatticePartialOrderProperties( 74 | val name: String, 75 | val parents: Seq[LatticePartialOrderProperties], 76 | val props: (String, Prop)* 77 | ) extends RuleSet { 78 | def bases = Nil 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /laws/shared/src/main/scala/algebra/laws/LogicLaws.scala: -------------------------------------------------------------------------------- 1 | package algebra.laws 2 | 3 | import algebra._ 4 | import algebra.lattice.{Heyting, Bool, GenBool} 5 | 6 | import org.scalacheck.{Arbitrary, Prop} 7 | import org.scalacheck.Prop._ 8 | 9 | object LogicLaws { 10 | def apply[A: Eq: Arbitrary] = new LogicLaws[A] { 11 | def Equ = Eq[A] 12 | def Arb = implicitly[Arbitrary[A]] 13 | } 14 | } 15 | 16 | trait LogicLaws[A] extends LatticeLaws[A] { 17 | 18 | def heyting(implicit A: Heyting[A]) = new LogicProperties( 19 | name = "heyting", 20 | parents = Seq(), 21 | ll = boundedDistributiveLattice, 22 | 23 | Rules.distributive(A.or)(A.and), 24 | 25 | "consistent" -> forAll { (x: A) => A.and(x, A.complement(x)) ?== A.zero }, 26 | "¬x = (x → 0)" -> forAll { (x: A) => A.complement(x) ?== A.imp(x, A.zero) }, 27 | "x → x = 1" -> forAll { (x: A) => A.imp(x, x) ?== A.one }, 28 | 29 | "if x → y and y → x then x=y" -> forAll { (x: A, y: A) => 30 | (A.imp(x, y) ?!= A.one) || (A.imp(y, x) ?!= A.one) || (x ?== y) 31 | }, 32 | 33 | "if (1 → x)=1 then x=1" -> forAll { (x: A) => 34 | (A.imp(A.one, x) ?!= A.one) || (x ?== A.one) 35 | }, 36 | 37 | "x → (y → x) = 1" -> forAll { (x: A, y: A) => A.imp(x, A.imp(y, x)) ?== A.one }, 38 | 39 | "(x→(y→z)) → ((x→y)→(x→z)) = 1" -> forAll { (x: A, y: A, z: A) => 40 | A.imp(A.imp(x, A.imp(y, z)), A.imp(A.imp(x, y), A.imp(x, z))) ?== A.one 41 | }, 42 | 43 | "x∧y → x = 1" -> forAll { (x: A, y: A) => A.imp(A.and(x, y), x) ?== A.one }, 44 | "x∧y → y = 1" -> forAll { (x: A, y: A) => A.imp(A.and(x, y), y) ?== A.one }, 45 | "x → y → (x∧y) = 1" -> forAll { (x: A, y: A) => A.imp(x, A.imp(y, A.and(x, y))) ?== A.one }, 46 | 47 | "x → x∨y" -> forAll { (x: A, y: A) => A.imp(x, A.or(x, y)) ?== A.one }, 48 | "y → x∨y" -> forAll { (x: A, y: A) => A.imp(y, A.or(x, y)) ?== A.one }, 49 | 50 | "(x → z) → ((y → z) → ((x | y) → z)) = 1" -> forAll { (x: A, y: A, z: A) => 51 | A.imp(A.imp(x, z), A.imp(A.imp(y, z), A.imp(A.or(x, y), z))) ?== A.one 52 | }, 53 | 54 | "(0 → x) = 1" -> forAll { (x: A) => A.imp(A.zero, x) ?== A.one } 55 | ) 56 | 57 | def generalizedBool(implicit A: GenBool[A]) = new LogicProperties( 58 | name = "generalized bool", 59 | parents = Seq(), 60 | ll = new LatticeProperties( 61 | name = "lowerBoundedDistributiveLattice", 62 | parents = Seq(boundedJoinSemilattice, distributiveLattice), 63 | join = Some(boundedSemilattice(A.joinSemilattice)), 64 | meet = Some(semilattice(A.meetSemilattice)) 65 | ), 66 | 67 | """x\y ∧ y = 0""" -> forAll { (x: A, y: A) => 68 | A.and(A.without(x, y), y) ?== A.zero }, 69 | 70 | """x\y ∨ y = x ∨ y""" -> forAll { (x: A, y: A) => 71 | A.or(A.without(x, y), y) ?== A.or(x, y) } 72 | ) 73 | 74 | def bool(implicit A: Bool[A]) = new LogicProperties( 75 | name = "bool", 76 | parents = Seq(heyting, generalizedBool), 77 | ll = boundedDistributiveLattice, 78 | 79 | "excluded middle" -> forAll { (x: A) => A.or(x, A.complement(x)) ?== A.one } 80 | ) 81 | 82 | class LogicProperties( 83 | val name: String, 84 | val parents: Seq[LogicProperties], 85 | val ll: LatticeProperties, 86 | val props: (String, Prop)* 87 | ) extends RuleSet { 88 | val bases = Seq("lattice" -> ll) 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /laws/shared/src/main/scala/algebra/laws/OrderLaws.scala: -------------------------------------------------------------------------------- 1 | package algebra.laws 2 | 3 | import cats.kernel._ 4 | 5 | import org.typelevel.discipline.Laws 6 | 7 | import org.scalacheck.{Arbitrary, Cogen, Prop} 8 | import org.scalacheck.Prop._ 9 | 10 | import cats.kernel.instances.all._ 11 | 12 | object OrderLaws { 13 | def apply[A: Eq: Arbitrary: Cogen]: OrderLaws[A] = 14 | new OrderLaws[A] { 15 | def Equ = Eq[A] 16 | def Arb = implicitly[Arbitrary[A]] 17 | def Cog = implicitly[Cogen[A]] 18 | } 19 | } 20 | 21 | trait OrderLaws[A] extends Laws { 22 | 23 | implicit def Equ: Eq[A] 24 | implicit def Arb: Arbitrary[A] 25 | implicit def Cog: Cogen[A] 26 | 27 | def eqv: OrderProperties = new OrderProperties( 28 | name = "eq", 29 | parent = None, 30 | Rules.serializable(Equ), 31 | "reflexitivity-eq" -> forAll { (x: A) => 32 | x ?== x 33 | }, 34 | "symmetry-eq" -> forAll { (x: A, y: A) => 35 | Equ.eqv(x, y) ?== Equ.eqv(y, x) 36 | }, 37 | "antisymmetry-eq" -> forAll { (x: A, y: A, f: A => A) => 38 | !Equ.eqv(x, y) ?|| Equ.eqv(f(x), f(y)) 39 | }, 40 | "transitivity-eq" -> forAll { (x: A, y: A, z: A) => 41 | !(Equ.eqv(x, y) && Equ.eqv(y, z)) ?|| Equ.eqv(x, z) 42 | } 43 | ) 44 | 45 | def partialOrder(implicit A: PartialOrder[A]): OrderProperties = new OrderProperties( 46 | name = "partialOrder", 47 | parent = Some(eqv), 48 | Rules.serializable(A), 49 | "reflexitivity" -> forAll { (x: A) => 50 | x ?<= x 51 | }, 52 | "antisymmetry" -> forAll { (x: A, y: A) => 53 | !(A.lteqv(x, y) && A.lteqv(y, x)) ?|| A.eqv(x, y) 54 | }, 55 | "transitivity" -> forAll { (x: A, y: A, z: A) => 56 | !(A.lteqv(x, y) && A.lteqv(y, z)) ?|| A.lteqv(x, z) 57 | }, 58 | "gteqv" -> forAll { (x: A, y: A) => 59 | A.lteqv(x, y) ?== A.gteqv(y, x) 60 | }, 61 | "lt" -> forAll { (x: A, y: A) => 62 | A.lt(x, y) ?== (A.lteqv(x, y) && A.neqv(x, y)) 63 | }, 64 | "gt" -> forAll { (x: A, y: A) => 65 | A.lt(x, y) ?== A.gt(y, x) 66 | }, 67 | "partialCompare" -> forAll { (x: A, y: A) => 68 | val c = A.partialCompare(x, y) 69 | ((c < 0) ?== A.lt(x, y)) && ((c == 0) ?== A.eqv(x, y)) && ((c > 0) ?== A.gt(x, y)) 70 | }, 71 | "pmin" -> forAll { (x: A, y: A) => 72 | val c = A.partialCompare(x, y) 73 | val m = A.pmin(x, y) 74 | if (c < 0) m ?== Some(x) 75 | else if (c == 0) (m ?== Some(x)) && (m ?== Some(y)) 76 | else if (c > 0) m ?== Some(y) 77 | else m ?== None 78 | }, 79 | "pmax" -> forAll { (x: A, y: A) => 80 | val c = A.partialCompare(x, y) 81 | val m = A.pmax(x, y) 82 | if (c < 0) m ?== Some(y) 83 | else if (c == 0) (m ?== Some(x)) && (m ?== Some(y)) 84 | else if (c > 0) m ?== Some(x) 85 | else m ?== None 86 | } 87 | ) 88 | 89 | def order(implicit A: Order[A]): OrderProperties = new OrderProperties( 90 | name = "order", 91 | parent = Some(partialOrder), 92 | "totality" -> forAll { (x: A, y: A) => 93 | A.lteqv(x, y) ?|| A.lteqv(y, x) 94 | }, 95 | "compare" -> forAll { (x: A, y: A) => 96 | val c = A.compare(x, y) 97 | ((c < 0) ?== A.lt(x, y)) && ((c == 0) ?== A.eqv(x, y)) && ((c > 0) ?== A.gt(x, y)) 98 | }, 99 | "min" -> forAll { (x: A, y: A) => 100 | val c = A.compare(x, y) 101 | val m = A.min(x, y) 102 | if (c < 0) m ?== x 103 | else if (c == 0) (m ?== x) && (m ?== y) 104 | else m ?== y 105 | }, 106 | "max" -> forAll { (x: A, y: A) => 107 | val c = A.compare(x, y) 108 | val m = A.max(x, y) 109 | if (c < 0) m ?== y 110 | else if (c == 0) (m ?== x) && (m ?== y) 111 | else m ?== x 112 | } 113 | ) 114 | 115 | class OrderProperties( 116 | name: String, 117 | parent: Option[RuleSet], 118 | props: (String, Prop)* 119 | ) extends DefaultRuleSet(name, parent, props: _*) 120 | 121 | } 122 | -------------------------------------------------------------------------------- /laws/shared/src/main/scala/algebra/laws/RingLaws.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | package laws 3 | 4 | import algebra.ring._ 5 | 6 | import algebra.laws.platform.Platform 7 | 8 | import org.typelevel.discipline.Predicate 9 | 10 | import org.scalacheck.{Arbitrary, Prop} 11 | import org.scalacheck.Arbitrary._ 12 | import org.scalacheck.Prop._ 13 | 14 | object RingLaws { 15 | def apply[A : Eq : Arbitrary: AdditiveMonoid]: RingLaws[A] = 16 | withPred[A](new Predicate[A] { 17 | def apply(a: A): Boolean = Eq[A].neqv(a, AdditiveMonoid[A].zero) 18 | }) 19 | 20 | def withPred[A](pred0: Predicate[A])(implicit eqv: Eq[A], arb: Arbitrary[A]): RingLaws[A] = new RingLaws[A] { 21 | def Arb = arb 22 | def pred = pred0 23 | val nonZeroLaws = new GroupLaws[A] { 24 | def Arb = Arbitrary(arbitrary[A](arb) filter pred) 25 | def Equ = eqv 26 | } 27 | } 28 | } 29 | 30 | trait RingLaws[A] extends GroupLaws[A] { self => 31 | 32 | // must be a val (stable identifier) 33 | val nonZeroLaws: GroupLaws[A] 34 | def pred: Predicate[A] 35 | 36 | def withPred(pred0: Predicate[A], replace: Boolean = true): RingLaws[A] = 37 | RingLaws.withPred(if (replace) pred0 else pred && pred0)(Equ, Arb) 38 | 39 | def setNonZeroParents(props: nonZeroLaws.GroupProperties, parents: Seq[nonZeroLaws.GroupProperties]): nonZeroLaws.GroupProperties = 40 | new nonZeroLaws.GroupProperties( 41 | name = props.name, 42 | parents = parents, 43 | props = props.props: _* 44 | ) 45 | 46 | implicit def Arb: Arbitrary[A] 47 | implicit def Equ: Eq[A] = nonZeroLaws.Equ 48 | 49 | // additive groups 50 | 51 | def additiveSemigroup(implicit A: AdditiveSemigroup[A]) = new AdditiveProperties( 52 | base = semigroup(A.additive), 53 | parents = Nil, 54 | Rules.serializable(A), 55 | Rules.repeat1("sumN")(A.sumN), 56 | Rules.repeat2("sumN", "+")(A.sumN)(A.plus) 57 | ) 58 | 59 | def additiveCommutativeSemigroup(implicit A: AdditiveCommutativeSemigroup[A]) = new AdditiveProperties( 60 | base = commutativeSemigroup(A.additive), 61 | parents = List(additiveSemigroup) 62 | ) 63 | 64 | def additiveMonoid(implicit A: AdditiveMonoid[A]) = new AdditiveProperties( 65 | base = monoid(A.additive), 66 | parents = List(additiveSemigroup), 67 | Rules.repeat0("sumN", "zero", A.zero)(A.sumN), 68 | Rules.collect0("sum", "zero", A.zero)(A.sum) 69 | ) 70 | 71 | def additiveCommutativeMonoid(implicit A: AdditiveCommutativeMonoid[A]) = new AdditiveProperties( 72 | base = commutativeMonoid(A.additive), 73 | parents = List(additiveMonoid) 74 | ) 75 | 76 | def additiveGroup(implicit A: AdditiveGroup[A]) = new AdditiveProperties( 77 | base = group(A.additive), 78 | parents = List(additiveMonoid), 79 | Rules.consistentInverse("subtract")(A.minus)(A.plus)(A.negate) 80 | ) 81 | 82 | def additiveCommutativeGroup(implicit A: AdditiveCommutativeGroup[A]) = new AdditiveProperties( 83 | base = commutativeGroup(A.additive), 84 | parents = List(additiveGroup) 85 | ) 86 | 87 | // multiplicative groups 88 | 89 | def multiplicativeSemigroup(implicit A: MultiplicativeSemigroup[A]) = new MultiplicativeProperties( 90 | base = semigroup(A.multiplicative), 91 | nonZeroBase = None, 92 | parent = None, 93 | Rules.serializable(A), 94 | Rules.repeat1("pow")(A.pow), 95 | Rules.repeat2("pow", "*")(A.pow)(A.times) 96 | ) 97 | 98 | def multiplicativeCommutativeSemigroup(implicit A: MultiplicativeCommutativeSemigroup[A]) = new MultiplicativeProperties( 99 | base = semigroup(A.multiplicative), 100 | nonZeroBase = None, 101 | parent = Some(multiplicativeSemigroup) 102 | ) 103 | 104 | def multiplicativeMonoid(implicit A: MultiplicativeMonoid[A]) = new MultiplicativeProperties( 105 | base = monoid(A.multiplicative), 106 | nonZeroBase = None, 107 | parent = Some(multiplicativeSemigroup), 108 | Rules.repeat0("pow", "one", A.one)(A.pow), 109 | Rules.collect0("product", "one", A.one)(A.product) 110 | ) 111 | 112 | def multiplicativeCommutativeMonoid(implicit A: MultiplicativeCommutativeMonoid[A]) = new MultiplicativeProperties( 113 | base = commutativeMonoid(A.multiplicative), 114 | nonZeroBase = None, 115 | parent = Some(multiplicativeMonoid) 116 | ) 117 | 118 | def multiplicativeGroup(implicit A: MultiplicativeGroup[A]) = new MultiplicativeProperties( 119 | base = monoid(A.multiplicative), 120 | nonZeroBase = Some(setNonZeroParents(nonZeroLaws.group(A.multiplicative), Nil)), 121 | parent = Some(multiplicativeMonoid), 122 | // pred is used to ensure y is not zero. 123 | "consistent division" -> forAll { (x: A, y: A) => 124 | pred(y) ==> (A.div(x, y) ?== A.times(x, A.reciprocal(y))) 125 | } 126 | ) 127 | 128 | def multiplicativeCommutativeGroup(implicit A: MultiplicativeCommutativeGroup[A]) = new MultiplicativeProperties( 129 | base = commutativeMonoid(A.multiplicative), 130 | nonZeroBase = Some(setNonZeroParents(nonZeroLaws.commutativeGroup(A.multiplicative), multiplicativeGroup.nonZeroBase.toSeq)), 131 | parent = Some(multiplicativeGroup) 132 | ) 133 | 134 | // rings 135 | 136 | def semiring(implicit A: Semiring[A]) = new RingProperties( 137 | name = "semiring", 138 | al = additiveCommutativeMonoid, 139 | ml = multiplicativeSemigroup, 140 | parents = Seq.empty, 141 | Rules.distributive(A.plus)(A.times) 142 | ) 143 | 144 | def rng(implicit A: Rng[A]) = new RingProperties( 145 | name = "rng", 146 | al = additiveCommutativeGroup, 147 | ml = multiplicativeSemigroup, 148 | parents = Seq(semiring) 149 | ) 150 | 151 | def rig(implicit A: Rig[A]) = new RingProperties( 152 | name = "rig", 153 | al = additiveCommutativeMonoid, 154 | ml = multiplicativeMonoid, 155 | parents = Seq(semiring) 156 | ) 157 | 158 | def ring(implicit A: Ring[A]) = new RingProperties( 159 | // TODO fromParents 160 | name = "ring", 161 | al = additiveCommutativeGroup, 162 | ml = multiplicativeMonoid, 163 | parents = Seq(rig, rng), 164 | "fromInt" -> forAll { (n: Int) => 165 | Ring.fromInt[A](n) ?== A.sumN(A.one, n) 166 | }, 167 | "fromBigInt" -> forAll { (ns: List[Int]) => 168 | val actual = Ring.fromBigInt[A](ns.map(BigInt(_)).foldLeft(BigInt(1))(_ * _)) 169 | val expected = ns.map(A.fromInt).foldLeft(A.one)(A.times) 170 | actual ?== expected 171 | } 172 | ) 173 | 174 | // commutative rings 175 | 176 | def commutativeSemiring(implicit A: CommutativeSemiring[A]) = new RingProperties( 177 | name = "commutativeSemiring", 178 | al = additiveCommutativeMonoid, 179 | ml = multiplicativeCommutativeSemigroup, 180 | parents = Seq(semiring) 181 | ) 182 | 183 | def commutativeRng(implicit A: CommutativeRng[A]) = new RingProperties( 184 | name = "commutativeRng", 185 | al = additiveCommutativeMonoid, 186 | ml = multiplicativeCommutativeSemigroup, 187 | parents = Seq(rng, commutativeSemiring) 188 | ) 189 | 190 | def commutativeRig(implicit A: CommutativeRig[A]) = new RingProperties( 191 | name = "commutativeRig", 192 | al = additiveCommutativeMonoid, 193 | ml = multiplicativeCommutativeMonoid, 194 | parents = Seq(rig, commutativeSemiring) 195 | ) 196 | 197 | def commutativeRing(implicit A: CommutativeRing[A]) = new RingProperties( 198 | name = "commutative ring", 199 | al = additiveCommutativeGroup, 200 | ml = multiplicativeCommutativeMonoid, 201 | parents = Seq(ring, commutativeRig, commutativeRng) 202 | ) 203 | 204 | // boolean rings 205 | 206 | def boolRng(implicit A: BoolRng[A]) = RingProperties.fromParent( 207 | name = "boolean rng", 208 | parent = commutativeRng, 209 | Rules.idempotence(A.times) 210 | ) 211 | 212 | def boolRing(implicit A: BoolRing[A]) = RingProperties.fromParent( 213 | name = "boolean ring", 214 | parent = commutativeRing, 215 | Rules.idempotence(A.times) 216 | ) 217 | 218 | // Everything below fields (e.g. rings) does not require their multiplication 219 | // operation to be a group. Hence, we do not check for the existence of an 220 | // inverse. On the other hand, fields require their multiplication to be an 221 | // abelian group. Now we have to worry about zero. 222 | // 223 | // The usual text book definition says: Fields consist of two abelian groups 224 | // (set, +, zero) and (set \ zero, *, one). We do the same thing here. 225 | // However, since law checking for the multiplication does not include zero 226 | // any more, it is not immediately clear that desired properties like 227 | // zero * x == x * zero hold. 228 | // Luckily, these follow from the other field and group axioms. 229 | def field(implicit A: Field[A]) = new RingProperties( 230 | name = "field", 231 | al = additiveCommutativeGroup, 232 | ml = multiplicativeCommutativeGroup, 233 | parents = Seq(commutativeRing), 234 | "fromDouble" -> forAll { (n: Double) => 235 | if (Platform.isJvm) { 236 | // TODO: BigDecimal(n) is busted in scalajs, so we skip this test. 237 | val bd = new java.math.BigDecimal(n) 238 | val unscaledValue = new BigInt(bd.unscaledValue) 239 | val expected = 240 | if (bd.scale > 0) { 241 | A.div(A.fromBigInt(unscaledValue), A.fromBigInt(BigInt(10).pow(bd.scale))) 242 | } else { 243 | A.fromBigInt(unscaledValue * BigInt(10).pow(-bd.scale)) 244 | } 245 | Field.fromDouble[A](n) ?== expected 246 | } else { 247 | Prop(true) 248 | } 249 | } 250 | ) 251 | 252 | // property classes 253 | 254 | class AdditiveProperties( 255 | val base: GroupLaws[A]#GroupProperties, 256 | val parents: Seq[AdditiveProperties], 257 | val props: (String, Prop)* 258 | ) extends RuleSet { 259 | val name = "additive " + base.name 260 | val bases = List("base" -> base) 261 | } 262 | 263 | class MultiplicativeProperties( 264 | val base: GroupLaws[A]#GroupProperties, 265 | val nonZeroBase: Option[nonZeroLaws.GroupProperties], 266 | val parent: Option[MultiplicativeProperties], 267 | val props: (String, Prop)* 268 | ) extends RuleSet with HasOneParent { 269 | val name = "multiplicative " + base.name 270 | val bases = Seq("base" -> base) ++ nonZeroBase.map("non-zero base" -> _) 271 | } 272 | 273 | object RingProperties { 274 | def fromParent(name: String, parent: RingProperties, props: (String, Prop)*) = 275 | new RingProperties(name, parent.al, parent.ml, Seq(parent), props: _*) 276 | } 277 | 278 | class RingProperties( 279 | val name: String, 280 | val al: AdditiveProperties, 281 | val ml: MultiplicativeProperties, 282 | val parents: Seq[RingProperties], 283 | val props: (String, Prop)* 284 | ) extends RuleSet { 285 | def bases = Seq("additive" -> al, "multiplicative" -> ml) 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /laws/shared/src/main/scala/algebra/laws/Rules.scala: -------------------------------------------------------------------------------- 1 | package algebra.laws 2 | 3 | import cats.kernel._ 4 | import org.scalacheck.Prop._ 5 | import org.scalacheck.{Arbitrary, Prop} 6 | import cats.kernel.instances.boolean._ 7 | 8 | object Rules { 9 | 10 | // Comparison operators for testing are supplied by CheckEqOps and 11 | // CheckOrderOps in package.scala. They are: 12 | // 13 | // ?== Ensure that x equals y 14 | // ?!= Ensure that x does not equal y 15 | // ?< Ensure that x < y 16 | // ?<= Ensure that x <= y 17 | // ?> Ensure that x > y 18 | // ?>= Ensure that x >= y 19 | // 20 | // The reason to prefer these operators is that when tests fail, we 21 | // will get more detaild output about what the failing values were 22 | // (in addition to the input values generated by ScalaCheck). 23 | 24 | def associativity[A: Arbitrary: Eq](f: (A, A) => A): (String, Prop) = 25 | "associativity" -> forAll { (x: A, y: A, z: A) => 26 | f(f(x, y), z) ?== f(x, f(y, z)) 27 | } 28 | 29 | def leftIdentity[A: Arbitrary: Eq](id: A)(f: (A, A) => A): (String, Prop) = 30 | "leftIdentity" -> forAll { (x: A) => 31 | f(id, x) ?== x 32 | } 33 | 34 | def rightIdentity[A: Arbitrary: Eq](id: A)(f: (A, A) => A): (String, Prop) = 35 | "rightIdentity" -> forAll { (x: A) => 36 | f(x, id) ?== x 37 | } 38 | 39 | def leftInverse[A: Arbitrary: Eq](id: A)(f: (A, A) => A)(inv: A => A): (String, Prop) = 40 | "left inverse" -> forAll { (x: A) => 41 | id ?== f(inv(x), x) 42 | } 43 | 44 | def rightInverse[A: Arbitrary: Eq](id: A)(f: (A, A) => A)(inv: A => A): (String, Prop) = 45 | "right inverse" -> forAll { (x: A) => 46 | id ?== f(x, inv(x)) 47 | } 48 | 49 | def commutative[A: Arbitrary: Eq](f: (A, A) => A): (String, Prop) = 50 | "commutative" -> forAll { (x: A, y: A) => 51 | f(x, y) ?== f(y, x) 52 | } 53 | 54 | def idempotence[A: Arbitrary: Eq](f: (A, A) => A): (String, Prop) = 55 | "idempotence" -> forAll { (x: A) => 56 | f(x, x) ?== x 57 | } 58 | 59 | def consistentInverse[A: Arbitrary: Eq](name: String)(m: (A, A) => A)(f: (A, A) => A)(inv: A => A): (String, Prop) = 60 | s"consistent $name" -> forAll { (x: A, y: A) => 61 | m(x, y) ?== f(x, inv(y)) 62 | } 63 | 64 | def repeat0[A: Arbitrary: Eq](name: String, sym: String, id: A)(r: (A, Int) => A): (String, Prop) = 65 | s"$name(a, 0) == $sym" -> forAll { (a: A) => 66 | r(a, 0) ?== id 67 | } 68 | 69 | def repeat1[A: Arbitrary: Eq](name: String)(r: (A, Int) => A): (String, Prop) = 70 | s"$name(a, 1) == a" -> forAll { (a: A) => 71 | r(a, 1) ?== a 72 | } 73 | 74 | def repeat2[A: Arbitrary: Eq](name: String, sym: String)(r: (A, Int) => A)(f: (A, A) => A): (String, Prop) = 75 | s"$name(a, 2) == a $sym a" -> forAll { (a: A) => 76 | r(a, 2) ?== f(a, a) 77 | } 78 | 79 | def collect0[A: Arbitrary: Eq](name: String, sym: String, id: A)(c: Seq[A] => A): (String, Prop) = 80 | s"$name(Nil) == $sym" -> forAll { (a: A) => 81 | c(Nil) ?== id 82 | } 83 | 84 | def isId[A: Arbitrary: Eq](name: String, id: A)(p: A => Boolean): (String, Prop) = 85 | name -> forAll { (x: A) => 86 | Eq.eqv(x, id) ?== p(x) 87 | } 88 | 89 | def distributive[A: Arbitrary: Eq](a: (A, A) => A)(m: (A, A) => A): (String, Prop) = 90 | "distributive" -> forAll { (x: A, y: A, z: A) => 91 | (m(x, a(y, z)) ?== a(m(x, y), m(x, z))) && 92 | (m(a(x, y), z) ?== a(m(x, z), m(y, z))) 93 | } 94 | 95 | // ugly platform-specific code follows 96 | 97 | def serializable[M](m: M): (String, Prop) = 98 | "serializable" -> (if (IsSerializable()) { 99 | Prop(_ => Result(status = Proof)) 100 | } else { 101 | Prop(_ => IsSerializable.testSerialization(m)) 102 | }) 103 | } 104 | -------------------------------------------------------------------------------- /laws/shared/src/main/scala/algebra/laws/package.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | 3 | import org.scalacheck._ 4 | import org.scalacheck.util.Pretty 5 | import Prop.{False, Proof, Result} 6 | 7 | package object laws { 8 | 9 | lazy val proved = Prop(Result(status = Proof)) 10 | 11 | lazy val falsified = Prop(Result(status = False)) 12 | 13 | object Ops { 14 | def run[A](sym: String)(lhs: A, rhs: A)(f: (A, A) => Boolean): Prop = 15 | if (f(lhs, rhs)) proved else falsified :| { 16 | val exp = Pretty.pretty(lhs, Pretty.Params(0)) 17 | val got = Pretty.pretty(rhs, Pretty.Params(0)) 18 | s"($exp $sym $got) failed" 19 | } 20 | } 21 | 22 | implicit class CheckEqOps[A](lhs: A)(implicit ev: Eq[A], pp: A => Pretty) { 23 | def ?==(rhs: A): Prop = Ops.run("?==")(lhs, rhs)(ev.eqv) 24 | def ?!=(rhs: A): Prop = Ops.run("?!=")(lhs, rhs)(ev.neqv) 25 | } 26 | 27 | implicit class CheckOrderOps[A](lhs: A)(implicit ev: PartialOrder[A], pp: A => Pretty) { 28 | def ?<(rhs: A): Prop = Ops.run("?<")(lhs, rhs)(ev.lt) 29 | def ?<=(rhs: A): Prop = Ops.run("?<=")(lhs, rhs)(ev.lteqv) 30 | def ?>(rhs: A): Prop = Ops.run("?>")(lhs, rhs)(ev.gt) 31 | def ?>=(rhs: A): Prop = Ops.run("?>=")(lhs, rhs)(ev.gteqv) 32 | } 33 | 34 | implicit class BooleanOps[A](lhs: Boolean)(implicit pp: Boolean => Pretty) { 35 | def ?&&(rhs: Boolean): Prop = Ops.run("?&&")(lhs, rhs)(_ && _) 36 | def ?||(rhs: Boolean): Prop = Ops.run("?||")(lhs, rhs)(_ || _) 37 | } 38 | } 39 | 40 | -------------------------------------------------------------------------------- /laws/shared/src/test/scala/algebra/laws/FPApprox.scala: -------------------------------------------------------------------------------- 1 | package algebra.laws 2 | 3 | import java.lang.{Double => JDouble, Float => JFloat} 4 | import java.math.MathContext 5 | 6 | import org.scalacheck.Arbitrary 7 | 8 | import algebra._ 9 | import algebra.ring._ 10 | 11 | /** 12 | * A wrapper type for approximate floating point values like Float, Double, and 13 | * BigDecimal which maintains an error bound on the current approximation. The 14 | * `Eq` instance for this type returns true if 2 values could be equal to each 15 | * other, given the error bounds, rather than if they actually are equal. So, 16 | * if x == 0.5, and y = 0.6, and the error bound of (x - y) is greater than or 17 | * equal to 0.1, then it's plausible they could be equal to each other, so we 18 | * return true. On the other hand, if the error bound is less than 0.1, then we 19 | * can definitely say they cannot be equal to each other. 20 | */ 21 | case class FPApprox[A](approx: A, mes: A, ind: BigInt) { 22 | import FPApprox.{abs, Epsilon} 23 | 24 | private def timesWithUnderflowCheck(x: A, y: A)(implicit ev: Semiring[A], eq: Eq[A], eps: Epsilon[A]): A = { 25 | val z = ev.times(x, y) 26 | if (eps.nearZero(z) && !ev.isZero(x) && !ev.isZero(y)) eps.minValue 27 | else z 28 | } 29 | 30 | def unary_-(implicit ev: Rng[A]): FPApprox[A] = 31 | FPApprox(ev.negate(approx), mes, ind) 32 | def +(that: FPApprox[A])(implicit ev: Semiring[A]): FPApprox[A] = 33 | FPApprox(ev.plus(approx, that.approx), ev.plus(mes, that.mes), ind.max(that.ind) + 1) 34 | def -(that: FPApprox[A])(implicit ev: Rng[A]): FPApprox[A] = 35 | FPApprox(ev.minus(approx, that.approx), ev.plus(mes, that.mes), ind.max(that.ind) + 1) 36 | def *(that: FPApprox[A])(implicit ev: Semiring[A], eq: Eq[A], eps: Epsilon[A]): FPApprox[A] = 37 | FPApprox(ev.times(approx, that.approx), timesWithUnderflowCheck(mes, that.mes), ind + that.ind + 1) 38 | def /(that: FPApprox[A])(implicit ev: Field[A], ord: Order[A], eps: Epsilon[A]): FPApprox[A] = { 39 | val tmp = abs(that.approx) 40 | val mesApx = ev.plus(ev.div(abs(approx), tmp), ev.div(mes, that.mes)) 41 | val mesCorrection = ev.minus(ev.div(tmp, that.mes), ev.times(ev.fromBigInt(that.ind + 1), eps.epsilon)) 42 | val mes0 = ev.div(mesApx, mesCorrection) 43 | val ind0 = ind.max(that.ind + 1) + 1 44 | FPApprox(ev.div(approx, that.approx), mes0, ind0) 45 | } 46 | 47 | def reciprocal(implicit ev: Field[A], ord: Order[A], eps: Epsilon[A]): FPApprox[A] = { 48 | val tmp = abs(approx) 49 | val mes0 = ev.div(ev.plus(ev.div(ev.one, tmp), ev.div(ev.one, mes)), ev.minus(ev.div(tmp, mes), eps.epsilon)) 50 | FPApprox(ev.reciprocal(approx), mes0, ind.max(1) + 1) 51 | } 52 | 53 | def pow(k: Int)(implicit ev: Field[A]): FPApprox[A] = { 54 | val k0 = if (k >= 0) BigInt(k) else -BigInt(k) 55 | FPApprox(ev.pow(approx, k), ev.pow(mes, k), (ind + 1) * k0 - 1) 56 | } 57 | 58 | def error(implicit ev: Ring[A], eps: Epsilon[A]): A = 59 | ev.times(ev.times(mes, ev.fromBigInt(ind)), eps.epsilon) 60 | } 61 | 62 | object FPApprox { 63 | final def abs[A](x: A)(implicit ev: Rng[A], ord: Order[A]): A = 64 | if (ord.lt(x, ev.zero)) ev.negate(x) else x 65 | 66 | def exact[A: Rng: Order](a: A): FPApprox[A] = FPApprox(a, abs(a), 0) 67 | def approx[A: Rng: Order](a: A): FPApprox[A] = FPApprox(a, abs(a), 1) 68 | 69 | trait Epsilon[A] { 70 | def minValue: A 71 | def epsilon: A 72 | def isFinite(a: A): Boolean 73 | def nearZero(a: A): Boolean 74 | } 75 | 76 | object Epsilon { 77 | def isFinite[A](a: A)(implicit eps: Epsilon[A]): Boolean = eps.isFinite(a) 78 | 79 | def instance[A](min: A, eps: A, isFin: A => Boolean, zero: A => Boolean): Epsilon[A] = 80 | new Epsilon[A] { 81 | def minValue: A = min 82 | def epsilon: A = eps 83 | def isFinite(a: A): Boolean = isFin(a) 84 | def nearZero(a: A): Boolean = zero(a) 85 | } 86 | 87 | private def isFin[A](a: A)(implicit f: A => Double): Boolean = 88 | !JDouble.isInfinite(f(a)) && !JDouble.isNaN(f(a)) 89 | 90 | // These are not the actual minimums, but closest we can get without 91 | // causing problems. 92 | private val minFloat: Float = JFloat.intBitsToFloat(1 << 23) 93 | private val minDouble: Double = JDouble.longBitsToDouble(1L << 52) 94 | 95 | implicit val floatEpsilon: Epsilon[Float] = 96 | instance(minFloat, 1.1920929E-7F, isFin(_), x => math.abs(x) < minFloat) 97 | implicit val doubleEpsilon: Epsilon[Double] = 98 | instance(minDouble, 2.220446049250313E-16, isFin(_), x => math.abs(x) < minDouble) 99 | def bigDecimalEpsilon(mc: MathContext): Epsilon[BigDecimal] = 100 | instance(BigDecimal(1, Int.MaxValue, mc), BigDecimal(1, mc.getPrecision - 1, mc), _ => true, _ == 0) 101 | } 102 | 103 | implicit def fpApproxAlgebra[A: Field: Order: Epsilon]: FPApproxAlgebra[A] = new FPApproxAlgebra[A] 104 | 105 | // An Eq instance that returns true if 2 values *could* be equal. 106 | implicit def fpApproxEq[A: Field: Order: Epsilon]: Eq[FPApprox[A]] = 107 | new Eq[FPApprox[A]] { 108 | def eqv(x: FPApprox[A], y: FPApprox[A]): Boolean = { 109 | // We want to check if z +/- error contains 0 110 | if (x.approx == y.approx) { 111 | true 112 | } else { 113 | val z = x - y 114 | val err = z.error 115 | if (Epsilon.isFinite(err)) { 116 | Order.lteqv(Ring[A].minus(z.approx, err), Ring[A].zero) && 117 | Order.gteqv(Ring[A].plus(z.approx, err), Ring[A].zero) 118 | } else { 119 | true 120 | } 121 | } 122 | } 123 | } 124 | 125 | implicit def arbFPApprox[A: Rng: Order: Arbitrary]: Arbitrary[FPApprox[A]] = 126 | Arbitrary(Arbitrary.arbitrary[A].map(FPApprox.exact[A](_))) 127 | } 128 | 129 | class FPApproxAlgebra[A: Order: FPApprox.Epsilon](implicit ev: Field[A]) extends Field[FPApprox[A]] with Serializable { 130 | def zero: FPApprox[A] = FPApprox.exact(ev.zero) 131 | def one: FPApprox[A] = FPApprox.exact(ev.one) 132 | 133 | def plus(x: FPApprox[A], y: FPApprox[A]): FPApprox[A] = x + y 134 | def negate(x: FPApprox[A]): FPApprox[A] = -x 135 | override def minus(x: FPApprox[A], y: FPApprox[A]): FPApprox[A] = x - y 136 | 137 | def times(x: FPApprox[A], y: FPApprox[A]): FPApprox[A] = x * y 138 | def div(x: FPApprox[A], y: FPApprox[A]): FPApprox[A] = x / y 139 | override def reciprocal(x: FPApprox[A]): FPApprox[A] = x.reciprocal // one / x 140 | override def pow(x: FPApprox[A], y: Int): FPApprox[A] = x.pow(y) 141 | 142 | override def fromInt(x: Int): FPApprox[A] = FPApprox.approx(ev.fromInt(x)) 143 | override def fromBigInt(x: BigInt): FPApprox[A] = FPApprox.approx(ev.fromBigInt(x)) 144 | override def fromDouble(x: Double): FPApprox[A] = FPApprox.approx(ev.fromDouble(x)) 145 | } 146 | -------------------------------------------------------------------------------- /laws/shared/src/test/scala/algebra/laws/LawTests.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | package laws 3 | 4 | import algebra.lattice._ 5 | import algebra.ring._ 6 | import algebra.instances.all._ 7 | import algebra.instances.BigDecimalAlgebra 8 | 9 | import algebra.laws.platform.Platform 10 | 11 | import org.scalacheck.{Arbitrary, Cogen} 12 | import Arbitrary.arbitrary 13 | import scala.collection.immutable.BitSet 14 | import scala.util.Random 15 | 16 | class LawTests extends munit.DisciplineSuite { 17 | 18 | implicit val byteLattice: Lattice[Byte] = ByteMinMaxLattice 19 | implicit val shortLattice: Lattice[Short] = ShortMinMaxLattice 20 | implicit val intLattice: BoundedDistributiveLattice[Int] = IntMinMaxLattice 21 | implicit val longLattice: BoundedDistributiveLattice[Long] = LongMinMaxLattice 22 | 23 | implicit def orderLaws[A: Cogen: Eq: Arbitrary]: OrderLaws[A] = OrderLaws[A] 24 | implicit def groupLaws[A: Eq: Arbitrary]: GroupLaws[A] = GroupLaws[A] 25 | implicit def logicLaws[A: Eq: Arbitrary]: LogicLaws[A] = LogicLaws[A] 26 | implicit def deMorganLaws[A: Eq: Arbitrary]: DeMorganLaws[A] = DeMorganLaws[A] 27 | 28 | implicit def latticeLaws[A: Eq: Arbitrary]: LatticeLaws[A] = LatticeLaws[A] 29 | implicit def ringLaws[A: Eq: Arbitrary: AdditiveMonoid]: RingLaws[A] = RingLaws[A] 30 | implicit def baseLaws[A: Eq: Arbitrary]: BaseLaws[A] = BaseLaws[A] 31 | implicit def latticePartialOrderLaws[A: Eq: Arbitrary]: LatticePartialOrderLaws[A] = LatticePartialOrderLaws[A] 32 | 33 | case class HasEq[A](a: A) 34 | 35 | object HasEq { 36 | implicit def hasEq[A: Eq]: Eq[HasEq[A]] = Eq.by(_.a) 37 | implicit def hasEqArbitrary[A: Arbitrary]: Arbitrary[HasEq[A]] = 38 | Arbitrary(arbitrary[A].map(HasEq(_))) 39 | implicit def hasEqCogen[A: Cogen]: Cogen[HasEq[A]] = 40 | Cogen[A].contramap[HasEq[A]](_.a) 41 | } 42 | 43 | case class HasPartialOrder[A](a: A) 44 | 45 | object HasPartialOrder { 46 | implicit def hasPartialOrder[A: PartialOrder]: PartialOrder[HasPartialOrder[A]] = PartialOrder.by(_.a) 47 | implicit def hasPartialOrderArbitrary[A: Arbitrary]: Arbitrary[HasPartialOrder[A]] = 48 | Arbitrary(arbitrary[A].map(HasPartialOrder(_))) 49 | implicit def hasPartialOrderCogen[A: Cogen]: Cogen[HasPartialOrder[A]] = 50 | Cogen[A].contramap[HasPartialOrder[A]](_.a) 51 | } 52 | 53 | checkAll("Boolean", OrderLaws[Boolean].order)//("Boolean").check(_.order) 54 | checkAll("Boolean", LogicLaws[Boolean].bool) 55 | checkAll("SimpleHeyting", DeMorganLaws[SimpleHeyting].logic(Logic.fromHeyting(Heyting[SimpleHeyting]))) 56 | checkAll("SimpleHeyting", LogicLaws[SimpleHeyting].heyting) 57 | checkAll("SimpleDeMorgan", DeMorganLaws[SimpleDeMorgan].deMorgan) 58 | checkAll("Boolean", DeMorganLaws[Boolean].deMorgan(DeMorgan.fromBool(Bool[Boolean]))) 59 | checkAll("Boolean", LatticePartialOrderLaws[Boolean].boundedLatticePartialOrder) 60 | checkAll("Boolean", RingLaws[Boolean].boolRing(booleanRing)) 61 | 62 | // ensure that Bool[A].asBoolRing is a valid BoolRing 63 | checkAll("Boolean-ring-from-bool", RingLaws[Boolean].boolRing(Bool[Boolean].asBoolRing)) 64 | 65 | // ensure that BoolRing[A].asBool is a valid Bool 66 | checkAll("Boolean- bool-from-ring", LogicLaws[Boolean].bool(new BoolFromBoolRing(booleanRing))) 67 | 68 | checkAll("String", OrderLaws[String].order) 69 | checkAll("String", GroupLaws[String].monoid) 70 | 71 | { 72 | checkAll("Option[HasEq[Int]]", OrderLaws[Option[HasEq[Int]]].eqv) 73 | checkAll("Option[HasPartialOrder[Int]]", OrderLaws[Option[HasPartialOrder[Int]]].partialOrder) 74 | checkAll("Option[Int]", OrderLaws[Option[Int]].order) 75 | checkAll("Option[Int]", GroupLaws[Option[Int]].monoid) 76 | checkAll("Option[HasEq[String]]", OrderLaws[Option[HasEq[String]]].eqv) 77 | checkAll("Option[HasPartialOrder[String]]", OrderLaws[Option[HasPartialOrder[String]]].partialOrder) 78 | checkAll("Option[String]", OrderLaws[Option[String]].order) 79 | checkAll("Option[String]", GroupLaws[Option[String]].monoid) 80 | } 81 | 82 | checkAll("List[HasEq[Int]]", OrderLaws[List[HasEq[Int]]].eqv) 83 | checkAll("List[HasPartialOrder[Int]]", OrderLaws[List[HasPartialOrder[Int]]].partialOrder) 84 | checkAll("List[Int]", OrderLaws[List[Int]].order) 85 | checkAll("List[Int]", GroupLaws[List[Int]].monoid) 86 | checkAll("List[HasEq[String]]", OrderLaws[List[HasEq[String]]].eqv) 87 | checkAll("List[HasPartialOrder[String]]", OrderLaws[List[HasPartialOrder[String]]].partialOrder) 88 | checkAll("List[String]", OrderLaws[List[String]].order) 89 | checkAll("List[String]", GroupLaws[List[String]].monoid) 90 | 91 | checkAll("Set[Byte]", LogicLaws[Set[Byte]].generalizedBool) 92 | checkAll("Set[Byte]", RingLaws[Set[Byte]].boolRng(setBoolRng[Byte])) 93 | checkAll("Set[Byte]-bool-from-rng", LogicLaws[Set[Byte]].generalizedBool(new GenBoolFromBoolRng(setBoolRng))) 94 | checkAll("Set[Byte]-rng-from-bool", RingLaws[Set[Byte]].boolRng(GenBool[Set[Byte]].asBoolRing)) 95 | checkAll("Set[Int]", OrderLaws[Set[Int]].partialOrder) 96 | checkAll("Set[Int]", RingLaws[Set[Int]].semiring) 97 | checkAll("Set[String]", RingLaws[Set[String]].semiring) 98 | 99 | checkAll("Map[Char, Int]", OrderLaws[Map[Char, Int]].eqv) 100 | checkAll("Map[Char, Int]", RingLaws[Map[Char, Int]].semiring) 101 | checkAll("Map[Int, BigInt]", OrderLaws[Map[Int, BigInt]].eqv) 102 | checkAll("Map[Int, BigInt]", RingLaws[Map[Int, BigInt]].semiring) 103 | 104 | checkAll("Byte", OrderLaws[Byte].order) 105 | checkAll("Byte", RingLaws[Byte].commutativeRing) 106 | checkAll("Byte", LatticeLaws[Byte].lattice) 107 | 108 | checkAll("Short", OrderLaws[Short].order) 109 | checkAll("Short", RingLaws[Short].commutativeRing) 110 | checkAll("Short", LatticeLaws[Short].lattice) 111 | 112 | checkAll("Char", OrderLaws[Char].order) 113 | 114 | checkAll("Int", OrderLaws[Int].order) 115 | checkAll("Int", RingLaws[Int].commutativeRing) 116 | checkAll("Int", LatticeLaws[Int].boundedDistributiveLattice) 117 | 118 | { 119 | checkAll("Int", RingLaws[Int].commutativeRig) 120 | } 121 | 122 | checkAll("Long", OrderLaws[Long].order) 123 | checkAll("Long", RingLaws[Long].commutativeRing) 124 | checkAll("Long", LatticeLaws[Long].boundedDistributiveLattice) 125 | 126 | checkAll("BigInt", RingLaws[BigInt].commutativeRing) 127 | 128 | checkAll("FPApprox[Float]", RingLaws[FPApprox[Float]].field) 129 | checkAll("FPApprox[Double]", RingLaws[FPApprox[Double]].field) 130 | 131 | // let's limit our BigDecimal-related tests to the JVM for now. 132 | if (Platform.isJvm) { 133 | 134 | { 135 | // we need a less intense arbitrary big decimal implementation. 136 | // this keeps the values relatively small/simple and avoids some 137 | // of the numerical errors we might hit. 138 | implicit val arbBigDecimal: Arbitrary[BigDecimal] = 139 | Arbitrary(arbitrary[Int].map(x => BigDecimal(x, java.math.MathContext.UNLIMITED))) 140 | 141 | // BigDecimal does have numerical errors, so we can't pass all of 142 | // the field laws. 143 | checkAll("BigDecimal", RingLaws[BigDecimal].ring) 144 | } 145 | 146 | { 147 | // We check the full field laws using a FPApprox. 148 | val mc = java.math.MathContext.DECIMAL32 149 | implicit val arbBigDecimal: Arbitrary[BigDecimal] = 150 | Arbitrary(arbitrary[Double].map(x => BigDecimal(x, mc))) 151 | implicit val epsBigDecimal = FPApprox.Epsilon.bigDecimalEpsilon(mc) 152 | implicit val algebra: FPApproxAlgebra[BigDecimal] = FPApprox.fpApproxAlgebra(new BigDecimalAlgebra(mc), Order[BigDecimal], epsBigDecimal) 153 | checkAll("FPApprox[BigDecimal]", RingLaws[FPApprox[BigDecimal]].field(algebra)) 154 | } 155 | } else () 156 | 157 | { 158 | implicit val arbBitSet: Arbitrary[BitSet] = 159 | Arbitrary(arbitrary[List[Byte]].map(s => BitSet(s.map(_ & 0xff): _*))) 160 | checkAll("BitSet", LogicLaws[BitSet].generalizedBool) 161 | } 162 | 163 | checkAll("(Int, Int)", RingLaws[(Int, Int)].ring) 164 | 165 | { 166 | implicit val band = new Band[(Int, Int)] { 167 | def combine(a: (Int, Int), b: (Int, Int)) = (a._1, b._2) 168 | } 169 | checkAll("(Int, Int) Band", GroupLaws[(Int, Int)].band) 170 | } 171 | 172 | checkAll("Unit", OrderLaws[Unit].order) 173 | checkAll("Unit", RingLaws[Unit].commutativeRing) 174 | checkAll("Unit", RingLaws[Unit].multiplicativeMonoid) 175 | checkAll("Unit", LatticeLaws[Unit].boundedSemilattice) 176 | 177 | { 178 | // In order to check the monoid laws for `Order[N]`, we need 179 | // `Arbitrary[Order[N]]` and `Eq[Order[N]]` instances. 180 | // Here we have a bit of a hack to create these instances. 181 | val nMax: Int = 13 182 | final case class N(n: Int) { require(n >= 0 && n < nMax) } 183 | // The arbitrary `Order[N]` values are created by mapping N values to random 184 | // integers. 185 | implicit val arbNOrder: Arbitrary[Order[N]] = Arbitrary(arbitrary[Int].map { seed => 186 | val order = new Random(seed).shuffle(Vector.range(0, nMax)) 187 | Order.by { (n: N) => order(n.n) } 188 | }) 189 | // The arbitrary `Eq[N]` values are created by mapping N values to random 190 | // integers. 191 | implicit val arbNEq: Arbitrary[Eq[N]] = Arbitrary(arbitrary[Int].map { seed => 192 | val mapping = new Random(seed).shuffle(Vector.range(0, nMax)) 193 | Eq.by { (n: N) => mapping(n.n) } 194 | }) 195 | // needed because currently we don't have Vector instances 196 | implicit val vectorNEq: Eq[Vector[N]] = Eq.fromUniversalEquals 197 | // The `Eq[Order[N]]` instance enumerates all possible `N` values in a 198 | // `Vector` and considers two `Order[N]` instances to be equal if they 199 | // result in the same sorting of that vector. 200 | implicit val NOrderEq: Eq[Order[N]] = Eq.by { (order: Order[N]) => 201 | Vector.tabulate(nMax)(N).sorted(order.toOrdering) 202 | } 203 | implicit val NEqEq: Eq[Eq[N]] = new Eq[Eq[N]] { 204 | def eqv(a: Eq[N], b: Eq[N]) = 205 | Iterator.tabulate(nMax)(N) 206 | .flatMap { x => Iterator.tabulate(nMax)(N).map((x, _)) } 207 | .forall { case (x, y) => a.eqv(x, y) == b.eqv(x, y) } 208 | } 209 | 210 | implicit val monoidOrderN: Monoid[Order[N]] = Order.whenEqualMonoid[N] 211 | checkAll("Order[N]", GroupLaws[Order[N]].monoid) 212 | 213 | { 214 | implicit val bsEqN: BoundedSemilattice[Eq[N]] = Eq.allEqualBoundedSemilattice[N] 215 | checkAll("Eq[N]", GroupLaws[Eq[N]].boundedSemilattice) 216 | } 217 | { 218 | implicit val sEqN: Semilattice[Eq[N]] = Eq.anyEqualSemilattice[N] 219 | checkAll("Eq[N]", GroupLaws[Eq[N]].semilattice) 220 | } 221 | } 222 | 223 | // checkAll("Int", "fromOrdering", OrderLaws[Int].order(Order.fromOrdering[Int])) 224 | checkAll("Array[Int]", OrderLaws[Array[Int]].order) 225 | checkAll("Array[Int]", OrderLaws[Array[Int]].partialOrder) 226 | 227 | // Rational tests do not return on Scala-js, so we make them JVM only. 228 | if (Platform.isJvm) checkAll("Rat", RingLaws[Rat].field) 229 | else () 230 | 231 | test("Field.fromDouble with subnormal") { 232 | val n = 1.9726888167225064E-308 233 | val bd = new java.math.BigDecimal(n) 234 | val unscaledValue = new BigInt(bd.unscaledValue) 235 | val expected = 236 | if (bd.scale > 0) { 237 | Ring[Rat].fromBigInt(unscaledValue) / Ring[Rat].fromBigInt(BigInt(10).pow(bd.scale)) 238 | } else { 239 | Ring[Rat].fromBigInt(unscaledValue * BigInt(10).pow(-bd.scale)) 240 | } 241 | assert(Field.fromDouble[Rat](n) == expected) 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /laws/shared/src/test/scala/algebra/laws/Rat.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | package laws 3 | 4 | import algebra.lattice.DistributiveLattice 5 | import algebra.ring._ 6 | import org.scalacheck.{Gen, Arbitrary} 7 | import org.scalacheck.Arbitrary.arbitrary 8 | 9 | class Rat(val num: BigInt, val den: BigInt) extends Serializable { lhs => 10 | 11 | override def toString: String = 12 | if (den == 1) s"$num" else s"$num/$den" 13 | 14 | override def equals(that: Any): Boolean = 15 | that match { 16 | case r: Rat => num == r.num && den == r.den 17 | case _ => false 18 | } 19 | 20 | override def hashCode(): Int = (num, den).## 21 | 22 | def isZero: Boolean = num == 0 23 | 24 | def isOne: Boolean = num == 1 && den == 1 25 | 26 | def compare(rhs: Rat): Int = 27 | (lhs.num * rhs.den) compare (rhs.num * lhs.den) 28 | 29 | def abs: Rat = Rat(num.abs, den) 30 | 31 | def signum: Int = num.signum 32 | 33 | def +(rhs: Rat): Rat = 34 | Rat((lhs.num * rhs.den) + (rhs.num * lhs.den), (lhs.den * rhs.den)) 35 | 36 | def unary_- : Rat = 37 | Rat(-lhs.num, lhs.den) 38 | 39 | def *(rhs: Rat): Rat = 40 | Rat(lhs.num * rhs.num, lhs.den * rhs.den) 41 | 42 | def /~(rhs: Rat) = lhs / rhs 43 | 44 | def %(rhs: Rat) = Rat.Zero 45 | 46 | def reciprocal: Rat = 47 | if (num == 0) throw new ArithmeticException("/0") else Rat(den, num) 48 | 49 | def /(rhs: Rat): Rat = 50 | lhs * rhs.reciprocal 51 | 52 | def **(k: Int): Rat = 53 | Rat(num.pow(k), den.pow(k)) 54 | 55 | def toDouble: Double = num.toDouble / den.toDouble 56 | 57 | def toInt: Int = toDouble.toInt 58 | 59 | def isWhole: Boolean = den == 1 60 | 61 | def ceil: Rat = 62 | if (num >= 0) Rat((num + den - 1) / den, 1) 63 | else Rat(num / den, 1) 64 | 65 | def floor: Rat = 66 | if (num >= 0) Rat(num / den, 1) 67 | else Rat((num - den + 1) / den, 1) 68 | 69 | def round: Rat = 70 | if (num >= 0) Rat((num + (den / 2)) / den, 1) 71 | else Rat((num - (den / 2)) / den, 1) 72 | 73 | } 74 | 75 | object Rat { 76 | 77 | val MinusOne: Rat = Rat(-1) 78 | val Zero: Rat = Rat(0) 79 | val One: Rat = Rat(1) 80 | val Two: Rat = Rat(2) 81 | 82 | def apply(n: BigInt): Rat = 83 | Rat(n, 1) 84 | 85 | def apply(num: BigInt, den: BigInt): Rat = 86 | if (den == 0) throw new ArithmeticException("/0") 87 | else if (den < 0) apply(-num, -den) 88 | else if (num == 0) new Rat(0, 1) 89 | else { 90 | val g = num gcd den 91 | new Rat(num / g, den / g) 92 | } 93 | 94 | def unapply(r: Rat): Some[(BigInt, BigInt)] = Some((r.num, r.den)) 95 | 96 | implicit val ratAlgebra: RatAlgebra = 97 | new RatAlgebra 98 | 99 | val RatMinMaxLattice: DistributiveLattice[Rat] = 100 | DistributiveLattice.minMax[Rat](ratAlgebra) 101 | 102 | // Is this horrible? Yes. Am I ashamed? Yes. 103 | private[this] def genNonZero: Gen[BigInt] = 104 | arbitrary[BigInt].flatMap { x => 105 | if (x != 0) Gen.const(x) 106 | else genNonZero 107 | } 108 | 109 | implicit val ratArbitrary: Arbitrary[Rat] = 110 | Arbitrary(for { 111 | n <- arbitrary[BigInt] 112 | d <- genNonZero 113 | } yield Rat(n, d)) 114 | } 115 | 116 | class RatAlgebra extends Field[Rat] with Order[Rat] with Serializable { 117 | 118 | def compare(x: Rat, y: Rat): Int = x compare y 119 | 120 | val zero: Rat = Rat.Zero 121 | val one: Rat = Rat.One 122 | 123 | def plus(a: Rat, b: Rat): Rat = a + b 124 | def negate(a: Rat): Rat = -a 125 | def times(a: Rat, b: Rat): Rat = a * b 126 | override def reciprocal(a: Rat): Rat = a.reciprocal 127 | def div(a: Rat, b: Rat): Rat = a / b 128 | 129 | override def fromInt(n: Int): Rat = Rat(n) 130 | override def fromBigInt(n: BigInt): Rat = Rat(n) 131 | 132 | def isWhole(a: Rat): Boolean = a.isWhole 133 | def ceil(a: Rat): Rat = a.ceil 134 | def floor(a: Rat): Rat = a.floor 135 | def round(a: Rat): Rat = a.round 136 | } 137 | -------------------------------------------------------------------------------- /laws/shared/src/test/scala/algebra/laws/SimpleDeMorgan.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | package laws 3 | 4 | import algebra.lattice.DeMorgan 5 | 6 | import org.scalacheck.Arbitrary 7 | import org.scalacheck.Gen.oneOf 8 | 9 | /** 10 | * The simplest De Morgan algebra that is not already a Boolean algebra. 11 | * It is the standard three valued logic. 12 | * Taken from https://en.wikipedia.org/wiki/De_Morgan_algebra#Kleene_algebra 13 | */ 14 | sealed trait SimpleDeMorgan 15 | 16 | object SimpleDeMorgan { 17 | private case object False extends SimpleDeMorgan 18 | private case object Unknown extends SimpleDeMorgan 19 | private case object True extends SimpleDeMorgan 20 | 21 | implicit val deMorgan: DeMorgan[SimpleDeMorgan] = new DeMorgan[SimpleDeMorgan] { 22 | def zero: SimpleDeMorgan = False 23 | def one: SimpleDeMorgan = True 24 | 25 | def and(a: SimpleDeMorgan, b: SimpleDeMorgan): SimpleDeMorgan = (a, b) match { 26 | case (False, _) => False 27 | case (_, False) => False 28 | case (Unknown, _) => Unknown 29 | case (_, Unknown) => Unknown 30 | case _ => True 31 | } 32 | 33 | def or(a: SimpleDeMorgan, b: SimpleDeMorgan): SimpleDeMorgan = (a, b) match { 34 | case (False, x) => x 35 | case (x, False) => x 36 | case (Unknown, x) => x 37 | case (x, Unknown) => x 38 | case _ => True 39 | } 40 | 41 | def not(a: SimpleDeMorgan): SimpleDeMorgan = a match { 42 | case False => True 43 | case Unknown => Unknown 44 | case True => False 45 | } 46 | } 47 | 48 | implicit val arbitrary: Arbitrary[SimpleDeMorgan] = Arbitrary(oneOf(False, Unknown, True)) 49 | 50 | implicit val eq: Eq[SimpleDeMorgan] = new Eq[SimpleDeMorgan] { 51 | def eqv(x: SimpleDeMorgan, y: SimpleDeMorgan): Boolean = x == y 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /laws/shared/src/test/scala/algebra/laws/SimpleHeyting.scala: -------------------------------------------------------------------------------- 1 | package algebra 2 | package laws 3 | 4 | import algebra.lattice.Heyting 5 | 6 | import org.scalacheck.Arbitrary 7 | import org.scalacheck.Gen.oneOf 8 | 9 | /** 10 | * The simplest Heyting algebra that is not already a Boolean algebra. 11 | * Taken from https://en.wikipedia.org/wiki/Heyting_algebra#Examples 12 | */ 13 | sealed trait SimpleHeyting 14 | 15 | object SimpleHeyting { 16 | private case object Zero extends SimpleHeyting 17 | private case object Half extends SimpleHeyting 18 | private case object One extends SimpleHeyting 19 | 20 | implicit val heyting: Heyting[SimpleHeyting] = new Heyting[SimpleHeyting] { 21 | def zero: SimpleHeyting = Zero 22 | def one: SimpleHeyting = One 23 | 24 | def and(a: SimpleHeyting, b: SimpleHeyting): SimpleHeyting = (a, b) match { 25 | case (Zero, _) => Zero 26 | case (_, Zero) => Zero 27 | case (Half, _) => Half 28 | case (_, Half) => Half 29 | case _ => One 30 | } 31 | 32 | def or(a: SimpleHeyting, b: SimpleHeyting): SimpleHeyting = (a, b) match { 33 | case (Zero, x) => x 34 | case (x, Zero) => x 35 | case (Half, x) => x 36 | case (x, Half) => x 37 | case _ => One 38 | } 39 | 40 | def complement(a: SimpleHeyting): SimpleHeyting = a match { 41 | case Zero => One 42 | case Half => Zero 43 | case One => Zero 44 | } 45 | 46 | def imp(a: SimpleHeyting, b: SimpleHeyting): SimpleHeyting = (a, b) match { 47 | case (Zero, _) => One 48 | case (_, Zero) => Zero 49 | case (Half, _) => One 50 | case (One, x) => x 51 | } 52 | } 53 | 54 | implicit val arbitrary: Arbitrary[SimpleHeyting] = Arbitrary(oneOf(Zero, Half, One)) 55 | 56 | implicit val eq: Eq[SimpleHeyting] = new Eq[SimpleHeyting] { 57 | def eqv(x: SimpleHeyting, y: SimpleHeyting): Boolean = x == y 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /project/Boilerplate.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | 3 | /** 4 | * Generate a range of boilerplate classes that would be tedious to write and maintain by hand. 5 | * 6 | * Copied, with some modifications, from 7 | * [[https://github.com/milessabin/shapeless/blob/master/project/Boilerplate.scala Shapeless]]. 8 | * 9 | * @author Miles Sabin 10 | * @author Kevin Wright 11 | */ 12 | object Boilerplate { 13 | import scala.StringContext._ 14 | 15 | implicit class BlockHelper(val sc: StringContext) extends AnyVal { 16 | def block(args: Any*): String = { 17 | val interpolated = sc.standardInterpolator(treatEscapes, args) 18 | val rawLines = interpolated.split('\n') 19 | val trimmedLines = rawLines.map(_.dropWhile(_.isWhitespace)) 20 | trimmedLines.mkString("\n") 21 | } 22 | } 23 | 24 | val templates: Seq[Template] = Seq( 25 | GenTupleInstances 26 | ) 27 | 28 | val header = "// auto-generated boilerplate" 29 | val maxArity = 22 30 | 31 | /** 32 | * Return a sequence of the generated files. 33 | * 34 | * As a side-effect, it actually generates them... 35 | */ 36 | def gen(dir: File): Seq[File] = templates.map { template => 37 | val tgtFile = template.filename(dir) 38 | IO.write(tgtFile, template.body) 39 | tgtFile 40 | } 41 | 42 | class TemplateVals(val arity: Int) { 43 | val synTypes = (0 until arity).map(n => s"A$n") 44 | val synVals = (0 until arity).map(n => s"a$n") 45 | val `A..N` = synTypes.mkString(", ") 46 | val `a..n` = synVals.mkString(", ") 47 | val `_.._` = Seq.fill(arity)("_").mkString(", ") 48 | val `(A..N)` = if (arity == 1) "Tuple1[A0]" else synTypes.mkString("(", ", ", ")") 49 | val `(_.._)` = if (arity == 1) "Tuple1[_]" else Seq.fill(arity)("_").mkString("(", ", ", ")") 50 | val `(a..n)` = if (arity == 1) "Tuple1(a)" else synVals.mkString("(", ", ", ")") 51 | } 52 | 53 | /** 54 | * Blocks in the templates below use a custom interpolator, combined with post-processing to 55 | * produce the body. 56 | * 57 | * - The contents of the `header` val is output first 58 | * - Then the first block of lines beginning with '|' 59 | * - Then the block of lines beginning with '-' is replicated once for each arity, 60 | * with the `templateVals` already pre-populated with relevant relevant vals for that arity 61 | * - Then the last block of lines prefixed with '|' 62 | * 63 | * The block otherwise behaves as a standard interpolated string with regards to variable 64 | * substitution. 65 | */ 66 | trait Template { 67 | def filename(root: File): File 68 | def content(tv: TemplateVals): String 69 | def range: IndexedSeq[Int] = 1 to maxArity 70 | def body: String = { 71 | val headerLines = header.split('\n') 72 | val raw = range.map(n => content(new TemplateVals(n)).split('\n').filterNot(_.isEmpty)) 73 | val preBody = raw.head.takeWhile(_.startsWith("|")).map(_.tail) 74 | val instances = raw.flatMap(_.filter(_.startsWith("-")).map(_.tail)) 75 | val postBody = raw.head.dropWhile(_.startsWith("|")).dropWhile(_.startsWith("-")).map(_.tail) 76 | (headerLines ++ preBody ++ instances ++ postBody).mkString("\n") 77 | } 78 | } 79 | 80 | object GenTupleInstances extends Template { 81 | override def range: IndexedSeq[Int] = 1 to maxArity 82 | 83 | def filename(root: File): File = root / "algebra" / "instances" / "TupleAlgebra.scala" 84 | 85 | def content(tv: TemplateVals): String = { 86 | import tv._ 87 | 88 | def constraints(constraint: String) = 89 | synTypes.map(tpe => s"${tpe}: ${constraint}[${tpe}]").mkString(", ") 90 | 91 | def tuple(results: TraversableOnce[String]) = { 92 | val resultsVec = results.toVector 93 | val a = synTypes.size 94 | val r = s"${0.until(a).map(i => resultsVec(i)).mkString(", ")}" 95 | if (a == 1) "Tuple1(" ++ r ++ ")" 96 | else s"(${r})" 97 | } 98 | 99 | def binMethod(name: String) = 100 | synTypes.zipWithIndex.iterator.map { 101 | case (tpe, i) => 102 | val j = i + 1 103 | s"${tpe}.${name}(x._${j}, y._${j})" 104 | } 105 | 106 | def binTuple(name: String) = 107 | tuple(binMethod(name)) 108 | 109 | def unaryTuple(name: String) = { 110 | val m = synTypes.zipWithIndex.map { case (tpe, i) => s"${tpe}.${name}(x._${i + 1})" } 111 | tuple(m) 112 | } 113 | 114 | def nullaryTuple(name: String) = { 115 | val m = synTypes.map(tpe => s"${tpe}.${name}") 116 | tuple(m) 117 | } 118 | 119 | block""" 120 | |package algebra 121 | |package instances 122 | | 123 | |import algebra.ring.{Rig, Ring, Rng, Semiring} 124 | | 125 | |trait TupleInstances extends cats.kernel.instances.TupleInstances { 126 | - 127 | - implicit def tuple${arity}Rig[${`A..N`}](implicit ${constraints("Rig")}): Rig[${`(A..N)`}] = 128 | - new Rig[${`(A..N)`}] { 129 | - def one: ${`(A..N)`} = ${nullaryTuple("one")} 130 | - def plus(x: ${`(A..N)`}, y: ${`(A..N)`}): ${`(A..N)`} = ${binTuple("plus")} 131 | - def times(x: ${`(A..N)`}, y: ${`(A..N)`}): ${`(A..N)`} = ${binTuple("times")} 132 | - def zero: ${`(A..N)`} = ${nullaryTuple("zero")} 133 | - } 134 | - 135 | - implicit def tuple${arity}Ring[${`A..N`}](implicit ${constraints("Ring")}): Ring[${`(A..N)`}] = 136 | - new Ring[${`(A..N)`}] { 137 | - def one: ${`(A..N)`} = ${nullaryTuple("one")} 138 | - def plus(x: ${`(A..N)`}, y: ${`(A..N)`}): ${`(A..N)`} = ${binTuple("plus")} 139 | - def times(x: ${`(A..N)`}, y: ${`(A..N)`}): ${`(A..N)`} = ${binTuple("times")} 140 | - def zero: ${`(A..N)`} = ${nullaryTuple("zero")} 141 | - def negate(x: ${`(A..N)`}): ${`(A..N)`} = ${unaryTuple("negate")} 142 | - } 143 | - 144 | - implicit def tuple${arity}Rng[${`A..N`}](implicit ${constraints("Rng")}): Rng[${`(A..N)`}] = 145 | - new Rng[${`(A..N)`}] { 146 | - def plus(x: ${`(A..N)`}, y: ${`(A..N)`}): ${`(A..N)`} = ${binTuple("plus")} 147 | - def times(x: ${`(A..N)`}, y: ${`(A..N)`}): ${`(A..N)`} = ${binTuple("times")} 148 | - def zero: ${`(A..N)`} = ${nullaryTuple("zero")} 149 | - def negate(x: ${`(A..N)`}): ${`(A..N)`} = ${unaryTuple("negate")} 150 | - } 151 | - 152 | - implicit def tuple${arity}Semiring[${`A..N`}](implicit ${constraints("Semiring")}): Semiring[${`(A..N)`}] = 153 | - new Semiring[${`(A..N)`}] { 154 | - def plus(x: ${`(A..N)`}, y: ${`(A..N)`}): ${`(A..N)`} = ${binTuple("plus")} 155 | - def times(x: ${`(A..N)`}, y: ${`(A..N)`}): ${`(A..N)`} = ${binTuple("times")} 156 | - def zero: ${`(A..N)`} = ${nullaryTuple("zero")} 157 | - } 158 | |} 159 | """ 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.5.1 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.eed3si9n" % "sbt-unidoc" % "0.4.3") 2 | addSbtPlugin("com.47deg" % "sbt-microsites" % "1.3.2") 3 | addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.2.17") 4 | addSbtPlugin("com.github.gseitz" % "sbt-release" % "1.0.13") 5 | addSbtPlugin("com.jsuereth" % "sbt-pgp" % "2.1.1") 6 | addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.9.5") 7 | addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.3") 8 | addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.5.1") 9 | addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.8.1") 10 | addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.4.0") 11 | 12 | addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0") 13 | addSbtPlugin("org.portable-scala" % "sbt-scala-native-crossproject" % "1.0.0") 14 | addSbtPlugin("com.codecommit" % "sbt-github-actions" % "0.10.1") 15 | addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.0") 16 | -------------------------------------------------------------------------------- /version.sbt: -------------------------------------------------------------------------------- 1 | version in ThisBuild := "2.2.4-SNAPSHOT" 2 | --------------------------------------------------------------------------------