2017-09-01 10 views
2

私はビジネスロジックを渡すための素早い型のプリミティブを得るために、Shapelessのタグ付き型を使用します。これらのタイプを定義することは簡単な使用を開始:コード生成(Scalaメタなど)を使用してボイラープレートをスクラップする

sealed trait MyTaggedStringTag 
type MyTaggedString = String @@ MyTaggedStringTag 

しかし、私はこれにヘルパー・ロジックの良いビットを追加しました、そして今、私の定義はより多くのように見える:

だから、
sealed trait MyTaggedStringTag 

type MyTaggedString = String @@ MyTaggedStringTag 
object MyTaggedString { 
    def fromString(untaggedMyTaggedString: String): MyTaggedString = { 
    val myTaggedString = tag[MyTaggedStringTag](untaggedMyTaggedString) 
    myTaggedString 
    } 
} 
implicit class MyTaggedStringOps(val myTaggedString: MyTaggedString) extends AnyVal { def untagged = myTaggedString.asInstanceOf[String] } 

、それはたくさんのです定義ごとの定型文。

@tagged[String] type MyTaggedString 

Scalaのメタ、または他のいくつかのコード生成ツールを使って、このような何かをする方法はあります:私は本当にのような何かをすることによって、これを生成できるようにしたいのですが?

答えて

1

これは今、完全に働いていると私はTaggyを呼ぶ新しいライブラリで見ることができ

更新しました。マクロの最新バージョンは次のとおりです。

class tagged extends scala.annotation.StaticAnnotation { 
    inline def apply(defn: Any): Any = meta { 
    // Macro annotation type and value parameters come back as AST data, not 
    // values, and are accessed by destructuring `this`. 
    defn match { 
     case q"..$mods type $newType = ${underlyingType: Type.Name}" => 
     TaggedImpl.expand(underlyingType, newType, mods) 
     case _ => 
     abort("Correct usage: @tagged type NewType = UnderlyingType") 
    } 
    } 
} 

object TaggedImpl { 
    def expand(underlyingType: Type.Name, newType: Type.Name, mods: Seq[Mod]) = { 
    // Shapeless needs a phantom type to join with the underlying type to 
    // create our tagged type. Ideally should never leak to external code. 
    val tag = Type.Name(newType.value + "Tag") 

    // The `fromX` helper will go in the companion object. 
    val companionObject = Term.Name(newType.value) 

    // We'll name the `fromX` method based on the underlying type. 
    val fromMethod = Term.Name("from" + underlyingType.value) 

    // The `untagged` helper goes in an implicit class, since the tagged type 
    // is only a type alias, and can't have real methods. 
    val opsClass = Type.Name(newType.value + "Ops") 

    q""" 
     sealed trait $tag 
     ..$mods type $newType = [email protected]@[$underlyingType, $tag] 
     ..$mods object $companionObject { 
     def $fromMethod(untagged: $underlyingType): $newType = { 
      val tagged = com.acjay.taggy.tag[$tag](untagged) 
      tagged 
     } 
     } 
     ..$mods implicit class $opsClass(val tagged: $newType) extends AnyVal { 
     def untagged = tagged.asInstanceOf[$underlyingType] 
     def modify(f: $underlyingType => $underlyingType) = $companionObject.$fromMethod(f(untagged)) 
     } 
    """ 
    } 
} 

object tag { 
    def apply[U] = new Tagger[U] 

    trait Tagged[U] 
    type @@[+T, U] = T with Tagged[U] 

    class Tagger[U] { 
    def apply[T](t : T) : T @@ U = t.asInstanceOf[T @@ U] 
    } 
} 

マクロ構文とコード生成の解析は、読みやすさのために区切られています。 TaggedImpl.expandmetaブロックにインラインできます。また、ここでの構文は@tagged type MyTaggedString = Stringになっています。

私はコンセプトの証明として働くそれを得た

オリジナルの答え。しかし、基になる型の文字列名をとります:

import scala.meta._ 

class tagged(_underlyingTypeName: String) extends scala.annotation.StaticAnnotation { 
    inline def apply(defn: Any): Any = meta { 
    // Can't figure out how to do this extraction as a quasiquote, so I 
    // figured out exactly the AST `this` produces to extract the string 
    // parameter. 
    val Term.New(
     Template(
     List(), 
     List(Term.Apply(Ctor.Ref.Name("tagged"), List(Lit.String(underlyingTypeName)))), 
     Term.Param(List(), Name.Anonymous(), None, None), 
     None 
    ) 
    ) = this 

    val q"..$mods type $tname[..$tparams]" = defn 
    val underlyingType = Type.Name(underlyingTypeName) 
    TaggedImpl.expand(tname, underlyingType) 
    } 
} 

object TaggedImpl { 
    def expand(taggedType: Type.Name, underlyingType: Type.Name) = { 
    val tag = Type.Name(taggedType.value + "Tag") 
    val companionObject = Term.Name(taggedType.value) 
    val fromMethodName = Term.Name("from" + underlyingType.value) 
    val opsClass = Type.Name(taggedType.value + "Ops") 

    q""" 
     sealed trait $tag 
     type $taggedType = [email protected]@[$underlyingType, $tag] 
     object $companionObject { 
     def $fromMethodName(untagged: $underlyingType): $taggedType = { 
      val tagged = shapeless.tag[$tag](untagged) 
      tagged 
     } 
     } 
     implicit class $opsClass(val tagged: $taggedType) extends AnyVal { 
     def untagged = tagged.asInstanceOf[$underlyingType] 
     } 
    """ 
    } 
} 
関連する問題