scala雾中风景(5): 中缀表达

scala中的中缀表达在3个地方存在:操作符、模式匹配、类型声明

1)中缀操作符最常见: a + bx foo y 里的+foo都是中缀操作符(严格的说scala里没有操作符,其实都是方法)。

其中需要注意的一点是名称最后以“:”结尾的,调用顺序与普通中缀操作符想反:从右往左。比如h :: t 实际是 t.::(h),它的意义在于可以让表达看起来更“顺眼”一些。比如我们自定义一个:

// 一个Cache单例,提供了 >>: 方法
scala> object Cache { def >>:(data:String) { println(data) } }
defined module Cache

scala> "myData" >>: Cache
myData

上面的方法很直观,让人一看就容易理解是把数据追加到cache中。还可以支持连续操作,稍修改一下:

scala> object Cache { def >>:(data:String):Cache.type = { println(data); Cache } }
defined module Cache

scala> "data2" >>: "data1" >>: Cache
data1
data2
res6: Cache.type = Cache$@45d2d12f

实际中,最常见的从右往左结合的中缀操作符是 List:: 方法

scala> val list = List()
list: List[Nothing] = List()

scala> "A" :: "B" :: list
res4: List[String] = List(A, B)

2) 当一个高阶类型有2个参数化类型,比如

scala> class Foo[A,B]

在声明变量类型时,也可以用 A Foo B 中缀形式来表达,Foo也称为中缀类型

scala> val x: Int Foo String = null

Int Foo String 等同于 Foo[Int,String]

3) 包含两个参数的构造器,在模式匹配时也可以用中缀表达

scala> case class Cons(a:String,b:String)
defined class Cons

scala> val x = Cons("one", "two")
x: Cons = Cons(one,two)

scala> x match { case "one" Cons "two" => println("ok") }
ok

"one" Cons "two" 等同于 Cons("one", "two")

对于2)和3)似乎也体现了某种一致性,不过暂对中缀类型的好处没有体会,对于类型系统中的这种风格也还不了解,暂不做比较。

现实中有个很容易迷惑人的地方,常用的 List ,即存在了一个:: 方法, 也存在了一个:: 名字的case类(List的子类)。

所以,构造一个list可以通过::的伴生对象提供的工厂方法,也可以通过Nil的::方法

scala> val l = ::("A",Nil) // 这里::是伴生对象, 相当于 ::.apply()
l: scala.collection.immutable.::[String] = List(A)

scala> val l2 = "A" :: Nil // 这里::是方法, 相当于 Nil.::()
l2: List[String] = List(A)

scala> l == l2
res1: Boolean = true

在模式匹配时,可以用中缀形式:

// 这里::是伴生对象,相当于 ::.unapply()
case head :: tail => …

所以::在不同的场景,意思不同,有时是方法名,有时是伴生对象名。这个设计,最初了解时很爱吐槽。但它提供了一种看上去很一致的表象,在中缀形式下都是表示”头”与”尾”的连接。

scala雾中风景(5): 中缀表达》上有13条评论

  1. 廖师虎

    开始是挺迷惑的,后来发现模式匹配用的是:: (case class), 其它多数都是::方法。

    回复
  2. mefan

    补充:
    case class Cons(a:String,b:String) 因为有unapply返回

    object 示例(伪):
    val Head(h) & State(code) & Body(b) = http

    回复
    1. hongjiang 文章作者

      你的示例是想表达与 unapply 有关系的用法吗?没看明白

      回复
  3. dd

    前几天看到代码里有 /: :\ 没搞明白为何用的时候不同, 原来是 : 规则
    其实 |> 和 <| 直观一点,

    回复
  4. Lifu Huang

    博主您好,想请教您文章中提到的 case class ::的伴生对象及对应的unapply方法在哪个文件里。我找了下case class ::所在的代码文件,好像并没有找到。谢谢!

    回复
    1. hongjiang 文章作者

      对于case class,编译器会对其生成一个伴生对象,这个对象里会提供unapply方法。

      ➜ cat C.scala
      case class C(name:String)
      ➜ scalac C.scala
      // 这里编译器生成两个class文件,一个是C.class,一个是C$.class
      ➜ javap C$
      Compiled from “C.scala”
      public final class C$ extends scala.runtime.AbstractFunction1 implements scala.Serializable {
      public static final C$ MODULE$;
      public static {};
      public final java.lang.String toString();
      public C apply(java.lang.String);
      public scala.Option
      unapply(C);
      public java.lang.Object apply(java.lang.Object);
      }

      回复
      1. hongjiang 文章作者

        用scalap看更清晰:

        ➜ scalap C
        case class C(name: scala.Predef.String) extends scala.AnyRef with scala.Product with scala.Serializable {
        val name: scala.Predef.String = { /* compiled code */ }
        def copy(name: scala.Predef.String): C = { /* compiled code */ }
        override def productPrefix: java.lang.String = { /* compiled code */ }
        def productArity: scala.Int = { /* compiled code */ }
        def productElement(x$1: scala.Int): scala.Any = { /* compiled code */ }
        override def productIterator: scala.collection.Iterator[scala.Any] = { /* compiled code */ }
        def canEqual(x$1: scala.Any): scala.Boolean = { /* compiled code */ }
        override def hashCode(): scala.Int = { /* compiled code */ }
        override def toString(): java.lang.String = { /* compiled code */ }
        override def equals(x$1: scala.Any): scala.Boolean = { /* compiled code */ }
        }
        object C extends scala.runtime.AbstractFunction1[scala.Predef.String, C] with scala.Serializable {
        def this() = { /* compiled code */ }
        final override def toString(): java.lang.String = { /* compiled code */ }
        def apply(name: scala.Predef.String): C = { /* compiled code */ }
        def unapply(x$0: C): scala.Option[scala.Predef.String] = { /* compiled code */ }
        }

        回复
        1. Lifu Huang

          所以您的意思是在pattern match List的时候,比如:

          List(1, 2, 3) match {
          case x :: y => “ok”
          }

          case class x::y是通过编译器自动生成的unapply匹配List(1, 2, 3)的?

          但是作为一个scala新手,我感到有些困惑。我能理解::自动生成的unapply会匹配::实例,但是不能理解为什么也能匹配一般的List对象(::是List的子类,但List不是::的子类),比如List(1, 2, 3)。
          请博主赐教,谢谢!

          回复
          1. hongjiang 文章作者

            List(1,2,3) 是调用 List.apply(1,2,3) 产生的结果是一个 :: 对象

            scala> val l = List(“A”)
            l: List[String] = List(A)

            scala> l.getClass
            res0: Class[_ <: List[String]] = class scala.collection.immutable.$colon$colon 看到这个class名称 $colon$colon 就是 :: 类

        2. Lifu Huang

          明白了,谢谢博主答复。所以Scala中一般的List实例其实都是::实例对吗?

          回复
          1. hongjiang 文章作者

            对,大多数你见到的非空的List基本都是::实例,List还有另外一个子类Nil,表示空。

  5. zile

    sealed trait Stream[+A]
    {
    def foldRight[B](z: => B)(f: (A, => B) => B): B =
    this match {
    case Cons(h, t) => f(h(), t().foldRight(z)(f))
    case _ => z
    }
    }
    case object Empty extends Stream[Nothing]
    case class Cons[+A](h: () => A, t: () => Stream[A]) extends Stream[A]

    scala 函数式变成第五章,这个foldRight中的f:(A, =>B)=>B,中的逗号怎么理解

    回复
    1. hongjiang 文章作者

      逗号就是分隔符,后边的=>B 表示一个传名参数类型

      回复

发表评论

电子邮件地址不会被公开。 必填项已用*标注