来自 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 来使用 truefalse 的单例类型。来自 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 第一个参数,将第二个参数的类型固定为 IntString 并将后者呈现为 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.typeFalse.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 的解决方案,它实现了 getStringOrIntvalToString

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
 }

所有这些工作使我能够实现 getStringOrIntvalToString

的体面版本
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 大放异彩的一个例子。