diff --git a/.changelogged.yaml b/.changelogged.yaml
new file mode 100644
index 00000000..46180488
--- /dev/null
+++ b/.changelogged.yaml
@@ -0,0 +1,14 @@
+changelogs:
+ - changelog: ChangeLog.md
+
+ ignore_files:
+ - "*ChangeLog*.md"
+ - .changelogged.yaml
+
+ ignore_commits: []
+
+branch: upstream/master
+
+entry_format: " - %message% (see [%link%](%id%));"
+
+editor_command: "nano -EiT 2"
diff --git a/.travis.yml b/.travis.yml
index bf8f8d78..2fb30bf7 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,33 +1,31 @@
-language: java
+# Use faster, Docker-based container (instead of OpenVZ)
+sudo: false
-sudo: required
+language: java
jdk:
- - oraclejdk8
-
-addons:
- apt:
- packages:
- - oracle-java8-installer
-
-before_script:
- - sudo service postgresql stop || true
- - sudo service mysql stop || true
- - sudo service memcached stop || true
- - sudo service bootlogd stop || true
- - sudo service elasticsearch stop || true
- - sudo service mongodb stop || true
- - sudo service neo4j stop || true
- - sudo service cassandra stop || true
- - sudo service riak stop || true
- - sudo service rsync stop || true
- - sudo service x11-common stop || true
+ - openjdk8
+
+matrix:
+ fast_finish: true
+
+ include:
+
+ allow_failures:
script:
- - jdk_switcher use openjdk6 && export JAVA6_HOME=$JAVA_HOME
- - jdk_switcher use oraclejdk7 && export JAVA7_HOME=$JAVA_HOME
- - jdk_switcher use oraclejdk8 && export JAVA8_HOME=$JAVA_HOME
- - ./gradlew build coverage -s -i
+ - ./gradlew build --no-daemon
+
+env:
+ global:
+ - secure: Bun+1FZ29Q3dR9gZ/5brxcSf+zcY5tWrsqOA4GUb5bYCMyORuXQB0FYXuhKR4wB1pFrk1a9EYwRwSu3GwRJVWb+UzF0CNOWF/QG5tGPx32IOXScwlL/KonI4Vhs7Oc0fF4Wdb7euNrT27BU61jbUugjJ642b3n0VBYFYDdquprU=
+ - secure: QAxhjqLRa+WHKIzgIJPZ/rM5a5uzqG7E5rsC0YvB25cO712oYXmzsYPia/oSp0chXlYLYMfk2UnLeQCSx2e6ogXRRRa977Q+B33Nt0Hd9SGLtduv6DBrbA2ehLU12Ib4DWe5VhF5eueAunycYcllTvqA5h+pzTtEVbd68ZHncM4=
+
+before_cache:
+ - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
+ - rm -fr $HOME/.gradle/caches/*/plugin-resolution/
-after_success:
- - ./gradlew jacocoRootReport coveralls
+cache:
+ directories:
+ - $HOME/.gradle/caches/
+ - $HOME/.gradle/wrapper/
diff --git a/README.adoc b/README.adoc
index 40fec847..ecf403e2 100644
--- a/README.adoc
+++ b/README.adoc
@@ -1,19 +1,19 @@
= Functional Java
-image:https://travis-ci.org/functionaljava/functionaljava.svg?branch=master["Build Status", link="https://travis-ci.org/functionaljava/functionaljava"]
-image:https://coveralls.io/repos/github/functionaljava/functionaljava/badge.svg?branch=master["Coverage Status", link="https://coveralls.io/github/functionaljava/functionaljava?branch=master"]
+image:https://travis-ci.org/functionaljava/functionaljava.svg?branch=master["Build Status", link="https://app.travis-ci.com/github/functionaljava/functionaljava"]
+image:https://codecov.io/gh/functionaljava/functionaljava/branch/master/graph/badge.svg["Code Coverage", link="https://codecov.io/gh/functionaljava/functionaljava"]
image:https://badges.gitter.im/functionaljava/functionaljava.svg[link="https://gitter.im/functionaljava/functionaljava?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"]
image::http://www.functionaljava.org/img/logo-600x144.png[]
Functional Java is an open source library facilitating functional programming in Java. The library implements numerous basic and advanced programming abstractions that assist composition oriented development. Functional Java also serves as a platform for learning functional programming concepts by introducing these concepts using a familiar language.
-The library is intended for use in production applications and is thoroughly tested using the technique of automated specification-based testing with ScalaCheck and Functional Java's quickcheck module. Functional Java is compiled with Java 8 targeting Java 7 bytecode. The use of lambdas within the project are backported with the Retro Lambda library, supporting Java versions 6 to 8.
+The library is intended for use in production applications and is thoroughly tested using the technique of automated specification-based testing with ScalaCheck and Functional Java's quickcheck module.
Functional Java provides abstractions for the following types:
* Basic Data Structures - total and partial functions, products, unit, option, unbiased and right biased unions (either and validation), void.
-* Immutable Collections - array, list, vector, stream, set, map, finger tree, heterogenous list, difference list.
+* Immutable Collections - array, list, vector, stream, set, map, priority queue, finger tree, heterogenous list, difference list.
* Other Abstractions - monoid, semigroup, natural, random number generator, reader, writer, state, input/output, parser, zipper, specification based testing (quickcheck), actors, optics (lens, prism, fold, traversal and others), concurrency and type conversion.
== URLs
@@ -22,8 +22,9 @@ Important URLs for the project are:
* Website, http://www.functionaljava.org
* Website repository, http://github.com/functionaljava/functionaljava.github.io
-* Travis continuous integration build, https://travis-ci.org/functionaljava/functionaljava
-* Sonatype Repository, https://oss.sonatype.org/content/groups/public/org/functionaljava/
+* Travis continuous integration build, https://app.travis-ci.com/github/functionaljava/functionaljava
+* Sonatype repository, https://oss.sonatype.org/content/groups/public/org/functionaljava/
+* Maven Central repository, https://mvnrepository.com/artifact/org.functionaljava/functionaljava
== Downloading
@@ -32,15 +33,14 @@ The recommended way to download and use the project is through your build tool.
The Functional Java artifact is published to Maven Central using the group `org.functionaljava` with three published artifacts:
* the core library (`functionaljava`)
-* Java 8 specific support (`functionaljava-java8`)
* property based testing (`functionaljava-quickcheck`)
+* a small amount of Java 8 support (`functionaljava-java-core`)
-The latest stable version is `4.5`. This can be added to your Gradle project by adding the dependencies:
+The latest stable version is `5.0`. This can be added to your Gradle project by adding the dependencies:
----
-compile "org.functionaljava:functionaljava:4.5"
-compile "org.functionaljava:functionaljava-java8:4.5"
-compile "org.functionaljava:functionaljava-quickcheck:4.5"
-compile "org.functionaljava:functionaljava-java-core:4.5"
+compile "org.functionaljava:functionaljava:5.0"
+compile "org.functionaljava:functionaljava-quickcheck:5.0"
+compile "org.functionaljava:functionaljava-java-core:5.0"
----
and in Maven:
@@ -48,34 +48,27 @@ and in Maven:
org.functionaljava
functionaljava
- 4.5
-
-
- org.functionaljava
- functionaljava-java8
- 4.5
+ 5.0
org.functionaljava
functionaljava-quickcheck
- 4.5
+ 5.0
org.functionaljava
functionaljava-java-core
- 4.5
+ 5.0
----
== Building
-FunctionalJava uses the Retro Lambda project to backport Java 8 lambdas to Java 6 bytecode. This requires access to both JDK 6 and 8. The build system requires the environment variables `JAVA6_HOME` and `JAVA8_HOME` to refer to the appropriate directories.
-
-Building is done using Gradle 2.10. In the root directory run:
+Building is done using Java 8 and Gradle 7.4. In the root directory run:
----
./gradlew
----
-This requires access to Java and will download the Gradle build tool and necessary dependencies and build FunctionalJava.
+This requires access to Java 8 and will download the Gradle build tool and necessary dependencies and build FunctionalJava.
== Features
@@ -100,6 +93,7 @@ A more complete description of the features mentioned above are:
** Immutable set implementation using a red/black tree (`fj.data.Set`).
** Immutable multi-way tree - aka rose tree (`fj.data.Tree`).
** Immutable tree-map using a red/black tree implementation (`fj.data.TreeMap`).
+** Immutable priority queue using finger trees (`fj.data.PriorityQueue`).
** Difference lists, a highly performant list.
* Other Abstractions
** Monoid (`fj.Monoid`).
@@ -121,3 +115,7 @@ A more complete description of the features mentioned above are:
== License
link:etc/LICENCE[The Functional Java license] uses the BSD 3 license (3-clause license) available at https://en.wikipedia.org/wiki/BSD_licenses[].
+
+== Release Notes
+
+For release notes for each version, see the directory link:etc/release-notes.
diff --git a/build.gradle b/build.gradle
index 7a3b81cf..c89c9536 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,31 +1,26 @@
defaultTasks 'build'
-ext {
-}
+apply plugin: "com.github.ben-manes.versions"
buildscript {
- ext {
- uptodateVersion = "1.6.0"
- retrolambdaVersion = "3.2.0"
- }
-
repositories {
mavenLocal()
- jcenter()
mavenCentral()
+ gradlePluginPortal()
}
dependencies {
- classpath "com.ofg:uptodate-gradle-plugin:$uptodateVersion"
- classpath "me.tatarka:gradle-retrolambda:$retrolambdaVersion"
- classpath 'org.kt3k.gradle.plugin:coveralls-gradle-plugin:2.6.3'
+ classpath "com.github.ben-manes:gradle-versions-plugin:0.42.0"
+ classpath "biz.aQute.bnd:biz.aQute.bnd.gradle:6.1.0"
}
-}
+ wrapper {
+ gradleVersion = "7.4"
+ distributionType = Wrapper.DistributionType.ALL
+ }
+}
-apply plugin: "jacoco"
-apply plugin: 'com.github.kt3k.coveralls'
if (JavaVersion.current().isJava8Compatible()) {
allprojects {
@@ -37,24 +32,24 @@ if (JavaVersion.current().isJava8Compatible()) {
allprojects {
-
+ apply plugin: "jacoco"
jacoco {
- toolVersion = "0.7.1.201405082137"
+ toolVersion = "0.8.7"
}
defaultTasks "build"
- ext {
+ ext {
isSnapshot = true
- fjBaseVersion = "4.6"
+ fjBaseVersion = "5.1"
snapshotAppendix = "-SNAPSHOT"
fjVersion = fjBaseVersion + (isSnapshot ? snapshotAppendix : "")
- fjConsumeVersion = "4.5"
+ fjConsumeVersion = "5.0"
signModule = false
- useRetroLambda = false
+ uploadModule = false
projectTitle = "Functional Java"
projectName = "functionaljava"
@@ -64,27 +59,32 @@ allprojects {
projectUrl = "http://functionaljava.org/"
scmUrl = "git://github.com/functionaljava/functionaljava.git"
scmGitFile = "scm:git@github.com:functionaljava/functionaljava.git"
+ scmSshGitFile = "scm:git:ssh://git@github.com/functionaljava/functionaljava.git"
+ licenseUrl = "https://github.com/functionaljava/functionaljava/blob/master/etc/LICENCE"
+ licenseName = "The BSD3 License"
+
+ issueUrl = "https://github.com/functionaljava/functionaljava/issues"
+ githubUrl = "https://github.com/functionaljava/functionaljava"
sonatypeBaseUrl = "https://oss.sonatype.org"
sonatypeSnapshotUrl = "$sonatypeBaseUrl/content/repositories/snapshots/"
sonatypeRepositoryUrl = "$sonatypeBaseUrl/content/groups/public"
sonatypeReleaseUrl = "$sonatypeBaseUrl/service/local/staging/deploy/maven2/"
+
sonatypeUploadUrl = isSnapshot ? sonatypeSnapshotUrl : sonatypeReleaseUrl
+
primaryEmail = "functionaljava@googlegroups.com"
- dependencyJunit = "junit:junit:4.12"
+ junitCompile = "junit:junit:4.13.2"
+ junitRuntime = "org.junit.vintage:junit-vintage-engine:5.8.2"
displayCompilerWarnings = true
-
- newJdkEnvVar = "JAVA8_HOME"
- oldJdkEnvVar = "JAVA6_HOME"
- retroLambdaTarget = JavaVersion.VERSION_1_6
+ generateTestReports = false
}
repositories {
- jcenter()
- mavenCentral()
mavenLocal()
+ mavenCentral()
}
version = fjVersion
@@ -97,68 +97,64 @@ subprojects {
buildscript {
repositories {
+ mavenLocal()
mavenCentral()
}
}
- apply plugin: "jacoco"
-
apply from: "$rootDir/lib.gradle"
- apply plugin: "java"
+ apply plugin: "java-library"
apply plugin: "eclipse"
- apply plugin: "com.ofg.uptodate"
repositories {
mavenLocal()
- jcenter()
mavenCentral()
maven {
url sonatypeRepositoryUrl
}
}
- if (displayCompilerWarnings) {
- tasks.withType(JavaCompile) {
+ tasks.withType(JavaCompile) {
+ if (displayCompilerWarnings) {
options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
- }
- }
+ }
+ }
- jacocoTestReport {
- reports {
- html.enabled = true
- xml.enabled = true
- csv.enabled = false
-// html.destination "${buildDir}/jacocoHtml"
+ tasks.withType(Test).configureEach {
+ maxParallelForks = Runtime.runtime.availableProcessors().intdiv(2) ?: 1
+ if (!generateTestReports) {
+ reports.html.required = false
+ reports.junitXml.required = false
}
}
- task coverage(dependsOn: ["test", "jacocoTestReport"]) << {
-
- }
-
}
task coverage(type: org.gradle.testing.jacoco.tasks.JacocoReport) {
- dependsOn = subprojects.test
- sourceDirectories = files(subprojects.sourceSets.main.allSource.srcDirs)
- classDirectories = files(subprojects.sourceSets.main.output)
- executionData = files(subprojects.jacocoTestReport.executionData)
+ dependsOn = subprojects*.test
+ executionData fileTree(project.rootDir.absolutePath).include("**/build/jacoco/*.exec")
+ // We only care about coverage of:
+ def projectForFoverage = ["core", "quickcheck", "java-core"]
+ getClassDirectories().from(files(subprojects.findAll {subproject -> subproject.name in projectForFoverage} .sourceSets.main.output))
+ getSourceDirectories().from(files(subprojects.findAll {subproject -> subproject.name in projectForFoverage} .sourceSets.main.allSource.srcDirs))
+
reports {
- html.enabled = true
- xml.enabled = true
- csv.enabled = false
+ html.required = true
+ xml.required = true
}
}
configure(subprojects.findAll { it.name != "props-core" }) {
- apply plugin: "maven"
+ apply plugin: "maven-publish"
apply plugin: "signing"
- apply plugin: "osgi"
-
+ apply plugin: "biz.aQute.bnd.builder"
sourceCompatibility = "1.8"
+ javadoc {
+ }
+
task javadocJar(type: Jar, dependsOn: "javadoc") {
classifier = 'javadoc'
from "build/docs/javadoc"
@@ -176,16 +172,14 @@ configure(subprojects.findAll { it.name != "props-core" }) {
}
jar {
- version project.fjVersion
- manifest {
- name = 'Functional Java'
- instruction 'Signature-Version', project.fjVersion
- instruction 'Bundle-ActivationPolicy', 'lazy'
- instruction 'Bundle-Vendor', 'functionaljava.org'
- if(project.name != "core") {
- instruction 'Require-Bundle', 'org.functionaljava;bundle-version="'+project.fjBaseVersion+'"'
- }
- }
+ archiveVersion = project.fjVersion
+ bnd (
+ 'Bundle-Name': 'Functional Java',
+ 'Signature-Version': project.fjVersion,
+ 'Bundle-ActivationPolicy': 'lazy',
+ 'Bundle-Vendor': 'functionaljava.org',
+ 'Automatic-Module-Name': "functionaljava${project.name == 'core' ? '' : ".$project.name"}",
+ )
}
eclipse {
@@ -197,7 +191,7 @@ configure(subprojects.findAll { it.name != "props-core" }) {
}
// Output MANIFEST.MF statically so eclipse can see it for plugin development
- task eclipsePluginManifest(dependsOn: jar) << {
+ task eclipsePluginManifest(dependsOn: jar) doLast {
file("META-INF").mkdirs()
jar.manifest.writeTo(file("META-INF/MANIFEST.MF"))
}
@@ -205,26 +199,6 @@ configure(subprojects.findAll { it.name != "props-core" }) {
eclipseProject.dependsOn eclipsePluginManifest
}
-task env << {
+task env doLast {
println System.getenv()
}
-
-task jacocoRootReport(type: org.gradle.testing.jacoco.tasks.JacocoReport) {
- dependsOn = subprojects.jacocoTestReport
- sourceDirectories = files(subprojects.sourceSets.main.allSource.srcDirs)
- classDirectories = files(subprojects.sourceSets.main.output)
- executionData = files(subprojects.jacocoTestReport.executionData)
- reports {
- xml.enabled true
- xml.destination ="${buildDir}/reports/jacoco/test/jacocoTestReport.xml"
- }
- // We could remove the following setOnlyIf line, but then
- // jacocoRootReport would silently be SKIPPED if something with
- // the projectsWithUnitTests is wrong (e.g. a project is missing
- // in there).
- setOnlyIf { true }
-}
-
-coveralls {
- sourceDirs = files(subprojects.sourceSets.main.allSource.srcDirs).files.absolutePath
-}
diff --git a/codecov.yml b/codecov.yml
new file mode 100644
index 00000000..b7ddeec5
--- /dev/null
+++ b/codecov.yml
@@ -0,0 +1,4 @@
+ignore:
+ - "demo/.*"
+ - "consume/.*"
+ - "performance/.*"
diff --git a/consume/build.gradle b/consume/build.gradle
index ff92a601..45dadefb 100644
--- a/consume/build.gradle
+++ b/consume/build.gradle
@@ -2,7 +2,8 @@
archivesBaseName = "${project.projectName}-${project.name}"
dependencies {
- compile("$group:$projectName:$fjConsumeVersion")
+ api "$group:$projectName:$fjConsumeVersion"
- testCompile dependencyJunit
+ testImplementation junitCompile
+ testRuntimeOnly junitRuntime
}
diff --git a/consume/src/test/java/fj/EmptyTest.java b/consume/src/test/java/fj/EmptyTest.java
index e112a97a..13675822 100644
--- a/consume/src/test/java/fj/EmptyTest.java
+++ b/consume/src/test/java/fj/EmptyTest.java
@@ -5,9 +5,6 @@
import org.junit.Assert;
-/**
- * Created by MarkPerry on 30/08/2015.
- */
public class EmptyTest {
@Ignore @Test
diff --git a/core/build.gradle b/core/build.gradle
index 1a1763e7..282ec191 100644
--- a/core/build.gradle
+++ b/core/build.gradle
@@ -1,17 +1,18 @@
ext {
signModule = true
+ uploadModule = true
+
}
archivesBaseName = project.projectName
dependencies {
- testCompile dependencyJunit
+ testImplementation junitCompile
+ testRuntimeOnly junitRuntime
+ testImplementation 'com.h2database:h2:2.1.210'
+ testImplementation 'commons-dbutils:commons-dbutils:1.7'
}
performSigning(signingEnabled, signModule)
-configureUpload(signingEnabled, signModule)
-
-uploadArchives.enabled = true
-
-configureAllRetroLambda()
+configureUpload(signingEnabled, signModule, uploadModule)
diff --git a/core/src/main/java/fj/Bottom.java b/core/src/main/java/fj/Bottom.java
index a9951928..5cc7f4e4 100644
--- a/core/src/main/java/fj/Bottom.java
+++ b/core/src/main/java/fj/Bottom.java
@@ -2,8 +2,6 @@
/**
* Represents the bottom _|_ value.
- *
- * @version %build.number%
*/
public final class Bottom {
private Bottom() {
@@ -72,7 +70,7 @@ public static Error decons(final A a, final Show sa) {
* @param c The type being deconstructed.
* @return A deconstruction failure that was non-exhaustive.
*/
- @SuppressWarnings({"UnnecessaryFullyQualifiedName"})
+ @SuppressWarnings("UnnecessaryFullyQualifiedName")
public static Error decons(final java.lang.Class c) {
return error("Deconstruction failure on type " + c);
}
@@ -83,7 +81,7 @@ public static Error decons(final java.lang.Class c) {
* @return A function that returns the toString for a throwable.
*/
public static F eToString() {
- return t -> t.toString();
+ return Throwable::toString;
}
/**
@@ -92,6 +90,6 @@ public static F eToString() {
* @return A function that returns the getMessage for a throwable.
*/
public static F eMessage() {
- return t -> t.getMessage();
+ return Throwable::getMessage;
}
}
diff --git a/core/src/main/java/fj/Bounded.java b/core/src/main/java/fj/Bounded.java
new file mode 100644
index 00000000..e100b6d6
--- /dev/null
+++ b/core/src/main/java/fj/Bounded.java
@@ -0,0 +1,50 @@
+package fj;
+
+/**
+ * The Bounded class is used to name the upper and lower limits of a type.
+ * Ord is not a superclass of Bounded since types that are not totally ordered may also have upper and lower bounds.
+ */
+public final class Bounded {
+
+ private final Definition def;
+
+ /**
+ * Minimal definition of Bounded
+ */
+ public interface Definition {
+ A min();
+
+ A max();
+ }
+
+ private Bounded(Definition definition) {
+ this.def = definition;
+ }
+
+ public A min() {
+ return def.min();
+ }
+
+ public A max() {
+ return def.max();
+ }
+
+ public static Bounded boundedDef(Definition def) {
+ return new Bounded<>(def);
+ }
+
+ public static Bounded bounded(A min, A max) {
+ return boundedDef(new Definition() {
+ @Override
+ public A min() {
+ return min;
+ }
+
+ @Override
+ public A max() {
+ return max;
+ }
+ });
+ }
+
+}
diff --git a/core/src/main/java/fj/Class.java b/core/src/main/java/fj/Class.java
index 6eac6e39..8bee1951 100644
--- a/core/src/main/java/fj/Class.java
+++ b/core/src/main/java/fj/Class.java
@@ -2,7 +2,6 @@
import fj.data.List;
import static fj.data.List.unfold;
-import fj.data.Option;
import static fj.data.Option.none;
import static fj.data.Option.some;
import fj.data.Tree;
@@ -12,8 +11,6 @@
/**
* A wrapper for a {@link java.lang.Class} that provides additional methods.
- *
- * @version %build.number%
*/
public final class Class {
private final java.lang.Class c;
@@ -39,14 +36,14 @@ public java.lang.Class super T> _1() {
return c2;
}
- @SuppressWarnings({"unchecked"})
+ @SuppressWarnings("unchecked")
public java.lang.Class super T> _2() {
return c2.getSuperclass();
}
};
return some(p);
}
- }, c).map(c1 -> clas(c1));
+ }, c).map(Class::clas);
}
/**
@@ -102,7 +99,7 @@ public static Tree typeParameterTree(final Type t) {
}
types = Tree.node(pt.getRawType(), typeArgs);
} else {
- types = Tree.node(t, List.>nil());
+ types = Tree.node(t, List.nil());
}
return types;
}
@@ -123,6 +120,6 @@ public java.lang.Class clas() {
* @return A class from the given argument.
*/
public static Class clas(final java.lang.Class c) {
- return new Class(c);
+ return new Class<>(c);
}
}
diff --git a/core/src/main/java/fj/Digit.java b/core/src/main/java/fj/Digit.java
index ba3c2245..4fec7b0c 100644
--- a/core/src/main/java/fj/Digit.java
+++ b/core/src/main/java/fj/Digit.java
@@ -6,8 +6,6 @@
/**
* The digits zero to nine.
- *
- * @version %build.number%
*/
public enum Digit {
/**
@@ -176,20 +174,20 @@ public static Option fromChar(final char c) {
/**
* First-class conversion from digit to a long.
*/
- public static final F toLong = d -> d.toLong();
+ public static final F toLong = Digit::toLong;
/**
* First-class conversion from a long to a digit.
*/
- public static final F fromLong = i -> fromLong(i);
+ public static final F fromLong = Digit::fromLong;
/**
* First-class conversion from a digit to a character.
*/
- public static final F toChar = d -> d.toChar();
+ public static final F toChar = Digit::toChar;
/**
* First-class conversion from a character to a digit.
*/
- public static final F> fromChar = c -> fromChar(c);
+ public static final F> fromChar = Digit::fromChar;
}
diff --git a/core/src/main/java/fj/Effect.java b/core/src/main/java/fj/Effect.java
index aec3e442..2b5dcd6f 100644
--- a/core/src/main/java/fj/Effect.java
+++ b/core/src/main/java/fj/Effect.java
@@ -14,10 +14,8 @@
/**
* Represents a side-effect.
- *
- * @version %build.number%
*/
-public class Effect {
+public final class Effect {
private Effect() {}
@@ -33,7 +31,7 @@ public static P1 f(Effect0 e) {
*
* @return The function using the given effect.
*/
- public static final F f(Effect1 e1) {
+ public static F f(Effect1 e1) {
return a -> {
e1.f(a);
return unit();
@@ -95,12 +93,12 @@ public static F5 f(Effect5 z
* @param f The function to map over the effect.
* @return An effect after a contra-variant map.
*/
- public static final Effect1 contramap(Effect1 e1, final F f) {
+ public static Effect1 contramap(Effect1 e1, final F f) {
return b -> e1.f(f.f(b));
}
public static Effect1 lazy(final F f) {
- return a -> f.f(a);
+ return f::f;
}
diff --git a/core/src/main/java/fj/Equal.java b/core/src/main/java/fj/Equal.java
index 60fcb86d..cba4a589 100644
--- a/core/src/main/java/fj/Equal.java
+++ b/core/src/main/java/fj/Equal.java
@@ -1,9 +1,9 @@
package fj;
-import static fj.Function.curry;
-
import fj.data.*;
+import fj.data.hamt.BitSet;
import fj.data.hlist.HList;
+import fj.data.optic.Traversal;
import fj.data.vector.V2;
import fj.data.vector.V3;
import fj.data.vector.V4;
@@ -11,20 +11,83 @@
import fj.data.vector.V6;
import fj.data.vector.V7;
import fj.data.vector.V8;
+import fj.parser.Result;
-import java.math.BigInteger;
import java.math.BigDecimal;
+import java.math.BigInteger;
+
+import static fj.Function.compose;
+import static fj.Function.constant;
+import static fj.Function.curry;
/**
* Tests for equality between two objects.
- *
- * @version %build.number%
*/
public final class Equal {
- private final F> f;
- private Equal(final F> f) {
- this.f = f;
+ /**
+ * Primitives functions of Equal: minimal definition and overridable methods.
+ */
+ public interface Definition {
+
+ F equal(A a);
+
+ default boolean equal(A a1, A a2) {
+ return equal(a1).f(a2);
+ }
+
+ /**
+ * Refine this equal definition, to tests equality of self and the mapped object in "and" manner.
+ * @see #equal()
+ *
+ * @param f The function to map the original object
+ * @param eq Equality for the mapped object
+ * @return A new equal definition
+ */
+ default Definition then(final F f, final Equal eq) {
+ Definition bEqDef = eq.def;
+ return new Definition() {
+ @Override
+ public F equal(A a1) {
+ F fa = Definition.this.equal(a1);
+ F fb = bEqDef.equal(f.f(a1));
+ return a2 -> fa.f(a2) && fb.f(f.f(a2));
+ }
+
+ @Override
+ public boolean equal(A a1, A a2) {
+ return Definition.this.equal(a1, a2) && bEqDef.equal(f.f(a1), f.f(a2));
+ }
+ };
+ }
+
+ /**
+ * Build an equal instance from this definition.
+ * to be called after some successive {@link #then(F, Equal)} calls.
+ */
+ default Equal equal() {
+ return equalDef(this);
+ }
+ }
+
+ /**
+ * Primitives functions of Equal: alternative minimal definition and overridable methods.
+ */
+ public interface AltDefinition extends Definition {
+
+ @Override
+ boolean equal(A a1, A a2);
+
+ @Override
+ default F equal(A a) {
+ return a2 -> equal(a, a2);
+ }
+ }
+
+ private final Definition def;
+
+ private Equal(final Definition def) {
+ this.def = def;
}
/**
@@ -35,7 +98,7 @@ private Equal(final F> f) {
* @return true if the two given arguments are equal, false otherwise.
*/
public boolean eq(final A a1, final A a2) {
- return f.f(a1).f(a2);
+ return def.equal(a1, a2);
}
/**
@@ -46,7 +109,7 @@ public boolean eq(final A a1, final A a2) {
* @return true if the two given arguments are not equal, false otherwise.
*/
public boolean notEq(final A a1, final A a2) {
- return !eq(a1, a2);
+ return !def.equal(a1, a2);
}
/**
@@ -55,7 +118,7 @@ public boolean notEq(final A a1, final A a2) {
* @return A function that returns true if the two given arguments are equal.
*/
public F2 eq() {
- return (a, a1) -> eq(a, a1);
+ return def::equal;
}
/**
@@ -65,7 +128,7 @@ public F2 eq() {
* @return A function that returns true if the given argument equals the argument to this method.
*/
public F eq(final A a) {
- return a1 -> eq(a, a1);
+ return def.equal(a);
}
/**
@@ -75,17 +138,91 @@ public F eq(final A a) {
* @return A new equal.
*/
public Equal contramap(final F f) {
- return equal(F1Functions.o(F1Functions.o(F1Functions.andThen(f), this.f), f));
+ return equalDef(contramapDef(f, def));
+ }
+
+ /**
+ * An equal instance, which reverts equality for self
+ *
+ * @return A new equal instance
+ */
+ public final Equal not() {
+ return equalDef((a1, a2) -> !def.equal(a1, a2));
+ }
+
+
+ private static Definition contramapDef(F f, Definition aEqDef) {
+ return new Definition(){
+ @Override
+ public F equal(B b) {
+ return compose(aEqDef.equal(f.f(b)), f);
+ }
+
+ @Override
+ public boolean equal(B b1, B b2) {
+ return aEqDef.equal(f.f(b1), f.f(b2));
+ }
+ };
+ }
+
+ /**
+ * Static version of {@link #contramap(F)}
+ */
+ public static Equal contramap(final F f, final Equal eq) {
+ return eq.contramap(f);
+ }
+
+ /**
+ * Begin definition of an equal instance.
+ * @see Definition#then(F, Equal)
+ */
+ public static Definition on(final F f, final Equal eq) {
+ return contramapDef(f, eq.def);
}
/**
* Constructs an equal instance from the given function.
*
+ * Java 8+ users: use {@link #equalDef(Definition)} instead.
+ *
* @param f The function to construct the equal with.
* @return An equal instance from the given function.
*/
public static Equal equal(final F> f) {
- return new Equal(f);
+ return new Equal<>(f::f);
+ }
+
+
+ /**
+ * Constructs an equal instance from the given function.
+ *
+ * Java 8+ users: use {@link #equalDef(AltDefinition)} instead.
+ *
+ * @param f The function to construct the equal with.
+ * @return An equal instance from the given function.
+ */
+ public static Equal equal(final F2 f) {
+ return equalDef(f::f);
+ }
+
+ /**
+ * Constructs an equal instance from the given definition.
+ *
+ * @param definition a definition of the equal instance.
+ * @return An equal instance from the given function.
+ */
+ public static Equal equalDef(final Definition definition) {
+ return new Equal<>(definition);
+ }
+
+ /**
+ * Constructs an equal instance from the given (alternative) definition.
+ *
+ * @param definition a definition of the equal instance.
+ * @return An equal instance from the given function.
+ */
+ public static Equal equalDef(final AltDefinition definition) {
+ return new Equal<>(definition);
}
/**
@@ -96,7 +233,17 @@ public static Equal equal(final F> f) {
* equality.
*/
public static Equal anyEqual() {
- return equal(a1 -> a2 -> a1.equals(a2));
+ return equalDef(new Definition() {
+ @Override
+ public F equal(A a) {
+ return a::equals;
+ }
+
+ @Override
+ public boolean equal(A a1, A a2) {
+ return a1.equals(a2);
+ }
+ });
}
/**
@@ -149,6 +296,11 @@ public static Equal anyEqual() {
*/
public static final Equal shortEqual = anyEqual();
+ /**
+ * An equal instance for the Natural type.
+ */
+ public static final Equal naturalEqual = bigintEqual.contramap(Natural::bigIntegerValue);
+
/**
* An equal instance for the {@link String} type.
*/
@@ -158,7 +310,7 @@ public static Equal anyEqual() {
* An equal instance for the {@link StringBuffer} type.
*/
public static final Equal stringBufferEqual =
- equal(sb1 -> sb2 -> {
+ equalDef((sb1, sb2) -> {
if (sb1.length() == sb2.length()) {
for (int i = 0; i < sb1.length(); i++)
if (sb1.charAt(i) != sb2.charAt(i))
@@ -172,7 +324,7 @@ public static Equal anyEqual() {
* An equal instance for the {@link StringBuilder} type.
*/
public static final Equal stringBuilderEqual =
- equal(sb1 -> sb2 -> {
+ equalDef((sb1, sb2) -> {
if (sb1.length() == sb2.length()) {
for (int i = 0; i < sb1.length(); i++)
if (sb1.charAt(i) != sb2.charAt(i))
@@ -182,6 +334,11 @@ public static Equal anyEqual() {
return false;
});
+ /**
+ * An equal instance for the {@link BitSet} type.
+ */
+ public static final Equal bitSetSequal = equalDef((bs1, bs2) -> bs1.longValue() == bs2.longValue());
+
/**
* An equal instance for the {@link Either} type.
*
@@ -190,10 +347,29 @@ public static Equal anyEqual() {
* @return An equal instance for the {@link Either} type.
*/
public static Equal> eitherEqual(final Equal ea, final Equal eb) {
- return equal(e1 -> e2 -> e1.isLeft() && e2.isLeft() && ea.f.f(e1.left().value()).f(e2.left().value()) ||
- e1.isRight() && e2.isRight() && eb.f.f(e1.right().value()).f(e2.right().value()));
+ Definition eaDef = ea.def;
+ Definition ebDef = eb.def;
+ return equalDef(e1 -> e1.either(
+ a1 -> Either.either_(eaDef.equal(a1), (B __) -> false),
+ b1 -> Either.either_((A __)-> false, ebDef.equal(b1))
+ ));
+ }
+
+ public static Equal> either3Equal(Equal ea, Equal eb, Equal ec) {
+ return equalDef((e1, e2) ->
+ optionEqual(ea).eq(e1.leftOption(), e2.leftOption()) &&
+ optionEqual(eb).eq(e1.middleOption(), e2.middleOption()) &&
+ optionEqual(ec).eq(e1.rightOption(), e2.rightOption())
+ );
}
+ public static Equal> resultEqual(final Equal ea, final Equal ei) {
+ Definition eaDef = ea.def;
+ Definition eiDef= ei.def;
+ return equalDef((r1, r2) -> eaDef.equal(r1.value(), r2.value()) && eiDef.equal(r1.rest(), r2.rest()));
+ }
+
+
/**
* An equal instance for the {@link Validation} type.
*
@@ -202,7 +378,7 @@ public static Equal> eitherEqual(final Equal ea, final Eq
* @return An equal instance for the {@link Validation} type.
*/
public static Equal> validationEqual(final Equal ea, final Equal eb) {
- return eitherEqual(ea, eb).contramap(Validation.either());
+ return eitherEqual(ea, eb).contramap(Validation.either());
}
/**
@@ -212,12 +388,13 @@ public static Equal> validationEqual(final Equal ea,
* @return An equal instance for the {@link List} type.
*/
public static Equal> listEqual(final Equal ea) {
- return equal(a1 -> a2 -> {
+ Definition eaDef = ea.def;
+ return equalDef((a1, a2) -> {
List x1 = a1;
List x2 = a2;
while (x1.isNotEmpty() && x2.isNotEmpty()) {
- if (!ea.eq(x1.head(), x2.head()))
+ if (!eaDef.equal(x1.head(), x2.head()))
return false;
x1 = x1.tail();
@@ -235,7 +412,7 @@ public static Equal> listEqual(final Equal ea) {
* @return An equal instance for the {@link NonEmptyList} type.
*/
public static Equal> nonEmptyListEqual(final Equal ea) {
- return listEqual(ea).contramap(NonEmptyList.toList_());
+ return listEqual(ea).contramap(NonEmptyList.toList_());
}
/**
@@ -245,12 +422,15 @@ public static Equal> nonEmptyListEqual(final Equal ea) {
* @return An equal instance for the {@link Option} type.
*/
public static Equal