(This post contains code that is supposed to demonstrate some properties of the type system; it has no direct practical applications, please don't use it for anything; "It can be done" does not imply "you should do it")
In your question, it's not entirely clear what the formulas such as "CollectionOfLines[X]
" and "String[X]
" are supposed to mean. I took the liberty to warp it into something implementable:
/**
* Claims that an `X` can be printed.
*/
trait Print[X]:
def apply(x: X): Unit
/**
* Claims that an instance of type `X` can
* be read from a file with a statically
* known `Name`.
*/
trait Read[Name <: String & Singleton, X]:
def apply(): X
/**
* Claims that an instance of type `Repr` can
* be read from a statically known file with a given `Name`,
* and then printed in `Repr`-specific way.
*/
trait PrintFile[Name, Repr]:
def apply(): Unit
given splitLines: Conversion[String, List[String]] with
def apply(s: String) = s.split("\n").toList
given printFile[Name <: String & Singleton, Repr]
(using n: ValueOf[Name], rd: Read[Name, Repr], prnt: Print[Repr])
: PrintFile[Name, Repr] with
def apply(): Unit =
val content = rd()
prnt(content)
given readThenConvert[Name <: String & Singleton, A, B]
(using name: ValueOf[Name], readA: Read[Name, A], conv: Conversion[A, B])
: Read[Name, B] with
def apply() = conv(readA())
inline given slurp[Name <: String & Singleton]
(using n: ValueOf[Name])
: Read[Name, String] with
def apply() = io.Source.fromFile(n.value).getLines.mkString("\n")
given printString: Print[String] with
def apply(s: String) = println(s)
given printList[X](using printOne: Print[X]): Print[List[X]] with
def apply(x: List[X]) = x.zipWithIndex.foreach((line, idx) => {
print(s"${"%3d".format(idx)}| ")
printOne(line)
})
@main def printItself(): Unit =
println("Print as monolithic string")
summon[PrintFile["example.scala", String]]()
println("=" * 80)
println("Print as separate lines")
summon[PrintFile["example.scala", List[String]]]()
When saved as example.scala
, it will print its own code twice, once as one long multiline-string, and once as a list of numbered lines.
As already mentioned elsewhere, this particular use case seems highly atypical, and the code looks quite unidiomatic.
You should try to reserve this mechanism for making true and precise statements about types and type constructors, not for enforcing an order between a bunch of imperative statements.
In this example, instead of carefully making universally true statements, we're making a bunch of half-baked not well thought-out statements, giving them semi-random names, and then trying to abuse the nominal type system and to coerce the reality to the artificial constraints that we have imposed by naming things this or that. This is usually a sign of a bad design: everything feels loose and kind-of "flabby". The apply(): Unit
in particular is clearly a red flag: there is not much that can be said about Unit
that could also be encoded as types, so instead of relying on the type system, one has to revert to "stringly-typed" naming-discipline, and then hope that one has interpreted the names correctly.
summon
here? What properties should it have? Again: I think you're trying to do something highly atypical here. I'd propose to get familiar with the more common patterns first, maybe go and implement a bunch of common type classes for your favorite data structures. This use case looks way too esoteric, maybe you should at least try to motivate it a little better. – Pimento