来自 Idris -> Scala 的 StringOrInt?
StringOrInt from Idris -> Scala?
Type Driven Development with Idris 介绍这个节目:
StringOrInt : Bool -> Type
StringOrInt x = case x of
True => Int
False => String
这样的方法怎么用Scala写的?
这将是一种方法(但它比在 Idris 中更受限制):
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 and http://wheaties.github.io/Presentations/Scala-Dep-Types/dependent-types.html。
Alexey 的回答很好,但我认为如果我们将此函数嵌入到稍微大一点的上下文中,我们可以获得该函数的更通用的 Scala 呈现。
Edwin 在函数 valToString
,
中展示了 StringOrInt
的用法
valToString : (x : Bool) -> StringOrInt x -> String
valToString x val = case x of
True => cast val
False => val
换句话说,valToString
采用 Bool
第一个参数,将第二个参数的类型固定为 Int
或 String
并将后者呈现为 String
适合其类型。
我们可以将其翻译成 Scala,如下所示,
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
}
这里我们使用 Scala 的各种有限的依赖类型特征来编码 Idris 的全谱依赖类型。
- 我们使用单例类型
True.type
和 False.type
从值级别跨越到类型级别。
- 我们将函数
StringOrInt
编码为由单例 Bool
类型索引的类型 class,Idris 函数的每种情况都由不同的隐式实例表示。
- 我们将
valToString
编写为 Scala 依赖方法,允许我们使用 Bool
参数的单例类型 x
到 select 隐式 StringOrInt
实例 si
,它又决定了类型参数 T
,它固定了第二个参数的类型 v
。
- 我们通过使用 selected
StringOrInt
实例将 v
参数提升到允许Scala 模式匹配以细化案例 RHS 上 v
的类型。
在 Scala REPL 上执行此操作,
scala> valToString(True)(23)
res0: String = 23
scala> valToString(False)("foo")
res1: String = foo
有很多障碍要跳过,还有很多偶然的复杂性,但是,这是可以做到的。
我注意到 getStringOrInt
示例没有被两个答案中的任何一个实现
getStringOrInt : (x : Bool) -> StringOrInt x
getStringOrInt x = case x of
True => 10
False => "Hello"
我发现 Miles Sabin 的解释非常有用,这种方法是基于他的。
我发现将类似 GADT 的构造与 Scala apply() 技巧分开并尝试将我的代码映射到 Idris/Haskell 概念更直观。我希望其他人觉得这很有用。我在名称中明确使用 GADT 以强调 GADT-ness。
此代码的 2 个成分是: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。这是 Idris 和 Haskell 大放异彩的一个例子。
Type Driven Development with Idris 介绍这个节目:
StringOrInt : Bool -> Type
StringOrInt x = case x of
True => Int
False => String
这样的方法怎么用Scala写的?
这将是一种方法(但它比在 Idris 中更受限制):
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 and http://wheaties.github.io/Presentations/Scala-Dep-Types/dependent-types.html。
Alexey 的回答很好,但我认为如果我们将此函数嵌入到稍微大一点的上下文中,我们可以获得该函数的更通用的 Scala 呈现。
Edwin 在函数 valToString
,
StringOrInt
的用法
valToString : (x : Bool) -> StringOrInt x -> String
valToString x val = case x of
True => cast val
False => val
换句话说,valToString
采用 Bool
第一个参数,将第二个参数的类型固定为 Int
或 String
并将后者呈现为 String
适合其类型。
我们可以将其翻译成 Scala,如下所示,
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
}
这里我们使用 Scala 的各种有限的依赖类型特征来编码 Idris 的全谱依赖类型。
- 我们使用单例类型
True.type
和False.type
从值级别跨越到类型级别。 - 我们将函数
StringOrInt
编码为由单例Bool
类型索引的类型 class,Idris 函数的每种情况都由不同的隐式实例表示。 - 我们将
valToString
编写为 Scala 依赖方法,允许我们使用Bool
参数的单例类型x
到 select 隐式StringOrInt
实例si
,它又决定了类型参数T
,它固定了第二个参数的类型v
。 - 我们通过使用 selected
StringOrInt
实例将v
参数提升到允许Scala 模式匹配以细化案例 RHS 上v
的类型。
在 Scala REPL 上执行此操作,
scala> valToString(True)(23)
res0: String = 23
scala> valToString(False)("foo")
res1: String = foo
有很多障碍要跳过,还有很多偶然的复杂性,但是,这是可以做到的。
我注意到 getStringOrInt
示例没有被两个答案中的任何一个实现
getStringOrInt : (x : Bool) -> StringOrInt x
getStringOrInt x = case x of
True => 10
False => "Hello"
我发现 Miles Sabin 的解释非常有用,这种方法是基于他的。 我发现将类似 GADT 的构造与 Scala apply() 技巧分开并尝试将我的代码映射到 Idris/Haskell 概念更直观。我希望其他人觉得这很有用。我在名称中明确使用 GADT 以强调 GADT-ness。 此代码的 2 个成分是: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。这是 Idris 和 Haskell 大放异彩的一个例子。