Coder Social home page Coder Social logo

tagged-types's Introduction

tagged-types

Build status Maven Central

Zero-dependency boilerplate-free tagged types for Scala.

Usage

sbt

Add the following to your build.sbt (replace %% with %%% for Scala.js):

libraryDependencies += "io.treev" %% "tagged-types" % "3.6.1"

Artifacts are published for Scala 2.12/2.13 and Scala.js 1.5.

API

Defining tagged types

import taggedtypes._

object Username extends TaggedType[String]

It's helpful to define a type alias for convenience, e.g. in package object:

object Username extends TaggedType[String]
type Username = Username.Type

TaggedType provides the following members:

  • apply method to construct tagged type from raw values, e.g. username("scooper");
  • Tag trait to access the tag, e.g. List("scooper").@@@[username.Tag] (see below for container tagging);
  • Raw type member to access raw type, e.g. to help with type inference where needed:
object Username extends TaggedType[String]
type Username = Username.Type

case class User(name: Username)

val users = List(User(Username("scooper")))
users.sortBy(_.name: Username.Raw)
  • Type type member to access tagged type.

Tagging values

sealed trait UsernameTag

val sheldon = "scooper".@@[UsernameTag]
sheldon: String @@ UsernameTag
// or "scooper".taggedWith[UsernameTag]

Or, if you have TaggedType instance:

object Username extends TaggedType[String]

val sheldon = "scooper" @@ Username
sheldon: String @@ Username.Tag
sheldon: Username.Type
// or "scooper" taggedWith Username
// or Username("scooper")

Tagging container values

val rawUsers = List("scooper", "lhofstadter", "rkoothrappali")
val users = rawUsers.@@@[UsernameTag]
users: List[String @@ UsernameTag]
// or rawUsers.taggedWithF[UsernameTag]

Can also tag using TaggedType instance as above.

Tagging arbitrarily nested container values

import scala.util.Try
val arbitrarilyNested = Some(List(Try("scooper"), Try("lhofstadter"), Try("rkoothrappali")))
val taggedArbitrarilyNested = arbitrarilyNested.@@@@[UsernameTag]
taggedArbitrarilyNested: Option[List[Try[String @@ UsernameTag]]]
// or arbitrarilyNested.taggedWithG[UsernameTag]

Can also tag using TaggedType instance as above.

Un-tagging

Immediate value:

val rawSheldon: String = sheldon.-@ // or sheldon.unTagged

Container value:

val rawUsers: List[String] = users.-@@ // or users.unTaggedF

Arbitrarily nested container value:

val rawArbitrarilyNested: Option[List[Try[String @@ UsernameTag]]] = taggedArbitrarilyNested.-@@@@ // or taggedArbitrarilyNested.unTaggedG

Adding more tags

Immediate value:

sealed trait OwnerTag

val username = "scooper".@@[UsernameTag]
val owner = username.+@[OwnerTag]
owner: String @@ (UsernameTag with OwnerTag)
// or username.andTaggedWith[OwnerTag]

Container value:

val owners = users.+@@[OwnerTag]
owners: List[String @@ (UsernameTag with OwnerTag)]
// or users.andTaggedWithF[OwnerTag]

Arbitrarily nested container value:

val owners = taggedArbitrarilyNested.+@@@[OwnerTag]
owners: Option[List[Try[String @@ (UsernameTag with OwnerTag)]]]
// or taggedArbitrarilyNested.andTaggedWithG[OwnerTag]:

Can also tag using TaggedType instance as above.

Auto tagging

Sometimes it's convenient to automatically convert raw values into tagged ones, e.g. in REPL or when integrating with external APIs. To achieve this, an import taggedtypes.auto._ is required:

import taggedtypes.auto._

val sheldon: Username = "scooper"

Typeclass auto tagging

To automatically lift a raw value typeclass into a tagged one, an import taggedtypes.auto.typeclass._ import is required:

import taggedtypes.auto.typeclass._

implicitly[Typeclass[Username]]

Migrating from value classes

Suppose you have a value class:

case class Username(value: String) extends AnyVal {
  def isValid: Boolean = !value.isEmpty
}
object Username {
  val FieldName: String = "username"
}

Then, it's a matter of changing it to:

object Username extends TaggedType[String]

Any methods on original case class instance turn into implicit extensions:

object Username extends TaggedType[String] {
  implicit class UsernameExtensions(val value: Type) extends AnyVal {
    def isValid: Boolean = !value.isEmpty
  }
}

Any constants on original case class' companion object are merged into Username object:

object Username extends TaggedType[String] {
  val FieldName: String = "username"
}

Integrating with libraries

Circe

Helpers for manually lifting Circe encoders/decoders.

import io.circe._
import taggedtypes._

def taggedDecoder[T: Decoder, U]: Decoder[T @@ U] =
  Decoder.instance(_.as[T].@@@[U])

def taggedTypeDecoder[T: Decoder](taggedType: TaggedType[T]): Decoder[taggedType.Type] =
  taggedDecoder[T, taggedType.Tag]

def taggedEncoder[T: Encoder, U]: Encoder[T @@ U] =
  Encoder[T].@@@[U]

def taggedTypeEncoder[T: Encoder](taggedType: TaggedType[T]): Encoder[taggedType.Type] =
  taggedEncoder[T, taggedType.Tag]

Slick

Helpers for manually lifting Slick column types.

import io.circe._
import scala.reflect.ClassTag
import slickProfile.api._

def taggedColumnType[T, U](implicit tColumnType: BaseColumnType[T],
                                    clsTag: ClassTag[T @@ U]): BaseColumnType[T @@ U] =
  MappedColumnType.base[T @@ U, T](identity, _.@@[U])

def taggedTypeColumnType[T](taggedType: TaggedType[T])
                           (implicit tColumnType: BaseColumnType[T],
                                     clsTag: ClassTag[taggedType.Type]): BaseColumnType[taggedType.Type] =
  taggedColumnType[T, taggedType.Tag]

tagged-types's People

Contributors

borissmidt avatar tvaroh avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

Forkers

borissmidt

tagged-types's Issues

Release 2.12, 2.11

Dear Tvaroh,

It seems you had support for 2.12 in release 3.5.1.
But dropped it after 3.6.0 after merging the PR i've made.
Would you be so kind to release it for 2.12 and maybe 2.11 if that is possible.
(our company just started to migrate to 2.13 so i can't use it yet)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.