在编译时添加两个相同大小的列表

时间:2016-01-20 01:08:51

标签: scala dependent-type idris

在Idris中,我可以通过以下方法添加两个相同大小的矢量:

module MatrixMath

import Data.Vect

addHelper : (Num n) => Vect k n -> Vect k n -> Vect k n
addHelper = zipWith (+)

在REPL上编译后:

*MatrixMath> :l MatrixMath.idr 
Type checking ./MatrixMath.idr

然后我可以用两个大小为3的向量来调用它:

*MatrixMath> addHelper [1,2,3] [4,5,6]
[5, 7, 9] : Vect 3 Integer

但是,当我尝试在两个不同大小的向量上调用addHelper时,它将无法编译:

*MatrixMath> addHelper [1,2,3] [1]
(input):1:20:When checking argument xs to constructor Data.Vect.:::
        Type mismatch between
                Vect 0 a (Type of [])
        and
                Vect 2 n (Expected type)

        Specifically:
                Type mismatch between
                        0
                and
                        2

我如何在Scala中写这个?

2 个答案:

答案 0 :(得分:6)

对于这类问题,shapeless通常是正确的问题。 Shapeless已经具有类型级数(shapless.Nat)和具有编译时已知大小(shapeless.Sized)的集合的抽象。

实施的第一个看法可能看起来像这样

import shapeless.{ Sized, Nat }
import shapeless.ops.nat.ToInt
import shapeless.syntax.sized._

def Vect[A](n: Nat)(xs: A*)(implicit toInt : ToInt[n.N]) =
  xs.toVector.sized(n).get

def add[A, N <: Nat](left: Sized[Vector[A], N], right: Sized[Vector[A], N])(implicit A: Numeric[A]) =
  Sized.wrap[Vector[A], N]((left, right).zipped.map(A.plus))

及其用法:

scala> add(Vect(3)(1, 2, 3), Vect(3)(4, 5, 6))
res0: shapeless.Sized[Vector[Int],shapeless.Succ[shapeless.Succ[shapeless.Succ[shapeless._0]]]] = Vector(5, 7, 9)

scala> add(Vect(3)(1, 2, 3), Vect(1)(1))
<console>:30: error: type mismatch;
  // long and misleading error message about variance
  // but at least it failed to compile

虽然这看起来很有效,但它有一个严重的缺点 - 您必须确保提供的长度和参数数量匹配,否则您将收到运行时错误。

scala> Vect(1)(1, 2, 3)
java.util.NoSuchElementException: None.get
  at scala.None$.get(Option.scala:347)
  at scala.None$.get(Option.scala:345)
  at .Vect(<console>:27)
  ... 33 elided

我们可以比这更好。您可以直接使用Sized而不是其他构造函数。 此外,如果我们使用两个参数列表定义add,我们可以获得更好的错误消息(它不像Idris提供的那样好,但它可以使用):

import shapeless.{ Sized, Nat }

def add[A, N <: Nat](left: Sized[IndexedSeq[A], N])(right: Sized[IndexedSeq[A], N])(implicit A: Numeric[A]) =
  Sized.wrap[IndexedSeq[A], N]((left, right).zipped.map(A.plus))

// ...

add(Sized(1, 2, 3))(Sized(4, 5, 6))
res0: shapeless.Sized[IndexedSeq[Int],shapeless.nat._3] = Vector(5, 7, 9)

scala> add(Sized(1, 2, 3))(Sized(1))
<console>:24: error: polymorphic expression cannot be instantiated to expected type;
 found   : [CC[_]]shapeless.Sized[CC[Int],shapeless.nat._1]
    (which expands to)  [CC[_]]shapeless.Sized[CC[Int],shapeless.Succ[shapeless._0]]
 required: shapeless.Sized[IndexedSeq[Int],shapeless.nat._3]
    (which expands to)  shapeless.Sized[IndexedSeq[Int],shapeless.Succ[shapeless.Succ[shapeless.Succ[shapeless._0]]]]
       add(Sized(1, 2, 3))(Sized(1))

但我们可以走得更远。 Shapeless还提供元组和Sized之间的转换,因此我们可以写:

import shapeless.{ Sized, Nat }
import shapeless.ops.tuple.ToSized

def Vect[A, P <: Product](xs: P)(implicit toSized: ToSized[P, Vector]) =
  toSized(xs)

def add[A, N <: Nat](left: Sized[Vector[A], N], right: Sized[Vector[A], N])(implicit A: Numeric[A]) =
  Sized.wrap[Vector[A], N]((left, right).zipped.map(A.plus))

这是有效的,大小是从提供的元组推断的:

scala> add(Vect(1, 2, 3), Vect(4, 5, 6))
res0: shapeless.Sized[Vector[Int],shapeless.Succ[shapeless.Succ[shapeless.Succ[shapeless._0]]]] = Vector(5, 7, 9)

scala> add(Vect(1, 2, 3))(Vect(1))
<console>:27: error: type mismatch;
 found   : shapeless.Sized[scala.collection.immutable.Vector[Int],shapeless.Succ[shapeless.Succ[shapeless.Succ[shapeless.Succ[shapeless._0]]]]]
 required: shapeless.Sized[Vector[Int],shapeless.Succ[shapeless.Succ[shapeless.Succ[shapeless._0]]]]
       add(Vect(1, 2, 3))(Vect(4, 5, 6, 7))

不幸的是,该示例的语法仅适用于名为参数自适应的功能,其中scalac会自动将多个参数从Vect转换为我们需要的元组。由于这个“功能”也可能导致非常讨厌的错误,我发现自己几乎总是使用-Yno-adapted-args禁用它。使用这个标志,我们必须自己明确地编写元组:

scala> Vect(1, 2, 3)
<console>:26: warning: No automatic adaptation here: use explicit parentheses.
        signature: Vect[A, P <: Product](xs: P)(implicit toSized: shapeless.ops.tuple.ToSized[P,Vector]): toSized.Out
  given arguments: 1, 2, 3
 after adaptation: Vect((1, 2, 3): (Int, Int, Int))
       Vect(1, 2, 3)
           ^
<console>:26: error: too many arguments for method Vect: (xs: (Int, Int, Int))(implicit toSized: shapeless.ops.tuple.ToSized[(Int, Int, Int),Vector])toSized.Out
       Vect(1, 2, 3)
           ^

scala> Vect((1, 2, 3))
res1: shapeless.Sized[scala.collection.immutable.Vector[Int],shapeless.Succ[shapeless.Succ[shapeless.Succ[shapeless._0]]]] = Vector(1, 2, 3)

scala> add(Vect((1, 2, 3)))(Vect((4, 5, 6)))
res2: shapeless.Sized[Vector[Int],shapeless.Succ[shapeless.Succ[shapeless.Succ[shapeless._0]]]] = Vector(5, 7, 9)

另外,我们只能使用长度达22,scala不支持更大的元组。

scala> Vect((1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23))
<console>:26: error: object <none> is not a member of package scala
       Vect((1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23))

那么,我们可以获得更好的语法吗?事实证明,我们可以。 Shapeless可以为我们做包装,而不是使用HList:

import shapeless.ops.hlist.ToSized
import shapeless.{ ProductArgs, HList, Nat, Sized }

object Vect extends ProductArgs {
  def applyProduct[L <: HList](l: L)(implicit toSized: ToSized[L, Vector]) =
    toSized(l)
}

def add[A, N <: Nat](left: Sized[Vector[A], N])(right: Sized[Vector[A], N])(implicit A: Numeric[A]) =
  Sized.wrap[Vector[A], N]((left, right).zipped.map(A.plus))

它有效:

scala> add(Vect(1, 2, 3))(Vect(4, 5, 6))
res0: shapeless.Sized[Vector[Int],shapeless.Succ[shapeless.Succ[shapeless.Succ[shapeless._0]]]] = Vector(5, 7, 9)

scala> add(Vect(1, 2, 3))(Vect(1))
<console>:27: error: type mismatch;
 found   : shapeless.Sized[scala.collection.immutable.Vector[Int],shapeless.Succ[shapeless._0]]
 required: shapeless.Sized[Vector[Int],shapeless.Succ[shapeless.Succ[shapeless.Succ[shapeless._0]]]]
       add(Vect(1, 2, 3))(Vect(1))
                              ^

scala> Vect(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23)
res2: shapeless.Sized[scala.collection.immutable.Vector[Int],shapeless.Succ[shapeless.Succ[... long type elided... ]]] = Vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23)

你可以从那里继续前进,将Sized包裹在你自己的班级中,例如

import shapeless.ops.hlist.ToSized
import shapeless.{ ProductArgs, HList, Nat, Sized }

object Vect extends ProductArgs {
  def applyProduct[L <: HList](l: L)(implicit toSized: ToSized[L, Vector]): Vect[toSized.Lub, toSized.N] =
    new Vect(toSized(l))
}

class Vect[A, N <: Nat] private (val self: Sized[Vector[A], N]) extends Proxy.Typed[Sized[Vector[A], N]] {
  def add(other: Vect[A, N])(implicit A: Numeric[A]): Vect[A, N] =
    new Vect(Sized.wrap[Vector[A], N]((self, other.self).zipped.map(A.plus)))
}

// ...

scala> Vect(1, 2, 3) add Vect(4, 5, 6)
res0: Vect[Int,shapeless.Succ[shapeless.Succ[shapeless.Succ[shapeless._0]]]] = Vector(5, 7, 9)

scala> Vect(1, 2, 3) add Vect(1)
<console>:26: error: type mismatch;
 found   : Vect[Int,shapeless.Succ[shapeless._0]]
 required: Vect[Int,shapeless.Succ[shapeless.Succ[shapeless.Succ[shapeless._0]]]]
       Vect(1, 2, 3) add Vect(1)

基本上,所有归结为使用SizedNat进行类型约束。

答案 1 :(得分:4)

Shapeless你可以帮助你:

import shapeless._
import syntax.sized._

def row(cols : Seq[String]) = cols.mkString("\"", "\", \"", "\"")

def csv[N <: Nat](hdrs : Sized[Seq[String], N], rows : List[Sized[Seq[String], N]]) =
    row(hdrs) :: rows.map(row(_))

val hdrs = Sized("Title", "Author") // Sized[IndexedSeq[String], _2]
val rows = List(                  // List[Sized[IndexedSeq[String], _2]]
  Sized("Types and Programming Languages", "Benjamin Pierce"),
  Sized("The Implementation of Functional Programming Languages", "Simon Peyton-Jones")
)

 // hdrs and rows statically known to have the same number of columns
val formatted = csv(hdrs, rows)

请注意方法csv如何Sized限制N <: Nat Num n,与您var evaluationSet = (BasicMLDataSet)EncogUtility.LoadCSV2Memory(Config.EvaluationNormalizedFile.ToString(), network.InputCount, network.OutputCount, true, CSVFormat.English, false); var analyst = new EncogAnalyst(); analyst.Load(Config.NormalizationAnalystFile); // Change this to whatever your output field index is int outputFieldIndex = 29; using (var resultsFile = new System.IO.StreamWriter(Config.TestResultsFile.ToString())) { foreach (var item in evaluationSet) { var normalizedActualOuput = (BasicMLData)network.Compute(item.Input); var actualOutput = analyst.Script.Normalize.NormalizedFields[outputFieldIndex].DeNormalize(normalizedActualOuput.Data[0]); var idealOutput = analyst.Script.Normalize.NormalizedFields[outputFieldIndex].DeNormalize(item.Ideal[0]); var resultLine = String.Format("{0},{1}", idealOutput, actualOutput); resultsFile.WriteLine(resultLine); } } 约束的示例相同。

我从Shapeless examples复制了这段代码,如果它没有按照它的方式编译,我很可能错过了一些东西。