Type Driven Development with Idris介绍了这个程序:
StringOrInt : Bool -> Type
StringOrInt x = case x of
True => Int
False => String
如何用Scala编写这样的方法?
答案 0 :(得分:15)
Alexey的答案很好,但我认为如果我们将它嵌入稍大的上下文中,我们可以使用更加通用的Scala渲染。
Edwin在函数StringOrInt
中显示了valToString
的使用,
valToString : (x : Bool) -> StringOrInt x -> String
valToString x val = case x of
True => cast val
False => val
简而言之,valToString
采用Bool
第一个参数,将第二个参数的类型修复为Int
或String
,并将后者渲染为{{1}适合其类型。
我们可以将此转换为Scala,如下所示,
String
在这里,我们使用各种Scala的有限依赖类型功能来编码Idris的全谱依赖类型。
sealed trait Bool
case object True extends Bool
case object False extends Bool
sealed trait StringOrInt[B, T] {
def apply(t: T): StringOrIntValue[T]
}
object StringOrInt {
implicit val trueInt: StringOrInt[True.type, Int] =
new StringOrInt[True.type, Int] {
def apply(t: Int) = I(t)
}
implicit val falseString: StringOrInt[False.type, String] =
new StringOrInt[False.type, String] {
def apply(t: String) = S(t)
}
}
sealed trait StringOrIntValue[T]
case class S(s: String) extends StringOrIntValue[String]
case class I(i: Int) extends StringOrIntValue[Int]
def valToString[T](x: Bool)(v: T)(implicit si: StringOrInt[x.type, T]): String =
si(v) match {
case S(s) => s
case I(i) => i.toString
}
和True.type
从值级别跨越到类型级别。False.type
编码为由单例StringOrInt
类型索引的类型类,每个Idris函数的情况由不同的隐式实例表示。Bool
写为Scala依赖方法,允许我们使用valToString
参数Bool
的单例类型来选择隐式x
实例{{1} },反过来确定类型参数StringOrInt
,它修复了第二个参数si
的类型。T
实例将v
参数提升到Scala GADT中来编码Idris valToString
中的从属模式匹配,该Scala GADT允许Scala模式匹配来细化类型StringOrInt
关于案件的RHS。在Scala REPL上执行此操作,
v
很多箍跳过,很多偶然的复杂性,但是,它可以做到。
答案 1 :(得分:10)
这将是一种方法(但它比伊德里斯更有限):
trait Type {
type T
}
def stringOrInt(x: Boolean) = // Scala infers Type { type T >: Int with String }
if (x) new Type { type T = Int } else new Type { type T = String }
然后使用它
def f(t: Type): t.T = ...
如果您愿意将自己限制为文字,则可以使用Shapeless使用单true
和false
的单例类型。来自examples:
import syntax.singleton._
val wTrue = Witness(true)
type True = wTrue.T
val wFalse = Witness(false)
type False = wFalse.T
trait Type1[A] { type T }
implicit val Type1True: Type1[True] = new Type1[True] { type T = Int }
implicit val Type1False: Type1[False] = new Type1[False] { type T = String }
另请参阅Any reason why scala does not explicitly support dependent types?,http://www.infoq.com/presentations/scala-idris和http://wheaties.github.io/Presentations/Scala-Dep-Types/dependent-types.html。
答案 2 :(得分:1)
我注意到两个答案中的任何一个都没有实现getStringOrInt
示例
getStringOrInt : (x : Bool) -> StringOrInt x
getStringOrInt x = case x of
True => 10
False => "Hello"
我发现Miles Sabin的解释非常有用,这种方法是基于他的。 我发现将类似GADT的构造从Scala apply()技巧中分离出来并尝试将我的代码映射到Idris / Haskell概念上更加直观。我希望其他人觉得这有用。我在名称中明确使用GADT来强调GADT的本质。 该代码的两个组成部分是:GADT概念和Scala的隐式内容。
这是Miles Sabin解决方案的略微修改,它同时实现了getStringOrInt
和valToString
。
sealed trait StringOrIntGADT[T]
case class S(s: String) extends StringOrIntGADT[String]
case class I(i: Int) extends StringOrIntGADT[Int]
/* this compiles with 2.12.6
before I would have to use ah-hoc polymorphic extract method */
def extractStringOrInt[T](t: StringOrIntGADT[T]) : T =
t match {
case S(s) => s
case I(i) => i
}
/* apply trickery gives T -> StringOrIntGADT[T] conversion */
sealed trait EncodeInStringOrInt[T] {
def apply(t: T): StringOrIntGADT[T]
}
object EncodeInStringOrInt {
implicit val encodeString : EncodeInStringOrInt[String] = new EncodeInStringOrInt[String]{
def apply(t: String) = S(t)
}
implicit val encodeInt : EncodeInStringOrInt[Int] = new EncodeInStringOrInt[Int]{
def apply(t: Int) = I(t)
}
}
/* Subtyping provides type level Boolean */
sealed trait Bool
case object True extends Bool
case object False extends Bool
/* Type level mapping between Bool and String/Int types.
Somewhat mimicking type family concept in type theory or Haskell */
sealed trait DecideStringOrIntGADT[B, T]
case object PickS extends DecideStringOrIntGADT[False.type, String]
case object PickI extends DecideStringOrIntGADT[True.type, Int]
object DecideStringOrIntGADT {
implicit val trueInt: DecideStringOrIntGADT[True.type, Int] = PickI
implicit val falseString: DecideStringOrIntGADT[False.type, String] = PickS
}
所有这些工作使我能够实现getStringOrInt
和valToString
的体面版本
def pickStringOrInt[B, T](c: DecideStringOrIntGADT[B, T]): StringOrIntGADT[T]=
c match {
case PickS => S("Hello")
case PickI => I(2)
}
def getStringOrInt[T](b: Bool)(implicit ev: DecideStringOrIntGADT[b.type, T]): T =
extractStringOrInt(pickStringOrInt(ev))
def valToString[T](b: Bool)(v: T)(implicit ev: EncodeInStringOrInt[T], de: DecideStringOrIntGADT[b.type, T]): String =
ev(v) match {
case S(s) => s
case I(i) => i.toString
}
所有这些(不幸的)复杂性似乎都是必需的,例如,它将无法编译
// def getStringOrInt2[T](b: Bool)(implicit ev: DecideStringOrIntGADT[b.type, T]): T =
// ev match {
// case PickS => "Hello"
// case PickI => 2
// }
我有一个宠物项目,在该项目中,我将Idris书中的所有代码与Haskell进行了比较。 https://github.com/rpeszek/IdrisTddNotes/wiki (我正在研究此比较的Scala版本。)
使用类型级别的布尔值(实际上是这里的内容),StringOrInt
的示例如果具有类型族(类型之间的部分函数),将变得非常简单。见底部
https://github.com/rpeszek/IdrisTddNotes/wiki/Part1_Sec1_4_5
这使Haskell / Idris代码变得更加简单,易于阅读和理解。
请注意,valToString
在StringOrIntGADT[T]/StringOrIntValue[T]
构造函数上匹配,而不是直接在Bool上匹配。那是伊德里斯和哈斯克尔大放异彩的一个例子。