捕获和链接用于理解的类型
Capturing and chaining types used in for comprehension
我正在寻找一种方法来捕获在理解本身的类型中用于理解的类型。为此,我指定了一个粗略的接口:
trait Chain[A]{
type ChainMethod = A => A //type of the method chained so far
def flatMap[B](f: A => Chain[B]): Chain[B] //the ChainMethod needs to be included in the return type somehow
def map[B](f: A => B): Chain[B]: Chain[B]
def fill: ChainMethod //Function has to be uncurried here
}
举几个具体类型的例子 Chain
:
object StringChain extends Chain[String]
object IntChain extends Chain[Int]
以及将要使用的案例class:
case class User(name:String, age:Int)
可以使用 for comprehension 创建一个链:
val form = for{
name <- StringChain
age <- IntChain
} yield User(name, age)
form
的类型应该是
Chain[User]{type ChainMethod = String => Int => User}
以便我们可以执行以下操作:
form.fill("John", 25) //should return User("John", 25)
我尝试了几种方法,包括结构类型和专门的 FlatMappedChain
特性,但我无法让类型系统按照我想要的方式运行。如果可能的话,我会喜欢一些关于如何指定接口的想法或建议,以便编译器能够识别它。
我认为在 Scala 中从头开始做这件事非常困难。您可能必须为不同数量的函数定义很多隐式 类 。
如果使用专为类型级计算设计的 shapeless
库,事情会变得更容易。下面的代码使用了一种稍微不同的方法,其中 Chain.fill
是一个从参数元组到结果的函数。 flatMap
的这种实现还允许将多种形式合并为一种形式:
import shapeless._
import shapeless.ops.{tuple => tp}
object Chain {
def of[T]: Chain[Tuple1[T], T] = new Chain[Tuple1[T], T] {
def fill(a: Tuple1[T]) = a._1
}
}
/** @tparam A Tuple of arguments for `fill`
* @tparam O Result of `fill`
*/
abstract class Chain[A, O] { self =>
def fill(a: A): O
def flatMap[A2, O2, Len <: Nat, R](next: O => Chain[A2, O2])(
implicit
// Append tuple A2 to tuple A to get a single tuple R
prepend: tp.Prepend.Aux[A, A2, R],
// Compute length Len of tuple A
length: tp.Length.Aux[A, Len],
// Take the first Len elements of tuple R,
// and assert that they are equivalent to A
take: tp.Take.Aux[R, Len, A],
// Drop the first Len elements of tuple R,
// and assert that the rest are equivalent to A2
drop: tp.Drop.Aux[R, Len, A2]
): Chain[R, O2] = new Chain[R, O2] {
def fill(r: R): O2 = next(self.fill(take(r))).fill(drop(r))
}
def map[O2](f: O => O2): Chain[A, O2] = new Chain[A, O2] {
def fill(a: A): O2 = f(self.fill(a))
}
}
下面是你如何使用它:
scala> case class Address(country: String, city: String)
defined class Address
scala> case class User(id: Int, name: String, address: Address)
defined class User
scala> val addressForm = for {
country <- Chain.of[String]
city <- Chain.of[String]
} yield Address(country, city)
addressForm: com.Main.Chain[this.Out,Address] = com.Main$Chain$$anon@3253e213
scala> val userForm = for {
id <- Chain.of[Int]
name <- Chain.of[String]
address <- addressForm
} yield User(id, name, address)
userForm: com.Main.Chain[this.Out,User] = com.Main$Chain$$anon@7ad40950
scala> userForm.fill(1, "John", "USA", "New York")
res0: User = User(1,John,Address(USA,New York))
我正在寻找一种方法来捕获在理解本身的类型中用于理解的类型。为此,我指定了一个粗略的接口:
trait Chain[A]{
type ChainMethod = A => A //type of the method chained so far
def flatMap[B](f: A => Chain[B]): Chain[B] //the ChainMethod needs to be included in the return type somehow
def map[B](f: A => B): Chain[B]: Chain[B]
def fill: ChainMethod //Function has to be uncurried here
}
举几个具体类型的例子 Chain
:
object StringChain extends Chain[String]
object IntChain extends Chain[Int]
以及将要使用的案例class:
case class User(name:String, age:Int)
可以使用 for comprehension 创建一个链:
val form = for{
name <- StringChain
age <- IntChain
} yield User(name, age)
form
的类型应该是
Chain[User]{type ChainMethod = String => Int => User}
以便我们可以执行以下操作:
form.fill("John", 25) //should return User("John", 25)
我尝试了几种方法,包括结构类型和专门的 FlatMappedChain
特性,但我无法让类型系统按照我想要的方式运行。如果可能的话,我会喜欢一些关于如何指定接口的想法或建议,以便编译器能够识别它。
我认为在 Scala 中从头开始做这件事非常困难。您可能必须为不同数量的函数定义很多隐式 类 。
如果使用专为类型级计算设计的 shapeless
库,事情会变得更容易。下面的代码使用了一种稍微不同的方法,其中 Chain.fill
是一个从参数元组到结果的函数。 flatMap
的这种实现还允许将多种形式合并为一种形式:
import shapeless._
import shapeless.ops.{tuple => tp}
object Chain {
def of[T]: Chain[Tuple1[T], T] = new Chain[Tuple1[T], T] {
def fill(a: Tuple1[T]) = a._1
}
}
/** @tparam A Tuple of arguments for `fill`
* @tparam O Result of `fill`
*/
abstract class Chain[A, O] { self =>
def fill(a: A): O
def flatMap[A2, O2, Len <: Nat, R](next: O => Chain[A2, O2])(
implicit
// Append tuple A2 to tuple A to get a single tuple R
prepend: tp.Prepend.Aux[A, A2, R],
// Compute length Len of tuple A
length: tp.Length.Aux[A, Len],
// Take the first Len elements of tuple R,
// and assert that they are equivalent to A
take: tp.Take.Aux[R, Len, A],
// Drop the first Len elements of tuple R,
// and assert that the rest are equivalent to A2
drop: tp.Drop.Aux[R, Len, A2]
): Chain[R, O2] = new Chain[R, O2] {
def fill(r: R): O2 = next(self.fill(take(r))).fill(drop(r))
}
def map[O2](f: O => O2): Chain[A, O2] = new Chain[A, O2] {
def fill(a: A): O2 = f(self.fill(a))
}
}
下面是你如何使用它:
scala> case class Address(country: String, city: String)
defined class Address
scala> case class User(id: Int, name: String, address: Address)
defined class User
scala> val addressForm = for {
country <- Chain.of[String]
city <- Chain.of[String]
} yield Address(country, city)
addressForm: com.Main.Chain[this.Out,Address] = com.Main$Chain$$anon@3253e213
scala> val userForm = for {
id <- Chain.of[Int]
name <- Chain.of[String]
address <- addressForm
} yield User(id, name, address)
userForm: com.Main.Chain[this.Out,User] = com.Main$Chain$$anon@7ad40950
scala> userForm.fill(1, "John", "USA", "New York")
res0: User = User(1,John,Address(USA,New York))