I'm learning Chisel following the documentation on Github
Thus far, everything worked flawlessly. But i'm stuck at chapter 13, "Functional Module Creation"
I can't get the code to work. I created all my .scala classes in a copy of the chisel-template-project. Here is what i wrote / copied to create a Mux4 with variable bit width:
/chisel-template/src/main/scala/Mux4.scala
import Chisel._
class Mux4(w: Int) extends Module {
val io = IO(new Bundle {
val sel = UInt(INPUT, 2)
val in0 = UInt(INPUT, w)
val in1 = UInt(INPUT, w)
val in2 = UInt(INPUT, w)
val in3 = UInt(INPUT, w)
val out = UInt(OUTPUT, w)
})
io.out := Mux2(io.sel(1),
Mux2(io.sel(0), io.in0, io.in1),
Mux2(io.sel(0), io.in2, io.in3))
}
class Mux2(w: Int) extends Module {
val io = IO(new Bundle {
val sel = Bool(INPUT)
val in0 = UInt(INPUT, w)
val in1 = UInt(INPUT, w)
val out = UInt(OUTPUT, w)
})
when(io.sel) {
io.out := io.in0
}.otherwise {
io.out := io.in1
}
}
object Mux2 {
def apply(sel: UInt, in0: UInt, in1: UInt): UInt = {
val m = new Mux2(in0.getWidth)
m.io.sel := sel.toBool()
m.io.in0 := in0
m.io.in1 := in1
m.io.out
}
}
The Tester scala class i wrote:
/chisel-template/src/test/scala/Mux4Test.scala
import Chisel.iotesters.{ChiselFlatSpec, Driver, PeekPokeTester}
class Mux4Test(c: Mux4) extends PeekPokeTester(c) {
val sel = 3
val (in0, in1, in2, in3) = (5, 7, 11, 15)
poke(c.io.sel, sel)
poke(c.io.in0, in0)
poke(c.io.in1, in1)
poke(c.io.in2, in2)
poke(c.io.in3, in3)
step(1)
System.out.println("Circuit: "+peek(c.io.out)
+" Expected: "+TestMux4.result(sel, in0, in1, in2, in3))
}
object TestMux4{
def result(sel: Int, in0: Int, in1: Int, in2: Int, in3: Int): Int = {
val out = sel match{
case 0 => in3
case 1 => in2
case 2 => in1
case 3 => in0
}
out
}
}
class Mux4Tester extends ChiselFlatSpec {
behavior of "Mux4"
backends foreach {backend =>
it should s"do Mux4 $backend" in {
Driver(() => new Mux4(4), backend)(c => new Mux4Test(c)) should be (true)
}
}
}
The important part from the output
STEP 0 -> 1
Circuit: 0 Expected: 5
The Mux4 class (Circuit) returns 0 as output, whereas it should be 5, because the selection process is as follows:
00 -> io.out = in3 = 15
01 -> io.out = in2 = 11
10 -> io.out = in1 = 7
11 -> io.out = in0 = 5
In the Mux4Test.scala class i wrote val sel = 3. The bit representation of this is 11 and therefore i'd expect in0 = 5.
Where am i wrong?
Thank you for your interest in Chisel!
I ran your example, and after scratching my head for a while I found the problem: when you instantiate a Chisel Module, you need to make sure to wrap it in Module(...) (EDIT: The code on the wiki omitted this wrapper. This has been fixed). Thus, object Mux2 should instead be:
object Mux2 {
def apply(sel: UInt, in0: UInt, in1: UInt): UInt = {
val m = Module(new Mux2(in0.getWidth)) // <- See Here
m.io.sel := sel.toBool()
m.io.in0 := in0
m.io.in1 := in1
m.io.out
}
}
With this change, it looks like the code works!
didn't read all your code, but I think the Mux2 arguments are in the wrong order here:
Mux2(io.sel(0), io.in0, io.in1)
Related
I have many modules with multiple parameters. Take as a toy example a modified version of the GCD in the template:
class GCD (len: Int = 16, validHigh: Boolean = true) extends Module {
val io = IO(new Bundle {
val value1 = Input(UInt(len.W))
val value2 = Input(UInt(len.W))
val loadingValues = Input(Bool())
val outputGCD = Output(UInt(len.W))
val outputValid = Output(Bool())
})
val x = Reg(UInt())
val y = Reg(UInt())
when(x > y) { x := x - y }
.otherwise { y := y - x }
when(io.loadingValues) {
x := io.value1
y := io.value2
}
io.outputGCD := x
if (validHigh) {
io.outputValid := (y === 0.U)
} else {
io.outputValid := (y =/= 0.U)
}
}
To test or synthesize many different designs, I want to change the values from the command line when I call the tester or the generator apps. Preferably, like this:
[generation or test command] --len 12 --validHigh false
but this or something similar would also be okay
[generation or test command] --param "len=12" --param "validHigh=false"
After some trial and error, I came up with a solution that looks like this:
gcd.scala
package gcd
import firrtl._
import chisel3._
case class GCDConfig(
len: Int = 16,
validHigh: Boolean = true
)
class GCD (val conf: GCDConfig = GCDConfig()) extends Module {
val io = IO(new Bundle {
val value1 = Input(UInt(conf.len.W))
val value2 = Input(UInt(conf.len.W))
val loadingValues = Input(Bool())
val outputGCD = Output(UInt(conf.len.W))
val outputValid = Output(Bool())
})
val x = Reg(UInt())
val y = Reg(UInt())
when(x > y) { x := x - y }
.otherwise { y := y - x }
when(io.loadingValues) {
x := io.value1
y := io.value2
}
io.outputGCD := x
if (conf.validHigh) {
io.outputValid := y === 0.U
} else {
io.outputValid := y =/= 0.U
}
}
trait HasParams {
self: ExecutionOptionsManager =>
var params: Map[String, String] = Map()
parser.note("Design Parameters")
parser.opt[Map[String, String]]('p', "params")
.valueName("k1=v1,k2=v2")
.foreach { v => params = v }
.text("Parameters of Design")
}
object GCD {
def apply(params: Map[String, String]): GCD = {
new GCD(params2conf(params))
}
def params2conf(params: Map[String, String]): GCDConfig = {
var conf = new GCDConfig
for ((k, v) <- params) {
(k, v) match {
case ("len", _) => conf = conf.copy(len = v.toInt)
case ("validHigh", _) => conf = conf.copy(validHigh = v.toBoolean)
case _ =>
}
}
conf
}
}
object GCDGen extends App {
val optionsManager = new ExecutionOptionsManager("gcdgen")
with HasChiselExecutionOptions with HasFirrtlOptions with HasParams
optionsManager.parse(args) match {
case true =>
chisel3.Driver.execute(optionsManager, () => GCD(optionsManager.params))
case _ =>
ChiselExecutionFailure("could not parse results")
}
}
and for tests
GCDSpec.scala
package gcd
import chisel3._
import firrtl._
import chisel3.tester._
import org.scalatest.FreeSpec
import chisel3.experimental.BundleLiterals._
import chiseltest.internal._
import chiseltest.experimental.TestOptionBuilder._
object GCDTest extends App {
val optionsManager = new ExecutionOptionsManager("gcdtest") with HasParams
optionsManager.parse(args) match {
case true =>
//println(optionsManager.commonOptions.programArgs)
(new GCDSpec(optionsManager.params)).execute()
case _ =>
ChiselExecutionFailure("could not parse results")
}
}
class GCDSpec(params: Map[String, String] = Map()) extends FreeSpec with ChiselScalatestTester {
"Gcd should calculate proper greatest common denominator" in {
test(GCD(params)) { dut =>
dut.io.value1.poke(95.U)
dut.io.value2.poke(10.U)
dut.io.loadingValues.poke(true.B)
dut.clock.step(1)
dut.io.loadingValues.poke(false.B)
while (dut.io.outputValid.peek().litToBoolean != dut.conf.validHigh) {
dut.clock.step(1)
}
dut.io.outputGCD.expect(5.U)
}
}
}
This way, I can generate different designs and test them with
sbt 'runMain gcd.GCDGen --params "len=12,validHigh=false"'
sbt 'test:runMain gcd.GCDTest --params "len=12,validHigh=false"'
But there are a couple of problems or annoyances with this solution:
It uses deprecated features (ExecutionOptionsManager and HasFirrtlOptions). I'm not sure if this solution is portable to the new FirrtlStage Infrastructure.
There is a lot of boilerplate involved. It becomes tedious to write new case classes and params2conf functions for every module and rewrite both when a parameter is added or removed.
Using conf.x instead of x all the time. But I guess, this is unavoidable because there is nothing like python's kwargs in Scala.
Is there a better way or one that is at least not deprecated?
Good Question.
I think you are you have pretty much everything right. I don't usually find that I need the command line to alter my tests, my development cycle usually is just poking values in the test params directly running. I use intelliJ which seems to make that easy (but may only work for my habits and the scale of projects I work on).
But I would like to offer you a suggestions that will get you away from ExecutionOptions style as that is going away fast.
In my example code below I offer basically two files here in line, in the first there a few library like tools that use the modern annotation idioms and, I believe, minimize boiler plate. They rely on stringy matching but that is fixable.
In the second, is your GCD, GCDSpec, slightly modified to pull out the params a bit differently. At the bottom of the second is some very minimal boiler plate that allows you to get the command line access you want.
Good luck, I hope this is mostly self explanatory.
First file:
import chisel3.stage.ChiselCli
import firrtl.AnnotationSeq
import firrtl.annotations.{Annotation, NoTargetAnnotation}
import firrtl.options.{HasShellOptions, Shell, ShellOption, Stage, Unserializable}
import firrtl.stage.FirrtlCli
trait TesterAnnotation {
this: Annotation =>
}
case class TestParams(params: Map[String, String] = Map.empty) {
val defaults: collection.mutable.HashMap[String, String] = new collection.mutable.HashMap()
def getInt(key: String): Int = params.getOrElse(key, defaults(key)).toInt
def getBoolean(key: String): Boolean = params.getOrElse(key, defaults(key)).toBoolean
def getString(key: String): String = params.getOrElse(key, defaults(key))
}
case class TesterParameterAnnotation(paramString: TestParams)
extends TesterAnnotation
with NoTargetAnnotation
with Unserializable
object TesterParameterAnnotation extends HasShellOptions {
val options = Seq(
new ShellOption[Map[String, String]](
longOption = "param-string",
toAnnotationSeq = (a: Map[String, String]) => Seq(TesterParameterAnnotation(TestParams(a))),
helpText = """a comma separated, space free list of additional paramters, e.g. --param-string "k1=7,k2=dog" """
)
)
}
trait TesterCli {
this: Shell =>
Seq(TesterParameterAnnotation).foreach(_.addOptions(parser))
}
class GenericTesterStage(thunk: (TestParams, AnnotationSeq) => Unit) extends Stage {
val shell: Shell = new Shell("chiseltest") with TesterCli with ChiselCli with FirrtlCli
def run(annotations: AnnotationSeq): AnnotationSeq = {
val params = annotations.collectFirst { case TesterParameterAnnotation(p) => p }.getOrElse(TestParams())
thunk(params, annotations)
annotations
}
}
Second File:
import chisel3._
import chisel3.tester._
import chiseltest.experimental.TestOptionBuilder._
import chiseltest.{ChiselScalatestTester, GenericTesterStage, TestParams}
import firrtl._
import firrtl.options.StageMain
import org.scalatest.freespec.AnyFreeSpec
case class GCD(testParams: TestParams) extends Module {
val bitWidth = testParams.getInt("len")
val validHigh = testParams.getBoolean("validHigh")
val io = IO(new Bundle {
val value1 = Input(UInt(bitWidth.W))
val value2 = Input(UInt(bitWidth.W))
val loadingValues = Input(Bool())
val outputGCD = Output(UInt(bitWidth.W))
val outputValid = Output(Bool())
})
val x = Reg(UInt())
val y = Reg(UInt())
when(x > y) { x := x - y }.otherwise { y := y - x }
when(io.loadingValues) {
x := io.value1
y := io.value2
}
io.outputGCD := x
if (validHigh) {
io.outputValid := y === 0.U
} else {
io.outputValid := y =/= 0.U
}
}
class GCDSpec(params: TestParams, annotations: AnnotationSeq = Seq()) extends AnyFreeSpec with ChiselScalatestTester {
"Gcd should calculate proper greatest common denominator" in {
test(GCD(params)).withAnnotations(annotations) { dut =>
dut.io.value1.poke(95.U)
dut.io.value2.poke(10.U)
dut.io.loadingValues.poke(true.B)
dut.clock.step(1)
dut.io.loadingValues.poke(false.B)
while (dut.io.outputValid.peek().litToBoolean != dut.validHigh) {
dut.clock.step(1)
}
dut.io.outputGCD.expect(5.U)
}
}
}
class GcdTesterStage
extends GenericTesterStage((params, annotations) => {
params.defaults ++= Seq("len" -> "16", "validHigh" -> "false")
(new GCDSpec(params, annotations)).execute()
})
object GcdTesterStage extends StageMain(new GcdTesterStage)
Based on http://blog.echo.sh/2013/11/04/exploring-scala-macros-map-to-case-class-conversion.html, I was able to find another way of removing the params2conf boilerplate using scala macros. I also extended Chick's answer with verilog generation since that was also part of the original question. A full repository of my solution can be found on github.
Basically there are three four files:
The macro that converts a map to a case class:
package mappable
import scala.language.experimental.macros
import scala.reflect.macros.whitebox.Context
trait Mappable[T] {
def toMap(t: T): Map[String, String]
def fromMap(map: Map[String, String]): T
}
object Mappable {
implicit def materializeMappable[T]: Mappable[T] = macro materializeMappableImpl[T]
def materializeMappableImpl[T: c.WeakTypeTag](c: Context): c.Expr[Mappable[T]] = {
import c.universe._
val tpe = weakTypeOf[T]
val companion = tpe.typeSymbol.companion
val fields = tpe.decls.collectFirst {
case m: MethodSymbol if m.isPrimaryConstructor => m
}.get.paramLists.head
val (toMapParams, fromMapParams) = fields.map { field =>
val name = field.name.toTermName
val decoded = name.decodedName.toString
val returnType = tpe.decl(name).typeSignature
val fromMapLine = returnType match {
case NullaryMethodType(res) if res =:= typeOf[Int] => q"map($decoded).toInt"
case NullaryMethodType(res) if res =:= typeOf[String] => q"map($decoded)"
case NullaryMethodType(res) if res =:= typeOf[Boolean] => q"map($decoded).toBoolean"
case _ => q""
}
(q"$decoded -> t.$name.toString", fromMapLine)
}.unzip
c.Expr[Mappable[T]] { q"""
new Mappable[$tpe] {
def toMap(t: $tpe): Map[String, String] = Map(..$toMapParams)
def fromMap(map: Map[String, String]): $tpe = $companion(..$fromMapParams)
}
""" }
}
}
Library like tools:
package cliparams
import chisel3.stage.{ChiselStage, ChiselGeneratorAnnotation, ChiselCli}
import firrtl.AnnotationSeq
import firrtl.annotations.{Annotation, NoTargetAnnotation}
import firrtl.options.{HasShellOptions, Shell, ShellOption, Stage, Unserializable, StageMain}
import firrtl.stage.FirrtlCli
import mappable._
trait SomeAnnotaion {
this: Annotation =>
}
case class ParameterAnnotation(map: Map[String, String])
extends SomeAnnotaion
with NoTargetAnnotation
with Unserializable
object ParameterAnnotation extends HasShellOptions {
val options = Seq(
new ShellOption[Map[String, String]](
longOption = "params",
toAnnotationSeq = (a: Map[String, String]) => Seq(ParameterAnnotation(a)),
helpText = """a comma separated, space free list of additional paramters, e.g. --param-string "k1=7,k2=dog" """
)
)
}
trait ParameterCli {
this: Shell =>
Seq(ParameterAnnotation).foreach(_.addOptions(parser))
}
class GenericParameterCliStage[P: Mappable](thunk: (P, AnnotationSeq) => Unit, default: P) extends Stage {
def mapify(p: P) = implicitly[Mappable[P]].toMap(p)
def materialize(map: Map[String, String]) = implicitly[Mappable[P]].fromMap(map)
val shell: Shell = new Shell("chiseltest") with ParameterCli with ChiselCli with FirrtlCli
def run(annotations: AnnotationSeq): AnnotationSeq = {
val params = annotations
.collectFirst {case ParameterAnnotation(map) => materialize(mapify(default) ++ map.toSeq)}
.getOrElse(default)
thunk(params, annotations)
annotations
}
}
The GCD source file
// See README.md for license details.
package gcd
import firrtl._
import chisel3._
import chisel3.stage.{ChiselStage, ChiselGeneratorAnnotation}
import firrtl.options.{StageMain}
// Both have to be imported
import mappable._
import cliparams._
case class GCDConfig(
len: Int = 16,
validHigh: Boolean = true
)
/**
* Compute GCD using subtraction method.
* Subtracts the smaller from the larger until register y is zero.
* value in register x is then the GCD
*/
class GCD (val conf: GCDConfig = GCDConfig()) extends Module {
val io = IO(new Bundle {
val value1 = Input(UInt(conf.len.W))
val value2 = Input(UInt(conf.len.W))
val loadingValues = Input(Bool())
val outputGCD = Output(UInt(conf.len.W))
val outputValid = Output(Bool())
})
val x = Reg(UInt())
val y = Reg(UInt())
when(x > y) { x := x - y }
.otherwise { y := y - x }
when(io.loadingValues) {
x := io.value1
y := io.value2
}
io.outputGCD := x
if (conf.validHigh) {
io.outputValid := y === 0.U
} else {
io.outputValid := y =/= 0.U
}
}
class GCDGenStage extends GenericParameterCliStage[GCDConfig]((params, annotations) => {
(new chisel3.stage.ChiselStage).execute(
Array("-X", "verilog"),
Seq(ChiselGeneratorAnnotation(() => new GCD(params))))}, GCDConfig())
object GCDGen extends StageMain(new GCDGenStage)
and the tests
// See README.md for license details.
package gcd
import chisel3._
import firrtl._
import chisel3.tester._
import org.scalatest.FreeSpec
import chisel3.experimental.BundleLiterals._
import chiseltest.internal._
import chiseltest.experimental.TestOptionBuilder._
import firrtl.options.{StageMain}
import mappable._
import cliparams._
class GCDSpec(params: GCDConfig, annotations: AnnotationSeq = Seq()) extends FreeSpec with ChiselScalatestTester {
"Gcd should calculate proper greatest common denominator" in {
test(new GCD(params)) { dut =>
dut.io.value1.poke(95.U)
dut.io.value2.poke(10.U)
dut.io.loadingValues.poke(true.B)
dut.clock.step(1)
dut.io.loadingValues.poke(false.B)
while (dut.io.outputValid.peek().litToBoolean != dut.conf.validHigh) {
dut.clock.step(1)
}
dut.io.outputGCD.expect(5.U)
}
}
}
class GCDTestStage extends GenericParameterCliStage[GCDConfig]((params, annotations) => {
(new GCDSpec(params, annotations)).execute()}, GCDConfig())
object GCDTest extends StageMain(new GCDTestStage)
Both, generation and tests can be parameterized via CLI as in the OQ:
sbt 'runMain gcd.GCDGen --params "len=12,validHigh=false"'
sbt 'test:runMain gcd.GCDTest --params "len=12,validHigh=false"'
I am trying to implement a method that should take an arbitrary method or code block and it should convert the method or code-block to retry-able method.
Following example is intended to demonstrate what I need
import scala.util.{Try,Success,Failure}
object Retry {
retry[A, B](f: A => Try[B], r: Int): A => Try[B] = {
// return a function g and when g is invoked with parameters
// f should be tried (if failed) r number of time otherwise
// result of first successful execution of f should be returned
// from g. retry should work with any arbitrary function with
// any number/type of parameters
}
}
If you want to abstract over arity, which is pretty advanced, you'll have to use shapeless, a library for generic programming.
Building on #chengpohi's answer:
import shapeless._, ops.function._
import scala.util.Try
def retry[F, L <: HList, R](f: F, r: Int = 1)(implicit fnToP: FnToProduct.Aux[F, L => R], fnFromP: FnFromProduct.Aux[L => R, F]): F = {
val fn = fnToP(f)
def repeat(a: L): R = {
for (_ <- 0 to r) {
val tried = Try(fn(a))
if (tried.isSuccess) {
return tried.get
}
}
throw new RuntimeException(s"retry $r failed")
}
fnFromP(repeat _)
}
It works:
scala> var i = 0
i: Int = 0
scala> val f = retry( (a: Int) => if (i < 10) {i += 1; println(s"try $i"); throw new RuntimeException} else a * 3, 42)
f: Int => Int = shapeless.ops.FnFromProductInstances$$anon$2$$Lambda$1489/1404497488#1d49a23c
scala> f(5)
try 1
try 2
try 3
try 4
try 5
try 6
try 7
try 8
try 9
try 10
res4: Int = 15
scala> var i = 0
i: Int = 0
scala> val f = retry( (a: String, b: Int) => if (i < 10) {i += 1; println(s"try $i"); throw new RuntimeException} else a * b, 42)
f: (String, Int) => String = shapeless.ops.FnFromProductInstances$$anon$3$$Lambda$1492/121867201#1a22b89c
scala> f("foo", 5)
try 1
try 2
try 3
try 4
try 5
try 6
try 7
try 8
try 9
try 10
res5: String = foofoofoofoofoo
def retry[A, B](f: A => B, r: Int = 1): A => B = {
def repeat(a: A): B = {
for (_ <- 0 to r) {
val tried = Try(f(a))
if (tried.isSuccess) {
return tried.get
}
}
throw new RuntimeException(s"retry $r failed")
}
repeat
}
There is a simple way to do this, try if Success and return, otherwise throw Exception
I want to write some mergesort function.
How to supply Ordering[T] to merge subfunction?
The overall structure of application is the following:
object Main extends App {
...
val array: Array[Int] = string.split(' ').map(_.toInt)
def mergesort[T](seq: IndexedSeq[T]): IndexedSeq[T] = {
def mergesortWithIndexes(seq: IndexedSeq[T],
startIdx: Int, endIdx: Int): IndexedSeq[T] = {
import Helpers.append
val seqLength = endIdx - startIdx
val splitLength = seq.length / 2
val (xs, ys) = seq.splitAt(splitLength)
val sortXs = mergesortWithIndexes(xs, startIdx, startIdx + seqLength)
val sortYs = mergesortWithIndexes(ys, startIdx + seqLength, endIdx)
def merge(sortXs: IndexedSeq[T], sortYs: IndexedSeq[T],
writeFun: Iterable[CharSequence] => Path)(ord: math.Ordering[T]): IndexedSeq[T] = {
...
while (firstIndex < firstLength || secondIndex < secondLength) {
if (firstIndex == firstLength)
buffer ++ sortYs
else if (secondIndex == secondLength)
buffer ++ sortXs
else {
if (ord.lteq(minFirst, minSecond)) {
...
} else {
...
}
}
}
buffer.toIndexedSeq
}
merge(sortXs, sortYs, append(output))
}
mergesortWithIndexes(seq, 0, seq.length)
}
val outSeq = mergesort(array)
Helpers.write(output)(Vector(outSeq.mkString(" ")))
}
I want to have general merge() function definition, but in application I use IndexedSeq[Int] and thus expecting pass predefined Ordering[Int].
Adding implicit Ordering[T] parameter to the outermost function should fix the problem, and passing non Ordering[T] arguments will result in compile error.
Scala's sort functions do the same thing: https://github.com/scala/scala/blob/2.12.x/src/library/scala/collection/SeqLike.scala#L635
def mergesort[T](seq: IndexedSeq[T])(implicit ord: math.Ordering[T]): IndexedSeq[T] = {
Background
I started out with a Shuffler class that does two things:
Shuffles n:Int indexes
Puts them into n_tranches:Int
I am trying to refactor this code such that almost the entire implementation is in Trancheur, which puts the indexes into n_tranches.
For example, I may want to put 50 cards into 6 stacks, which I call tranches.
Original Code
class Shuffler( n:Int, n_tranches:Int )
{
val v = scala.util.Random.shuffle( (0 to n-1).toVector )
// returns tranche[0,n_tranches-1] which we live in
def tranche( i:Int ) = idxs(i).map( v ).sorted.toVector
private val idxs = cut( 0 to (n-1), n_tranches ).toVector
private def cut[A](xs: Seq[A], n: Int) = {
val (quot, rem) = (xs.size / n, xs.size % n)
val (smaller, bigger) = xs.splitAt(xs.size - rem * (quot + 1))
smaller.grouped(quot) ++ bigger.grouped(quot + 1)
}
}
New Code
class Shuffler( n:Int, n_tranches:Int )
extends Trancheur( n, n_tranches, scala.util.Random.shuffle )
{
}
class Trancheur( n:Int, n_tranches:Int, shuffler ) // WHAT SHOULD I PUT HERE?!?!?!?
{
val v = shuffler( (0 to n-1).toVector )
// returns tranche[0,n_tranches-1] which we live in
def tranche( i:Int ) = idxs(i).map( v ).sorted.toVector
private val idxs = cut( 0 to (n-1), n_tranches ).toVector
private def cut[A](xs: Seq[A], n: Int) = {
val (quot, rem) = (xs.size / n, xs.size % n)
val (smaller, bigger) = xs.splitAt(xs.size - rem * (quot + 1))
smaller.grouped(quot) ++ bigger.grouped(quot + 1)
}
}
Problem
I want Shuffler to call Trancheur with the functor scala.util.Random.shuffle. I think the call is fine.
But as a default, I want the Trancheur to have an identity functor which does nothing: it just returns the same results as before. I am having trouble with the constructor signature and with what to define as the identity functor.
NOTE: I apologize in advance if I have used the wrong term in calling scala.util.Random.shuffle a functor - that's what we call it in C++. Not sure if Functor means something else in Scala.
shuffle is a function. So shuffler (the parameter) should expect a function. For your case Seq[Int] => Seq[Int] should be sufficient. Scala also provides a predefined identity function.
This should do it:
class Trancheur( n:Int, n_tranches:Int, shuffler: Seq[Int] => Seq[Int] = identity)
I'd love to have let construct similar to the one in Haskell in Scala. I tried a few ways, but none seems to be good. Here's some code:
object CustomLet extends App {
val data = for (i <- 1 to 1024; j <- 1 to 512) yield (i % j) * i * (i + 1) - 1
def heavyCalc() = { println("heavyCalc called"); data.sum }
def doSomethingWithRes(res: Int) = {
println(s"${res * res}")
1
}
def cond(value: Int): Boolean = value > 256
// not really usable, even though it's an expression (2x heavyCalc calls)
def withoutLet() = if (cond(heavyCalc())) doSomethingWithRes(heavyCalc()) else 0
// not an expression
def letWithVal(): Int = {
val res = heavyCalc()
if (cond(res)) doSomethingWithRes(res)
else 0
}
// a lot of code to simulate "let", at least it is an expression
def letWithMatch(): Int = heavyCalc() match {
case res => if (cond(res)) doSomethingWithRes(res) else 0
}
// not perfect solution from
// http://stackoverflow.com/questions/3241101/with-statement-equivalent-for-scala/3241249#3241249
def let[A, B](param: A)(body: A => B): B = body(param)
// not bad, but I'm not sure if it could handle more bindings at once
def letWithApp(): Int = let(heavyCalc()) {res => if (cond(res)) doSomethingWithRes(res) else 0}
List[(String, () => Int)](
("withoutLet", withoutLet),
("letWithVal", letWithVal),
("letWithMatch", letWithMatch),
("letWithApp", letWithApp)
).foreach(
item => item match {
case (title, func) => {
println(s"executing $title")
val ret = func()
println(s"$title finished with $ret")
println()
}
}
)
}
This is the ideal look of it (with only one binding, more could be separated by ,; not sure about the in keyword):
// desired look
def letTest(): Int =
let res = heavyCalc() in
if (cond(res)) doSomethingWithRes(res) else 0
I'm not sure if it's possible, but I have no experience with most of advanced Scala stuff like macros, so I can't really tell.
EDIT1: To be clear, the main things I'm expecting from it are: being expression and relatively simple syntax (like the one outlined above).
You could use a forward pipe:
object ForwardPipeContainer {
implicit class ForwardPipe[A](val value: A) extends AnyVal {
def |>[B](f: A => B): B = f(value)
}
}
to be used like this:
import ForwardPipeContainer._
def f(i: Int) = i * i
println( f(3) |> (x => x * x) )
You can put multiple arguments in a tuple:
println( (f(2), f(3)) |> (x => x._1 * x._2) )
which looks better if combined with partial function synatx:
println( (f(2), f(3)) |> { case (x, y) => x * y } )
This answer is a variation of What is a good way of reusing function result in Scala, and both are based on Cache an intermediate variable in an one-liner where I got the initial idea from.
def letTest(): Int =
let res = heavyCalc() in
if (cond(res)) doSomethingWithRes(res) else 0
I would write this:
def letTest(): Int = {
val res = heavyCalc()
if (cond(res)) doSomethingWithRes(res) else 0
}
Ignoring laziness, let is just a construct that introduces a lexical scope, binds some terms to some names then returns an expression. So in Scala you would do
{ // new lexical scope
// bind terms section
val a = f()
def b = a + g() // may be I don't want g to be evaluated unless b is needed
val c = h()
// result expression
if (c) b else a
}
Macros should be able to enforce this syntactic layout if you want to ensure that there is nothing else going on in the block. There is actually a SIP (Scala Improvement Process) proposal called Spores that would enforce some of the same constraints (and an additional one: that you don't capture a reference of an enclosing object unknowingly).
Note that blocks in Scala are expressions that evaluate to the last expression in the block. So let me take a random let example from Haskell:
aaa = let y = 1+2
z = 4+6
in let f = 3
e = 3
in e+f
This translates to:
val aaa = {
val y = 1 + 2
val z = 4 + 6
val u = {
val f = 3
val e = 3
e + f
}
u
}
As you can see the block statement can be used as an expression.