依赖方法类型以前曾是一个实验性功能,现在默认情况下已在中继中启用,显然这似乎在Scala社区中引起了一些兴奋.
初看起来,这并不是显而易见的.Heiko Seeberger在这里发布了一个简单的依赖方法类型示例,从评论中可以看出,可以很容易地在方法上使用类型参数进行复制.所以这不是一个非常引人注目的例子.(我可能会遗漏一些明显的东西.如果是这样,请纠正我.)
对于依赖方法类型的用例有哪些实用且有用的例子,它们明显优于替代方法?
我们可以用以前不可能/容易的事情做些什么有趣的事情?
他们通过现有的类型系统功能为我们买了什么?
此外,依赖方法类型是否类似于或从其他高级类型语言(如Haskell,OCaml)的类型系统中找到的任何功能中汲取灵感?
haskell type-systems programming-languages scala dependent-method-type
我很确定我在这里遗漏了一些东西,因为我对Shapeless很新,而且我正在学习,但是Aux技术何时需要呢?我看到它用于通过将type语句提升到另一个"伴侣" type定义的签名来公开语句.
trait F[A] { type R; def value: R }
object F { type Aux[A,RR] = F[A] { type R = RR } }
Run Code Online (Sandbox Code Playgroud)
但是这不等于将R放入F的类型签名中吗?
trait F[A,R] { def value: R }
implicit def fint = new F[Int,Long] { val value = 1L }
implicit def ffloat = new F[Float,Double] { val value = 2.0D }
def f[T,R](t:T)(implicit f: F[T,R]): R = f.value
f(100) // res4: Long = 1L
f(100.0f) // res5: Double = …Run Code Online (Sandbox Code Playgroud) 我似乎不理解Scala类型系统.我正在尝试实现两个基本特征和一系列算法的特性来使用它们.我在下面做错了什么?
移动和状态的基本特征; 这些被简化为仅包括暴露问题的方法.
trait Move
trait State[M <: Move] {
def moves: List[M]
def successor(m: M): State[M]
}
Run Code Online (Sandbox Code Playgroud)
这是使用上述算法系列的特性.我不确定这是对的!可能会涉及一些+ M/-S的东西......
trait Algorithm {
def bestMove[M <: Move, S <: State[M]](s: S): M
}
Run Code Online (Sandbox Code Playgroud)
具体举动和状态:
case class MyMove(x: Int) extends Move
class MyState(val s: Map[MyMove,Int]) extends State[MyMove] {
def moves = MyMove(1) :: MyMove(2) :: Nil
def successor(p: MyMove) = new MyState(s.updated(p, 1))
}
Run Code Online (Sandbox Code Playgroud)
我对下面的内容非常不满意,但编译器似乎接受了它...试图对算法特性进行具体实现.
object MyAlgorithm extends Algorithm {
def bestMove(s: State[Move]) = s.moves.head
}
Run Code Online (Sandbox Code Playgroud)
到目前为止,没有编译错误; 当我尝试将所有部件放在一起时,它们会出现:
object Main extends …Run Code Online (Sandbox Code Playgroud) 当我使用Java(或类似语言)编程时,我经常使用简单版本的策略模式,使用接口和实现类,在我的代码中提供特定概念的运行时可选实现.
作为一个非常人为的例子,我可能希望拥有一个可以在我的Java代码中产生噪音的Animal的一般概念,并希望能够在运行时选择动物的类型.所以我会沿着这些方向编写代码:
interface Animal {
void makeNoise();
}
class Cat extends Animal {
void makeNoise() { System.out.println("Meow"); }
}
class Dog extends Animal {
void makeNoise() { System.out.println("Woof"); }
}
class AnimalContainer {
Animal myAnimal;
AnimalContainer(String whichOne) {
if (whichOne.equals("Cat"))
myAnimal = new Cat();
else
myAnimal = new Dog();
}
void doAnimalStuff() {
...
// Time for the animal to make a noise
myAnimal.makeNoise();
...
}
Run Code Online (Sandbox Code Playgroud)
很简单.不过,最近,我一直在Scala开展一个项目,我想做同样的事情.使用特征这样做似乎很容易,例如:
trait Animal {
def makeNoise:Unit
}
class Cat extends Animal {
override def …Run Code Online (Sandbox Code Playgroud) 我有一些像这样的代码:
sealed trait Foo[A] {
def value: A
}
case class StringFoo(value: String) extends Foo[String]
case class IntFoo(value: Int) extends Foo[Int]
Run Code Online (Sandbox Code Playgroud)
我想要一个可以使用A给定子类型参数的类型的函数.
// Hypothetical invocation
val i: Int = dostuff[IntFoo](param)
val s: String = dostuff[StringFoo](param)
Run Code Online (Sandbox Code Playgroud)
我无法弄清楚如何以dostuff一种有效的方式声明.我能弄清楚的最接近的事情是
def dostuff[B <: Foo[A]](p: Param): A
Run Code Online (Sandbox Code Playgroud)
但这不起作用,因为A在该位置未定义.我可以做点什么
def dostuff[A, B <: Foo[A]](p: Param): A
Run Code Online (Sandbox Code Playgroud)
但后来我必须调用它,就像dostuff[String, StringFoo](param)它非常难看.
看起来编译器应该具有移动A到返回类型所需的所有信息,如何在标准scala或库中进行此工作.如果这会影响答案,我现在在scala 2.10上.如果有可能的话,我会接受2.11的解决方案,但在2.10中是不可能的
我有一些简单的特征(下面示例中的实体),它们由我的应用程序中的案例类扩展。我想创建一个 EntityMapper 特征,它提供一个接口来处理扩展实体特征的案例类(下面示例中的 Foo )。我认为我应该能够使用泛型和边界很容易地做到这一点,但我已经花了几个小时在它上面,我还没有让它正常工作。下面的代码是我认为我应该能够做的,但它因编译器错误而失败。错误是
Test.scala:15: 错误:值 id 不是类型参数 Foo \ println(e.id) 的成员
package experiment
trait Entity {
val id: Option[Long]
}
case class Foo(val id: Option[Long] = None) extends Entity
trait EntityMapper {
def create[E <: Entity](e: E): E
}
object FooMapper extends EntityMapper {
def create[Foo](e: Foo): Foo = {
println(e.id)
e
}
}
object Main extends App {
val foo = FooMapper.create(Foo(None))
}
Run Code Online (Sandbox Code Playgroud)
我尝试了几种不同的方法来解决问题,但没有任何效果。如果我注释掉有问题的行“println(e.id)”,它会编译,但这没有用,因为我无法访问或修改 Foo 的任何属性。
我曾尝试对映射器特征使用协变参数,然后将类型提供给 FooMapper 对象定义,但这会产生相同的错误。该尝试的代码如下:
trait EntityMapper[+Entity] {
def create[E <: Entity](e: E): …Run Code Online (Sandbox Code Playgroud) 有时人们可能想要声明x与...相同的类型y.使用vals类型推断处理得非常好,但这在其他一些方面不起作用,例如函数类型.
一个程序员这似乎是显而易见的溶液与一些C++的经验将是一个decltype.目前的Scala似乎没有这样的设施.
对链接问题的回答告诉我们:
因为类型不是一等公民
我不得不承认我不理解这一点.我不认为类型是C++中的一等公民,但它仍然可以拥有decltype.我不是在询问有关decltype泛型中的类型参数或类似参数的任何内容(我理解泛型不是模板,并且类型被删除).尽管如此,我认为一个允许我在一个预期类型的地方使用一种表达式的运算符 - 当然编译器必须能够计算一个表达式类型,否则val就不可能进行类型推断以进行定义.
A decltype可以像下面这样使用 - 代码不是试图做任何有用的事情,只是为了说明语法和基本用法:
case class A(x:Int = 0)
val a = new A(10)
val b = new decltype(a)
def f(c:decltype(a)) : decltype(a.x+a.x)
Run Code Online (Sandbox Code Playgroud)
没有decltype刻意的决定,还是有一些特定的原因让Scala无法拥有它?是否有一些使用编译时反射的解决方案可以实现这一点?
据我了解,依赖类型允许您不指定输出类型:
例如,如果您有一个类型类:
trait Last[In] {
type Out
}
Run Code Online (Sandbox Code Playgroud)
那么你可以在不指定输出类型的情况下召唤一个实例:
implicitly(Last[String :: Int :: HNil]) // output type calculated as Int
Run Code Online (Sandbox Code Playgroud)
Aux 模式允许您再次指定输出类型:
implicitly(Last.Aux[String :: Int :: HNil, Int])
Run Code Online (Sandbox Code Playgroud)
您需要在隐式参数列表中使用它,以便对输出类型做一些有用的事情(解决 Scala 对依赖类型的限制)。
但是如果你总是需要指定(或分配一个类型参数)输出类型,为什么首先要使用依赖类型(然后是 Aux)呢?
我尝试Last从 Shapeless 的 src复制类型类,替换type Out为特征中的附加类型参数并删除 Aux。它仍然有效。
当我真正需要它们时是什么情况?
scala ×10
shapeless ×2
types ×2
decltype ×1
generics ×1
haskell ×1
polymorphism ×1
type-bounds ×1
type-systems ×1