Forked from chaotic3quilibrium/ DIY Scala Enumeration - README.txt
Last active
August 29, 2015 14:15
-
-
Save techmag/0b75f03a3f35078715b8 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
README.txt - DIY Scala Enumeration | |
Copyright (C) 2014 Jim O'Flaherty | |
Overview: | |
Provide in Scala the closest equivalent to Java Enum | |
- includes decorating each declared Enum member with extended information | |
- guarantees pattern matching exhaustiveness checking | |
- this is not available with scala.Enumeration | |
StackOverflow answer covering entire Scala enumeration options domain: | |
http://stackoverflow.com/a/25923651/501113 | |
Gist Files: | |
Enumeration.scala - Baseline to extend for any implementation | |
ChessPiecesSimplest.scala - Shows the absolute minimum required to generate an implementation of Enumeration | |
WorkSheetChessPiecesSimplestTest.scala - Simple test cases for validating ChessPiecesSimplest | |
ChessPiecesEnhanced.scala - Shows a more elaborate method to extend Enumeration to associate data to a Member | |
WorkSheetChessPiecesEnhancedTest.scala - Simple test cases for validating ChessPiecesEnhanced | |
EnumerationDecorated.scala - Extends Enumeration to enable safely adding a non-Serialized singleton-ness Decoration to each declared member | |
ChessPiecesEnhancedDecorated.scala - Shows how ChessPiecesEnhanced.scala can be simplified by using this Enumeration extension | |
WorkSheetChessPiecesEnhancedDecoratedTest.scala - Simple test cases for validating ChessPiecesEnhancedDecorated | |
Most terse usage (see example files for more expansive details): | |
object ChessPiecesSimplest extends Enumeration { | |
case object KING extends Member | |
case object QUEEN extends Member | |
case object BISHOP extends Member | |
case object KNIGHT extends Member | |
case object ROOK extends Member | |
case object PAWN extends Member | |
protected val orderedSet: List[Member] = List(KING, QUEEN, BISHOP, KNIGHT, ROOK, PAWN) | |
sealed trait Member extends MemberBase | |
override def typeTagMember: TypeTag[_] = typeTag[Member] | |
} | |
-------------------- | |
Details: | |
Below is a shorter version of a more thorough answer I posted on StackOverflow: | |
http://stackoverflow.com/a/25923651/501113 | |
Ever since moving to Scala, I have longed to have the Scala-ized equivalent of Java's Enum. This meant having the following benefits: | |
A) Not requiring a Scala/Java mixed project just to use Java's Enum | |
B) Minimize the amount of overhead/boilerplate required to declare an Enumeration | |
C) Minimize the amount of overhead/boilerplate required to associate extended attributes to an Enumeration | |
D) Leverage the more sophisticated Scala compiler's ability to perform pattern matching exhaustiveness checking | |
E) If there was any requirement to "duplicate" the enumeration members, runtime catch any discrepancies | |
F) Deal with the many non-obvious JVM class/object initialization issues which have undermined other elegant solutions | |
After looking at scala.Enumeration and discovering it did not work for D, I searched for solutions that might optimally work. I came across the following StackOverflow solution: | |
http://stackoverflow.com/a/8620085/501113 | |
The comments on this solution end up chasing down some weird JVM class/object initialization issues which are detailed here: | |
http://stackoverflow.com/questions/14947179/using-a-custom-enum-in-the-scala-worksheet-i-am-receiving-an-error-java-lang-ex | |
This solution pathway didn't work out (see final comment on original post). It just wasn't reasonable to require a client access "the correct case object" to guarantee proper Enumeration initialization. And without that, the same case object might received a different ordinal on any future run. | |
Pause two years. I need to have a reasonable implementation of the above benefits. I searched hoping something had turned up. I became quite excited when I came across Viktor Klang's (attempted) solution to the problem: | |
https://gist.github.com/viktorklang/1057513 | |
Well, after spending considerable time trying to get Viktor's solution to work (in 2.11.2) with pattern matching exhaustiveness checking, I finally punted and decided to tackle this problem again. And close to 50 hours and a bazillion tangents later, this solution emerged. And it now offers all of the above benefits. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//ChessPiecesEnhanced.scala | |
// - Example usage of org.public_domain.scala.util.Enumeration | |
//Copyright (C) 2014 Jim O'Flaherty | |
// | |
// | |
//This is free and unencumbered software released into the public domain. | |
// | |
//Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, | |
//either in source code form or as a compiled binary, for any purpose, commercial or | |
//non-commercial, and by any means. | |
// | |
//In jurisdictions that recognize copyright laws, the author or authors of this software | |
//dedicate any and all copyright interest in the software to the public domain. We make this | |
//dedication for the benefit of the public at large and to the detriment of our heirs and | |
//successors. We intend this dedication to be an overt act of relinquishment in perpetuity | |
//of all present and future rights to this software under copyright law. | |
// | |
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, | |
//INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR | |
//PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES | |
//OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT | |
//OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
// | |
//For more information, please refer to <http://unlicense.org> | |
// | |
//If you would like to obtain a custom/different/commercial license for this, please send an | |
//email with your request to <jim.oflaherty.jr@gmail.com>. | |
package org.public_domain.chess | |
import scala.reflect.runtime.universe.{TypeTag,typeTag} | |
import org.public_domain.scala.utils.Enumeration | |
object ChessPiecesEnhanced extends Enumeration { | |
//A. Enumerated values | |
case object KING extends Member | |
case object QUEEN extends Member | |
case object BISHOP extends Member | |
case object KNIGHT extends Member | |
case object ROOK extends Member | |
case object PAWN extends Member | |
//B. from Enumeration [item B], required type extension | |
sealed trait Member extends MemberBase { | |
//due to the unpredictable nature of object initialization, avoid | |
//using val anywhere within this trait (use only def and lazy val) | |
//if val must be used, it must not depend up on any aspect of the initialization of | |
// this Member instance, the enclosing base Enumeration object or it's derivation (within which | |
// this definition sits, which in this example is ChessPiecesEnhanced) | |
//B.1 Adding extended information via a def to avoid serialization overhead | |
def attributes: Attributes = attributesByMember(this) | |
//change to lazy val only if serialization isn't important and you *know* Serialization will | |
// never be important (which is a very difficult assertion to make for the entire future use | |
// of this) | |
} | |
//C. from Enumeration [item L] | |
override def typeTagMember: TypeTag[_] = typeTag[Member] | |
//D. Safe place to extend any/all needed data for a specific Member; i.e use all the vals you like | |
sealed case class Attributes private[ChessPiecesEnhanced] (member: Member, char: Char, pointValue: Int) { | |
val description: String = member.name.toLowerCase.capitalize | |
} | |
//E. Defining the associated data | |
val attributesList: List[Attributes] = | |
List( | |
Attributes(KING, 'K', 0) | |
, Attributes(QUEEN, 'Q', 9) | |
, Attributes(BISHOP, 'B', 3) | |
, Attributes(KNIGHT, 'N', 3) | |
, Attributes(ROOK, 'R', 5) | |
, Attributes(PAWN, 'P', 1) | |
) | |
//F. Generate the association between the specific Attributes instance and its member | |
// This is used by the Member trait [item B] above | |
val attributesByMember: Map[Member, Attributes] = | |
attributesList.map(attributes => (attributes.member, attributes)).toMap | |
//G. From Enumeration [item M], required ordering of case objects | |
// it's pushed way down here to enable an interation of attributeList to avoid having to | |
// specify the members a third time | |
protected val orderedSet: List[Member] = attributesList.map(_.member) | |
//because object/class initialization ordering is not predictable | |
//if client happens to access ChessPiecesSimplest.ROOK first, the initialization order is... | |
//object ChessPiecesSimplest, complete ROOK and then initialize KING, QUEEN, BISHOP, KNIGHT PAWN | |
//because ChessPieces.getClass.getDeclaredClasses order of returned classes is not predictable | |
//H. from Enumeration [item N], optional ordinal assignment | |
override def ordinalStart = 5 | |
override def ordinalStep = 5 | |
} | |
// | |
//I. Simple test for exhaustive pattern matching | |
// - just comment out any of the case entries | |
object ChessPiecesEnhancedTestExhaustivePatternMatch { | |
def stringMe(member: ChessPiecesEnhanced.Member): String = | |
member match { | |
case ChessPiecesEnhanced.KING => "Of the World" | |
case ChessPiecesEnhanced.QUEEN => "Of the World" | |
case ChessPiecesEnhanced.BISHOP => "Of the Church" | |
case ChessPiecesEnhanced.KNIGHT => "Of the Church" | |
case ChessPiecesEnhanced.ROOK => "Of the Castle" | |
case ChessPiecesEnhanced.PAWN => "Of the Field" | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//ChessPiecesEnhancedDecorated.scala | |
// - Example usage of org.public_domain.scala.util.Enumeration | |
//Copyright (C) 2014 Jim O'Flaherty | |
// | |
// | |
//This is free and unencumbered software released into the public domain. | |
// | |
//Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, | |
//either in source code form or as a compiled binary, for any purpose, commercial or | |
//non-commercial, and by any means. | |
// | |
//In jurisdictions that recognize copyright laws, the author or authors of this software | |
//dedicate any and all copyright interest in the software to the public domain. We make this | |
//dedication for the benefit of the public at large and to the detriment of our heirs and | |
//successors. We intend this dedication to be an overt act of relinquishment in perpetuity | |
//of all present and future rights to this software under copyright law. | |
// | |
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, | |
//INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR | |
//PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES | |
//OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT | |
//OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
// | |
//For more information, please refer to <http://unlicense.org> | |
// | |
//If you would like to obtain a custom/different/commercial license for this, please send an | |
//email with your request to <jim.oflaherty.jr@gmail.com>. | |
package org.public_domain.chess | |
import scala.reflect.runtime.universe.{TypeTag,typeTag} | |
import org.public_domain.scala.utils.EnumerationDecorated | |
object ChessPiecesEnhancedDecorated extends EnumerationDecorated { | |
//A. Enumerated values | |
case object KING extends Member | |
case object QUEEN extends Member | |
case object BISHOP extends Member | |
case object KNIGHT extends Member | |
case object ROOK extends Member | |
case object PAWN extends Member | |
//B. Defining the associated data | |
val decorationOrderedSet: List[Decoration] = | |
List( | |
Decoration(KING, 'K', 0) | |
, Decoration(QUEEN, 'Q', 9) | |
, Decoration(BISHOP, 'B', 3) | |
, Decoration(KNIGHT, 'N', 3) | |
, Decoration(ROOK, 'R', 5) | |
, Decoration(PAWN, 'P', 1) | |
) | |
//C. Safe place to extend any/all needed data for a specific Member; i.e use all the vals you like | |
final case class Decoration private[ChessPiecesEnhancedDecorated] (member: Member, char: Char, pointValue: Int) extends DecorationBase { | |
val description: String = member.name.toLowerCase.capitalize | |
} | |
//D. from Enumeration [item L] | |
override def typeTagMember: TypeTag[_] = typeTag[Member] | |
//E. from EnumerationDecoration [item C], required type extension | |
sealed trait Member extends MemberDecorated | |
} | |
// | |
//F. Simple test for exhaustive pattern matching | |
// - just comment out any of the case entries | |
object ChessPiecesEnhancedDecoratedTestExhaustivePatternMatch { | |
def stringMe(member: ChessPiecesEnhancedDecorated.Member): String = | |
member match { | |
case ChessPiecesEnhancedDecorated.KING => "Of the World" | |
case ChessPiecesEnhancedDecorated.QUEEN => "Of the World" | |
case ChessPiecesEnhancedDecorated.BISHOP => "Of the Church" | |
case ChessPiecesEnhancedDecorated.KNIGHT => "Of the Church" | |
case ChessPiecesEnhancedDecorated.ROOK => "Of the Castle" | |
case ChessPiecesEnhancedDecorated.PAWN => "Of the Field" | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//ChessPiecesSimplest.scala | |
// - Example usage of org.public_domain.scala.util.Enumeration | |
//Copyright (C) 2014 Jim O'Flaherty | |
// | |
// | |
//This is free and unencumbered software released into the public domain. | |
// | |
//Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, | |
//either in source code form or as a compiled binary, for any purpose, commercial or | |
//non-commercial, and by any means. | |
// | |
//In jurisdictions that recognize copyright laws, the author or authors of this software | |
//dedicate any and all copyright interest in the software to the public domain. We make this | |
//dedication for the benefit of the public at large and to the detriment of our heirs and | |
//successors. We intend this dedication to be an overt act of relinquishment in perpetuity | |
//of all present and future rights to this software under copyright law. | |
// | |
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, | |
//INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR | |
//PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES | |
//OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT | |
//OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
// | |
//For more information, please refer to <http://unlicense.org> | |
// | |
//If you would like to obtain a custom/different/commercial license for this, please send an | |
//email with your request to <jim.oflaherty.jr@gmail.com>. | |
package org.public_domain.chess | |
import scala.reflect.runtime.universe.{TypeTag,typeTag} | |
import org.public_domain.scala.utils.Enumeration | |
object ChessPiecesSimplest extends Enumeration { | |
//A. Enumerated values | |
case object KING extends Member | |
case object QUEEN extends Member | |
case object BISHOP extends Member | |
case object KNIGHT extends Member | |
case object ROOK extends Member | |
case object PAWN extends Member | |
//case object PAWN2 extends Member | |
//B. from Enumeration [item M], required ordering of case objects | |
protected val orderedSet: List[Member] = List(KING, QUEEN, BISHOP, KNIGHT, ROOK, PAWN) | |
//required because object/class initialization ordering is not predictable | |
//if client happens to access ChessPiecesSimplest.ROOK first, the resulting object | |
// initialization order is: | |
//ROOK (to add), ChessPiecesSimplest, ROOK (after add), KING, QUEEN, BISHOP, KNIGHT PAWN | |
//C. from Enumeration [item B], required type extension | |
sealed trait Member extends MemberBase | |
//D. from Enumeration [item L] | |
override def typeTagMember: TypeTag[_] = typeTag[Member] | |
} | |
// | |
//E. Simple test for exhaustive pattern matching | |
// - just comment out any of the case entries | |
object ChessPiecesSimplestTestExhaustivePatternMatch { | |
def stringMe(chessPiece: ChessPiecesSimplest.Member): String = | |
chessPiece match { | |
case ChessPiecesSimplest.KING => "Of the World" | |
case ChessPiecesSimplest.QUEEN => "Of the World" | |
case ChessPiecesSimplest.BISHOP => "Of the Church" | |
case ChessPiecesSimplest.KNIGHT => "Of the Church" | |
case ChessPiecesSimplest.ROOK => "Of the Castle" | |
case ChessPiecesSimplest.PAWN => "Of the Field" | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//Enumeration.scala | |
// - DIY Scala Enumeration (closest possible Java Enum equivalent with guaranteed pattern | |
// matching exhaustiveness checking) | |
//Copyright (C) 2014 Jim O'Flaherty | |
// | |
// | |
//This Scala class is free software: you can redistribute it and/or modify it under the terms | |
//of the GNU General Public License as published by the Free Software Foundation, either | |
//version 3 of the License, or any later version. | |
// | |
//This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; | |
//without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | |
//See the GNU General Public License for more details. | |
// | |
//To see details of the GPLv3 License, please see <http://www.gnu.org/copyleft/gpl.html>. | |
//To see details of the GNU General Public License, please see <http://www.gnu.org/licenses/>. | |
// | |
//If you would like to obtain a custom/different/commercial license for this, please send an | |
//email with your request to <jim.oflaherty.jr@gmail.com>. | |
package org.public_domain.scala.utils | |
import scala.reflect.runtime.universe.{Symbol,TypeTag} | |
import scala.util.{Failure, Success, Try} | |
//import org.public_domain.scala.utils.Try_.{CompletedNoException,completedNoExceptionSingleton} | |
trait Enumeration { | |
//A. Placeholder Success type+singleton for Try | |
// - manually inlined from org.public_domain.scala.utils.Try_ | |
sealed case class CompletedNoException() | |
final val completedNoExceptionSingleton = new CompletedNoException() | |
//B. Type Member which must be found and _sealed_ in the descendant object | |
// - see protected trait MemberBase [item O] | |
type Member <: MemberBase | |
//C. Collects all of the sealed class descendants of Member | |
// - depends upon protected (abstract) def typeTagMember [item L] being implemented in client descendant | |
// - making client implement is ugly | |
// - working to replace with experimental change to use getClass entry point for Member | |
private lazy val memberSealedTraitDescendantNames: Try[Set[String]] = { | |
def fetchMemberSealedTraitDescendantNames: Try[Set[String]] = { | |
def fetchSealedTraitDescendantsViaTraitTypeTag: Option[Set[Symbol]] = { | |
def fetchSealedTraitDescendantsViaTraitSymbol(symbol: Symbol): Option[Set[Symbol]] = { | |
val internal = symbol.asInstanceOf[scala.reflect.internal.Symbols#Symbol] | |
if (internal.isSealed) | |
Some(internal.sealedDescendants.map(_.asInstanceOf[Symbol]) - symbol) | |
else None | |
} | |
fetchSealedTraitDescendantsViaTraitSymbol(typeTagMember.tpe.typeSymbol) | |
} | |
fetchSealedTraitDescendantsViaTraitTypeTag match { | |
case Some(symbols) => Success(symbols.map(_.toString.stripPrefix("object "))) | |
case None => Failure(new IllegalStateException("reflection failed to obtain descendants of sealed trait Member")) | |
} | |
} | |
fetchMemberSealedTraitDescendantNames | |
} | |
//D. Adds a member to temporary internal storage | |
// - for errors, see val status in protected trait MemberBase [item O.D] | |
// - Does NOT generate the ordinal (due to JVM class initialization order - see val orderedSet below for all reasons) | |
@volatile | |
private var addFailure: Option[Throwable] = None | |
@volatile | |
private var isAddClosed: Boolean = false | |
@volatile | |
private var membersTempInternal: List[Member] = Nil | |
@volatile | |
private var memberNamesLowerCaseAsSetInternal: Set[String] = Set() | |
final private def add(member: Member): Try[CompletedNoException] = { | |
def postAddFailure(throwable: Throwable): Try[CompletedNoException] = { | |
val failure = Failure(throwable) | |
addFailure = Some(throwable) | |
failure | |
} | |
this.synchronized { | |
addFailure match { | |
case Some(throwable) => Failure(throwable) | |
case None => | |
if (!isAddClosed) | |
memberSealedTraitDescendantNames.flatMap(memberSealedTraitDescendantNamesFm => { | |
val memberSealedTraitDescendantNamesFmLowerCase = memberSealedTraitDescendantNamesFm.map(_.toLowerCase) | |
if (getClass.getName.toLowerCase == member.nameSpace.toLowerCase) | |
if (!memberNamesLowerCaseAsSetInternal.contains(member.name.toLowerCase)) { | |
membersTempInternal = member :: membersTempInternal | |
memberNamesLowerCaseAsSetInternal = memberNamesLowerCaseAsSetInternal + member.name.toLowerCase | |
isAddClosed = memberNamesLowerCaseAsSetInternal == memberSealedTraitDescendantNamesFmLowerCase | |
if (isAddClosed) | |
if (orderedSet.nonEmpty) { | |
val orderedSetAsRealSet = orderedSet.toSet | |
if (orderedSet.size == orderedSetAsRealSet.size) { | |
val orderedSetNamesLowerCase = orderedSetAsRealSet.map(_.name.toLowerCase).toSet | |
if (orderedSetNamesLowerCase == memberSealedTraitDescendantNamesFmLowerCase) | |
//D.9 Info: Everything checks out and we're done; | |
// - all case object initializations equal the reflection discovered sealed trait | |
// descendant names equals the client provided orderedSet | |
Success(completedNoExceptionSingleton) | |
else { | |
//D.8 Paranoimia: Catch possible weird initialization effects | |
// - hard to imagine how this could occur | |
val inOnlyOrderedSet = | |
orderedSetNamesLowerCase.--(memberSealedTraitDescendantNamesFmLowerCase) | |
val inOnlyMemberSealedTraitDescendantNamesFm = | |
memberSealedTraitDescendantNamesFmLowerCase.--(orderedSetNamesLowerCase) | |
postAddFailure( | |
new IllegalStateException( | |
s"orderedSetNamesLowerCase [${orderedSetNamesLowerCase.mkString(",")}] is not equal to " | |
+ s"memberSealedTraitDescendantNamesFmLowerCase" | |
+ s" [${memberSealedTraitDescendantNamesFmLowerCase.mkString(",")}]" | |
+ (if (inOnlyOrderedSet.nonEmpty) | |
s" - orderedSetNamesLowerCase contains values [${inOnlyOrderedSet.mkString(",")}]" | |
+ s" not in memberSealedTraitDescendantNamesFmLowerCase" | |
else "" | |
) | |
+ (if (inOnlyMemberSealedTraitDescendantNamesFm.nonEmpty) | |
s" - memberSealedTraitDescendantNamesFmLowerCase contains values" | |
+ s" [${inOnlyMemberSealedTraitDescendantNamesFm.mkString(",")}] not in" | |
+ s" orderedSetNamesLowerCase" | |
else "" | |
) | |
) | |
) | |
} | |
} | |
else | |
//D.7 Guard: Client attempted to provide a non-unique List (there is no insertion order Set | |
// implementation in the Scala collections library) | |
postAddFailure( | |
new IllegalStateException( | |
s"orderedSet.size [${orderedSet.size}] must be equal to orderedSetAsRealSet.size" | |
+ s" [${orderedSetAsRealSet.size}] (isAddClosed is true)" | |
) | |
) | |
} | |
else | |
//D.6 Guard: Client attempted to provide an empty set | |
postAddFailure(new IllegalStateException("orderedSet must not be empty (isAddClosed is true)")) | |
else | |
if (memberNamesLowerCaseAsSetInternal.size != memberSealedTraitDescendantNamesFmLowerCase.size) | |
//D.5 Info: Initializations remain | |
Success(completedNoExceptionSingleton) | |
else { | |
//D.4 Paranoimia: Catch possible weird initialization effects | |
// - hard to imagine this could occur if the compiler is ensuring the sealed trait | |
// contract | |
val inOnlyMemberNamesLowerCaseAsSetInternal = | |
memberNamesLowerCaseAsSetInternal.--(memberSealedTraitDescendantNamesFmLowerCase) | |
val inOnlyMemberSealedTraitDescendantNamesFm = | |
memberSealedTraitDescendantNamesFmLowerCase.--(memberNamesLowerCaseAsSetInternal) | |
postAddFailure( | |
new IllegalStateException( | |
s"while being the same size [${memberNamesLowerCaseAsSetInternal.size}], both" | |
+ s" memberNamesLowerCaseAsSetInternal" | |
+ s" [${memberNamesLowerCaseAsSetInternal.mkString(",")}] and" | |
+ s" memberSealedTraitDescendantNamesFmLowerCase" | |
+ s" [${memberSealedTraitDescendantNamesFmLowerCase.mkString(",")}] are not equal (not sure how" | |
+ s" this is even possible)" | |
+ (if (inOnlyMemberNamesLowerCaseAsSetInternal.nonEmpty) | |
s" - memberNamesLowerCaseAsSetInternal contains values" | |
+ s" [${inOnlyMemberNamesLowerCaseAsSetInternal.mkString(",")}] not in" | |
+ s" memberSealedTraitDescendantNamesFmLowerCase" | |
else "" | |
) | |
+ (if (inOnlyMemberSealedTraitDescendantNamesFm.nonEmpty) | |
s" - memberSealedTraitDescendantNamesFmLowerCase contains values" | |
+ s" [${inOnlyMemberSealedTraitDescendantNamesFm.mkString(",")}] not in" | |
+ s" memberNamesLowerCaseAsSetInternal" | |
else "" | |
) | |
) | |
) | |
} | |
} | |
else | |
//D.3 Guard: Disallow case name sensitivity | |
// - KING and King are considered equivalent and whichever initializes second will be rejected | |
postAddFailure( | |
new IllegalArgumentException( | |
s"attempting to add member with name [${member.name}] which was previously " | |
+ s"(case insensitively) added [${member.name.toLowerCase}]" | |
) | |
) | |
else | |
//D.2 Watchful: Require all instances must come from the parent name space | |
postAddFailure( | |
new IllegalArgumentException( | |
s"member [${member.name}] (with nameSpace [${member.nameSpace}]) must be declared" | |
+ s"inside of the derived Enumeration's nameSpace [${getClass.getName}]" | |
) | |
) | |
} | |
) | |
else | |
//D.1 Paranoimia: Catch possible weird multi-threading conflicts | |
// - hard to imagine this could occur given how strongly defined JVM class | |
// initialization is defined | |
postAddFailure( | |
new IllegalStateException(s"attempting to add member [${member.name}] after isAddClosed was set true") | |
) | |
} | |
} | |
} | |
//E. Generates the ordinals for the members (only if all classes/objects initialized correctly) | |
// - uses lazy val to postpone defining/assigning ordinal until after all case object | |
// initializations complete i.e. just prior to completing client's first call | |
// - returns Try to facilitate external capturing/logging/reporting Failure (in def status below) | |
private lazy val membersInternal: Try[List[Member]] = { | |
this.synchronized { | |
addFailure match { | |
case Some(throwable) => Failure(throwable) | |
case None => | |
if (isAddClosed) | |
Success(orderedSet) | |
else | |
if (orderedSet.nonEmpty) { | |
val orderedSetAsRealSet = orderedSet.toSet | |
if (orderedSet.size == orderedSetAsRealSet.size) { | |
val orderedSetNamesLowerCase = orderedSetAsRealSet.map(_.name.toLowerCase).toSet | |
memberSealedTraitDescendantNames.flatMap(memberSealedTraitDescendantNamesFm => { | |
val memberSealedTraitDescendantNamesFmLowerCase = memberSealedTraitDescendantNamesFm.map(_.toLowerCase) | |
val inOnlyOrderedSet = orderedSetNamesLowerCase.--(memberSealedTraitDescendantNamesFmLowerCase) | |
val inOnlyMemberSealedTraitDescendantNamesFm = | |
memberSealedTraitDescendantNamesFmLowerCase.--(orderedSetNamesLowerCase) | |
Failure( | |
new IllegalStateException( | |
s"orderedSetNamesLowerCase [${orderedSetNamesLowerCase.mkString(",")}] is not equal to " | |
+ s"memberSealedTraitDescendantNamesFmLowerCase" | |
+ s"[${memberSealedTraitDescendantNamesFmLowerCase.mkString(",")}]" | |
+ (if (inOnlyOrderedSet.nonEmpty) | |
s" - orderedSetNamesLowerCase contains values [${inOnlyOrderedSet.mkString(",")}] not in" | |
+ s" memberSealedTraitDescendantNamesFmLowerCase" | |
else "" | |
) | |
+ (if (inOnlyMemberSealedTraitDescendantNamesFm.nonEmpty) | |
s" - memberSealedTraitDescendantNamesFmLowerCase contains values" | |
+ s" [${inOnlyMemberSealedTraitDescendantNamesFm.mkString(",")}] not in orderedSetNamesLowerCase" | |
else "" | |
) | |
) | |
) | |
}) | |
} | |
else | |
Failure( | |
new IllegalStateException( | |
s"orderedSet.size [${orderedSet.size}] must be equal to orderedSetAsRealSet.size" | |
+ s" [${orderedSetAsRealSet.size}] (isAddClosed is true)" | |
) | |
) | |
} | |
else | |
Failure(new IllegalStateException("orderedSet must not be empty")) | |
} | |
} | |
} | |
//F. Provide range covering all ordinals | |
final lazy val ordinalRange: Range = | |
ordinalRangeLift match { | |
case Success(range) => range | |
case Failure(exception) => throw exception | |
} | |
final lazy val ordinalRangeLift: Try[Range] = | |
if (ordinalStep != 0) | |
membersLift.flatMap(membersFm => | |
Success(ordinalStart.until(ordinalStart + (membersFm.size * ordinalStep), ordinalStep)) | |
) | |
else | |
Failure(new IllegalStateException(s"ordinalStep must not be equal to 0")) | |
//G. Enable lookup for ordinal by Member (used by Member.ordinal) | |
final lazy val ordinalByMember: Map[Member, Int] = | |
ordinalByMemberLift match { | |
case Success(map) => map | |
case Failure(exception) => throw exception | |
} | |
final lazy val ordinalByMemberLift: Try[Map[Member, Int]] = | |
membersInternal.flatMap( | |
membersInternalFm => { | |
ordinalRangeLift.flatMap( | |
ordinalRangeFm => | |
Success(membersInternalFm.zip(ordinalRangeFm).toMap) | |
) | |
} | |
) | |
//H. Enable lookup for Member by ordinal | |
final lazy val memberByOrdinal: Map[Int, Member] = | |
memberByOrdinalLift match { | |
case Success(map) => map | |
case Failure(exception) => throw exception | |
} | |
final lazy val memberByOrdinalLift: Try[Map[Int, Member]] = | |
ordinalByMemberLift.flatMap( | |
ordinalByMemberFm => { | |
val ordinalAndMemberPairs = | |
ordinalByMemberFm.map( | |
memberAndOrdinalPair => | |
(memberAndOrdinalPair._2, memberAndOrdinalPair._1) | |
) | |
Success(ordinalAndMemberPairs.toMap) | |
} | |
) | |
//I. Enable lookup for Member by name | |
private lazy val memberNames: String = | |
membersLift match { | |
case Success(membersLiftGet) => membersLiftGet.map(_.name).mkString(",") | |
case Failure(exception) => exception.getMessage | |
} | |
final def memberByName(name: String): Member = | |
memberByNameLift(name) match { | |
case Success(member) => member | |
case Failure(exception) => throw exception | |
} | |
final def memberByNameLift(name: String): Try[Member] = | |
memberByNameUpperCaseLift.flatMap( | |
map => | |
map.get(name.toUpperCase) match { | |
case Some(member) => | |
Success(member) | |
case None => | |
Failure(new IllegalArgumentException(s"name [$name.toUpperCase] not found in member names [$memberNames]")) | |
} | |
) | |
final lazy val memberByNameUpperCase: Map[String, Member] = | |
memberByNameUpperCaseLift match { | |
case Success(map) => map | |
case Failure(exception) => throw exception | |
} | |
final lazy val memberByNameUpperCaseLift: Try[Map[String, Member]] = | |
membersInternal.flatMap( | |
membersInternalFm => { | |
val nameUpperCaseAndMemberPairs = | |
membersInternalFm.map( | |
member => | |
(member.name.toUpperCase, member) | |
) | |
Success(nameUpperCaseAndMemberPairs.toMap) | |
} | |
) | |
//J. orderedSet of Members in their ordinal order | |
// - assuming stable class object initializations and validation, it is a reference to | |
// exactly the same data provided by the client in orderedSet) | |
final def members: List[Member] = | |
membersLift match { | |
case Success(list) => list | |
case Failure(exception) => throw exception | |
} | |
final def membersLift: Try[List[Member]] = membersInternal | |
//K. Provide Try status of initialization/validation | |
final lazy val status: Try[CompletedNoException] = | |
membersLift.flatMap(_ => Success(completedNoExceptionSingleton)) | |
//L. Client provided reflection hook | |
// - ugly implementation requirement until I can figure out the route using Member.getClass | |
protected def typeTagMember: TypeTag[_] | |
//M. Required client provided ordering | |
// - orderedSet.size must be equal to orderedSet.toSet.size | |
// - client must declare as val to force immediate evaluation (or declare as lazy val and | |
// have another declared val depend upon on it similar to EnumerationDecorated's | |
// implementation) | |
// - required because object/class initialization ordering is not predictable | |
// - if client happens to access any explicit case object member (other than the first), | |
// the case object initialization order is undefined | |
// - required because the order of the returned classes from getClass.getDeclaredClasses | |
// is undefined | |
protected def orderedSet: List[Member] | |
//N. Optional client override for starting ordinal and step | |
protected def ordinalStart = 0 | |
protected def ordinalStep = 1 //requirement: cannot equal 0 | |
//O. Required client implementation to provide basis for unique Enumeration members | |
protected trait MemberBase { | |
self: Member => | |
//O.A Overridden for serialization's readResolve to ensure singleton-ness | |
// by fetching existing instance via case insensitive name | |
private def readResolve(): Object = | |
memberByName(name) | |
//O.B Automatic naming based on getClass | |
// - all case objects must be in the same containing name space | |
// - name is case insensitive, IOW adding two different members where they only differ in | |
// letter casing is restricted; i.e. attempting to declare KING and King will cause | |
// an initialization exception | |
final def nameSpace: String = | |
getClass.getName.stripSuffix(getClass.getSimpleName) | |
final val name: String = | |
getClass.getSimpleName.stripSuffix("$") | |
//O.C The assigned ordinal based on Enumeration.order above | |
final lazy val ordinal: Int = | |
ordinalLift match { | |
case Success(int) => int | |
case Failure(exception) => throw exception | |
} | |
final lazy val ordinalLift: Try[Int] = | |
Enumeration.this.status match { | |
case Success(_) => | |
ordinalByMemberLift match { | |
case Success(map) => Success(map(this)) | |
case Failure(exception) => Failure(exception) | |
} | |
case Failure(exception) => | |
Failure( | |
new IllegalStateException(s"access denied - Enumeration.status is a Failure - ${exception.getMessage}") | |
) | |
} | |
//O.D Provide Try status of specific Member initialization/validation | |
final val status: Try[CompletedNoException] = add(this) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//EnumerationDecorated.scala | |
// - DIY Scala Enumeration (closest possible Java Enum equivalent with guaranteed pattern | |
// matching exhaustiveness checking) with extended attributes within a decoration | |
//Copyright (C) 2014 Jim O'Flaherty | |
// | |
// | |
//This Scala class is free software: you can redistribute it and/or modify it under the terms | |
//of the GNU General Public License as published by the Free Software Foundation, either | |
//version 3 of the License, or any later version. | |
// | |
//This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; | |
//without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | |
//See the GNU General Public License for more details. | |
// | |
//To see details of the GPLv3 License, please see <http://www.gnu.org/copyleft/gpl.html>. | |
//To see details of the GNU General Public License, please see <http://www.gnu.org/licenses/>. | |
// | |
//If you would like to obtain a custom/different/commercial license for this, please send an | |
//email with your request to <jim.oflaherty.jr@gmail.com>. | |
package org.public_domain.scala.utils | |
trait EnumerationDecorated extends Enumeration { | |
//A. Type Member which must be found in the descendant object | |
// - see protected trait DecorationBase [item B] | |
type Decoration <: DecorationBase | |
//B. From Enumeration [item M], required ordering of case objects | |
// it's pushed way down here to enable an iteration of decorationOrderedSet to avoid | |
// having to specify the members a third time | |
protected lazy val orderedSet: List[Member] = decorationOrderedSet.map(_.member) | |
//C. from Enumeration [item B], required type extension which must be overridden | |
// and _sealed_ by client | |
trait MemberDecorated extends MemberBase { | |
self: Member => | |
def decoration: Decoration = decorationByMember(this) | |
} | |
//D. Defining the Decoration data | |
// - intended to be the only other time the Members must be mapped out right | |
// along with their specific decorative additions | |
val decorationOrderedSet: List[Decoration] | |
//E. Enable Decoration lookup by Member | |
lazy val decorationByMember: Map[Member, Decoration] = | |
decorationOrderedSet.map(decoration => (decoration.member, decoration)).toMap | |
//F. Required client implementation to provide basis for Decorations | |
trait DecorationBase { | |
self: Decoration => | |
def member: Member | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package org.public_domain.chess | |
object WorkSheetChessPiecesEnhancedDecoratedTest { | |
println("Welcome to the Scala worksheet") | |
// | |
import scala.util.{Failure, Success} | |
// | |
import org.public_domain.chess.{ChessPiecesEnhancedDecorated=>ChessPieces} | |
// | |
def stringMe(chessPiece: ChessPieces.Member): String = | |
chessPiece match { | |
case ChessPieces.KING => "Of the World" | |
case ChessPieces.QUEEN => "Of the World" | |
case ChessPieces.BISHOP => "Of the Church" | |
case ChessPieces.KNIGHT => "Of the Church" | |
case ChessPieces.ROOK => "Of the Castle" | |
case ChessPieces.PAWN => "Of the Field" | |
} | |
//to test various initialization ordering issues, rearrange these six lines into any order | |
val rook = ChessPieces.ROOK | |
val king = ChessPieces.KING | |
val pawn = ChessPieces.PAWN | |
val queen = ChessPieces.QUEEN | |
val bishop = ChessPieces.BISHOP | |
val knight = ChessPieces.KNIGHT | |
// | |
val statusChessPieces = | |
ChessPieces.status | |
val isStatusChessPiecesSuccess = | |
statusChessPieces.isSuccess | |
val statusChessPiecesMemberFailures = | |
ChessPieces.members.map(_.status).filter(_.isFailure) | |
val isStatusChessPiecesMembersSuccess = | |
statusChessPiecesMemberFailures.isEmpty | |
val isOrderedCorrectly = | |
ChessPieces.members == List(ChessPieces.KING, ChessPieces.QUEEN, ChessPieces.BISHOP, ChessPieces.KNIGHT, ChessPieces.ROOK, ChessPieces.PAWN) | |
val ordinalRange = | |
ChessPieces.ordinalRange | |
val ordinalNamePairs = | |
ChessPieces.members.map(member => (member.ordinal, member.name)) | |
// | |
val bishopByName = ChessPieces.memberByName("bIsHop") | |
// | |
val decorationBishop = ChessPieces.decorationByMember(ChessPieces.BISHOP) | |
val decorations = ChessPieces.decorationOrderedSet | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package org.public_domain.chess | |
object WorkSheetChessPiecesEnhancedTest { | |
println("Welcome to the Scala worksheet") | |
// | |
import scala.util.{Failure, Success} | |
// | |
import org.public_domain.chess.{ChessPiecesEnhanced=>ChessPieces} | |
// | |
def stringMe(chessPiece: ChessPieces.Member): String = | |
chessPiece match { | |
case ChessPieces.KING => "Of the World" | |
case ChessPieces.QUEEN => "Of the World" | |
case ChessPieces.BISHOP => "Of the Church" | |
case ChessPieces.KNIGHT => "Of the Church" | |
case ChessPieces.ROOK => "Of the Castle" | |
case ChessPieces.PAWN => "Of the Field" | |
} | |
//to test various initialization ordering issues, rearrange these six lines into any order | |
val rook = ChessPieces.ROOK | |
val king = ChessPieces.KING | |
val pawn = ChessPieces.PAWN | |
val queen = ChessPieces.QUEEN | |
val bishop = ChessPieces.BISHOP | |
val knight = ChessPieces.KNIGHT | |
// | |
val statusChessPieces = | |
ChessPieces.status | |
val isStatusChessPiecesSuccess = | |
statusChessPieces.isSuccess | |
val statusChessPiecesMemberFailures = | |
ChessPieces.members.map(_.status).filter(_.isFailure) | |
val isStatusChessPiecesMembersSuccess = | |
statusChessPiecesMemberFailures.isEmpty | |
val isOrderedCorrectly = | |
ChessPieces.members == List(ChessPieces.KING, ChessPieces.QUEEN, ChessPieces.BISHOP, ChessPieces.KNIGHT, ChessPieces.ROOK, ChessPieces.PAWN) | |
val ordinalRange = | |
ChessPieces.ordinalRange | |
val ordinalNamePairs = | |
ChessPieces.members.map(member => (member.ordinal, member.name)) | |
// | |
val bishopByName = ChessPieces.memberByName("bIsHop") | |
// | |
val decorationBishop = ChessPieces.attributesByMember(ChessPieces.BISHOP) | |
val decorations = ChessPieces.attributesList | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package org.public_domain.chess | |
object WorkSheetChessPiecesSimplestTest { | |
println("Welcome to the Scala worksheet") | |
// | |
import scala.util.{Failure, Success} | |
// | |
import org.public_domain.chess.{ChessPiecesSimplest=>ChessPieces} | |
// | |
def stringMe(chessPiece: ChessPieces.Member): String = | |
chessPiece match { | |
case ChessPieces.KING => "Of the World" | |
case ChessPieces.QUEEN => "Of the World" | |
case ChessPieces.BISHOP => "Of the Church" | |
case ChessPieces.KNIGHT => "Of the Church" | |
case ChessPieces.ROOK => "Of the Castle" | |
case ChessPieces.PAWN => "Of the Field" | |
} | |
// | |
//to test various initialization ordering issues, rearrange these six lines into any order | |
val rook = ChessPieces.ROOK | |
val king = ChessPieces.KING | |
val pawn = ChessPieces.PAWN | |
val queen = ChessPieces.QUEEN | |
val bishop = ChessPieces.BISHOP | |
val knight = ChessPieces.KNIGHT | |
// | |
val statusChessPieces = | |
ChessPieces.status | |
val isStatusChessPiecesSuccess = | |
statusChessPieces.isSuccess | |
val statusChessPiecesMemberFailures = | |
ChessPieces.members.map(_.status).filter(_.isFailure) | |
val isStatusChessPiecesMembersSuccess = | |
statusChessPiecesMemberFailures.isEmpty | |
val isOrderedCorrectly = | |
ChessPieces.members == List(ChessPieces.KING, ChessPieces.QUEEN, ChessPieces.BISHOP, ChessPieces.KNIGHT, ChessPieces.ROOK, ChessPieces.PAWN) | |
val ordinalRange = | |
ChessPieces.ordinalRange | |
val ordinalNamePairs = | |
ChessPieces.members.map(member => (member.ordinal, member.name)) | |
// | |
val bishopByName = ChessPieces.memberByName("bIsHop") | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment