来自Idris的StringOrInt - > Scala呢?

时间:2015-11-20 04:03:56

标签: scala idris

Type Driven Development with Idris介绍了这个程序:

StringOrInt : Bool -> Type
StringOrInt x = case x of
                  True => Int
                  False => String

如何用Scala编写这样的方法?

3 个答案:

答案 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第一个参数,将第二个参数的类型修复为IntString,并将后者渲染为{{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使用单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-idrishttp://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解决方案的略微修改,它同时实现了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代码变得更加简单,易于阅读和理解。

请注意,valToStringStringOrIntGADT[T]/StringOrIntValue[T]构造函数上匹配,而不是直接在Bool上匹配。那是伊德里斯和哈斯克尔大放异彩的一个例子。