Scala中的syb`mkT`函数

1l5u6lss  于 2022-11-09  发布在  Scala
关注(0)|答案(2)|浏览(156)

从我的previous question开始,我尝试在Scala 3中实现Scrap Your Boilerplate,现在遇到了本文中描述的mkT函数的问题。假设cast的定义如下:

trait Cast[A, B]:
    def apply(a: A): Option[B]

  object Cast:
    given cSome[A, B](using t: A =:= B): Cast[A, B] with
      def apply(a: A) = Some(t(a))

    given cNone[A, B](using t: NotGiven[A =:= B]): Cast[A, B] with
      def apply(a: A) = None

    def cast[A, B](a: A)(using c: Cast[A, B]): Option[B] = c(a)

我试着让mkT如下所示:

class MakeTransform[A] (val f: A => A) {
    def apply[B](b: B)(using c: Cast[A => A, B => B]): B = c(f) match {
      case Some(fb) => fb(b)
      case _ => b
    }
  }

  def mkT[A](f: A => A): MakeTransform[A] = MakeTransform(f)

这似乎适用于布尔型示例:

def not(a: Boolean): Boolean = !a

mkT(not)(true) // false, function is clearly called on input value
mkT(not)('a') // 'a'

但是,当我尝试使用公司模型对象时,只有当我提供显式类型调用并且参数与该类型匹配时,才能使其按预期运行。因此,给定以下Salary定义:

sealed trait Salary
case class S(amt: Float) extends Salary

def incS(amt: Float): Salary => Salary = {
  case S(a) => S(a * (1 + amt))
}

val ralf: Employee = E(P("Ralf", "Amsterdam"), S(8000))

我尝试引发Salary

inc(.1)(S(8000)) // S(8000) <= no change

但是,除非我明确指出以下类型:

inc(.1)[Salary](S(8000)) // S(8800.0)

但当我这样做时,我只能将指定类型的对象作为输入传递:

inc(.1)[Salary](ralf) // does not compile

这显然违背了目的。
我的想法是,因为MakeTransformapply方法接受类型参数,所以输入类型将通过传递给它的值来推断,但情况似乎并不总是这样。更让我困惑的是BooleanSalary示例之间的不一致行为。你知道为什么吗?另外,在调试这样的事情时,是否有方法可以看到推断的类型?调试器会显示变量的运行时类型,但如果有方法在运行时查看哪些类型参数,这将是很有帮助的。
更新:新的想法,这是否与S <: Salary而不是S =:= Salary有关?

mcvgt66p

mcvgt66p1#

您似乎又错过了一个隐式参数(Haskell术语中的约束)

inc :: Typeable a => Float -> a -> a
--     ^^^^^^^^^^
inc k = mkT (incS k)

会商

def inc[A](amt: Float): A => A = mkT(incS(amt))(_)

inc(.1)(S(8000)) // S(8000.0) -- not changed

使用

def inc[A](amt: Float)(using c: Cast[Salary => Salary, A => A]): A => A = mkT(incS(amt))(_)
//                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

inc(.1)(S(8000)) // S(8800.0) -- changed

整个代码
https://scastie.scala-lang.org/DmytroMitin/v82LGbOtRieGmJX7gCb99A/1
关于调试,您可以打开

scalacOptions ++= Seq("-Xprint:typer", "-Xprint-types")

build.sbt中。

ykejflvf

ykejflvf2#

你的mkT看起来和报纸上的完全不同。以下是我对此的看法:

import util.NotGiven

case class Cast[A, B](ev: Option[A =:= B])

object Cast:
  given cSome[A, B](using t: A =:= B): Cast[A, B] = Cast(Some(t))
  given cNone[A, B](using t: NotGiven[A =:= B]): Cast[A, B] = Cast(None)

def cast[A, B](a: A)(using c: Cast[A, B]): Option[B] = c.ev.map(e => e(a))

def mkT[A, B](f: B => B)(a: A)(using c: Cast[A, B]): A =
  c.ev match 
    case Some(aToB) => aToB.flip(f(aToB(a)))
    case None => a

def not(a: Boolean): Boolean = !a

println(mkT(not)(true)) // false
println(mkT(not)('a'))  // 'a'

sealed trait Salary
case class S(amt: Float) extends Salary

def incS(amt: Float): S => S = {
  case S(a) => S(a * (1 + amt))
}

def inc[A](k: Float)(a: A)(using c: Cast[A, S]): A = mkT(incS(k))(a)
println(inc(.1)(S(8000))) // increased to `S(8800.0)`
println(inc(.1)('a')) // left as-is

当您将incS的类型从Salary => Salary更改为S => S时,它可以很好地工作,因为在您的例子中,SSalary的一个子类型,它不等于Salary

相关问题