├── web ├── finagle_filter.png ├── style.css ├── finagle_client_server.png ├── small-screens.css ├── _layouts │ ├── default.html │ └── post.html ├── _includes │ └── footer.html ├── zh_cn │ ├── index.html │ ├── pattern-matching-and-functional-composition.textile │ ├── specs.textile │ ├── basics2.textile │ ├── java.textile │ ├── type-basics.textile │ ├── basics.textile │ └── collections.textile ├── ko │ ├── index.html │ ├── pattern-matching-and-functional-composition.textile │ ├── specs.textile │ ├── basics2.textile │ └── java.textile ├── index.html ├── ru │ ├── index.html │ ├── pattern-matching-and-functional-composition.textile │ ├── specs.textile │ └── basics2.textile ├── pattern-matching-and-functional-composition.textile ├── _config.yml ├── searchbird-1.svg ├── specs.textile ├── searchbird-2.svg └── basics2.textile ├── lesson_7 ├── .gitignore ├── project │ ├── build.properties │ ├── plugins │ │ └── Plugins.scala │ └── build │ │ └── InteropProject.scala ├── .ensime ├── src │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── twitter │ │ │ │ └── interop │ │ │ │ └── JTraitImpl.java │ │ └── scala │ │ │ └── com │ │ │ └── twitter │ │ │ └── interop │ │ │ └── Scalaisms.scala │ └── test │ │ └── java │ │ └── com │ │ └── twitter │ │ └── interop │ │ └── MyTraitTest.java └── notes.md ├── Makefile ├── .gitignore ├── searchbird ├── Gemfile ├── project │ ├── build.properties │ ├── plugins │ │ └── Plugins.scala │ └── build │ │ └── SearchbirdProject.scala ├── src │ ├── main │ │ ├── scala │ │ │ └── com │ │ │ │ └── twitter │ │ │ │ └── searchbird │ │ │ │ ├── Main.scala │ │ │ │ ├── SearchbirdServiceImpl.scala │ │ │ │ ├── Client.scala │ │ │ │ ├── config │ │ │ │ └── SearchbirdServiceConfig.scala │ │ │ │ └── Index.scala │ │ └── thrift │ │ │ └── searchbird.thrift │ ├── test │ │ └── scala │ │ │ └── com │ │ │ └── twitter │ │ │ └── searchbird │ │ │ ├── SearchbirdServiceSpec.scala │ │ │ └── AbstractSpec.scala │ └── scripts │ │ ├── console │ │ └── searchbird.sh ├── Capfile ├── README.md ├── config │ ├── test.scala │ ├── development.scala │ ├── staging.scala │ └── production.scala ├── run └── TUTORIAL.md ├── publish.sh ├── README.md └── notes.txt /web/finagle_filter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twitter/scala_school/HEAD/web/finagle_filter.png -------------------------------------------------------------------------------- /web/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | max-width: 940px; 3 | margin-right: auto; 4 | margin-left: auto; 5 | } 6 | -------------------------------------------------------------------------------- /web/finagle_client_server.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twitter/scala_school/HEAD/web/finagle_client_server.png -------------------------------------------------------------------------------- /lesson_7/.gitignore: -------------------------------------------------------------------------------- 1 | lib_managed/ 2 | target/ 3 | dist/ 4 | project/boot/ 5 | project/plugins/project/ 6 | project/plugins/src_managed/ 7 | *.log 8 | *.tmproj 9 | .DS_Store 10 | *.swp 11 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | jekyll build -s web -d web.out 3 | 4 | serve: 5 | jekyll serve --watch -s web -d web.out 6 | 7 | publish: all 8 | ./publish.sh web.out 9 | 10 | .PHONY: all serve publish 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | web.out/ 2 | .DS_Store 3 | searchbird/project/boot 4 | searchbird/project/plugins/project/ 5 | searchbird/project/plugins/src_managed/ 6 | searchbird/*.log 7 | searchbird/*.lock 8 | lib_managed/ 9 | target/ 10 | -------------------------------------------------------------------------------- /lesson_7/project/build.properties: -------------------------------------------------------------------------------- 1 | #Project properties 2 | #Wed Jan 12 10:07:59 PST 2011 3 | project.organization=com.twitter 4 | project.name=interop 5 | sbt.version=0.7.4 6 | project.version=1.0 7 | build.scala.versions=2.8.1 8 | project.initialize=false 9 | -------------------------------------------------------------------------------- /searchbird/Gemfile: -------------------------------------------------------------------------------- 1 | # use "bundle install" to update gems; "gem install bundler" to install bundler. 2 | source :rubygems 3 | source "https://gems.local.twitter.com" 4 | gem "thrift_client", "0.6.2" 5 | gem "thrift", "0.6" 6 | gem "railsless-deploy" 7 | gem "capistrano" 8 | -------------------------------------------------------------------------------- /lesson_7/project/plugins/Plugins.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | 3 | class Plugins(info: ProjectInfo) extends PluginDefinition(info) { 4 | val twitterMaven = "twitter.com" at "https://maven.twttr.com/" 5 | val defaultProject = "com.twitter" % "standard-project" % "0.9.0" 6 | } 7 | -------------------------------------------------------------------------------- /searchbird/project/build.properties: -------------------------------------------------------------------------------- 1 | #Project properties 2 | #Thu Feb 24 16:45:35 PST 2011 3 | project.organization=com.twitter 4 | project.name=searchbird 5 | sbt.version=0.7.4 6 | project.version=1.0.0-SNAPSHOT 7 | build.scala.versions=2.8.1 8 | project.initialize=false 9 | -------------------------------------------------------------------------------- /lesson_7/.ensime: -------------------------------------------------------------------------------- 1 | ;; This config was generated using ensime-config-gen. Feel free to customize its contents manually. 2 | 3 | ( 4 | 5 | :project-package "com.twitter.interop" 6 | 7 | :use-sbt t 8 | 9 | :compile-jars ("libs" "lib_managed/compile" "lib_managed/test") 10 | ) 11 | -------------------------------------------------------------------------------- /web/small-screens.css: -------------------------------------------------------------------------------- 1 | @media screen and (max-width: 1024px) { 2 | dl dd { 3 | margin-bottom: 18px; 4 | } 5 | div.container { 6 | width: 90%; 7 | } 8 | .row .span16, 9 | .row .span8, 10 | .row .span6 { 11 | float: none; 12 | width: 100%; 13 | margin-left: 0; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /searchbird/src/main/scala/com/twitter/searchbird/Main.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.searchbird 2 | 3 | import com.twitter.ostrich.admin.RuntimeEnvironment 4 | 5 | object Main { 6 | def main(args: Array[String]) { 7 | val env = RuntimeEnvironment(this, args) 8 | val service = env.loadRuntimeConfig[SearchbirdServiceServer] 9 | service.start() 10 | } 11 | } 12 | 13 | -------------------------------------------------------------------------------- /publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | root=$( 4 | cd $(dirname $0) 5 | /bin/pwd 6 | ) 7 | 8 | dir=$1 9 | out=/tmp/school.$$ 10 | 11 | trap "rm -fr $out" 0 1 2 12 | 13 | git clone $root/.git $out 14 | cd $out 15 | git remote set-url origin git@github.com:twitter/scala_school.git 16 | git fetch 17 | git checkout gh-pages 18 | cd $root 19 | cp -r $dir/* $out/ 20 | cd $out 21 | git add . 22 | git commit -am"publish by $USER" 23 | git push origin gh-pages 24 | -------------------------------------------------------------------------------- /lesson_7/src/main/java/com/twitter/interop/JTraitImpl.java: -------------------------------------------------------------------------------- 1 | package com.twitter.interop; 2 | 3 | public class JTraitImpl implements MyTrait { 4 | private String name = null; 5 | 6 | public JTraitImpl(String name) { 7 | this.name = name; 8 | } 9 | 10 | public String traitName() { 11 | return name; 12 | } 13 | 14 | public String upperTraitName() { 15 | return MyTrait$class.upperTraitName(this); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /searchbird/src/test/scala/com/twitter/searchbird/SearchbirdServiceSpec.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.searchbird 2 | 3 | class SearchbirdServiceSpec extends AbstractSpec { 4 | "SearchbirdService" should { 5 | 6 | // TODO: Please implement your own tests. 7 | 8 | "set a key, get a key" in { 9 | searchbird.put("name", "bluebird")() 10 | searchbird.get("name")() mustEqual "bluebird" 11 | searchbird.get("what?")() must throwA[Exception] 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lesson_7/notes.md: -------------------------------------------------------------------------------- 1 | Outline 2 | 3 | Accessors 4 | Constructor args 5 | vals 6 | beanProperties 7 | vals 8 | vars 9 | @BooleanBeanProperty 10 | Exceptions 11 | @Throws 12 | Erasure! 13 | 14 | Annotation ref https://www.scala-lang.org/node/106 15 | 16 | Traits 17 | 2 classes defined 18 | Interface: TraitName 19 | Impl: TraitName$class 20 | 21 | Class definitions 22 | 23 | Objects 24 | 25 | Closures/Functions 26 | 27 | Variance 28 | 29 | Javap 30 | 31 | Questions? 32 | -------------------------------------------------------------------------------- /lesson_7/project/build/InteropProject.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | import com.twitter.sbt._ 3 | 4 | class InteropProject(info: ProjectInfo) extends DefaultProject(info) { 5 | override def compileOrder = CompileOrder.ScalaThenJava 6 | 7 | override def compileOptions = super.compileOptions ++ Seq(Unchecked) ++ 8 | compileOptions("-encoding", "utf8") ++ 9 | compileOptions("-Xcheckinit", "-Xwarninit") 10 | 11 | val junitInterface = "com.novocode" % "junit-interface" % "0.5" % "test->default" 12 | } 13 | -------------------------------------------------------------------------------- /searchbird/src/main/scala/com/twitter/searchbird/SearchbirdServiceImpl.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.searchbird 2 | 3 | import com.twitter.util._ 4 | import config._ 5 | 6 | class SearchbirdServiceImpl(config: SearchbirdServiceConfig, index: Index) extends SearchbirdServiceServer { 7 | val serverName = "Searchbird" 8 | val thriftPort = config.thriftPort 9 | 10 | def get(key: String) = index.get(key) 11 | def put(key: String, value: String) = 12 | index.put(key, value) map { _ => null: java.lang.Void } 13 | def search(query: String) = index.search(query) 14 | } 15 | -------------------------------------------------------------------------------- /searchbird/Capfile: -------------------------------------------------------------------------------- 1 | begin 2 | require 'rubygems' 3 | require "bundler/setup" 4 | require "railsless-deploy" 5 | rescue LoadError => e 6 | puts e.message 7 | abort "Please gem install railsless-deploy" 8 | end 9 | 10 | set :user, :twitter 11 | set :application, "searchbird" 12 | set :repository, "https://git.local.twitter.com/ro/#{application}" 13 | 14 | task :staging do 15 | role :app, "server1", "server2", "etc" 16 | end 17 | 18 | task :canary do 19 | role :app, "server1" 20 | end 21 | 22 | task :production do 23 | role :app, "server1", "server2", "etc" 24 | end 25 | -------------------------------------------------------------------------------- /searchbird/src/test/scala/com/twitter/searchbird/AbstractSpec.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.searchbird 2 | 3 | import org.specs.Specification 4 | import com.twitter.ostrich.admin._ 5 | import com.twitter.util._ 6 | import com.twitter.conversions.time._ 7 | 8 | abstract class AbstractSpec extends Specification { 9 | val env = RuntimeEnvironment(this, Array("-f", "config/test.scala")) 10 | lazy val searchbird = { 11 | val out = env.loadRuntimeConfig[SearchbirdService] 12 | 13 | // You don't really want the thrift server active, particularly if you 14 | // are running repetitively via ~test 15 | ServiceTracker.shutdown // all services 16 | out 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /searchbird/src/main/thrift/searchbird.thrift: -------------------------------------------------------------------------------- 1 | namespace java com.twitter.searchbird.thrift 2 | namespace rb Searchbird 3 | 4 | /** 5 | * It's considered good form to declare an exception type for your service. 6 | * Thrift will serialize and transmit them transparently. 7 | */ 8 | exception SearchbirdException { 9 | 1: string description 10 | } 11 | 12 | /** 13 | * A simple memcache-like service, which stores strings by key/value. 14 | * You should replace this with your actual service. 15 | */ 16 | service SearchbirdService { 17 | string get(1: string key) throws(1: SearchbirdException ex) 18 | 19 | void put(1: string key, 2: string value) 20 | list search(1: string query) 21 | } 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is Scala School - a set of lessons covering the Scala programming language. 2 | 3 | We use [jekyll](https://github.com/mojombo/jekyll) to generate the site. In order to build it, you must first install it: 4 | 5 | $ gem install jekyll 6 | 7 | should do. Now, build the site with `make`. This will create a copy of the lessons in the `web.out` folder. 8 | 9 | For development, you'll also need to install [RedCloth](https://redcloth.org/). 10 | 11 | $ gem install RedCloth 12 | $ gem install jekyll-textile-converter 13 | 14 | Then `make serve` will launch `jekyll` in serving mode: a web server will be launched on port 4000, and changing files will automatically rebuild the site. 15 | 16 | To publish to https://twitter.github.io/scala_school: 17 | 18 | $ make publish 19 | -------------------------------------------------------------------------------- /searchbird/README.md: -------------------------------------------------------------------------------- 1 | # Project Searchbird 2 | 3 | *Deprecated content*: this lesson depended on a Ruby library that is 4 | no longer in use at Twitter and has been unpublished. Because of the 5 | missing dependency this lesson no longer works. 6 | 7 | Welcome to your searchbird project! To make sure things are working 8 | properly, you may want to: 9 | 10 | $ sbt update test 11 | 12 | There is a tutorial for what to do next, which you can find in the 13 | scala-bootstrapper README.rdoc file. 14 | 15 | # Configuring Intellij 16 | 17 | If you want to setup Intellij, it has to happen off to the side: 18 | 19 | $ sbt 20 | > *sbtIdeaRepo at https://mpeltonen.github.com/maven/ 21 | > *idea is com.github.mpeltonen sbt-idea-processor 0.4.0 22 | > update 23 | > idea 24 | -------------------------------------------------------------------------------- /searchbird/src/scripts/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | $: << File.dirname(__FILE__) + "/../../target/gen-rb" 3 | 4 | require "rubygems" 5 | require "bundler/setup" 6 | 7 | require "thrift" 8 | require "thrift_client" 9 | require "searchbird_service" 10 | require "irb" 11 | 12 | class SearchbirdClient < ThriftClient 13 | DEFAULTS = { :transport_wrapper => Thrift::FramedTransport } 14 | def initialize(servers = nil, options = {}) 15 | if servers.nil? or servers.empty? 16 | STDERR.puts "No servers specified, using 127.0.0.1:9999" 17 | servers = ['127.0.0.1:9999'] 18 | else 19 | servers = Array(servers) 20 | end 21 | 22 | super(Searchbird::SearchbirdService::Client, servers, DEFAULTS.merge(options)) 23 | end 24 | end 25 | 26 | puts "Hint: the client is in the variable `$client`" 27 | $client = SearchbirdClient.new ARGV.shift 28 | 29 | IRB.start 30 | -------------------------------------------------------------------------------- /searchbird/src/main/scala/com/twitter/searchbird/Client.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.searchbird 2 | 3 | import com.twitter.finagle.builder.ClientBuilder 4 | import com.twitter.finagle.thrift.ThriftClientFramedCodec 5 | import com.twitter.searchbird.thrift._ 6 | import org.apache.thrift.protocol.TBinaryProtocol 7 | import java.net.InetSocketAddress 8 | 9 | class Client { 10 | val service = ClientBuilder() 11 | .hosts(Seq(new InetSocketAddress("localhost", 9999))) 12 | .codec(ThriftClientFramedCodec()) 13 | .hostConnectionLimit(1) 14 | .build() 15 | 16 | val client = new SearchbirdServiceClientAdapter( 17 | new thrift.SearchbirdService.ServiceToClient( 18 | service, new TBinaryProtocol.Factory)) 19 | 20 | def get(key: String) = client.get(key)() 21 | def put(key: String, value: String) = client.put(key, value)() 22 | def search(query: String) = client.search(query) 23 | } 24 | -------------------------------------------------------------------------------- /searchbird/config/test.scala: -------------------------------------------------------------------------------- 1 | import com.twitter.conversions.time._ 2 | import com.twitter.logging.config._ 3 | import com.twitter.ostrich.admin.config._ 4 | import com.twitter.searchbird.config._ 5 | 6 | // test mode. 7 | new SearchbirdServiceConfig { 8 | 9 | // Add your own config here 10 | 11 | // Where your service will be exposed. 12 | thriftPort = 9999 13 | 14 | // Ostrich http admin port. Curl this for stats, etc 15 | admin.httpPort = 9900 16 | 17 | // End user configuration 18 | 19 | // Expert-only: Ostrich stats and logger configuration. 20 | admin.statsNodes = new StatsConfig { 21 | reporters = new JsonStatsLoggerConfig { 22 | loggerName = "stats" 23 | serviceName = "searchbird" 24 | } :: new TimeSeriesCollectorConfig 25 | } 26 | 27 | loggers = 28 | new LoggerConfig { 29 | level = Level.INFO 30 | handlers = new ConsoleHandlerConfig 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /searchbird/project/plugins/Plugins.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | 3 | class Plugins(info: ProjectInfo) extends PluginDefinition(info) { 4 | import scala.collection.jcl 5 | val environment = jcl.Map(System.getenv()) 6 | def isSBTOpenTwitter = environment.get("SBT_OPEN_TWITTER").isDefined 7 | def isSBTTwitter = environment.get("SBT_TWITTER").isDefined 8 | 9 | override def repositories = if (isSBTOpenTwitter) { 10 | Set("twitter.artifactory" at "https://artifactory.local.twitter.com/open-source/") 11 | } else if (isSBTTwitter) { 12 | Set("twitter.artifactory" at "https://artifactory.local.twitter.com/repo/") 13 | } else { 14 | super.repositories ++ Set( 15 | "twitter.com" at "https://maven.twttr.com/", 16 | "scala-tools" at "https://scala-tools.org/repo-releases/", 17 | "freemarker" at "https://freemarker.sourceforge.net/maven2/" 18 | ) 19 | } 20 | override def ivyRepositories = Seq(Resolver.defaultLocal(None)) ++ repositories 21 | 22 | val standardProject = "com.twitter" % "standard-project" % "0.12.7" 23 | val sbtThrift = "com.twitter" % "sbt-thrift" % "1.4.4" 24 | } 25 | -------------------------------------------------------------------------------- /searchbird/config/development.scala: -------------------------------------------------------------------------------- 1 | import com.twitter.conversions.time._ 2 | import com.twitter.logging.config._ 3 | import com.twitter.ostrich.admin.config._ 4 | import com.twitter.searchbird.config._ 5 | 6 | // development mode. 7 | new SearchbirdServiceConfig { 8 | 9 | shards = Seq( 10 | "localhost:9000", 11 | "localhost:9001", 12 | "localhost:9002" 13 | ) 14 | 15 | // Where your service will be exposed. 16 | thriftPort = 9999 17 | 18 | // Expert-only: Ostrich stats and logger configuration. 19 | admin.statsNodes = new StatsConfig { 20 | reporters = new JsonStatsLoggerConfig { 21 | loggerName = "stats" 22 | serviceName = "searchbird" 23 | } :: new TimeSeriesCollectorConfig 24 | } 25 | 26 | loggers = 27 | new LoggerConfig { 28 | level = Level.DEBUG 29 | handlers = new FileHandlerConfig { 30 | filename = "searchbird.log" 31 | roll = Policy.SigHup 32 | } 33 | } :: new LoggerConfig { 34 | node = "stats" 35 | level = Level.INFO 36 | useParents = false 37 | handlers = new FileHandlerConfig { 38 | filename = "stats.log" 39 | formatter = BareFormatterConfig 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /searchbird/src/main/scala/com/twitter/searchbird/config/SearchbirdServiceConfig.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.searchbird 2 | package config 3 | 4 | import com.twitter.logging.Logger 5 | import com.twitter.logging.config._ 6 | import com.twitter.ostrich.admin.{RuntimeEnvironment, ServiceTracker} 7 | import com.twitter.ostrich.admin.config._ 8 | import com.twitter.util.Config 9 | 10 | class SearchbirdServiceConfig extends ServerConfig[SearchbirdServiceServer] { 11 | var thriftPort: Int = 9999 12 | var shards: Seq[String] = Seq() 13 | 14 | def apply(runtime: RuntimeEnvironment) = { 15 | val index = runtime.arguments.get("shard") match { 16 | case Some(arg) => 17 | val which = arg.toInt 18 | if (which >= shards.size || which < 0) 19 | throw new Exception("invalid shard number %d".format(which)) 20 | 21 | // override with the shard port 22 | val Array(_, port) = shards(which).split(":") 23 | thriftPort = port.toInt 24 | 25 | new ResidentIndex 26 | 27 | case None => 28 | require(!shards.isEmpty) 29 | val remotes = shards map { new RemoteIndex(_) } 30 | new CompositeIndex(remotes) 31 | } 32 | 33 | new SearchbirdServiceImpl(this, index) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /searchbird/config/staging.scala: -------------------------------------------------------------------------------- 1 | import com.twitter.ostrich.admin.config._ 2 | import com.twitter.conversions.time._ 3 | import com.twitter.logging.config._ 4 | import com.twitter.searchbird.config._ 5 | 6 | // staging mode. 7 | new SearchbirdServiceConfig { 8 | 9 | // Add your own config here 10 | 11 | // Where your service will be exposed. 12 | thriftPort = 9999 13 | 14 | // Ostrich http admin port. Curl this for stats, etc 15 | admin.httpPort = 9900 16 | 17 | // End user configuration 18 | 19 | // Expert-only: Ostrich stats and logger configuration. 20 | admin.statsNodes = new StatsConfig { 21 | reporters = new JsonStatsLoggerConfig { 22 | loggerName = "stats" 23 | serviceName = "searchbird" 24 | } :: new TimeSeriesCollectorConfig 25 | } 26 | 27 | loggers = 28 | new LoggerConfig { 29 | level = Level.INFO 30 | handlers = new FileHandlerConfig { 31 | filename = "/var/log/searchbird/production.log" 32 | roll = Policy.SigHup 33 | } 34 | } :: new LoggerConfig { 35 | node = "stats" 36 | level = Level.INFO 37 | useParents = false 38 | handlers = new FileHandlerConfig { 39 | filename = "stats.log" 40 | formatter = BareFormatterConfig 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /searchbird/config/production.scala: -------------------------------------------------------------------------------- 1 | import com.twitter.conversions.time._ 2 | import com.twitter.logging.config._ 3 | import com.twitter.ostrich.admin.config._ 4 | import com.twitter.searchbird.config._ 5 | 6 | // production mode. 7 | new SearchbirdServiceConfig { 8 | 9 | // Add your own config here 10 | 11 | // Where your service will be exposed. 12 | thriftPort = 9999 13 | 14 | // Ostrich http admin port. Curl this for stats, etc 15 | admin.httpPort = 9900 16 | 17 | // End user configuration 18 | 19 | // Expert-only: Ostrich stats and logger configuration. 20 | admin.statsNodes = new StatsConfig { 21 | reporters = new JsonStatsLoggerConfig { 22 | loggerName = "stats" 23 | serviceName = "searchbird" 24 | } :: new TimeSeriesCollectorConfig 25 | } 26 | 27 | loggers = 28 | new LoggerConfig { 29 | level = Level.INFO 30 | handlers = new FileHandlerConfig { 31 | filename = "/var/log/searchbird/production.log" 32 | roll = Policy.SigHup 33 | } 34 | } :: new LoggerConfig { 35 | node = "stats" 36 | level = Level.INFO 37 | useParents = false 38 | handlers = new FileHandlerConfig { 39 | filename = "stats.log" 40 | formatter = BareFormatterConfig 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lesson_7/src/main/scala/com/twitter/interop/Scalaisms.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.interop 2 | 3 | import java.io.IOException 4 | import scala.throws 5 | import scala.reflect.{BeanProperty, BooleanBeanProperty} 6 | 7 | class SimpleClass(name: String, val acc: String, @BeanProperty var mutable: String) { 8 | val foo = "foo" 9 | var bar = "bar" 10 | @BeanProperty 11 | val fooBean = "foobean" 12 | @BeanProperty 13 | var barBean = "barbean" 14 | @BooleanBeanProperty 15 | var awesome = true 16 | 17 | def dangerFoo() = { 18 | throw new IOException("SURPRISE!" + name) 19 | } 20 | 21 | @throws(classOf[IOException]) 22 | def dangerBar() = { 23 | throw new IOException("NO SURPRISE!" + name) 24 | } 25 | } 26 | 27 | trait MyTrait { 28 | def traitName:String 29 | def upperTraitName = traitName.toUpperCase 30 | } 31 | 32 | class TraitImpl(name: String) extends MyTrait { 33 | def traitName = name 34 | } 35 | 36 | object TraitImpl { 37 | def apply = new TraitImpl("foo") 38 | def apply(name: String) = new TraitImpl(name) 39 | } 40 | 41 | class ClosureClass[+V] { 42 | def printResult[T](f: => T) = { 43 | println(f) 44 | } 45 | 46 | def printResult[T <: Number](f: String => T) = { 47 | println(f("HI THERE")) 48 | } 49 | } 50 | 51 | class VarianceClass[-T >: CharSequence, +V](v: V) { 52 | def get = v 53 | def printT(t: T) = { 54 | println(t) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /web/_layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ page.title }} 6 | 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 22 | 32 | 33 | 34 | 35 |
36 | 37 | {{ content }} 38 | 39 |
40 | 41 | {% include footer.html %} 42 | 43 | 44 | -------------------------------------------------------------------------------- /web/_includes/footer.html: -------------------------------------------------------------------------------- 1 | 2 | 36 | -------------------------------------------------------------------------------- /web/_layouts/post.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Scala School - {{ page.title }} 6 | 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 28 | 29 | 30 | 31 | 32 |
33 |
34 |
35 |

{{ page.title }}

36 | 44 | 45 |
46 |
47 |
48 | 49 |
50 | {{ content }} 51 |
52 | {% include footer.html %} 53 | 54 | 55 | -------------------------------------------------------------------------------- /searchbird/project/build/SearchbirdProject.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | import Process._ 3 | import com.twitter.sbt._ 4 | 5 | /** 6 | * Sbt project files are written in a DSL in scala. 7 | * 8 | * The % operator is just turning strings into maven dependency declarations, so lines like 9 | * val example = "com.example" % "exampleland" % "1.0.3" 10 | * mean to add a dependency on exampleland version 1.0.3 from provider "com.example". 11 | */ 12 | class SearchbirdProject(info: ProjectInfo) extends StandardServiceProject(info) 13 | with CompileThriftScala 14 | with NoisyDependencies 15 | with DefaultRepos 16 | with SubversionPublisher 17 | with PublishSourcesAndJavadocs 18 | with PublishSite 19 | { 20 | val finagleVersion = "1.8.4" 21 | 22 | val finagleC = "com.twitter" % "finagle-core" % finagleVersion 23 | val finagleT = "com.twitter" % "finagle-thrift" % finagleVersion 24 | val finagleO = "com.twitter" % "finagle-ostrich4" % finagleVersion 25 | 26 | // thrift 27 | val libthrift = "thrift" % "libthrift" % "0.5.0" 28 | val util = "com.twitter" % "util" % "1.11.2" 29 | 30 | override def originalThriftNamespaces = Map("Searchbird" -> "com.twitter.searchbird.thrift") 31 | override val scalaThriftTargetNamespace = "com.twitter.searchbird" 32 | 33 | val slf4jVersion = "1.5.11" 34 | val slf4jApi = "org.slf4j" % "slf4j-api" % slf4jVersion withSources() intransitive() 35 | val slf4jBindings = "org.slf4j" % "slf4j-jdk14" % slf4jVersion withSources() intransitive() 36 | 37 | // for tests 38 | val specs = "org.scala-tools.testing" % "specs_2.8.1" % "1.6.7" % "test" withSources() 39 | val jmock = "org.jmock" % "jmock" % "2.4.0" % "test" 40 | val hamcrest_all = "org.hamcrest" % "hamcrest-all" % "1.1" % "test" 41 | val cglib = "cglib" % "cglib" % "2.1_3" % "test" 42 | val asm = "asm" % "asm" % "1.5.3" % "test" 43 | val objenesis = "org.objenesis" % "objenesis" % "1.1" % "test" 44 | 45 | override def mainClass = Some("com.twitter.searchbird.Main") 46 | 47 | override def subversionRepository = Some("https://svn.local.twitter.com/maven-public") 48 | } 49 | -------------------------------------------------------------------------------- /web/zh_cn/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Scala 课堂 4 | --- 5 | 6 |
7 |

Scala 课堂!

8 |

从 ∅ 到分布式服务

9 |
10 | 11 | 12 |
13 |
14 |

其他语言版本: English Русский

15 | 16 |

关于

17 |

Scala课堂是Twitter启动的一系列讲座,用来帮助有经验的工程师成为高效的Scala 程序员。Scala是一种相对较新的语言,但借鉴了许多熟悉的概念。因此,课程中的讲座假设听众知道这些概念,并展示了如何在Scala中使用它们。我们发现这是一个让新工程师能够快速上手的有效方法。网站里的是伴随这些讲座的书面材料,这些文字材料本身也是很有用的。

18 | 19 |

方法

20 |

我们认为最有意义的教学方式是,不要把Scala看做是改进的Java,而是把它作为一门新的语言。所以这里不会介绍Java的使用经验,而将聚焦在解释器和“对象-函数式”的风格,以及我们的编程风格。特别强调了可维护性,清晰的表达,和利用类型系统的优势。

21 |

大部分课程除了Scala的交互命令行之外不需要其他软件。我们鼓励读者按顺序学习,并且不仅限于此。让这些课程作为您探索Scala的起点吧!

22 | 23 |

另外

24 |

通过这些链接您可以了解更多: 25 |

30 | 31 |
32 |
33 |

课程

34 |
35 | {% for lesson in site.lessons %} 36 |
{{ lesson.title_zh_cn }}
37 |
{{ lesson.desc_zh_cn }}
38 | {% endfor %} 39 |
40 |
41 |
42 | 43 | 44 | 45 | Fork me on GitHub 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /web/ko/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: 스칼라 학교 4 | --- 5 | 6 |
7 |

스칼라 학교!

8 |

∅ 에서 분산 서비스까지

9 |
10 | 11 | 12 |
13 |
14 |

다른 언어: English Русский

15 | 16 |

소개

17 |

스칼라 학교는 트위터에서 기획된 강좌로, 경력이 많은 엔지니어들이 생산성 높은 스칼라(Scala) 프로그래머가 되도록 준비시키기 위한 것이었다. 스칼라는 비교적 새로운 언어이지만, 많은 기존 개념을 활용하고 있다. 따라서 이 강좌에서는 독자들이 개념에 대해서는 어느정도 잘 알고 있다고 가정하고 이를 스칼라에서 어떻게 사용하는지를 보여줄 것이다. 이런 방식을 택하면 엔지니어들이 더 효과적이고 빠르게 일정 수준에 도달할 수 있음을 알게 되었다. 이 글은 강좌에서 사용된 보조 교재이다. 하지만 그 자체로도 유용하다는 사실을 알게 되었다. 18 |

19 | 20 |

접근방식

21 |

스칼라를 가르칠 때, 발전된 자바로 다루기 보다는 새로운 언어로 취급하는 편이 더 합리적이라 생각한다. 자바 경험이 필요하지는 않다. 인터프리터와 객체지향-함수형 언어 스타일, 그리고 여기(트위터)에서 프로그래밍하는 스타일에 더 촛점을 맞출 것이다. 유지보수성, 표현의 명확성, 그리고 타입 시스템의 기능을 최대한 활용하는 쪽에 강조점이 주어질 것이다.

22 |

대부분의 강좌에서는 스칼라 REPL만 있으면 충분하다. 강좌를 따라해 보고 더 많은 실험을 해보길 바란다. 각 강좌를 스칼라를 탐구하기 위한 출발점으로 삼아라.

23 | 24 |

추가 정보

25 |

다음 링크에서 더 많은 것을 배울 수 있다. 26 |

31 | 32 |

한글 번역: 오현석(Hyunsok Oh) 33 | 34 |

35 |
36 |

Lessons

37 |
38 | {% for lesson in site.lessons %} 39 |
{{ lesson.title_ko }}
40 |
{{ lesson.desc_ko }}
41 | {% endfor %} 42 |
43 |
44 |
45 | 46 | 47 | 48 | Fork me on GitHub 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /lesson_7/src/test/java/com/twitter/interop/MyTraitTest.java: -------------------------------------------------------------------------------- 1 | package com.twitter.interop; 2 | 3 | import java.io.IOException; 4 | 5 | import org.junit.*; 6 | import static org.junit.Assert.*; 7 | import scala.runtime.AbstractFunction0; 8 | import scala.runtime.AbstractFunction1; 9 | 10 | public class MyTraitTest { 11 | @Test public void simpleClassTest() { 12 | SimpleClass s = new SimpleClass("foo", "bar", "mutable"); 13 | // vals 14 | assertTrue(s.foo().equals("foo")); 15 | // vars 16 | assertTrue(s.bar().equals("bar")); 17 | s.bar_$eq("newbar"); 18 | assertTrue(s.bar().equals("newbar")); 19 | // bean vals 20 | assertTrue(s.getFooBean().equals("foobean")); 21 | assertTrue(s.getBarBean().equals("barbean")); 22 | s.setBarBean("newbarbean"); 23 | assertTrue(s.getBarBean().equals("newbarbean")); 24 | 25 | // boolean bean vals 26 | assertTrue(s.isAwesome()); 27 | s.setAwesome(false); 28 | assertFalse(s.isAwesome()); 29 | 30 | // bean properties work on class args too 31 | s.setMutable("newmutable"); 32 | assertTrue(s.getMutable().equals("newmutable")); 33 | 34 | // exception erasure! 35 | try { 36 | s.dangerFoo(); 37 | } catch (Throwable t) { 38 | // UGLY 39 | } 40 | // @throws works 41 | try { 42 | s.dangerBar(); 43 | } catch (IOException e) { 44 | // whew! 45 | } 46 | } 47 | @Test public void traitTest() { 48 | // the ugly way 49 | MyTrait foo = TraitImpl$.MODULE$.apply("foo"); 50 | // the prettier way: forwarding methods 51 | foo = TraitImpl.apply("foo"); 52 | } 53 | 54 | @Test public void closureTest() { 55 | ClosureClass c = new ClosureClass(); 56 | c.printResult(new AbstractFunction0() { 57 | public String apply() { 58 | return "foo"; 59 | } 60 | }); 61 | c.printResult(new AbstractFunction1() { 62 | public String apply(String arg) { 63 | return arg + "foo"; 64 | } 65 | }); 66 | } 67 | 68 | @Test public void varianceTest() { 69 | VarianceClass v1 = new VarianceClass("foo"); 70 | Locale l = Locale$en_US$.MODULE$; 71 | System.out.println(l); 72 | v1.printT("foo"); 73 | } 74 | } 75 | 76 | -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Scala School 4 | --- 5 | 6 |
7 |

Scala School!

8 |

From ∅ to Distributed Service

9 |
10 | 11 | 12 |
13 |
14 | 15 |

Other Languages:

16 | 17 | 22 | 23 |

About

24 |

Scala school started as a series of lectures at Twitter to prepare experienced engineers to be productive Scala programmers. Scala is a relatively new language, but draws on many familiar concepts. Thus, these lectures assumed the audience knew the concepts and showed how to use them in Scala. We found this an effective way of getting new engineers up to speed quickly. This is the written material that accompanied those lectures. We have found that these are useful in their own right. 25 |

26 | 27 |

Approach

28 |

We think it makes the most sense to approach teaching Scala not as if it were an improved Java but instead as a new language. Experience in Java is not expected. Focus will be on the interpreter and the object-functional style as well as the style of programming we do here. An emphasis will be placed on maintainability, clarity of expression, and leveraging the type system.

29 |

Most of the lessons require no software other than a Scala REPL. The reader is encouraged to follow along, but also to go further! Use these lessons as a starting point to explore the language.

30 | 31 |

Also

32 |

You can learn more elsewhere: 33 |

38 | 39 |
40 |
41 |

Lessons

42 |
43 | {% for lesson in site.lessons %} 44 |
{{ lesson.title }}
45 |
{{ lesson.desc }}
46 | {% endfor %} 47 |
48 |
49 |
50 | 51 | 52 | 53 | Fork me on GitHub 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /searchbird/run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function path_append { 4 | _var=$1 5 | _path=$2 6 | eval " 7 | if [[ -z \"\$$_var\" ]] ; then 8 | export $_var=$_path 9 | elif ! echo \$$_var | egrep -q \"(^|:)$_path($|:)\" ; then 10 | export $_var=\$$_var:$_path 11 | fi" 12 | } 13 | 14 | function path_prepend() { 15 | _var=$1 16 | _path=$2 17 | eval " 18 | if [[ -z \"\$$_var\" ]] ; then 19 | export $_var=$_path 20 | elif ! echo \$$_var | egrep -q \"(^|:)$_path($|:)\" ; then 21 | export $_var=$_path:\$$_var 22 | fi" 23 | } 24 | 25 | function find_sbt_root { 26 | while [ ! -d project -a "x$PWD" != "x/" ] ; do 27 | cd .. 28 | done 29 | 30 | if [ "x$PWD" = "/" ]; then 31 | echo "couldn't find sbt project!" 1>&2 32 | exit 1 33 | fi 34 | 35 | echo $PWD 36 | } 37 | 38 | function ensure_java_bin_on_path { 39 | if [ -d "$JAVA_HOME" ]; then 40 | __java_bindir="$JAVA_HOME/bin" 41 | else 42 | __java_bindir=`which java | xargs readlink | xargs dirname` 43 | fi 44 | 45 | if [ -x "$__java_bindir/java" ]; then 46 | path_append PATH $__java_bindir 47 | else 48 | echo "Binary 'java' is not on the PATH, and JAVA_HOME is not set. Fix one of these." 49 | exit 1 50 | fi 51 | } 52 | 53 | function include { 54 | _var=$1 55 | _dir=$2 56 | 57 | for jar in $_dir/lib_managed/compile/*.jar; do 58 | path_append $_var $jar 59 | done 60 | 61 | for jar in $_dir/lib/*.jar; do 62 | path_append $_var $jar 63 | done 64 | 65 | path_append $_var $_dir/src/main/resources 66 | path_append $_var $_dir/target/classes 67 | } 68 | 69 | ensure_java_bin_on_path 70 | 71 | root=$(find_sbt_root) 72 | if [ $? -ne 0 ]; then 73 | exit 1 74 | fi 75 | 76 | if [ -z $project ]; then 77 | project="." 78 | fi 79 | 80 | set -- $(getopt i:ygdh: "$@") 81 | while [ $# -gt 0 ]; do 82 | case "$1" in 83 | -i) 84 | include CP $2 85 | ;; 86 | -y) 87 | JAVA_OPTS="-agentlib:yjpagent $JAVA_OPTS" 88 | ;; 89 | -g) 90 | GC_OPTS="-verbosegc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -Xloggc:/tmp/gc.log" 91 | ;; 92 | -d) 93 | JAVA_OPTS="-Xdebug -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n $JAVA_OPTS" 94 | ;; 95 | -h) 96 | path_prepend DYLD_LIBRARY_PATH $2 97 | JAVA_OPTS="-Xbootclasspath/a:$2 -agentlib:heapster $JAVA_OPTS" 98 | ;; 99 | --) shift; break;; 100 | esac 101 | shift 102 | done 103 | 104 | if [ $# -eq 0 ]; then 105 | echo "usage: $0 [-i INCLUDE] [-h HEAPSTER] [-ygd] CLASS ..." >&2 106 | echo " -i INCLUDE include in the classpath the project with the sbt root INCLUDE" >&2 107 | echo " -h HEAPSTER use heapster in the directory HEAPSTER" >&2 108 | echo " -y enable yourkit debugging" >&2 109 | echo " -g enable GC debugging/logging" >&2 110 | echo " -d enable JVM debugging" >&2 111 | exit 1 112 | fi 113 | 114 | ## Set up the classpath. Scala base jars first. 115 | path_prepend CP $root/project/boot/scala-2.8.1/lib/scala-library.jar 116 | path_prepend CP $root/project/boot/scala-2.8.1/lib/scala-compiler.jar 117 | 118 | # This goes last: 119 | include CP $root 120 | 121 | # Disable IPv6 122 | export JAVA_OPTS="-Djava.net.preferIPv4Stack=true $JAVA_OPTS" 123 | 124 | export JAVA_OPTS="$JAVA_OPTS -server -Xmx2G -XX:MaxPermSize=256m -XX:+UseConcMarkSweepGC -XX:+UseParNewGC $GC_OPTS" 125 | exec java $JAVA_OPTS -cp $CP "$@" 126 | -------------------------------------------------------------------------------- /web/ru/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Scala Школа 4 | --- 5 | 6 |
7 |

Scala Школа!

8 |

Из ∅ к Распределенному Сервису

9 |
10 | 11 | 12 |
13 |
14 | 15 |

Other Languages:

16 | 21 | 22 |

Об этих уроках

23 |

Scala школа начиналась как серия лекций в компании Twitter, для того чтобы подготовить опытных инженеров к тому, чтобы стать продуктивными Scala программистами. Scala является относительно новым языком, но опирается на многие привычные понятия. Таким образом, эти лекции предполагают, что читатели знакомы с основными понятиями, а лекции помогут показать, как эти понятия могут использоваться в Scala. Мы считаем это эффективным способом подготовки новых инженеров в короткие сроки. Существуют дополнительные материалы, которые сопровождают эти лекции. Мы считаем, что они будут к месту. 24 |

25 | 26 |

Как будут проходить уроки?

27 |

Мы считаем, что лучше всего подойти к изучению Scala не так, будто это улучшенная Java, а словно это новый язык. Опыт работы с Java не требуется. Основное внимание будет уделяться интерпретатору и объектно-ориентированному и функциональному стилю, а также стилю программирования, который используется в нашей компании. Акцент будет сделан на удобство сопровождения, доходчивость, и использование системы типов.

28 |

Для большинства уроков не требует никакого программного обеспечения, кроме REPL(англ. read-eval-print-loop; рус. интерактивная среда программирования - прим. переводчика) Scala. Читателю предлагается выполнять задания вместе с нами, и не останавливаться на достигнутом! Считайте эти уроки отправной точкой для изучения языка.

29 | 30 |

Дополнительные материалы

31 |

Хотите знать больше? 32 |

37 | 38 |
39 |
40 |

Уроки

41 |
42 | {% for lesson in site.lessons %} 43 |
{{ lesson.title_ru }}
44 |
{{ lesson.desc_ru }}
45 | {% endfor %} 46 |
47 |
48 |
49 | 50 | 51 | Fork me on GitHub 52 | 53 | 54 | -------------------------------------------------------------------------------- /searchbird/src/main/scala/com/twitter/searchbird/Index.scala: -------------------------------------------------------------------------------- 1 | package com.twitter.searchbird 2 | 3 | import scala.collection.mutable 4 | import org.apache.thrift.protocol.TBinaryProtocol 5 | 6 | import com.twitter.util._ 7 | import com.twitter.conversions.time._ 8 | import com.twitter.logging.Logger 9 | import com.twitter.finagle.builder.ClientBuilder 10 | import com.twitter.finagle.thrift.ThriftClientFramedCodec 11 | 12 | 13 | trait Index { 14 | def get(key: String): Future[String] 15 | def put(key: String, value: String): Future[Unit] 16 | def search(key: String): Future[List[String]] 17 | } 18 | 19 | class ResidentIndex extends Index { 20 | val log = Logger.get(getClass) 21 | 22 | val forward = new mutable.HashMap[String, String] 23 | with mutable.SynchronizedMap[String, String] 24 | val reverse = new mutable.HashMap[String, Set[String]] 25 | with mutable.SynchronizedMap[String, Set[String]] 26 | 27 | def get(key: String) = { 28 | forward.get(key) match { 29 | case None => 30 | log.debug("get %s: miss", key) 31 | Future.exception(new SearchbirdException("No such key")) 32 | case Some(value) => 33 | log.debug("get %s: hit", key) 34 | Future(value) 35 | } 36 | } 37 | 38 | def put(key: String, value: String) = { 39 | log.debug("put %s", key) 40 | 41 | forward(key) = value 42 | 43 | // admit only one updater. 44 | synchronized { 45 | (Set() ++ value.split(" ")) foreach { token => 46 | val current = reverse.get(token) getOrElse Set() 47 | reverse(token) = current + key 48 | } 49 | } 50 | 51 | Future.Unit 52 | } 53 | 54 | def search(query: String) = Future.value { 55 | val tokens = query.split(" ") 56 | val hits = tokens map { token => reverse.getOrElse(token, Set()) } 57 | val intersected = hits reduceLeftOption { _ & _ } getOrElse Set() 58 | intersected.toList 59 | } 60 | } 61 | 62 | class CompositeIndex(indices: Seq[Index]) extends Index { 63 | require(!indices.isEmpty) 64 | 65 | def get(key: String) = try { 66 | println("GET", key) 67 | val queries = indices.map { idx => 68 | idx.get(key) map { r => Some(r) } handle { case e => None } 69 | } 70 | 71 | Future.collect(queries) flatMap { results => 72 | println("got results", results.mkString(",")) 73 | results.find { _.isDefined } map { _.get } match { 74 | case Some(v) => Future.value(v) 75 | case None => Future.exception(new SearchbirdException("No such key")) 76 | } 77 | } 78 | } catch { 79 | case e => 80 | println("got exc", e) 81 | throw e 82 | } 83 | 84 | def put(key: String, value: String) = 85 | Future.exception(new SearchbirdException("put() not supported by CompositeIndex")) 86 | 87 | def search(query: String) = { 88 | val queries = indices.map { _.search(query) rescue { case _=> Future.value(Nil) } } 89 | Future.collect(queries) map { results => (Set() ++ results.flatten) toList } 90 | } 91 | } 92 | 93 | class RemoteIndex(hosts: String) extends Index { 94 | val transport = ClientBuilder() 95 | .name("remoteIndex") 96 | .hosts(hosts) 97 | .codec(ThriftClientFramedCodec()) 98 | .hostConnectionLimit(1) 99 | .timeout(500.milliseconds) 100 | .build() 101 | val client = new SearchbirdServiceClientAdapter( 102 | new thrift.SearchbirdService.ServiceToClient( 103 | transport, new TBinaryProtocol.Factory)) 104 | 105 | def get(key: String) = client.get(key) 106 | def put(key: String, value: String) = client.put(key, value) map { _ => () } 107 | def search(query: String) = client.search(query) map { _.toList } 108 | } 109 | -------------------------------------------------------------------------------- /searchbird/src/scripts/searchbird.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # searchbird init.d script. 4 | # 5 | # All java services require the same directory structure: 6 | # /usr/local/$APP_NAME 7 | # /var/log/$APP_NAME 8 | # /var/run/$APP_NAME 9 | 10 | APP_NAME="searchbird" 11 | ADMIN_PORT="9900" 12 | VERSION="@VERSION@" 13 | APP_HOME="/usr/local/$APP_NAME/current" 14 | DAEMON="/usr/local/bin/daemon" 15 | 16 | JAR_NAME="$APP_NAME-$VERSION.jar" 17 | STAGE="production" 18 | 19 | HEAP_OPTS="-Xmx4096m -Xms4096m -XX:NewSize=768m" 20 | GC_OPTS="-XX:+UseParallelOldGC -XX:+UseAdaptiveSizePolicy -XX:MaxGCPauseMillis=1000 -XX:GCTimeRatio=99" 21 | GC_LOG_OPTS="-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintTenuringDistribution -XX:+PrintHeapAtGC" 22 | GC_LOG="-Xloggc:/var/log/$APP_NAME/gc.log" 23 | DEBUG_OPTS="-XX:ErrorFile=/var/log/$APP_NAME/java_error%p.log" 24 | JAVA_OPTS="-server -Dstage=$STAGE $GC_OPTS $GC_LOG_OPTS $GC_LOG $HEAP_OPTS $DEBUG_OPTS" 25 | 26 | pidfile="/var/run/$APP_NAME/$APP_NAME.pid" 27 | daemon_pidfile="/var/run/$APP_NAME/$APP_NAME-daemon.pid" 28 | daemon_args="--name $APP_NAME --pidfile $daemon_pidfile --core --chdir /" 29 | daemon_start_args="--stdout=/var/log/$APP_NAME/stdout --stderr=/var/log/$APP_NAME/error" 30 | 31 | function running() { 32 | $DAEMON $daemon_args --running 33 | } 34 | 35 | function find_java() { 36 | if [ ! -z "$JAVA_HOME" ]; then 37 | return 38 | fi 39 | for dir in /opt/jdk /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home /usr/java/default; do 40 | if [ -x $dir/bin/java ]; then 41 | JAVA_HOME=$dir 42 | break 43 | fi 44 | done 45 | } 46 | 47 | find_java 48 | 49 | 50 | case "$1" in 51 | start) 52 | echo -n "Starting $APP_NAME... " 53 | 54 | if [ ! -r $APP_HOME/$JAR_NAME ]; then 55 | echo "FAIL" 56 | echo "*** $APP_NAME jar missing: $APP_HOME/$JAR_NAME - not starting" 57 | exit 1 58 | fi 59 | if [ ! -x $JAVA_HOME/bin/java ]; then 60 | echo "FAIL" 61 | echo "*** $JAVA_HOME/bin/java doesn't exist -- check JAVA_HOME?" 62 | exit 1 63 | fi 64 | if running; then 65 | echo "already running." 66 | exit 0 67 | fi 68 | 69 | ulimit -c unlimited || echo -n " (no coredump)" 70 | $DAEMON $daemon_args $daemon_start_args -- sh -c "echo "'$$'" > $pidfile; exec ${JAVA_HOME}/bin/java ${JAVA_OPTS} -jar ${APP_HOME}/${JAR_NAME}" 71 | tries=0 72 | while ! running; do 73 | tries=$((tries + 1)) 74 | if [ $tries -ge 5 ]; then 75 | echo "FAIL" 76 | exit 1 77 | fi 78 | sleep 1 79 | done 80 | echo "done." 81 | ;; 82 | 83 | stop) 84 | echo -n "Stopping $APP_NAME... " 85 | if ! running; then 86 | echo "wasn't running." 87 | exit 0 88 | fi 89 | 90 | curl -m 5 -s https://localhost:${ADMIN_PORT}/shutdown.txt > /dev/null 91 | tries=0 92 | while running; do 93 | tries=$((tries + 1)) 94 | if [ $tries -ge 15 ]; then 95 | echo "FAILED SOFT SHUTDOWN, TRYING HARDER" 96 | if [ -f $pidfile ]; then 97 | kill $(cat $pidfile) 98 | else 99 | echo "CAN'T FIND PID, TRY KILL MANUALLY" 100 | exit 1 101 | fi 102 | hardtries=0 103 | while running; do 104 | hardtries=$((hardtries + 1)) 105 | if [ $hardtries -ge 5 ]; then 106 | echo "FAILED HARD SHUTDOWN, TRY KILL -9 MANUALLY" 107 | exit 1 108 | fi 109 | sleep 1 110 | done 111 | fi 112 | sleep 1 113 | done 114 | echo "done." 115 | ;; 116 | 117 | status) 118 | if running; then 119 | echo "$APP_NAME is running." 120 | else 121 | echo "$APP_NAME is NOT running." 122 | fi 123 | ;; 124 | 125 | restart) 126 | $0 stop 127 | sleep 2 128 | $0 start 129 | ;; 130 | 131 | *) 132 | echo "Usage: /etc/init.d/${APP_NAME}.sh {start|stop|restart|status}" 133 | exit 1 134 | ;; 135 | esac 136 | 137 | exit 0 138 | -------------------------------------------------------------------------------- /web/zh_cn/pattern-matching-and-functional-composition.textile: -------------------------------------------------------------------------------- 1 | --- 2 | prev: collections.textile 3 | next: type-basics.textile 4 | title: 模式匹配与函数组合 5 | layout: post 6 | --- 7 | 8 | 课程内容: 9 | 10 | * "函数组合":#composition 11 | ** compose 12 | ** andThen 13 | * "柯里化 vs 偏应用":#curryvspartial 14 | * "偏函数 PartialFunctions":#PartialFunction 15 | ** 范围和域 range and domain 16 | ** 使用orElse进行组合 17 | * "case 之谜":#case 18 | 19 | h2(#composition). 函数组合 20 | 21 | 让我们创建两个函数: 22 | 23 |
 24 | scala> def f(s: String) = "f(" + s + ")"
 25 | f: (String)java.lang.String
 26 | 
 27 | scala> def g(s: String) = "g(" + s + ")"
 28 | g: (String)java.lang.String
 29 | 
30 | 31 | h3. compose 32 | 33 | compose 组合其他函数形成一个新的函数 f(g(x)) 34 | 35 |
 36 | scala> val fComposeG = f _ compose g _
 37 | fComposeG: (String) => java.lang.String = 
 38 | 
 39 | scala> fComposeG("yay")
 40 | res0: java.lang.String = f(g(yay))
 41 | 
42 | 43 | h3. andThen 44 | 45 | andThencompose很像,但是调用顺序是先调用第一个函数,然后调用第二个,即g(f(x)) 46 | 47 |
 48 | scala> val fAndThenG = f _ andThen g _
 49 | fAndThenG: (String) => java.lang.String = 
 50 | 
 51 | scala> fAndThenG("yay")
 52 | res1: java.lang.String = g(f(yay))
 53 | 
54 | 55 | h2(#curryvspartial). 柯里化 vs 偏应用 56 | 57 | h3. case 语句 58 | 59 | h4. 那么究竟什么是case语句? 60 | 61 | 这是一个名为PartialFunction的函数的子类。 62 | 63 | h4. 多个case语句的集合是什么? 64 | 65 | 他们是共同组合在一起的多个PartialFunction。 66 | 67 | h2(#PartialFunction). 理解PartialFunction(偏函数) 68 | 69 | 对给定的输入参数类型,函数可接受该类型的任何值。换句话说,一个(Int) => String 的函数可以接收任意Int值,并返回一个字符串。 70 | 71 | 对给定的输入参数类型,偏函数只能接受该类型的某些特定的值。一个定义为(Int) => String 的偏函数可能不能接受所有Int值为输入。 72 | 73 | isDefinedAt 是PartialFunction的一个方法,用来确定PartialFunction是否能接受一个给定的参数。 74 | 75 | __注意__ 偏函数PartialFunction 和我们前面提到的部分应用函数是无关的。 76 | 77 | *参考* Effective Scala 对PartialFunction的意见。 78 | 79 |
 80 | scala> val one: PartialFunction[Int, String] = { case 1 => "one" }
 81 | one: PartialFunction[Int,String] = 
 82 | 
 83 | scala> one.isDefinedAt(1)
 84 | res0: Boolean = true
 85 | 
 86 | scala> one.isDefinedAt(2)
 87 | res1: Boolean = false
 88 | 
89 | 90 | 您可以调用一个偏函数。 91 | 92 |
 93 | scala> one(1)
 94 | res2: String = one
 95 | 
96 | 97 | PartialFunctions可以使用orElse组成新的函数,得到的PartialFunction反映了是否对给定参数进行了定义。 98 | 99 |
100 | scala> val two: PartialFunction[Int, String] = { case 2 => "two" }
101 | two: PartialFunction[Int,String] = 
102 | 
103 | scala> val three: PartialFunction[Int, String] = { case 3 => "three" }
104 | three: PartialFunction[Int,String] = 
105 | 
106 | scala> val wildcard: PartialFunction[Int, String] = { case _ => "something else" }
107 | wildcard: PartialFunction[Int,String] = 
108 | 
109 | scala> val partial = one orElse two orElse three orElse wildcard
110 | partial: PartialFunction[Int,String] = 
111 | 
112 | scala> partial(5)
113 | res24: String = something else
114 | 
115 | scala> partial(3)
116 | res25: String = three
117 | 
118 | scala> partial(2)
119 | res26: String = two
120 | 
121 | scala> partial(1)
122 | res27: String = one
123 | 
124 | scala> partial(0)
125 | res28: String = something else
126 | 
127 | 128 | h3(#case). case 之谜 129 | 130 | 上周我们看到一些新奇的东西。我们在通常应该使用函数的地方看到了一个case语句。 131 | 132 |
133 | scala> case class PhoneExt(name: String, ext: Int)
134 | defined class PhoneExt
135 | 
136 | scala> val extensions = List(PhoneExt("steve", 100), PhoneExt("robey", 200))
137 | extensions: List[PhoneExt] = List(PhoneExt(steve,100), PhoneExt(robey,200))
138 | 
139 | scala> extensions.filter { case PhoneExt(name, extension) => extension < 200 }
140 | res0: List[PhoneExt] = List(PhoneExt(steve,100))
141 | 
142 | 143 | 为什么这段代码可以工作? 144 | 145 | filter使用一个函数。在这个例子中是一个谓词函数(PhoneExt) => Boolean。 146 | 147 | PartialFunction是Function的子类型,所以filter也可以使用PartialFunction! 148 | -------------------------------------------------------------------------------- /web/ko/pattern-matching-and-functional-composition.textile: -------------------------------------------------------------------------------- 1 | --- 2 | prev: collections.textile 3 | next: type-basics.textile 4 | title: 패턴 매치와 함수 합성 5 | layout: post 6 | --- 7 | 8 | 이번 강좌에서 다루는 내용은 다음과 같다. 9 | 10 | * "함수 합성":#composition 11 | ** compose 12 | ** andThen 13 | * "커링과 부분 적용 비교":#curryvspartial 14 | * "PartialFunction(부분 함수 클래스)":#PartialFunction 15 | ** 치역과 정의역 16 | ** orElse로 합성하기 17 | * "case 문이란 무엇인가?":#case 18 | 19 | h2(#composition). 함수 합성 20 | 21 | 다음 두 함수가 유용하다고 가정하자. 22 | 23 |
 24 | scala> def addUmm(x: String) = x + " umm"
 25 | addUmm: (x: String)String
 26 | 
 27 | scala>  def addAhem(x: String) = x + " ahem"
 28 | addAhem: (x: String)String
 29 | 
30 | 31 | h3. compose 32 | 33 | f compose g를 하면 두 함수를 f(g(x))과 같이 합성한다. 34 | 35 |
 36 | scala> val ummThenAhem = addAhem _ compose addUmm _
 37 | ummThenAhem: (String) => String = 
 38 | 
 39 | scala> ummThenAhem("well")
 40 | res0: String = well umm ahem
 41 | 
42 | 43 | h3. andThen 44 | 45 | andThencompose와 비슷하지만, 처음 오는 함수를 먼저 호출한 다음, 두번째 함수를 호출한다. 즉, f andThen gg(f(x)) 46 | 47 |
 48 | scala> val ahemThenUmm = addAhem _ andThen addUmm _
 49 | ahemThenUmm: (String) => String = 
 50 | 
 51 | scala> ahemThenUmm("well")
 52 | res1: String = well ahem umm
 53 | 
54 | 55 | h2(#curryvspartial). 커링과 부분 적용의 비교 56 | 57 | h3. 케이스 문 58 | 59 | h4. 케이스 문은 도대체 무엇인가? 60 | 61 | 케이스 문은 PartialFunction이라 불리는 함수의 하위 클래스이다. 62 | 63 | h4. 케이스 문을 여러개 사용하는 것은 무엇인가? 64 | 65 | 여러 PartialFunction들이 서로 compose된 것이다. 66 | 67 | h2(#PartialFunction). PartialFunction 68 | 69 | 함수는 정의된 모든 인자 값에 대해 동작한다. 다른식으로 말하자면, (Int) => String 타입으로 정의된 함수는 모든 Int에 대해 어떤 String을 반환한다. 즉, 어떤 정수이건 그에 대응하는 String이 있기 마련이다. 70 | 71 | 하지만 부분 함수는 인자 타입의 값 중 일부에 대해서만 정의되어 있다. (Int) => String 타입의 부분함수는 일부 Int는 취급하지 않을 것이다. 72 | 73 | isDefinedAt은 PartialFunction에 정의되어 있는 메소드로, 해당 PartialFunction이 주어진 인자를 받을 수 있는지를 알려준다. 74 | 75 | __Note__ PartialFunction은 앞에서 본 부분 적용된 함수와는 전혀 관계가 없다. 76 | 77 | *See Also* 효율적 스칼라에도 부분함수에 대한 내용이 있다. 78 | 79 |
 80 | scala> val one: PartialFunction[Int, String] = { case 1 => "one" }
 81 | one: PartialFunction[Int,String] = 
 82 | 
 83 | scala> one.isDefinedAt(1)
 84 | res0: Boolean = true
 85 | 
 86 | scala> one.isDefinedAt(2)
 87 | res1: Boolean = false
 88 | 
89 | 90 | 부분 함수도 함수처럼 적용이 가능하다. 91 | 92 |
 93 | scala> one(1)
 94 | res2: String = one
 95 | 
96 | 97 | PartialFunction들은 orElse라 불리는 다른 함수를 사용해 합성이 가능하다. orElse는 각 PartialFunction이 인자 값에 대해 정의되어 있는지 여부에 따라 적절한 최종 값을 반환한다. 98 | 99 |
100 | scala> val two: PartialFunction[Int, String] = { case 2 => "two" }
101 | two: PartialFunction[Int,String] = 
102 | 
103 | scala> val three: PartialFunction[Int, String] = { case 3 => "three" }
104 | three: PartialFunction[Int,String] = 
105 | 
106 | scala> val wildcard: PartialFunction[Int, String] = { case _ => "something else" }
107 | wildcard: PartialFunction[Int,String] = 
108 | 
109 | scala> val partial = one orElse two orElse three orElse wildcard
110 | partial: PartialFunction[Int,String] = 
111 | 
112 | scala> partial(5)
113 | res24: String = something else
114 | 
115 | scala> partial(3)
116 | res25: String = three
117 | 
118 | scala> partial(2)
119 | res26: String = two
120 | 
121 | scala> partial(1)
122 | res27: String = one
123 | 
124 | scala> partial(0)
125 | res28: String = something else
126 | 
127 | 128 | h3(#case). 케이스 문의 신비 129 | 130 | 지난 강의에서 무언가 이상한 부분이 있었다. 즉, 케이스 문을 함수가 필요한 위치에 사용하는 것을 보았다. 131 | 132 |
133 | scala> case class PhoneExt(name: String, ext: Int)
134 | defined class PhoneExt
135 | 
136 | scala> val extensions = List(PhoneExt("steve", 100), PhoneExt("robey", 200))
137 | extensions: List[PhoneExt] = List(PhoneExt(steve,100), PhoneExt(robey,200))
138 | 
139 | scala> extensions.filter { case PhoneExt(name, extension) => extension < 200 }
140 | res0: List[PhoneExt] = List(PhoneExt(steve,100))
141 | 
142 | 143 | 어떻게 이런 일이 가능할까? 144 | 145 | 필터는 함수를 받는다. 위 코드에서 필터가 받아야 하는 함수는 (PhoneExt) => Boolean 타입이다. 146 | 147 | PartialFunction은 Function의 하위타입이므로 필터는 PartialFunction도 받을 수 있다! 148 | -------------------------------------------------------------------------------- /web/pattern-matching-and-functional-composition.textile: -------------------------------------------------------------------------------- 1 | --- 2 | prev: collections.textile 3 | next: type-basics.textile 4 | title: Pattern matching & functional composition 5 | layout: post 6 | --- 7 | 8 | This lesson covers: 9 | 10 | * "Function Composition":#composition 11 | ** compose 12 | ** andThen 13 | * "Currying vs Partial Application":#curryvspartial 14 | * "PartialFunctions":#PartialFunction 15 | ** range and domain 16 | ** composition with orElse 17 | * "What is a case statement?":#case 18 | 19 | h2(#composition). Function Composition 20 | 21 | Let's make two aptly-named functions: 22 | 23 |
 24 | scala> def f(s: String) = "f(" + s + ")"
 25 | f: (String)java.lang.String
 26 | 
 27 | scala> def g(s: String) = "g(" + s + ")"
 28 | g: (String)java.lang.String
 29 | 
30 | 31 | h3. compose 32 | 33 | compose makes a new function that composes other functions f(g(x)) 34 | 35 |
 36 | scala> val fComposeG = f _ compose g _
 37 | fComposeG: (String) => java.lang.String = 
 38 | 
 39 | scala> fComposeG("yay")
 40 | res0: java.lang.String = f(g(yay))
 41 | 
42 | 43 | h3. andThen 44 | 45 | andThen is like compose, but calls the first function and then the second, g(f(x)) 46 | 47 |
 48 | scala> val fAndThenG = f _ andThen g _
 49 | fAndThenG: (String) => java.lang.String = 
 50 | 
 51 | scala> fAndThenG("yay")
 52 | res1: java.lang.String = g(f(yay))
 53 | 
54 | 55 | h2(#curryvspartial). Currying vs Partial Application 56 | 57 | h3. case statements 58 | 59 | h4. So just what are case statements? 60 | 61 | It's a subclass of function called a PartialFunction. 62 | 63 | h4. What is a collection of multiple case statements? 64 | 65 | They are multiple PartialFunctions composed together. 66 | 67 | h2(#PartialFunction). Understanding PartialFunction 68 | 69 | A function works for every argument of the defined type. In other words, a function defined as (Int) => String takes any Int and returns a String. 70 | 71 | A Partial Function is only defined for certain values of the defined type. A Partial Function (Int) => String might not accept every Int. 72 | 73 | isDefinedAt is a method on PartialFunction that can be used to determine if the PartialFunction will accept a given argument. 74 | 75 | __Note__ PartialFunction is unrelated to a partially applied function that we talked about earlier. 76 | 77 | *See Also* Effective Scala has opinions about PartialFunction. 78 | 79 |
 80 | scala> val one: PartialFunction[Int, String] = { case 1 => "one" }
 81 | one: PartialFunction[Int,String] = 
 82 | 
 83 | scala> one.isDefinedAt(1)
 84 | res0: Boolean = true
 85 | 
 86 | scala> one.isDefinedAt(2)
 87 | res1: Boolean = false
 88 | 
89 | 90 | You can apply a partial function. 91 | 92 |
 93 | scala> one(1)
 94 | res2: String = one
 95 | 
96 | 97 | PartialFunctions can be composed with something new, called orElse, that reflects whether the PartialFunction is defined over the supplied argument. 98 | 99 |
100 | scala> val two: PartialFunction[Int, String] = { case 2 => "two" }
101 | two: PartialFunction[Int,String] = 
102 | 
103 | scala> val three: PartialFunction[Int, String] = { case 3 => "three" }
104 | three: PartialFunction[Int,String] = 
105 | 
106 | scala> val wildcard: PartialFunction[Int, String] = { case _ => "something else" }
107 | wildcard: PartialFunction[Int,String] = 
108 | 
109 | scala> val partial = one orElse two orElse three orElse wildcard
110 | partial: PartialFunction[Int,String] = 
111 | 
112 | scala> partial(5)
113 | res24: String = something else
114 | 
115 | scala> partial(3)
116 | res25: String = three
117 | 
118 | scala> partial(2)
119 | res26: String = two
120 | 
121 | scala> partial(1)
122 | res27: String = one
123 | 
124 | scala> partial(0)
125 | res28: String = something else
126 | 
127 | 128 | h3(#case). The mystery of case. 129 | 130 | Last week we saw something curious. We saw a case statement used where a function is normally used. 131 | 132 |
133 | scala> case class PhoneExt(name: String, ext: Int)
134 | defined class PhoneExt
135 | 
136 | scala> val extensions = List(PhoneExt("steve", 100), PhoneExt("robey", 200))
137 | extensions: List[PhoneExt] = List(PhoneExt(steve,100), PhoneExt(robey,200))
138 | 
139 | scala> extensions.filter { case PhoneExt(name, extension) => extension < 200 }
140 | res0: List[PhoneExt] = List(PhoneExt(steve,100))
141 | 
142 | 143 | Why does this work? 144 | 145 | filter takes a function. In this case a predicate function of (PhoneExt) => Boolean. 146 | 147 | A PartialFunction is a subtype of Function so filter can also take a PartialFunction! 148 | -------------------------------------------------------------------------------- /web/ru/pattern-matching-and-functional-composition.textile: -------------------------------------------------------------------------------- 1 | --- 2 | prev: collections.textile 3 | next: type-basics.textile 4 | title: Сопоставление с образцом и функциональная композиция 5 | layout: post 6 | --- 7 | 8 | В этом уроке вы узнаете: 9 | 10 | * «Объединение функций»:#composition 11 | ** compose 12 | ** andThen 13 | * «Каррирование и Частичный вызов функций»:#curryvspartial 14 | * «PartialFunction»:#PartialFunction 15 | ** range и domain 16 | ** композиция с помощью orElse 17 | * «Что же такое case утверждения?»:#case 18 | 19 | h2(#composition). Объединение функций 20 | 21 | Давайте создадим две полезные функции: 22 | 23 |
 24 | scala> def addUmm(x: String) = x + " umm"
 25 | addUmm: (x: String)String
 26 | 
 27 | scala>  def addAhem(x: String) = x + " ahem"
 28 | addAhem: (x: String)String
 29 | 
30 | 31 | h3. compose 32 | 33 | compose создает новую функцию, которая является объединением других функций f(g(x)) 34 | 35 |
 36 | scala> val ummThenAhem = addAhem _ compose addUmm _
 37 | ummThenAhem: (String) => String = 
 38 | 
 39 | scala> ummThenAhem("well")
 40 | res0: String = well umm ahem
 41 | 
42 | 43 | h3. andThen 44 | 45 | andThen похожа на compose, но сначала вызывается первая функция, затем вторая g(f(x)) 46 | 47 |
 48 | scala> val ahemThenUmm = addAhem _ andThen addUmm _
 49 | ahemThenUmm: (String) => String = 
 50 | 
 51 | scala> ahemThenUmm("well")
 52 | res1: String = well ahem umm
 53 | 
54 | 55 | h2(#curryvspartial). Каррирование и Частичный вызов функций 56 | 57 | h3. case утверждения 58 | 59 | h4. Что же такое case утверждения? 60 | 61 | Это подкласс функций, который называется PartialFunction. 62 | 63 | h4. Что представляет собой совокупность множества case утверждений? 64 | 65 | Это множество объединений PartialFunctions вместе. 66 | 67 | h2(#PartialFunction). Понимание PartialFunction 68 | 69 | Функция работает для каждого аргумента определенного типа. Другими словами, функция объявляется как (Int) => String, принимающая любой Int и возвращающая строку. 70 | 71 | Частичная функция определена только для определенных значений определенного типа. Частичные функции (Int) => String не может принимать любой Int. 72 | 73 | isDefinedAt это метод PartialFunction, который может использоваться, чтобы определить, будет ли PartialFunction принимать данный аргумент. 74 | 75 | __Заметьте__ PartialFunction не связана с частично вызываемыми функциями, о которых мы говорили ранее. 76 | 77 | *Смотрите также:* В Effective Scala описывается PartialFunction. 78 | 79 |
 80 | scala> val one: PartialFunction[Int, String] = { case 1 => "one" }
 81 | one: PartialFunction[Int,String] = 
 82 | 
 83 | scala> one.isDefinedAt(1)
 84 | res0: Boolean = true
 85 | 
 86 | scala> one.isDefinedAt(2)
 87 | res1: Boolean = false
 88 | 
89 | 90 | Вы можете использовать частичную функцию. 91 | 92 |
 93 | scala> one(1)
 94 | res2: String = one
 95 | 
96 | 97 | PartialFunctions может быть объединена с чем-то новым, называемым OrElse, который показывает, определен ли PartialFunction с передаваемым аргументом. 98 | 99 |
100 | scala> val two: PartialFunction[Int, String] = { case 2 => "two" }
101 | two: PartialFunction[Int,String] = 
102 | 
103 | scala> val three: PartialFunction[Int, String] = { case 3 => "three" }
104 | three: PartialFunction[Int,String] = 
105 | 
106 | scala> val wildcard: PartialFunction[Int, String] = { case _ => "something else" }
107 | wildcard: PartialFunction[Int,String] = 
108 | 
109 | scala> val partial = one orElse two orElse three orElse wildcard
110 | partial: PartialFunction[Int,String] = 
111 | 
112 | scala> partial(5)
113 | res24: String = something else
114 | 
115 | scala> partial(3)
116 | res25: String = three
117 | 
118 | scala> partial(2)
119 | res26: String = two
120 | 
121 | scala> partial(1)
122 | res27: String = one
123 | 
124 | scala> partial(0)
125 | res28: String = something else
126 | 
127 | 128 | h3(#case). Тайна о case. 129 | 130 | На прошлой неделе мы увидели нечто любопытное. Мы видели case утверждение там, где обычно использовалась функция. 131 | 132 |
133 | scala> case class PhoneExt(name: String, ext: Int)
134 | defined class PhoneExt
135 | 
136 | scala> val extensions = List(PhoneExt("steve", 100), PhoneExt("robey", 200))
137 | extensions: List[PhoneExt] = List(PhoneExt(steve,100), PhoneExt(robey,200))
138 | 
139 | scala> extensions.filter { case PhoneExt(name, extension) => extension < 200 }
140 | res0: List[PhoneExt] = List(PhoneExt(steve,100))
141 | 
142 | 143 | Почему это работает? 144 | 145 | filter принимает функцию. В этом случае используется функция-предикат (PhoneExt) => Boolean. 146 | 147 | PartialFunction — это подтип Function, поэтому filter может принимать PartialFunction в качестве аргумента! 148 | -------------------------------------------------------------------------------- /web/_config.yml: -------------------------------------------------------------------------------- 1 | gems: 2 | - jekyll-textile-converter 3 | permalink: none 4 | lessons: 5 | - 6 | title: Basics 7 | title_ko: 기초 8 | title_ru: Основы 9 | title_zh_cn: 基础 10 | url: basics.html 11 | desc: Values, functions, classes, methods, inheritance, try-catch-finally. Expression-oriented programming 12 | desc_ko: 값, 함수, 클래스, 메소드, 상속, try-catch-finally. 식 중심의 프로그래밍 13 | desc_ru: Переменные, функции, классы, методы, наследование, try-catch-finally. Программирование ориентированное на выражения 14 | desc_zh_cn: 值,函数,类,方法,继承,try-catch-finally。面向表达式编程 15 | - 16 | title: Basics continued 17 | title_ko: 기초(계속) 18 | title_ru: Основы языка. Продолжение 19 | title_zh_cn: 基础知识(续) 20 | url: basics2.html 21 | desc: Case classes, objects, packages, apply, update, Functions are Objects (uniform access principle), pattern matching. 22 | desc_ko: 케이스 클래스, 객체, 패키지, apply, update, 함수는 객체이다(동일한 억세스 원칙), 패턴 매치 23 | desc_ru: Case классы, объекты, пакеты, apply, update, Функции как Объекты (единый принцип доступа), сопоставление с образцом. 24 | desc_zh_cn: 样本类,对象,包,应用,更新,函数即对象(统一访问原则),模式匹配。 25 | - 26 | title: Collections 27 | title_ko: 컬렉션 28 | title_ru: Коллекции 29 | title_zh_cn: 集合 30 | url: collections.html 31 | desc: Lists, Maps, functional combinators (map, foreach, filter, zip, folds) 32 | desc_ko: 리스트, 맵, 함수 콤비네이터(map, foreach, filter, zip, fold들) 33 | desc_ru: Списки, Карты, функциональные комбинаторы (map, foreach, filter, zip, folds) 34 | desc_zh_cn: 列表,映射,功能组合(map, foreach, filter, zip, folds) 35 | - 36 | title: Pattern matching & functional composition 37 | title_ko: 패턴 매치와 함수 합성 38 | title_ru: Сопоставление с образцом и функкциональная композиция 39 | title_zh_cn: 模式匹配与函数组合 40 | url: pattern-matching-and-functional-composition.html 41 | desc: More functions! PartialFunctions, more Pattern Matching 42 | desc_ko: 함수에 대한 추가 설명, 부분함수, 패턴 매치에 대한 설명 43 | desc_ru: Еще больше функций! Частичные функции, еще больше сопоставления с образцом 44 | desc_zh_cn: 更多函数!偏函数,更多模式匹配 45 | - 46 | title: Type & polymorphism basics 47 | title_ko: 타입과 다형성의 기초 48 | title_ru: Основы типов и полиморфизма 49 | title_zh_cn: 类型和多态基础 50 | url: type-basics.html 51 | desc: Basic Types and type polymorphism, type inference, variance, bounds, quantification 52 | desc_ko: 기본 타입, 타입 다형성, 타입 추론, 공변성, 바운드, 한정하기 53 | desc_ru: Основные типы и полиморфизм типов, вывод типов, изменчивость, пределы, квантификация 54 | desc_zh_cn: 基本类型和类型多态性,类型推断,变性,边界,量化 55 | - 56 | title: Advanced types 57 | title_ko: 타입(고급 주제) 58 | title_ru: Дополнительные типы 59 | title_zh_cn: 高级类型 60 | url: advanced-types.html 61 | desc: Advanced Types, view bounds, higher-kinded types, recursive types, structural types 62 | desc_ko: 타입에 대한 추가 설명, 뷰 바운드, 상류 타입, 재귀적 타입, 구조적 타입 63 | desc_ru: Дополнительные типы, видимое ограничение, типы высшего порядка, рекурсивные типы, структурные типы 64 | desc_zh_cn: 高级类型,视界,更高级多态性类型,递归类型,结构类型 65 | - 66 | title: Simple Build Tool 67 | title_ko: SBT 68 | title_ru: Simple Build Tool 69 | title_zh_cn: 简单构建工具 70 | url: sbt.html 71 | desc: All about SBT, the standard Scala build tool 72 | desc_ko: 표준 스칼라 빌드 도구인 SBT에 대한 설명 73 | desc_ru: Все о SBT, Стандартное средство сборки приложений для Scala 74 | desc_zh_cn: 关于SBT——标准的Scala构建工具 75 | - 76 | title: More collections 77 | title_ko: 컬렉션(계속) 78 | title_ru: Подробнее о коллекциях 79 | title_zh_cn: 更多的集合 80 | url: coll2.html 81 | desc: Tour of the Scala Collections library 82 | desc_ko: 스칼라 컬렉션 라이브러리 소개 83 | desc_ru: Рассказ о библиотеке коллекций Scala 84 | desc_zh_cn: Scala Collections库指南 85 | - 86 | title: Testing with specs 87 | title_ko: specs로 테스트하기 88 | title_ru: Тестирование с помощью specs 89 | title_zh_cn: 使用specs测试 90 | url: specs.html 91 | desc_ko: Scala 테스트 프레임워크 Specs로 테스트하기 92 | desc_ru: Написание тестов с помощью Specs, Scala BDD фреймфорк для тестирования 93 | - 94 | title: Concurrency in Scala 95 | title_ko: 스칼라의 동시성 96 | title_ru: Параллельность в Scala 97 | title_zh_cn: Scala 并发编程 98 | url: concurrency.html 99 | desc: Runnable, Callable, threads, Futures 100 | desc_ko: Runnable, Callable, 쓰레드, Future 101 | desc_ru: Runnable, Callable, потоки, Futures 102 | desc_zh_cn: Runnable, Callable, 线程, Futures 103 | - 104 | title: Java + Scala 105 | title_ko: Java + Scala 106 | title_ru: Java + Scala 107 | title_zh_cn: Java + Scala 108 | url: java.html 109 | desc: 'Java interop: Using Scala from Java' 110 | desc_ko: '자바에서 스칼라 사용하기' 111 | desc_ru: 'Java совместимость: Использование Scala из Java' 112 | desc_zh_cn: 'Java跨平台交互:在Java中使用Scala' 113 | - 114 | title: An introduction to Finagle 115 | title_ko: 피네이글(Finagle) 소개 116 | title_ru: Введение в Finagle 117 | title_zh_cn: Finagle介绍 118 | url: finagle.html 119 | desc: 'Finagle primitives: Future, Service, Filter, Builder' 120 | desc_ko: '피네이글 기본 요소: Future, Servie, Filter, Builder' 121 | desc_ru: 'Finagle примитивы: Future, Service, Filter, Builder' 122 | desc_zh_cn: 'Finagle原语:Future, Service, Filter, Builder' 123 | - 124 | title: Searchbird 125 | title_ko: 검색조(Searchbird) 126 | title_ru: Searchbird 127 | title_zh_cn: Searchbird 128 | url: searchbird.html 129 | desc: Building a distributed search engine using Finagle 130 | desc_ko: 피네이글로 분산 검색 엔진 만들기 131 | desc_ru: Создание распределенного поискового движка, используя Finagle 132 | desc_zh_cn: 利用Finagle构建一个分布式搜索引擎 133 | 134 | -------------------------------------------------------------------------------- /web/zh_cn/specs.textile: -------------------------------------------------------------------------------- 1 | --- 2 | prev: coll2.textile 3 | next: concurrency.textile 4 | title: 使用specs进行测试 5 | layout: post 6 | --- 7 | 8 | 本节课将介绍如何使用specs —— 一个Scala行为驱动设计(BDD)框架,来进行测试。 9 | 10 | * "扩展规格":#example 11 | ** nested examples 12 | * "执行模型":#scope 13 | * "Setup and TearDown":#setup 14 | ** doFirst 15 | ** doBefore 16 | ** doAfter 17 | * "Matchers 匹配器":#matchers 18 | ** mustEqual 19 | ** contains 20 | ** sameSize? 21 | ** 编写你自己的匹配器 22 | * "Mocks":#mocks 23 | * "Spies":#spies 24 | * "在sbt中运行":#sbt 25 | 26 | 27 | h2(#example). 扩展规格 28 | 29 | 让我们直接开始。 30 | 31 |
 32 | import org.specs._
 33 | 
 34 | object ArithmeticSpec extends Specification {
 35 |   "Arithmetic" should {
 36 |     "add two numbers" in {
 37 |       1 + 1 mustEqual 2
 38 |     }
 39 |     "add three numbers" in {
 40 |       1 + 1 + 1 mustEqual 3
 41 |     }
 42 |   }
 43 | }
 44 | 
45 | 46 | *Arithmetic(算术)* 是一个 *规范约束下的系统* 47 | 48 | *add(加)* 是一个上下文。 49 | 50 | *add two numbers(两个数相加)*,和 *add three numbers(三个数字相加)* 是例子。 51 | 52 | @mustEqual@ 表示 *预期* 53 | 54 | @1 mustEqual 1@ 是编写实际测试前使用的一种常见的 *预期* 占位符。所有的测试用例都应该至少有一个预期。 55 | 56 | h3. 复制 57 | 58 | 注意到两个测试都是怎样将 @add@ 加在其名称中的吗?我们可以通过 *嵌套* 预期摆脱这种重复。 59 | 60 |
 61 | import org.specs._
 62 | 
 63 | object ArithmeticSpec extends Specification {
 64 |   "Arithmetic" should {
 65 |     "add" in {
 66 |       "two numbers" in {
 67 |         1 + 1 mustEqual 2
 68 |       }
 69 |       "three numbers" in {
 70 |         1 + 1 + 1 mustEqual 3
 71 |       }
 72 |     }
 73 |   }
 74 | }
 75 | 
76 | 77 | h2(#scope). 执行模型 78 | 79 |
 80 | object ExecSpec extends Specification {
 81 |   "Mutations are isolated" should {
 82 |     var x = 0
 83 |     "x equals 1 if we set it." in {
 84 |       x = 1
 85 |       x mustEqual 1
 86 |     }
 87 |     "x is the default value if we don't change it" in {
 88 |       x mustEqual 0
 89 |     }
 90 |   }
 91 | }
 92 | 
93 | 94 | h2(#setup). Setup, Teardown 95 | 96 | h3. doBefore & doAfter 97 | 98 |
 99 | "my system" should {
100 |   doBefore { resetTheSystem() /** user-defined reset function */ }
101 |   "mess up the system" in {...}
102 |   "and again" in {...}
103 |   doAfter { cleanThingsUp() }
104 | }
105 | 
106 | 107 | *注意* @doBefore@/@doAfter@ 只能运行在叶子用例上。 108 | 109 | h3. doFirst & doLast 110 | 111 | @doFirst@/@doLast@ 用来做一次性的设置。(需要例子,我不使用这个) 112 | 113 |
114 | "Foo" should {
115 |   doFirst { openTheCurtains() }
116 |   "test stateless methods" in {...}
117 |   "test other stateless methods" in {...}
118 |   doLast { closeTheCurtains() }
119 | }
120 | 
121 | 122 | h2(#matchers). Matchers 123 | 124 | 你有数据,并且想要确保它是正确的。让我们看看最常用的匹配器是如何帮助你的。 (参考 "匹配器指南":https://code.google.com/p/specs/wiki/MatchersGuide ) 125 | 126 | h3. mustEqual 127 | 128 | 我们已经看到几个mustEqual的例子了。 129 | 130 |
131 | 1 mustEqual 1
132 | 
133 | "a" mustEqual "a"
134 | 
135 | 136 | 引用相等,值相等。 137 | 138 | h3. 序列中的元素 139 | 140 |
141 | val numbers = List(1, 2, 3)
142 | 
143 | numbers must contain(1)
144 | numbers must not contain(4)
145 | 
146 | numbers must containAll(List(1, 2, 3))
147 | numbers must containInOrder(List(1, 2, 3))
148 | 
149 | List(1, List(2, 3, List(4)), 5) must haveTheSameElementsAs(List(5, List(List(4), 2, 3), 1))
150 | 
151 | 152 | 153 | h3. 映射中的元素 154 | 155 |
156 | map must haveKey(k)
157 | map must notHaveKey(k)
158 | 
159 | map must haveValue(v)
160 | map must notHaveValue(v)
161 | 
162 | 163 | h3. 数字 164 | 165 |
166 | a must beGreaterThan(b)
167 | a must beGreaterThanOrEqualTo(b)
168 | 
169 | a must beLessThan(b)
170 | a must beLessThanOrEqualTo(b)
171 | 
172 | a must beCloseTo(b, delta)
173 | 
174 | 175 | 176 | h3. Options 177 | 178 |
179 | a must beNone
180 | 
181 | a must beSome[Type]
182 | 
183 | a must beSomething
184 | 
185 | a must beSome(value)
186 | 
187 | 188 | h3. throwA 189 | 190 |
191 | a must throwA[WhateverException]
192 | 
193 | 194 | 这是一个针对try\catch块中有异常抛出的用例的简写。 195 | 196 | 您也可以期望一个特定的消息 197 | 198 |
199 | a must throwA(WhateverException("message"))
200 | 
201 | 202 | 您也可以匹配异常: 203 | 204 |
205 | a must throwA(new Exception) like {
206 |   case Exception(m) => m.startsWith("bad")
207 | }
208 | 
209 | 210 | 211 | h3. 编写你自己的匹配器 212 | 213 |
214 | import org.specs.matcher.Matcher
215 | 
216 | 217 | h4. 作为一个不变量 218 | 219 |
220 | "A matcher" should {
221 |   "be created as a val" in {
222 |     val beEven = new Matcher[Int] {
223 |       def apply(n: => Int) = {
224 |         (n % 2 == 0, "%d is even".format(n), "%d is odd".format(n))
225 |       }
226 |     }
227 |     2 must beEven
228 |   }
229 | }
230 | 
231 | 232 | 契约是返回一个包含三个值的元组,分别是期望是否为真、为真时的消息和为假时的消息。 233 | 234 | h4. 作为一个样本类 235 | 236 |
237 | case class beEven(b: Int) extends Matcher[Int]() {
238 |   def apply(n: => Int) =  (n % 2 == 0, "%d is even".format(n), "%d is odd".format(n))
239 | }
240 | 
241 | 242 | 使用样本类可以增加代码的重用性。 243 | 244 | h2(#mocks). Mocks 245 | 246 |
247 | import org.specs.Specification
248 | import org.specs.mock.Mockito
249 | 
250 | abstract class Foo[T] {
251 |   def get(i: Int): T
252 | }
253 | 
254 | object MockExampleSpec extends Specification with Mockito {
255 |   val m = mock[Foo[String]]
256 | 
257 |   m.get(0) returns "one"
258 | 
259 |   m.get(0)
260 | 
261 |   there was one(m).get(0)
262 | 
263 |   there was no(m).get(1)
264 | }
265 | 
266 | 267 | *参考* "Using Mockito":https://code.google.com/p/specs/wiki/UsingMockito 268 | 269 | h2(#spies). Spies 270 | 271 | Spies(间谍)可以对真正的对象做一些“局部mocking”: 272 | 273 |
274 | val list = new LinkedList[String]
275 | val spiedList = spy(list)
276 | 
277 | // methods can be stubbed on a spy
278 | spiedList.size returns 100
279 | 
280 | // other methods can also be used
281 | spiedList.add("one")
282 | spiedList.add("two")
283 | 
284 | // and verification can happen on a spy
285 | there was one(spiedList).add("one")
286 | 
287 | 288 | 然而,使用间谍可能会出现非常诡异的情况: 289 | 290 |
291 | // if the list is empty, this will throws an IndexOutOfBoundsException
292 | spiedList.get(0) returns "one"
293 | 
294 | 295 | 这里必须使用 @doReturn@ : 296 | 297 |
298 | doReturn("one").when(spiedList).get(0)
299 | 
300 | 301 | h2(#sbt). 在sbt中运行单个specs 302 | 303 |
304 | > test-only com.twitter.yourservice.UserSpec
305 | 
306 | 307 | 将只运行那个规范。 308 | 309 |
310 | > ~ test-only com.twitter.yourservice.UserSpec
311 | 
312 | 313 | 将在一个循环中运行该测试,文件的每一次修改都将触发测试运行。 314 | -------------------------------------------------------------------------------- /searchbird/TUTORIAL.md: -------------------------------------------------------------------------------- 1 | # Welcome to Searchbird! 2 | 3 | ## Setup 4 | 5 | Scala-bootstrapper has created a fully-functional Scala service for 6 | you. You can verify that things are set up correctly by doing: 7 | 8 | $ sbt update test 9 | 10 | ## Tutorial 11 | 12 | ### Run your service! 13 | 14 | There are two ways to start your service. You can build a runnable 15 | jar and tell java to run it directly: 16 | 17 | $ sbt package-dist 18 | $ java -Dstage=development -jar ./dist/searchbird/searchbird-1.0.0-SNAPSHOT.jar 19 | 20 | or you can ask sbt to run your service: 21 | 22 | $ sbt 'run -f config/development.scala' 23 | 24 | ### Verify that the service is running 25 | 26 | The java/sbt command-lines will "hang" because the server is running in the 27 | foreground. (In production, we use libslack-daemon to wrap java processes into 28 | unix daemons.) Go to another terminal and check for a logfile. If your server 29 | is named "searchbird", there should be a `searchbird.log` with contents like this: 30 | 31 | INF [20110615-14:05:41.656] stats: Starting JsonStatsLogger 32 | INF [20110615-14:05:41.674] admin: Starting TimeSeriesCollector 33 | DEB [20110615-14:05:41.792] nio: Using the autodetected NIO constraint level: 0 34 | 35 | That's your indication that the server is running. :) 36 | 37 | ### View the Thrift IDL for your service 38 | 39 | The IDL for your service is in `src/main/thrift/searchbird.thrift`. The 40 | Thrift compiler uses the IDL to generate bindings for various 41 | languages, making it easy for scripts in those languages to talk to 42 | your service. More information about Thrift and how to write an IDL 43 | for your service can be found [here](https://wiki.apache.org/thrift/Tutorial). 44 | 45 | ### Call your service from ruby 46 | 47 | Your service implements simple get() and put() methods. Once you have 48 | your server running, as above, bring up a different shell and: 49 | 50 | $ cd searchbird 51 | $ bundle install 52 | $ ./dist/searchbird/scripts/console 53 | >> $client 54 | >> $client.put("key1", "valueForKey") 55 | >> $client.get("key1") 56 | 57 | ### Look at the stats for your service 58 | 59 | By default, your project is configured to use 60 | [Ostrich](https://github.com/twitter/ostrich), a library for service 61 | configuration, administration, and stats reporting. Your config file 62 | in `config/development.scala` defines which port ostrich uses for admin 63 | requests. You can view the stats via that port: 64 | 65 | $ curl localhost:9900/stats.txt 66 | counters: 67 | Searchbird/connects: 1 68 | Searchbird/requests: 2 69 | Searchbird/success: 2 70 | ... 71 | 72 | Ostrich also stores historial stats data and can build 73 | [graphs](https://localhost:9900/graph/) for you. 74 | 75 | ### Stop the service 76 | 77 | You can ask the server to shutdown over the admin port also: 78 | 79 | $ curl localhost:9900/shutdown.txt 80 | ok 81 | 82 | ### View the implementation of get() and put() 83 | 84 | In `src/main/scala`, take a look at `SearchbirdServiceImpl.scala`. (This may 85 | have a different name, based on what you called your server.) 86 | 87 | The base interface is specified by thrift. Additionally, we're using Twitter's 88 | async I/O framework: finagle. Finagle (and a lot of great documentation about 89 | it) is hosted here: https://github.com/twitter/finagle 90 | 91 | ### Try adding some timers and counters 92 | 93 | At the top of SearchbirdServiceImpl.scala, add: 94 | 95 | import com.twitter.ostrich.stats.Stats 96 | 97 | Then inside get(): 98 | 99 | Stats.incr("searchbird.gets") 100 | 101 | and inside put(): 102 | 103 | Stats.incr("searchbird.puts") 104 | 105 | Then restart your server, talk to the server via console, and check 106 | your stats: 107 | 108 | $ curl localhost:9900/stats.txt 109 | counters: 110 | Searchbird/connects: 1 111 | Searchbird/requests: 2 112 | Searchbird/success: 2 113 | searchbird.gets: 1 114 | searchbird.puts: 1 115 | 116 | You can also time various things that your server is doing, for 117 | example: 118 | 119 | Stats.time("searchbird.put.latency") { 120 | Thread.sleep(10) // so you can see it 121 | database(key) = value 122 | } 123 | 124 | ### Specs: let's add some tests 125 | 126 | [Specs](https://code.google.com/p/specs/) is a Behavior-Driven Design 127 | framework that allows you to write semi-human-readable descriptions of 128 | how your service should behave and test that those descriptions are 129 | valid. You already have some Specs code for your project in 130 | src/test/scala/com/twitter/searchbird/SearchbirdServiceSpec.scala. Check 131 | out the existing test and add a new one for the counter functionality 132 | we just added. 133 | 134 | import com.twitter.ostrich.stats.Stats 135 | 136 | ... 137 | 138 | "verify stats" in { 139 | val counters = Stats.getCounters 140 | foofa.put("name", "bluebird")() 141 | foofa.get("name")() mustEqual "bluebird" 142 | counters.getOrElse("foofa.gets", 1) must_==1 143 | counters.getOrElse("foofa.puts", 1) must_==1 144 | } 145 | 146 | TODO: add link to scala school lesson on Specs 147 | 148 | ### Automatically compile and test your server when you change code 149 | 150 | By now you've had to Ctrl-C your server and restart it to get changes 151 | to show up. This gets a little tiresome. The build tool we are 152 | using, 153 | [SBT (simple build tool)](https://code.google.com/p/simple-build-tool/) 154 | has a console that you can access by just running "sbt" from the 155 | command line. 156 | 157 | $ sbt 158 | [info] Standard project rules 0.11.4 loaded (2011-03-18). 159 | [warn] No .svnrepo file; no svn repo will be configured. 160 | [info] Building project searchbird 1.0.0-SNAPSHOT against Scala 2.8.1 161 | [info] using SearchbirdProject with sbt 0.7.4 and Scala 2.7.7 162 | 163 | SBT has a wide array of features, but a useful one right now is to 164 | use the "~ test" command. 165 | 166 | > ~ test 167 | 168 | The tilde tells SBT to look for changes to your source files and 169 | re-execute the command when it detects a change. 170 | 171 | TODO: add link to scala school lesson on SBT 172 | 173 | ### Add an admin / dashboard page. 174 | 175 | ### Add a new dependency to your project, perhaps twitter/util? 176 | 177 | ### Take a tour of the logs our service is producing. 178 | 179 | ### Add command-line parameters for your service. 180 | -D foo=bar 181 | runtime.arguments.get("foo") 182 | 183 | ### Storage: let's persist the data in Cassandra! 184 | 185 | ### Twitter API: let's listen to the Firehose! 186 | 187 | ### Twitter API: let's fetch some statuses & users & stuff. 188 | -------------------------------------------------------------------------------- /web/ko/specs.textile: -------------------------------------------------------------------------------- 1 | --- 2 | prev: coll2.textile 3 | next: concurrency.textile 4 | title: specs로 테스트 하기 5 | layout: post 6 | --- 7 | 8 | 이번 강좌에서는 Specs를 사용해 테스트하는 법을 다룬다. Specs는 스칼라용 동작 주도 설계(Behavior-Driven Design, BDD) 프레임워크이다. 9 | 10 | * "Specification(명세 클래스) 확장하기":#example 11 | ** 내포 12 | * "실행 모델":#scope 13 | * "준비작업과 정리작업-Setup, Teardown":#setup 14 | ** doFirst 15 | ** doBefore 16 | ** doAfter 17 | * "매처-Matcher":#matchers 18 | ** mustEqual 19 | ** contains 20 | ** sameSize? 21 | ** 필요한 매처 만들어내기 22 | * "목업":#mocks 23 | * "스파이":#spies 24 | * "sbt에서 실행하기":#sbt 25 | 26 | 27 | h2(#example). Specifcation(명세 클래스) 확장하기 28 | 29 | 코드부터 살펴보자. 30 | 31 |
 32 | import org.specs._
 33 | 
 34 | object ArithmeticSpec extends Specification {
 35 |   "Arithmetic" should {
 36 |     "add two numbers" in {
 37 |       1 + 1 mustEqual 2
 38 |     }
 39 |     "add three numbers" in {
 40 |       1 + 1 + 1 mustEqual 3
 41 |     }
 42 |   }
 43 | }
 44 | 
45 | 46 | *Arithmetic* 은 *명세를 지정할 시스템(System under Specification)* 이다. 47 | 48 | *add* 는 컨택스트(context)라 한다. 49 | 50 | *add two numbers*와 *add three numbers* 는 예제(example)라 한다. 51 | 52 | @mustEqual@ 은 *예상결과(expectation)* 이다. 53 | 54 | @1 mustEqual 1@ 은 "예상결과"를 담을 수 있는 틀로 활용할 수 있다. 예제는 반드시 하나 이상의 예상결과를 포함해야 한다. 55 | 56 | h3. 중복 57 | 58 | 두 테스트의 이름에 @add@ 가 같이 포함되어 있는게 보이는가? 이런 중복은 예상결과를 *내포* 시킴으로써 없앨 수 있다. 59 | 60 |
 61 | import org.specs._
 62 | 
 63 | object ArithmeticSpec extends Specification {
 64 |   "Arithmetic" should {
 65 |     "add" in {
 66 |       "two numbers" in {
 67 |         1 + 1 mustEqual 2
 68 |       }
 69 |       "three numbers" in {
 70 |         1 + 1 + 1 mustEqual 3
 71 |       }
 72 |     }
 73 |   }
 74 | }
 75 | 
76 | 77 | h2(#scope). 실행 모델 78 | 79 |
 80 | object ExecSpec extends Specification {
 81 |   "Mutations are isolated" should {
 82 |     var x = 0
 83 |     "x equals 1 if we set it." in {
 84 |       x = 1
 85 |       x mustEqual 1
 86 |     }
 87 |     "x is the default value if we don't change it" in {
 88 |       x mustEqual 0
 89 |     }
 90 |   }
 91 | }
 92 | 
93 | 94 | h2(#setup). Setup(준비작업)과 Teardown(정리작업) 95 | 96 | h3. doBefore & doAfter 97 | 98 |
 99 | "my system" should {
100 |   doBefore { resetTheSystem() /** user-defined reset function */ }
101 |   "mess up the system" in {...}
102 |   "and again" in {...}
103 |   doAfter { cleanThingsUp() }
104 | }
105 | 
106 | 107 | *NOTE* @doBefore@ / @doAfter@ 는 말단 예제에 대해에서만 실행된다. 108 | 109 | h3. doFirst & doLast 110 | 111 | @doFirst@ / @doLast@는 일회성 설정을 위한 장치이다. (예제가 필요하다. 나(즉, 원저자)는 이를 사용하지 않는다.) 112 | 113 |
114 | "Foo" should {
115 |   doFirst { openTheCurtains() }
116 |   "test stateless methods" in {...}
117 |   "test other stateless methods" in {...}
118 |   doLast { closeTheCurtains() }
119 | }
120 | 
121 | 122 | h2(#matchers). Matcher(매처) 123 | 124 | 데이터가 있고, 그 데이터가 올바른지 확인할 때 매처를 사용한다. 가장 흔히 사용되는 것들을 살펴보자. ("매처 가이드":https://code.google.com/p/specs/wiki/MatchersGuide 를 살펴 보라) 125 | 126 | h3. mustEqual 127 | 128 | 이미 mustEqual 예제는 여러 개 살펴 보았다. 129 | 130 |
131 | 1 mustEqual 1
132 | 
133 | "a" mustEqual "a"
134 | 
135 | 136 | 동일성에는 참조 동일성과 값 동일성이 있다. 137 | 138 | h3. 열의 원소 139 | 140 |
141 | val numbers = List(1, 2, 3)
142 | 
143 | numbers must contain(1)
144 | numbers must not contain(4)
145 | 
146 | numbers must containAll(List(1, 2, 3))
147 | numbers must containInOrder(List(1, 2, 3))
148 | 
149 | List(1, List(2, 3, List(4)), 5) must haveTheSameElementsAs(List(5, List(List(4), 2, 3), 1))
150 | 
151 | 152 | 153 | h3. 맵의 원소 154 | 155 |
156 | map must haveKey(k)
157 | map must notHaveKey(k)
158 | 
159 | map must haveValue(v)
160 | map must notHaveValue(v)
161 | 
162 | 163 | h3. 수 164 | 165 |
166 | a must beGreaterThan(b)
167 | a must beGreaterThanOrEqualTo(b)
168 | 
169 | a must beLessThan(b)
170 | a must beLessThanOrEqualTo(b)
171 | 
172 | a must beCloseTo(b, delta)
173 | 
174 | 175 | 176 | h3. 옵션(Option) 177 | 178 |
179 | a must beNone
180 | 
181 | a must beSome[Type]
182 | 
183 | a must beSomething
184 | 
185 | a must beSome(value)
186 | 
187 | 188 | h3. throwA 189 | 190 |
191 | a must throwA[WhateverException]
192 | 
193 | 194 | 이렇게 쓰는게 테스트 몸체에서 예외를 캐치해서 실패로 만드는 것 보다 더 짧다. 195 | 196 | 또한 특정 예외 메시지를 명시할 수도 있다. 197 | 198 |
199 | a must throwA(WhateverException("message"))
200 | 
201 | 202 | 예외의 타입에 대해 매치할 수도 있다. 203 | 204 |
205 | a must throwA(new Exception) like {
206 |   case Exception(m) => m.startsWith("bad")
207 | }
208 | 
209 | 210 | 211 | h3. 필요한 매처 만들어내기 212 | 213 |
214 | import org.specs.matcher.Matcher
215 | 
216 | 217 | h4. val로 정의하기 218 | 219 |
220 | "A matcher" should {
221 |   "be created as a val" in {
222 |     val beEven = new Matcher[Int] {
223 |       def apply(n: => Int) = {
224 |         (n % 2 == 0, "%d is even".format(n), "%d is odd".format(n))
225 |       }
226 |     }
227 |     2 must beEven
228 |   }
229 | }
230 | 
231 | 232 | 매처 정의시 지켜야할 계약은 3-튜플을 반환하는 것이다. 첫 원소에는 예상 결과를 만족하는지 여부, 두번째 원소에는 만족시의 메시지, 마지막 원소에는 실패시의 메시지가 들어가야 한다. 233 | 234 | h4. 케이스 클래스로 정의하기 235 | 236 |
237 | case class beEven(b: Int) extends Matcher[Int]() {
238 |   def apply(n: => Int) =  (n % 2 == 0, "%d is even".format(n), "%d is odd".format(n))
239 | }
240 | 
241 | 242 | 케이스 클래스로 만들면 더 공유하기 쉬워진다. 243 | 244 | h2(#mocks). 목업 245 | 246 |
247 | import org.specs.Specification
248 | import org.specs.mock.Mockito
249 | 
250 | abstract class Foo[T] {
251 |   def get(i: Int): T
252 | }
253 | 
254 | object MockExampleSpec extends Specification with Mockito {
255 |   val m = mock[Foo[String]]
256 | 
257 |   m.get(0) returns "one"
258 | 
259 |   m.get(0)
260 | 
261 |   there was one(m).get(0)
262 | 
263 |   there was no(m).get(1)
264 | }
265 | 
266 | 267 | *See Also* "Mockito 활용하기":https://code.google.com/p/specs/wiki/UsingMockito 268 | 269 | h2(#spies). 스파이 270 | 271 | 스파이를 사용해 실제 객체를 "부분적으로 흉내내기"할 수 있다. 272 | 273 |
274 | val list = new LinkedList[String]
275 | val spiedList = spy(list)
276 | 
277 | // methods can be stubbed on a spy
278 | spiedList.size returns 100
279 | 
280 | // other methods can also be used
281 | spiedList.add("one")
282 | spiedList.add("two")
283 | 
284 | // and verification can happen on a spy
285 | there was one(spiedList).add("one")
286 | 
287 | 288 | 하지만, 스파이를 쓰면 때로 문제가 생길 수 있다. 289 | 290 |
291 | // 리스트가 비어있다면, IndexOutOfBoundsException가 발생할 것이다.
292 | spiedList.get(0) returns "one"
293 | 
294 | 295 | 이 경우 @doReturn@ 을 사용해야만 한다. 296 | 297 |
298 | doReturn("one").when(spiedList).get(0)
299 | 
300 | 301 | 302 | h2(#sbt). 개별 specs를 sbt에서 실행하기 303 | 304 | 305 |
306 | > test-only com.twitter.yourservice.UserSpec
307 | 
308 | 309 | 라고 하면 해당 spec을 실행할 것이다. 310 | 311 | 312 |
313 | > ~ test-only com.twitter.yourservice.UserSpec
314 | 
315 | 316 | 라고 하면 테스트를 실행하되, 파일이 바뀔 때마다 테스트가 재실행될 것이다. 317 | -------------------------------------------------------------------------------- /web/searchbird-1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 2012-08-20 17:48ZCanvas 1Layer 1$client in consoleServertype: SearchbirdService.ThriftServerinterface: get, putThriftport9999Clienttype: SearchbirdService.FinagledClienthttpadminport9000stats 4 | -------------------------------------------------------------------------------- /web/specs.textile: -------------------------------------------------------------------------------- 1 | --- 2 | prev: coll2.textile 3 | next: concurrency.textile 4 | title: Testing with specs 5 | layout: post 6 | --- 7 | 8 | This lesson covers testing with Specs, a Behavior-Driven Design (BDD) Framework for Scala. 9 | 10 | * "extends Specification":#example 11 | ** nested examples 12 | * "Execution Model":#scope 13 | * "Setup and TearDown":#setup 14 | ** doFirst 15 | ** doBefore 16 | ** doAfter 17 | * "Matchers":#matchers 18 | ** mustEqual 19 | ** contains 20 | ** sameSize? 21 | ** Write your own 22 | * "Mocks":#mocks 23 | * "Spies":#spies 24 | * "run in sbt":#sbt 25 | 26 | 27 | h2(#example). extends Specification 28 | 29 | Let's just jump in. 30 | 31 |
 32 | import org.specs._
 33 | 
 34 | object ArithmeticSpec extends Specification {
 35 |   "Arithmetic" should {
 36 |     "add two numbers" in {
 37 |       1 + 1 mustEqual 2
 38 |     }
 39 |     "add three numbers" in {
 40 |       1 + 1 + 1 mustEqual 3
 41 |     }
 42 |   }
 43 | }
 44 | 
45 | 46 | *Arithmetic* is the *System Under Specification* 47 | 48 | *add* is a context. 49 | 50 | *add two numbers* and *add three numbers* are examples. 51 | 52 | @mustEqual@ indicates an *expectation* 53 | 54 | @1 mustEqual 1@ is a common placeholder *expectation* before you start writing real tests. All examples should have at least one expectation. 55 | 56 | h3. Duplication 57 | 58 | Notice how two tests both have @add@ in their name? We can get rid of that by *nesting* expectations. 59 | 60 |
 61 | import org.specs._
 62 | 
 63 | object ArithmeticSpec extends Specification {
 64 |   "Arithmetic" should {
 65 |     "add" in {
 66 |       "two numbers" in {
 67 |         1 + 1 mustEqual 2
 68 |       }
 69 |       "three numbers" in {
 70 |         1 + 1 + 1 mustEqual 3
 71 |       }
 72 |     }
 73 |   }
 74 | }
 75 | 
76 | 77 | h2(#scope). Execution Model 78 | 79 |
 80 | object ExecSpec extends Specification {
 81 |   "Mutations are isolated" should {
 82 |     var x = 0
 83 |     "x equals 1 if we set it." in {
 84 |       x = 1
 85 |       x mustEqual 1
 86 |     }
 87 |     "x is the default value if we don't change it" in {
 88 |       x mustEqual 0
 89 |     }
 90 |   }
 91 | }
 92 | 
93 | 94 | h2(#setup). Setup, Teardown 95 | 96 | h3. doBefore & doAfter 97 | 98 |
 99 | "my system" should {
100 |   doBefore { resetTheSystem() /** user-defined reset function */ }
101 |   "mess up the system" in {...}
102 |   "and again" in {...}
103 |   doAfter { cleanThingsUp() }
104 | }
105 | 
106 | 107 | *NOTE* @doBefore@/@doAfter@ are only run on leaf examples. 108 | 109 | h3. doFirst & doLast 110 | 111 | @doFirst@/@doLast@ is for single-time setup. (need example, I don't use this) 112 | 113 |
114 | "Foo" should {
115 |   doFirst { openTheCurtains() }
116 |   "test stateless methods" in {...}
117 |   "test other stateless methods" in {...}
118 |   doLast { closeTheCurtains() }
119 | }
120 | 
121 | 122 | h2(#matchers). Matchers 123 | 124 | You have data, you want to make sure it's right. Let's tour the most commonly used matchers. (See Also "Matchers Guide":https://code.google.com/p/specs/wiki/MatchersGuide) 125 | 126 | h3. mustEqual 127 | 128 | We've seen several examples of mustEqual already. 129 | 130 |
131 | 1 mustEqual 1
132 | 
133 | "a" mustEqual "a"
134 | 
135 | 136 | Reference equality, value equality. 137 | 138 | h3. elements in a Sequence 139 | 140 |
141 | val numbers = List(1, 2, 3)
142 | 
143 | numbers must contain(1)
144 | numbers must not contain(4)
145 | 
146 | numbers must containAll(List(1, 2, 3))
147 | numbers must containInOrder(List(1, 2, 3))
148 | 
149 | List(1, List(2, 3, List(4)), 5) must haveTheSameElementsAs(List(5, List(List(4), 2, 3), 1))
150 | 
151 | 152 | 153 | h3. Items in a Map 154 | 155 |
156 | map must haveKey(k)
157 | map must notHaveKey(k)
158 | 
159 | map must haveValue(v)
160 | map must notHaveValue(v)
161 | 
162 | 163 | h3. Numbers 164 | 165 |
166 | a must beGreaterThan(b)
167 | a must beGreaterThanOrEqualTo(b)
168 | 
169 | a must beLessThan(b)
170 | a must beLessThanOrEqualTo(b)
171 | 
172 | a must beCloseTo(b, delta)
173 | 
174 | 175 | 176 | h3. Options 177 | 178 |
179 | a must beNone
180 | 
181 | a must beSome[Type]
182 | 
183 | a must beSomething
184 | 
185 | a must beSome(value)
186 | 
187 | 188 | h3. throwA 189 | 190 |
191 | a must throwA[WhateverException]
192 | 
193 | 194 | This is shorter than a try catch with a fail in the body. 195 | 196 | You can also expect a specific message 197 | 198 |
199 | a must throwA(WhateverException("message"))
200 | 
201 | 202 | You can also match on the exception: 203 | 204 |
205 | a must throwA(new Exception) like {
206 |   case Exception(m) => m.startsWith("bad")
207 | }
208 | 
209 | 210 | 211 | h3. Write your own Matchers 212 | 213 |
214 | import org.specs.matcher.Matcher
215 | 
216 | 217 | h4. As a val 218 | 219 |
220 | "A matcher" should {
221 |   "be created as a val" in {
222 |     val beEven = new Matcher[Int] {
223 |       def apply(n: => Int) = {
224 |         (n % 2 == 0, "%d is even".format(n), "%d is odd".format(n))
225 |       }
226 |     }
227 |     2 must beEven
228 |   }
229 | }
230 | 
231 | 232 | The contract is to return a tuple containing whether the expectation is true, and a message for when it is and isn't true. 233 | 234 | h4. As a case class 235 | 236 |
237 | case class beEven(b: Int) extends Matcher[Int]() {
238 |   def apply(n: => Int) =  (n % 2 == 0, "%d is even".format(n), "%d is odd".format(n))
239 | }
240 | 
241 | 242 | Using a case class makes it more shareable. 243 | 244 | h2(#mocks). Mocks 245 | 246 |
247 | import org.specs.Specification
248 | import org.specs.mock.Mockito
249 | 
250 | abstract class Foo[T] {
251 |   def get(i: Int): T
252 | }
253 | 
254 | object MockExampleSpec extends Specification with Mockito {
255 |   val m = mock[Foo[String]]
256 | 
257 |   m.get(0) returns "one"
258 | 
259 |   m.get(0)
260 | 
261 |   there was one(m).get(0)
262 | 
263 |   there was no(m).get(1)
264 | }
265 | 
266 | 267 | *See Also* "Using Mockito":https://code.google.com/p/specs/wiki/UsingMockito 268 | 269 | h2(#spies). Spies 270 | 271 | Spies can also be used in order to do some "partial mocking" of real objects: 272 | 273 |
274 | val list = new LinkedList[String]
275 | val spiedList = spy(list)
276 | 
277 | // methods can be stubbed on a spy
278 | spiedList.size returns 100
279 | 
280 | // other methods can also be used
281 | spiedList.add("one")
282 | spiedList.add("two")
283 | 
284 | // and verification can happen on a spy
285 | there was one(spiedList).add("one")
286 | 
287 | 288 | However, working with spies can be tricky: 289 | 290 |
291 | // if the list is empty, this will throws an IndexOutOfBoundsException
292 | spiedList.get(0) returns "one"
293 | 
294 | 295 | @doReturn@ must be used in that case: 296 | 297 |
298 | doReturn("one").when(spiedList).get(0)
299 | 
300 | 301 | 302 | h2(#sbt). Run individual specs in sbt 303 | 304 | 305 |
306 | > test-only com.twitter.yourservice.UserSpec
307 | 
308 | 309 | Will run just that spec. 310 | 311 | 312 |
313 | > ~ test-only com.twitter.yourservice.UserSpec
314 | 
315 | 316 | Will run that test in a loop, with each file modification triggering a test run. 317 | -------------------------------------------------------------------------------- /web/searchbird-2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 2012-08-20 17:49ZCanvas 1Layer 1$client in consoleServertype: SearchbirdService.ThriftServerinterface: get, put, searchThriftport9999Clienttype: SearchbirdService.FinagledClienthttpadminport9000stats 4 | -------------------------------------------------------------------------------- /web/zh_cn/basics2.textile: -------------------------------------------------------------------------------- 1 | --- 2 | prev: basics.textile 3 | next: collections.textile 4 | title: 基础知识(续) 5 | layout: post 6 | --- 7 | 8 | 课程内容: 9 | * "apply方法":#apply 10 | * "单例对象":#object 11 | * "函数即对象":#fnobj 12 | * "包":#package 13 | * "模式匹配":#match 14 | * "样本类":#caseclass 15 | * "try-catch-finally":#exception 16 | 17 | h2(#apply). apply 方法 18 | 19 | 当类或对象有一个主要用途的时候,apply方法为你提供了一个很好的语法糖。 20 | 21 |
 22 | scala> class Foo {}
 23 | defined class Foo
 24 | 
 25 | scala> object FooMaker {
 26 |      |   def apply() = new Foo
 27 |      | }
 28 | defined module FooMaker
 29 | 
 30 | scala> val newFoo = FooMaker()
 31 | newFoo: Foo = Foo@5b83f762
 32 | 
33 | 34 | 或 35 | 36 |
 37 | scala> class Bar {
 38 |      |   def apply() = 0
 39 |      | }
 40 | defined class Bar
 41 | 
 42 | scala> val bar = new Bar
 43 | bar: Bar = Bar@47711479
 44 | 
 45 | scala> bar()
 46 | res8: Int = 0
 47 | 
48 | 49 | 在这里,我们实例化对象看起来像是在调用一个方法。以后会有更多介绍! 50 | 51 | 52 | h2(#object). 单例对象 53 | 54 | 单例对象用于持有一个类的唯一实例。通常用于工厂模式。 55 | 56 |
 57 | object Timer {
 58 |   var count = 0
 59 | 
 60 |   def currentCount(): Long = {
 61 |     count += 1
 62 |     count
 63 |   }
 64 | }
 65 | 
66 | 67 | 可以这样使用: 68 | 69 |
 70 | scala> Timer.currentCount()
 71 | res0: Long = 1
 72 | 
73 | 74 | 单例对象可以和类具有相同的名称,此时该对象也被称为“伴生对象”。我们通常将伴生对象作为工厂使用。 75 | 76 | 下面是一个简单的例子,可以不需要使用'new'来创建一个实例了。 77 | 78 |
 79 | class Bar(foo: String)
 80 | 
 81 | object Bar {
 82 |   def apply(foo: String) = new Bar(foo)
 83 | }
 84 | 
85 | 86 | 87 | h2(#fnobj). 函数即对象 88 | 89 | 在Scala中,我们经常谈论对象的函数式编程。这是什么意思?到底什么是函数呢? 90 | 91 | 函数是一些特质的集合。具体来说,具有一个参数的函数是Function1特质的一个实例。这个特质定义了apply()语法糖,让你调用一个对象时就像你在调用一个函数。 92 | 93 |
 94 | scala> object addOne extends Function1[Int, Int] {
 95 |      |   def apply(m: Int): Int = m + 1
 96 |      | }
 97 | defined module addOne
 98 | 
 99 | scala> addOne(1)
100 | res2: Int = 2
101 | 
102 | 103 | 这个Function特质集合下标从0开始一直到22。为什么是22?这是一个主观的魔幻数字(magic number)。我从来没有使用过多于22个参数的函数,所以这个数字似乎是合理的。 104 | 105 | apply语法糖有助于统一对象和函数式编程的二重性。你可以传递类,并把它们当做函数使用,而函数本质上是类的实例。 106 | 107 | 这是否意味着,当你在类中定义一个方法时,得到的实际上是一个Function*的实例?不是的,在类中定义的方法是方法而不是函数。在repl中独立定义的方法是Function*的实例。 108 | 109 | 类也可以扩展Function,这些类的实例可以使用()调用。 110 | 111 |
112 | scala> class AddOne extends Function1[Int, Int] {
113 |      |   def apply(m: Int): Int = m + 1
114 |      | }
115 | defined class AddOne
116 | 
117 | scala> val plusOne = new AddOne()
118 | plusOne: AddOne = 
119 | 
120 | scala> plusOne(1)
121 | res0: Int = 2
122 | 
123 | 124 | 可以使用更直观快捷的extends (Int => Int)代替extends Function1[Int, Int] 125 | 126 |
127 | class AddOne extends (Int => Int) {
128 |   def apply(m: Int): Int = m + 1
129 | }
130 | 
131 | 132 | 133 | h2(#package). 包 134 | 135 | 你可以将代码组织在包里。 136 | 137 |
138 | package com.twitter.example
139 | 
140 | 141 | 在文件头部定义包,会将文件中所有的代码声明在那个包中。 142 | 143 | 值和函数不能在类或单例对象之外定义。单例对象是组织静态函数(static function)的有效工具。 144 | 145 |
146 | package com.twitter.example
147 | 
148 | object colorHolder {
149 |   val BLUE = "Blue"
150 |   val RED = "Red"
151 | }
152 | 
153 | 154 | 现在你可以直接访问这些成员 155 | 156 |
157 | println("the color is: " + com.twitter.example.colorHolder.BLUE)
158 | 
159 | 160 | 注意在你定义这个对象时Scala解释器的返回: 161 | 162 |
163 | scala> object colorHolder {
164 |      |   val Blue = "Blue"
165 |      |   val Red = "Red"
166 |      | }
167 | defined module colorHolder
168 | 
169 | 170 | 这暗示了Scala的设计者是把对象作为Scala的模块系统的一部分进行设计的。 171 | 172 | h2(#match). 模式匹配 173 | 174 | 这是Scala中最有用的部分之一。 175 | 176 | 匹配值 177 | 178 |
179 | val times = 1
180 | 
181 | times match {
182 |   case 1 => "one"
183 |   case 2 => "two"
184 |   case _ => "some other number"
185 | }
186 | 
187 | 188 | 使用守卫进行匹配 189 | 190 |
191 | times match {
192 |   case i if i == 1 => "one"
193 |   case i if i == 2 => "two"
194 |   case _ => "some other number"
195 | }
196 | 
197 | 198 | 注意我们是怎样获取变量'i'的值的。 199 | 200 | 在最后一行指令中的_是一个通配符;它保证了我们可以处理所有的情况。 201 | 否则当传进一个不能被匹配的数字的时候,你将获得一个运行时错误。我们以后会继续讨论这个话题的。 202 | 203 | *参考* Effective Scala 对什么时候使用模式匹配模式匹配格式化的建议. A Tour of Scala 也描述了 模式匹配 204 | 205 | 206 | h3. 匹配类型 207 | 208 | 你可以使用 match来分别处理不同类型的值。 209 | 210 |
211 | def bigger(o: Any): Any = {
212 |   o match {
213 |     case i: Int if i < 0 => i - 1
214 |     case i: Int => i + 1
215 |     case d: Double if d < 0.0 => d - 0.1
216 |     case d: Double => d + 0.1
217 |     case text: String => text + "s"
218 |   }
219 | }
220 | 
221 | 222 | h3. 匹配类成员 223 | 224 | 还记得我们之前的计算器吗。 225 | 226 | 让我们通过类型对它们进行分类。 227 | 228 | 一开始会很痛苦。 229 | 230 |
231 | def calcType(calc: Calculator) = calc match {
232 |   case _ if calc.brand == "HP" && calc.model == "20B" => "financial"
233 |   case _ if calc.brand == "HP" && calc.model == "48G" => "scientific"
234 |   case _ if calc.brand == "HP" && calc.model == "30B" => "business"
235 |   case _ => "unknown"
236 | }
237 | 
238 | 239 | (⊙o⊙)哦,太痛苦了。幸好Scala提供了一些应对这种情况的有效工具。 240 | 241 | h2(#caseclass). 样本类 Case Classes 242 | 243 | 使用样本类可以方便得存储和匹配类的内容。不用new关键字就可以创建它们。 244 | 245 |
246 | scala> case class Calculator(brand: String, model: String)
247 | defined class Calculator
248 | 
249 | scala> val hp20b = Calculator("HP", "20b")
250 | hp20b: Calculator = Calculator(hp,20b)
251 | 
252 | 253 | 样本类基于构造函数的参数,自动地实现了相等性和易读的toString方法。 254 | 255 |
256 | scala> val hp20b = Calculator("HP", "20b")
257 | hp20b: Calculator = Calculator(hp,20b)
258 | 
259 | scala> val hp20B = Calculator("HP", "20b")
260 | hp20B: Calculator = Calculator(hp,20b)
261 | 
262 | scala> hp20b == hp20B
263 | res6: Boolean = true
264 | 
265 | 266 | 样本类也可以像普通类那样拥有方法。 267 | 268 | h3. 使用样本类进行模式匹配 269 | 270 | 样本类就是被设计用在模式匹配中的。让我们简化之前的计算器分类器的例子。 271 | 272 |
273 | val hp20b = Calculator("HP", "20B")
274 | val hp30b = Calculator("HP", "30B")
275 | 
276 | def calcType(calc: Calculator) = calc match {
277 |   case Calculator("HP", "20B") => "financial"
278 |   case Calculator("HP", "48G") => "scientific"
279 |   case Calculator("HP", "30B") => "business"
280 |   case Calculator(ourBrand, ourModel) => "Calculator: %s %s is of unknown type".format(ourBrand, ourModel)
281 | }
282 | 
283 | 284 | 最后一句也可以这样写 285 | 286 |
287 |   case Calculator(_, _) => "Calculator of unknown type"
288 | 
289 | 290 | 或者我们完全可以不将匹配对象指定为Calculator类型 291 | 292 |
293 |   case _ => "Calculator of unknown type"
294 | 
295 | 296 | 或者我们也可以将匹配的值重新命名。 297 | 298 |
299 |   case c@Calculator(_, _) => "Calculator: %s of unknown type".format(c)
300 | 
301 | 302 | h2(#exception). 异常 303 | 304 | Scala中的异常可以在try-catch-finally语法中通过模式匹配使用。 305 | 306 |
307 | try {
308 |   remoteCalculatorService.add(1, 2)
309 | } catch {
310 |   case e: ServerIsDownException => log.error(e, "the remote calculator service is unavailable. should have kept your trusty HP.")
311 | } finally {
312 |   remoteCalculatorService.close()
313 | }
314 | 
315 | 316 | try也是面向表达式的 317 | 318 |
319 | val result: Int = try {
320 |   remoteCalculatorService.add(1, 2)
321 | } catch {
322 |   case e: ServerIsDownException => {
323 |     log.error(e, "the remote calculator service is unavailable. should have kept your trusty HP.")
324 |     0
325 |   }
326 | } finally {
327 |   remoteCalculatorService.close()
328 | }
329 | 
330 | 331 | 这并不是一个完美编程风格的展示,而只是一个例子,用来说明try-catch-finally和Scala中其他大部分事物一样是表达式。 332 | 333 | 当一个异常被捕获处理了,finally块将被调用;它不是表达式的一部分。 334 | -------------------------------------------------------------------------------- /web/ru/specs.textile: -------------------------------------------------------------------------------- 1 | --- 2 | prev: coll2.textile 3 | next: concurrency.textile 4 | title: Тестирование с помощью specs 5 | layout: post 6 | --- 7 | 8 | В этом уроке вы узнаете о тестировании с помощью Specs, Scala фреймворк для Разработки, основанная на функционировании. 9 | 10 | * «расширяем Спецификацию»:#example 11 | ** вложенные примеры 12 | * «Модель Выполнения»:#scope 13 | * «Установка и Распаковка»:#setup 14 | ** doFirst 15 | ** doBefore 16 | ** doAfter 17 | * «Конструкции сравнения»:#matchers 18 | ** mustEqual 19 | ** contains 20 | ** sameSize? 21 | ** пишем свою конструкцию 22 | * «Подделки (Mocks)»:#mocks 23 | * «Шпионы (Spies)»:#spies 24 | * «Запуск в sbt»:#sbt 25 | 26 | 27 | h2(#example). Расширяем Спецификацию 28 | 29 | Давайте сразу посмотрим код. 30 | 31 |
 32 | import org.specs._
 33 | 
 34 | object ArithmeticSpec extends Specification {
 35 |   "Arithmetic" should {
 36 |     "add two numbers" in {
 37 |       1 + 1 mustEqual 2
 38 |     }
 39 |     "add three numbers" in {
 40 |       1 + 1 + 1 mustEqual 3
 41 |     }
 42 |   }
 43 | }
 44 | 
45 | 46 | *Arithmetic* означает, что *System Under Specification*(Система имеет спецификацию) 47 | 48 | *add* — это контекст. 49 | 50 | *add two numbers* и *add three numbers* — это примеры. 51 | 52 | @mustEqual@ отражает наше *ожидание* 53 | 54 | @1 mustEqual 1@ является общим заменителем *ожидания*, прежде чем мы начнем писать реальные тесты. Все примеры должны иметь по крайней мере одно ожидание. 55 | 56 | h3. Повторяемость 57 | 58 | Заметили, что оба теста содержат @add@ в своем имени? Мы избавиться от этого благодаря *вложенным* ожиданиям. 59 | 60 |
 61 | import org.specs._
 62 | 
 63 | object ArithmeticSpec extends Specification {
 64 |   "Arithmetic" should {
 65 |     "add" in {
 66 |       "two numbers" in {
 67 |         1 + 1 mustEqual 2
 68 |       }
 69 |       "three numbers" in {
 70 |         1 + 1 + 1 mustEqual 3
 71 |       }
 72 |     }
 73 |   }
 74 | }
 75 | 
76 | 77 | h2(#scope). Модель выполнения 78 | 79 |
 80 | object ExecSpec extends Specification {
 81 |   "Mutations are isolated" should {
 82 |     var x = 0
 83 |     "x equals 1 if we set it." in {
 84 |       x = 1
 85 |       x mustEqual 1
 86 |     }
 87 |     "x is the default value if we don't change it" in {
 88 |       x mustEqual 0
 89 |     }
 90 |   }
 91 | }
 92 | 
93 | 94 | h2(#setup). Установка, Распаковка 95 | 96 | h3. doBefore и doAfter 97 | 98 |
 99 | "my system" should {
100 |   doBefore { resetTheSystem() /** определенная пользователем функция сброса */ }
101 |   "mess up the system" in {...}
102 |   "and again" in {...}
103 |   doAfter { cleanThingsUp() }
104 | }
105 | 
106 | 107 | *ЗАМЕЧАНИЕ* @doBefore@/@doAfter@ будут работать только с черновыми примерами. 108 | 109 | h3. doFirst и doLast 110 | 111 | @doFirst@/@doLast@ предназначены во время установки (здесь нужен пример, я не использую их) 112 | 113 |
114 | "Foo" should {
115 |   doFirst { openTheCurtains() }
116 |   "test stateless methods" in {...}
117 |   "test other stateless methods" in {...}
118 |   doLast { closeTheCurtains() }
119 | }
120 | 
121 | 122 | h2(#matchers). Конструкции сравнения 123 | 124 | У вас есть данные, и вы хотите убедиться, что они верные. Давайте рассмотрим наиболее часто используемые конструкции сравнения. (Смотрите также «Matchers Guide»:https://code.google.com/p/specs/wiki/MatchersGuide) 125 | 126 | h3. mustEqual 127 | 128 | Вы уже видели несколько примеров с mustEqual. 129 | 130 |
131 | 1 mustEqual 1
132 | 
133 | "a" mustEqual "a"
134 | 
135 | 136 | Равенство ссылок, равенство значений. 137 | 138 | h3. элементы в Sequence 139 | 140 |
141 | val numbers = List(1, 2, 3)
142 | 
143 | numbers must contain(1)
144 | numbers must not contain(4)
145 | 
146 | numbers must containAll(List(1, 2, 3))
147 | numbers must containInOrder(List(1, 2, 3))
148 | 
149 | List(1, List(2, 3, List(4)), 5) must haveTheSameElementsAs(List(5, List(List(4), 2, 3), 1))
150 | 
151 | 152 | 153 | h3. Записи в Map 154 | 155 |
156 | map must haveKey(k)
157 | map must notHaveKey(k)
158 | 
159 | map must haveValue(v)
160 | map must notHaveValue(v)
161 | 
162 | 163 | h3. Числа 164 | 165 |
166 | a must beGreaterThan(b)
167 | a must beGreaterThanOrEqualTo(b)
168 | 
169 | a must beLessThan(b)
170 | a must beLessThanOrEqualTo(b)
171 | 
172 | a must beCloseTo(b, delta)
173 | 
174 | 175 | 176 | h3. Опции 177 | 178 |
179 | a must beNone
180 | 
181 | a must beSome[Type]
182 | 
183 | a must beSomething
184 | 
185 | a must beSome(value)
186 | 
187 | 188 | h3. throwA 189 | 190 |
191 | a must throwA[WhateverException]
192 | 
193 | 194 | Запись короче чем try catch с его попыткой выброса исключения в теле блока. 195 | 196 | Вы можете выдать специальное сообщение 197 | 198 |
199 | a must throwA(WhateverException("message"))
200 | 
201 | 202 | Вы можете также поймать определенное исключение: 203 | 204 |
205 | a must throwA(new Exception) like {
206 |   case Exception(m) => m.startsWith("bad")
207 | }
208 | 
209 | 210 | 211 | h3. Пишем свои условные конструкции 212 | 213 |
214 | import org.specs.matcher.Matcher
215 | 
216 | 217 | h4. Подобно val 218 | 219 |
220 | "A matcher" should {
221 |   "be created as a val" in {
222 |     val beEven = new Matcher[Int] {
223 |       def apply(n: => Int) = {
224 |         (n % 2 == 0, "%d is even".format(n), "%d is odd".format(n))
225 |       }
226 |     }
227 |     2 must beEven
228 |   }
229 | }
230 | 
231 | 232 | Контракт вернет кортеж, содержащий запись о том верно ожидание или нет, и сообщение когда оно верно или наоборот. 233 | 234 | h4. Подобно case классу 235 | 236 |
237 | case class beEven(b: Int) extends Matcher[Int]() {
238 |   def apply(n: => Int) =  (n % 2 == 0, "%d is even".format(n), "%d is odd".format(n))
239 | }
240 | 
241 | 242 | Используя case класс, вы делаете его менее специализированным. 243 | 244 | h2(#mocks). Подделки(Mocks) 245 | 246 |
247 | import org.specs.Specification
248 | import org.specs.mock.Mockito
249 | 
250 | abstract class Foo[T] {
251 |   def get(i: Int): T
252 | }
253 | 
254 | object MockExampleSpec extends Specification with Mockito {
255 |   val m = mock[Foo[String]]
256 | 
257 |   m.get(0) returns "one"
258 | 
259 |   m.get(0)
260 | 
261 |   there was one(m).get(0)
262 | 
263 |   there was no(m).get(1)
264 | }
265 | 
266 | 267 | *Смотрите также:* «Использование Mockito»:https://code.google.com/p/specs/wiki/UsingMockito 268 | 269 | h2(#spies). Шпионы(Spies) 270 | 271 | Шпионы могут также быть использованы для того, чтобы сделать некоторую «частичную подделку» реальных объектов: 272 | 273 |
274 | val list = new LinkedList[String]
275 | val spiedList = spy(list)
276 | 
277 | // методы могут выдать ошибку при использовании шпиона
278 | spiedList.size returns 100
279 | 
280 | // другие методы также могут быть использованы
281 | spiedList.add("one")
282 | spiedList.add("two")
283 | 
284 | // и проверка может происходить с помощью шпиона
285 | there was one(spiedList).add("one")
286 | 
287 | 288 | Однако, работа со шпионами может быть сложной: 289 | 290 |
291 | // если список пуст, то бросается исключение IndexOutOfBoundsException
292 | spiedList.get(0) returns "one"
293 | 
294 | 295 | @doReturn@ должен быть исопльзован в этом случае: 296 | 297 |
298 | doReturn("one").when(spiedList).get(0)
299 | 
300 | 301 | 302 | h2(#sbt). Запуск индивидальных спеков(тестов) в sbt 303 | 304 | 305 |
306 | > test-only com.twitter.yourservice.UserSpec
307 | 
308 | 309 | Запустится только данный спек. 310 | 311 | 312 |
313 | > ~ test-only com.twitter.yourservice.UserSpec
314 | 
315 | 316 | Будет запускаться тест в цикле, с каждой модификацией файла будет срабатывать триггер и будет запускаться тест. 317 | -------------------------------------------------------------------------------- /web/ko/basics2.textile: -------------------------------------------------------------------------------- 1 | --- 2 | prev: basics.textile 3 | next: collections.textile 4 | title: 기초(계속) 5 | layout: post 6 | --- 7 | 8 | 이번 강좌에서 다루는 내용은 다음과 같다. 9 | 10 | * "apply 메소드":#apply 11 | * "객체":#object 12 | * "함수도 객체다":#fnobj 13 | * "패키지":#package 14 | * "패턴 매치":#match 15 | * "케이스 클래스":#caseclass 16 | * "try-catch-finally 예외 처리":#exception 17 | 18 | h2(#apply). apply 메소드 19 | 20 | apply 메소드를 사용하면 클래스나 객체의 용도가 주로 하나만 있는 경우를 아주 멋지게 표현할 수 있다. 21 | 22 |
 23 | scala> class Foo {}
 24 | defined class Foo
 25 | 
 26 | scala> object FooMaker {
 27 |      |   def apply() = new Foo
 28 |      | }
 29 | defined module FooMaker
 30 | 
 31 | scala> val newFoo = FooMaker()
 32 | newFoo: Foo = Foo@5b83f762
 33 | 
34 | 35 | 위와 같이 사용하거나, 다음과 같이 쓸 수 있다. 36 | 37 |
 38 | scala> class Bar {
 39 |      |   def apply() = 0
 40 |      | }
 41 | defined class Bar
 42 | 
 43 | scala> val bar = new Bar
 44 | bar: Bar = Bar@47711479
 45 | 
 46 | scala> bar()
 47 | res8: Int = 0
 48 | 
49 | 50 | apply를 정의하면 메소드를 호출하듯 객체 인스턴스를 호출할 수 있다. 객체 인스턴스를 호출하면 그 객체(클래스)에 정의된 apply()가 호출된다. 자세한 것은 나중에 살펴볼 것이다. 51 | 52 | h2(#object). 객체 53 | 54 | 객체(여기서는 object 키워드로 선언하는 객체를 말함)는 클래스의 유일한 인스턴스를 넣기 위해 사용한다. 보통 팩토리에 사용된다. 55 | 56 |
 57 | object Timer {
 58 |   var count = 0
 59 | 
 60 |   def currentCount(): Long = {
 61 |     count += 1
 62 |     count
 63 |   }
 64 | }
 65 | 
66 | 67 | 위와 같이 정의하면 다음과 같이 사용할 수 있다. 68 | 69 |
 70 | scala> Timer.currentCount()
 71 | res0: Long = 1
 72 | 
73 | 74 | 클래스와 객체가 같은 이름을 가질 수도 있다. 이런 객체는 '짝 객체(Companion Object)'라 한다. 보통 팩토리를 만들 때 짝 객체를 사용한다. 75 | 76 | 다음 예는 'new'를 사용하지 않고 새 객체를 만들 수 있음을 보여준다. 77 | 78 |
 79 | class Bar(foo: String)
 80 | 
 81 | object Bar {
 82 |   def apply(foo: String) = new Bar(foo)
 83 | }
 84 | 
85 | 86 | 87 | h2(#fnobj). 함수는 객체이다 88 | 89 | 스칼라에 대해 이야기할 떄, 객체-함수형 프로그래밍이라는 말을 하고는 한다. 그 말이 무슨 뜻일까? 함수란 실제로 무엇일까? 90 | 91 | 함수는 트레잇의 집합이다. 구체적으로 말하자면, 인자를 하나만 받는 함수는 Function1 트레잇의 인스턴스이다. 이 트레잇에는 앞에서 설명했던 apply()가 정의되어 있다. 따라서 함수를 호출하듯 객체를 호출할 수 있다. 92 | 93 |
 94 | scala> object addOne extends Function1[Int, Int] {
 95 |      |   def apply(m: Int): Int = m + 1
 96 |      | }
 97 | defined module addOne
 98 | 
 99 | scala> addOne(1)
100 | res2: Int = 2
101 | 
102 | 103 | 스칼라에는 Function이 1부터 22까지 준비되어 있다. 22인 이유는? 그냥 그렇게 정한 것이다. 저자는 인자가 22개 보다 더 많이 필요한 함수를 본 적이 없다. 22개면 충분하리라 본다. 104 | 105 | apply를 통한 편리 문법(syntactic sugar)을 통해 객체와 함수 프로그래밍 양쪽을 잘 통합할 수 있다. 여러분은 클래스를 여기저기 넘기면서 함수 처럼 호출해 사용할 수 있고, 함수는 한꺼풀 벗겨보면 단지 클래스의 인스턴스일 뿐이다. 106 | 107 | 그렇다면 클래스의 메소드를 정의할 때마다 실제로 Function*의 인스턴스가 만들어지는 걸까? 아니다. 클래스 내부의 메소드는 메소드이다. repl(read eval print loop. 입력을 받아 값을 계산하고 결과를 출력하는 루프. 스칼라 인터프리터라 생각하면 대략 맞다)에서 정의한 개별 메소드는 Function*의 인스턴스이다. 108 | 109 | Function*을 확장한 클래스를 정의할 수도 있다. 물론 이런 클래스도 ()로 호출할 수 있다. 110 | 111 |
112 | scala> class AddOne extends Function1[Int, Int] {
113 |      |   def apply(m: Int): Int = m + 1
114 |      | }
115 | defined class AddOne
116 | 
117 | scala> val plusOne = new AddOne()
118 | plusOne: AddOne = 
119 | 
120 | scala> plusOne(1)
121 | res0: Int = 2
122 | 
123 | 124 | extends Function1[Int, Int]extends (Int => Int)라고 더 알아보기 쉽게 쓸 수 있다. 125 | 126 |
127 | class AddOne extends (Int => Int) {
128 |   def apply(m: Int): Int = m + 1
129 | }
130 | 
131 | 132 | h2(#package). 패키지 133 | 134 | 코드를 패키지로 구성할 수 있다. 135 | 136 |
137 | package com.twitter.example
138 | 
139 | 140 | 위와 같이 파일의 맨 앞에서 선언하면 파일 내의 모든 것이 위 패키지 안에 포함된다. 141 | 142 | 값이나 함수는 클래스나 객체 바깥에 존재할 수 없다. 객체(여기서도 object로 선언한 객체를 의미함)를 사용하면 정적인(자바의 정적 함수와 동일) 함수를 관리하기 쉽다. 143 | 144 |
145 | package com.twitter.example
146 | 
147 | object colorHolder {
148 |   val BLUE = "Blue"
149 |   val RED = "Red"
150 | }
151 | 
152 | 153 | 이제 직접 객체의 멤버를 사용할 수 있다. 154 | 155 |
156 | println("the color is: " + com.twitter.example.colorHolder.BLUE)
157 | 
158 | 159 | 여러분이 이렇게 객체를 정의하면 스칼라 repl은 다음과 같이 표시해준다. 160 | 161 |
162 | scala> object colorHolder {
163 |      |   val Blue = "Blue"
164 |      |   val Red = "Red"
165 |      | }
166 | defined module colorHolder
167 | 
168 | 169 | 모듈이라고 repl이 응답하는 것에 유의하라. 이는 스칼라 언어를 설계시 객체를 모듈 시스템의 일부로 생각하고 설계했음을 보여준다. 170 | 171 | h2(#match). 패턴 매칭 172 | 173 | 패턴 매치는 스칼라에서 가장 유용한 기능 중 하나이다. 174 | 175 | 값에 대해 매칭할 수 있다. 176 | 177 |
178 | val times = 1
179 | 
180 | times match {
181 |   case 1 => "one"
182 |   case 2 => "two"
183 |   case _ => "some other number"
184 | }
185 | 
186 | 187 | 가드(조건문)를 사용해 매칭할 수 있다. 188 | 189 |
190 | times match {
191 |   case i if i == 1 => "one"
192 |   case i if i == 2 => "two"
193 |   case _ => "some other number"
194 | }
195 | 
196 | 197 | 변수 'i'에 어떻게 값을 잡아 넣었는지 주의깊게 살펴보라. 198 | 199 | 마지막 경우의 _는 와일드카드이다. 즉, 모든 경우를 처리한다. 만약 이 부분이 없다면 매치되지 않는 값이 들어온 경우 런타임 에러가 발생할 것이다. 이에 대해서는 나중에 살펴보겠다. 200 | 201 | *See Also* 효율적인 스칼라에서 패턴매치를 사용해야 하는 경우패턴 매칭을 어떤 형식으로 할지에 대해 설명한다. 스칼라 여행에서도 패턴매칭을 다룬다. 202 | 203 | h3. 타입에 대해 매치시키기 204 | 205 | match를 사용해 타입이 다른 값을 서로 다른 방식으로 처리 가능하다. 206 | 207 |
208 | def bigger(o: Any): Any = {
209 |   o match {
210 |     case i: Int if i < 0 => i - 1
211 |     case i: Int => i + 1
212 |     case d: Double if d < 0.0 => d - 0.1
213 |     case d: Double => d + 0.1
214 |     case text: String => text + "s"
215 |   }
216 | }
217 | 
218 | 219 | h3. 클래스 멤버에 대해 매치시키기 220 | 221 | 앞에서 봤던 계산기 예제를 다시 떠올려보자. 222 | 223 | 타입(계산기의 유형)에 따라 계산기를 구분하자. 224 | 225 |
226 | def calcType(calc: Calculator) = calc match {
227 |   case calc.brand == "HP" && calc.model == "20B" => "financial"
228 |   case calc.brand == "HP" && calc.model == "48G" => "scientific"
229 |   case calc.brand == "HP" && calc.model == "30B" => "business"
230 |   case _ => "unknown"
231 | }
232 | 
233 | 234 | 아이구, 힘들어 죽겄다. 스칼라는 이런 처리를 쉽게 할 수 있는 도구를 제공한다. 235 | 236 | h2(#caseclass). 케이스 클래스(case class) 237 | 238 | 케이스 클래스는 손쉽게 내용을 어떤 클래스에 저장하고, 그에 따라 매치를 하고 싶은 경우 사용한다. new를 사용하지 않고도 케이스 클래스의 인스턴스 생성이 가능하다. 239 | 240 |
241 | scala> case class Calculator(brand: String, model: String)
242 | defined class Calculator
243 | 
244 | scala> val hp20b = Calculator("HP", "20b")
245 | hp20b: Calculator = Calculator(hp,20b)
246 | 
247 | 
248 | 249 | 케이스 클래스는 자동으로 생성자 인자에 따른 동등성 검사를 제공하며, 또한 보기 좋은 toString 메소드도 제공한다. 250 | 251 |
252 | scala> val hp20b = Calculator("HP", "20b")
253 | hp20b: Calculator = Calculator(hp,20b)
254 | 
255 | scala> val hp20B = Calculator("HP", "20b")
256 | hp20B: Calculator = Calculator(hp,20b)
257 | 
258 | scala> hp20b == hp20B
259 | res6: Boolean = true
260 | 
261 | 262 | 케이스 클래스 안에도 일반 클래스와 똑같이 메소드를 정의할 수 있다. 263 | 264 | h6. 케이스 클래스와 패턴 매칭 265 | 266 | 케이스 클래스는 패턴 매치와 사용하기 위해 설계된 것이다. 앞의 계산기 분류 예제를 간략하게 만들어보자. 267 | 268 |
269 | val hp20b = Calculator("HP", "20B")
270 | val hp30b = Calculator("HP", "30B")
271 | 
272 | def calcType(calc: Calculator) = calc match {
273 |   case Calculator("HP", "20B") => "financial"
274 |   case Calculator("HP", "48G") => "scientific"
275 |   case Calculator("HP", "30B") => "business"
276 |   case Calculator(ourBrand, ourModel) => "Calculator: %s %s is of unknown type".format(ourBrand, ourModel)
277 | }
278 | 
279 | 280 | 마지막 매치는 다음과 같이 쓸 수도 있다. 281 | 282 |
283 |   case Calculator(_, _) => "Calculator of unknown type"
284 | 
285 | 286 | 혹은, 그냥 calc가 계산기인지 아닌지도 명시하지 않아도 된다. 287 | 288 |
289 |   case _ => "Calculator of unknown type"
290 | 
291 | 292 | 아니면, 매치된 값에 다른 이름을 붙일 수도 있다. 293 | 294 |
295 |   case c@Calculator(_, _) => "Calculator: %s of unknown type".format(c)
296 | 
297 | 298 | h2(#exception). 예외 299 | 300 | 스칼라에서는 예외 처리시 try-catch-finally 문법에 패턴 매치를 사용할 수 있다. 301 | 302 |
303 | try {
304 |   remoteCalculatorService.add(1, 2)
305 | } catch {
306 |   case e: ServerIsDownException => log.error(e, "the remote calculator service is unavailble. should have kept your trustry HP.")
307 | } finally {
308 |   remoteCalculatorService.close()
309 | }
310 | 
311 | 312 | try 역시 식 중심의 구문이다. 313 | 314 |
315 | val result: Int = try {
316 |   remoteCalculatorService.add(1, 2)
317 | } catch {
318 |   case e: ServerIsDownException => {
319 |     log.error(e, "the remote calculator service is unavailble. should have kept your trustry HP.")
320 |     0
321 |   }
322 | } finally {
323 |   remoteCalculatorService.close()
324 | }
325 | 
326 | 327 | 이렇게 하는게 좋은 프로그램 스타일은 아니다. 위 내용은 단지 다른 대부분의 스칼라 구성 요소와 마찬가지로 try-catch-finally도 결과값을 내는 식임을 보여주기 위한 예일 뿐이다. 328 | 329 | finally는 예외가 처리(catch)된 다음에 실행될 것이다. 이 부분은 전체 식의 일부가 아니다. 예외가 발생하지 않으면 try {} 안의 마지막 식의 값이 try-catch-finally 전체의 값이 되고, 예외가 발생하는 경우에는 catch 안의 식의 값이 전체 식의 최종 값이 된다. 330 | -------------------------------------------------------------------------------- /web/zh_cn/java.textile: -------------------------------------------------------------------------------- 1 | --- 2 | prev: concurrency.textile 3 | next: finagle.textile 4 | title: Java + Scala 5 | layout: post 6 | --- 7 | 8 | 课程内容涵盖了Java互操作性。 9 | 10 | * Javap 11 | * 类 12 | * 异常 13 | * 特质 14 | * 单例对象 15 | * 闭包和函数 16 | * 变化性 17 | 18 | h2. Javap 19 | 20 | javap的是JDK附带的一个工具。不是JRE,这里是有区别的。 javap反编译类定义,给你展示里面有什么。用法很简单 21 | 22 |
 23 | [local ~/projects/interop/target/scala_2.8.1/classes/com/twitter/interop]$ javap MyTrait
 24 | Compiled from "Scalaisms.scala"
 25 | public interface com.twitter.interop.MyTrait extends scala.ScalaObject{
 26 |     public abstract java.lang.String traitName();
 27 |     public abstract java.lang.String upperTraitName();
 28 | }
 29 | 
30 | 31 | 如果你是底层控可以看看字节码 32 | 33 |
 34 | [local ~/projects/interop/target/scala_2.8.1/classes/com/twitter/interop]$ javap -c MyTrait\$class
 35 | Compiled from "Scalaisms.scala"
 36 | public abstract class com.twitter.interop.MyTrait$class extends java.lang.Object{
 37 | public static java.lang.String upperTraitName(com.twitter.interop.MyTrait);
 38 |   Code:
 39 |    0:	aload_0
 40 |    1:	invokeinterface	#12,  1; //InterfaceMethod com/twitter/interop/MyTrait.traitName:()Ljava/lang/String;
 41 |    6:	invokevirtual	#17; //Method java/lang/String.toUpperCase:()Ljava/lang/String;
 42 |    9:	areturn
 43 | 
 44 | public static void $init$(com.twitter.interop.MyTrait);
 45 |   Code:
 46 |    0:	return
 47 | 
 48 | }
 49 | 
50 | 51 | 如果你搞不清为什么程序在Java上不起作用,就用javap看看吧! 52 | 53 | h2. 类 54 | 55 | 在Java中使用Scala _类_ 时要考虑的四个要点 56 | 57 | * 类参数 58 | * 类常量 59 | * 类变量 60 | * 异常 61 | 62 | 我们将构建一个简单的Scala类来展示这一系列实体 63 | 64 |
 65 | package com.twitter.interop
 66 | 
 67 | import java.io.IOException
 68 | import scala.throws
 69 | import scala.reflect.{BeanProperty, BooleanBeanProperty}
 70 | 
 71 | class SimpleClass(name: String, val acc: String, @BeanProperty var mutable: String) {
 72 |   val foo = "foo"
 73 |   var bar = "bar"
 74 |   @BeanProperty
 75 |   val fooBean = "foobean"
 76 |   @BeanProperty
 77 |   var barBean = "barbean"
 78 |   @BooleanBeanProperty
 79 |   var awesome = true
 80 | 
 81 |   def dangerFoo() = {
 82 |     throw new IOException("SURPRISE!")
 83 |   }
 84 | 
 85 |   @throws(classOf[IOException])
 86 |   def dangerBar() = {
 87 |     throw new IOException("NO SURPRISE!")
 88 |   }
 89 | }
 90 | 
91 | 92 | h3. 类参数 93 | 94 | * 默认情况下,类参数都是有效的Java构造函数的参数。这意味着你不能从类的外部访问。 95 | * 声明一个类参数为val/var 和这段代码是相同的 96 | 97 |
 98 | class SimpleClass(acc_: String) {
 99 |   val acc = acc_
100 | }
101 | 
102 | 103 | 这使得它在Java代码中就像其他常量一样可以被访问 104 | 105 | h3. 类常量 106 | 107 | * 常量(val)在Java中定义了一个获取方法。你可以通过方法“foo()”访问“val foo”的值 108 | 109 | h3. 类变量 110 | 111 | * 变量(var)会生成一个 _$eq 方法。你可以这样调用它 112 | 113 |
114 | foo$_eq("newfoo");
115 | 
116 | 117 | h3. BeanProperty 118 | 119 | 你可以通过@BeanProperty注解val和var定义。这会按照POJO定义生成getter/setter方法。如果你想生成isFoo方法,使用BooleanBeanProperty注解。丑陋的foo$_eq将变为 120 | 121 |
122 | setFoo("newfoo");
123 | getFoo();
124 | 
125 | 126 | 127 | h3. 异常 128 | 129 | Scala没有像java那样有受检异常(checked exception)。需不需要受检异常是一个我们不会进入的哲学辩论,不过当你需要在Java中捕获它时就 *很重要* 了。dangerFoo和dangerBar将演示这一点。在Java中不能这样做 130 | 131 |
132 |         // exception erasure!
133 |         try {
134 |             s.dangerFoo();
135 |         } catch (IOException e) {
136 |             // UGLY
137 |         }
138 | 
139 | 140 | Java会抱怨说 s.dangerFoo从未抛出过 IOException异常。我们可以通过捕获Throwable来跳过,但是这样不好。 141 | 142 | 相反,作为一个良好的Scala公民,可以很体面地像在dangerBar中那样使用throws注解。这使我们能够继续在Java中使用受检异常。 143 | 144 | h3. 进一步阅读 145 | 146 | 支持Java互操作的Scala注解的完整列表在这里 https://www.scala-lang.org/node/106。 147 | 148 | h2. 特质 149 | 150 | 你如何获得一个接口+实现?让我们看一个简单的特质定义 151 | 152 |
153 | trait MyTrait {
154 |   def traitName:String
155 |   def upperTraitName = traitName.toUpperCase
156 | }
157 | 
158 | 159 | 这个特质有一个抽象方法(traitName)和一个实现的方法(upperTraitName)。Scala为我们生成了什么呢?一个名为MyTrait的的接口,和一个名为MyTrait$class的实现类。 160 | 161 | MyTrait和你期望的一样 162 | 163 |
164 | [local ~/projects/interop/target/scala_2.8.1/classes/com/twitter/interop]$ javap MyTrait
165 | Compiled from "Scalaisms.scala"
166 | public interface com.twitter.interop.MyTrait extends scala.ScalaObject{
167 |     public abstract java.lang.String traitName();
168 |     public abstract java.lang.String upperTraitName();
169 | }
170 | 
171 | 172 | MyTrait$class更有趣 173 | 174 |
175 | [local ~/projects/interop/target/scala_2.8.1/classes/com/twitter/interop]$ javap MyTrait\$class
176 | Compiled from "Scalaisms.scala"
177 | public abstract class com.twitter.interop.MyTrait$class extends java.lang.Object{
178 |     public static java.lang.String upperTraitName(com.twitter.interop.MyTrait);
179 |     public static void $init$(com.twitter.interop.MyTrait);
180 | }
181 | 
182 | 183 | MyTrait$class只有以MyTrait实例为参数的静态方法。这给了我们一个如何在Java中来扩展一个特质的提示。 184 | 185 | 首先尝试下面的操作 186 | 187 |
188 | package com.twitter.interop;
189 | 
190 | public class JTraitImpl implements MyTrait {
191 |     private String name = null;
192 | 
193 |     public JTraitImpl(String name) {
194 |         this.name = name;
195 |     }
196 | 
197 |     public String traitName() {
198 |         return name;
199 |     }
200 | }
201 | 
202 | 203 | 我们会得到以下错误 204 | 205 |
206 | [info] Compiling main sources...
207 | [error] /Users/mmcbride/projects/interop/src/main/java/com/twitter/interop/JTraitImpl.java:3: com.twitter.interop.JTraitImpl is not abstract and does not override abstract method upperTraitName() in com.twitter.interop.MyTrait
208 | [error] public class JTraitImpl implements MyTrait {
209 | [error]        ^
210 | 
211 | 212 | 我们 _可以_ 自己实现。但有一个鬼鬼祟祟的方式。 213 | 214 |
215 | package com.twitter.interop;
216 | 
217 |     public String upperTraitName() {
218 |         return MyTrait$class.upperTraitName(this);
219 |     }
220 | 
221 | 222 | 我们只要把调用代理到生成的Scala实现上就可以了。如果愿意我们也可以覆盖它。 223 | 224 | h2. 单例对象 225 | 226 | 单例对象是Scala实现静态方法/单例模式的方式。在Java中使用它会有点奇怪。没有一个使用它们的完美风格,但在Scala2.8中用起来并不很糟糕 227 | 228 | 一个Scala单例对象会被编译成由“$”结尾的类。让我们创建一个类和一个伴生对象 229 | 230 |
231 | class TraitImpl(name: String) extends MyTrait {
232 |   def traitName = name
233 | }
234 | 
235 | object TraitImpl {
236 |   def apply = new TraitImpl("foo")
237 |   def apply(name: String) = new TraitImpl(name)
238 | }
239 | 
240 | 241 | 我们可以像这样天真地在Java中访问 242 | 243 |
244 | MyTrait foo = TraitImpl$.MODULE$.apply("foo");
245 | 
246 | 247 | 现在你可能会问自己,这是神马玩意?这是一个正常的反应。让我们来看看TraitImpl$里面实际上是什么 248 | 249 |
250 | local ~/projects/interop/target/scala_2.8.1/classes/com/twitter/interop]$ javap TraitImpl\$
251 | Compiled from "Scalaisms.scala"
252 | public final class com.twitter.interop.TraitImpl$ extends java.lang.Object implements scala.ScalaObject{
253 |     public static final com.twitter.interop.TraitImpl$ MODULE$;
254 |     public static {};
255 |     public com.twitter.interop.TraitImpl apply();
256 |     public com.twitter.interop.TraitImpl apply(java.lang.String);
257 | }
258 | 
259 | 260 | 其实它里面没有任何静态方法。取而代之的是一个名为MODULE$的静态成员。方法实现被委托给该成员。这使得访问代码很难看,但却是可行的。 261 | 262 | h3. 转发方法(Forwarding Methods) 263 | 264 | 在Scala2.8中处理单例对象变得相对容易一点。如果你有一个类与一个伴生对象,2.8编译器会生成转发方法在伴生类中。所以,如果你用2.8,你可以像这样调用TraitImpl单例对象的方法 265 | 266 |
267 | MyTrait foo = TraitImpl.apply("foo");
268 | 
269 | 270 | h2. 闭包函数 271 | 272 | Scala的最重要的特点之一,就是把函数作为头等公民。让我们来定义一个类,它定义了一些以函数作为参数的方法。 273 | 274 |
275 | class ClosureClass {
276 |   def printResult[T](f: => T) = {
277 |     println(f)
278 |   }
279 | 
280 |   def printResult[T](f: String => T) = {
281 |     println(f("HI THERE"))
282 |   }
283 | }
284 | 
285 | 286 | 在Scala中可以像这样调用 287 | 288 |
289 | val cc = new ClosureClass
290 | cc.printResult { "HI MOM" }
291 | 
292 | 293 | 在Java中却不那么容易,不过也并不可怕。让我们来看看ClosureClass实际上被编译成什么: 294 | 295 |
296 | [local ~/projects/interop/target/scala_2.8.1/classes/com/twitter/interop]$ javap ClosureClass
297 | Compiled from "Scalaisms.scala"
298 | public class com.twitter.interop.ClosureClass extends java.lang.Object implements scala.ScalaObject{
299 |     public void printResult(scala.Function0);
300 |     public void printResult(scala.Function1);
301 |     public com.twitter.interop.ClosureClass();
302 | }
303 | 
304 | 305 | 这也不是那么恐怖。 “f: => T”被转义成“Function0”,“f: String => T”被转义成“Function1”。Scala实际上从Function0定义到Function22,最多支持22个参数。这真的应该足够了。 306 | 307 | 现在我们只需要弄清楚如何在Java中使用这些东东。我们可以传入Scala提供的AbstractFunction0和AbstractFunction1,像这样 308 | 309 |
310 |     @Test public void closureTest() {
311 |         ClosureClass c = new ClosureClass();
312 |         c.printResult(new AbstractFunction0() {
313 |                 public String apply() {
314 |                     return "foo";
315 |                 }
316 |             });
317 |         c.printResult(new AbstractFunction1() {
318 |                 public String apply(String arg) {
319 |                     return arg + "foo";
320 |                 }
321 |             });
322 |     }
323 | 
324 | 325 | 注意我们可以使用泛型参数。 326 | -------------------------------------------------------------------------------- /web/zh_cn/type-basics.textile: -------------------------------------------------------------------------------- 1 | --- 2 | prev: pattern-matching-and-functional-composition.textile 3 | next: advanced-types.textile 4 | title: 类型和多态基础 5 | layout: post 6 | --- 7 | 8 | 课程内容: 9 | 10 | * "什么是静态类型":#background 11 | * "Scala中的类型":#scala 12 | * "参数化多态性":#parametricpoly 13 | * "类型推断: Hindley-Milner算法 vs. 局部类型推理":#inference 14 | * "变性":#variance 15 | * "边界":#bounds 16 | * "量化":#quantification 17 | 18 | h2(#background). 什么是静态类型?它们为什么有用? 19 | 20 | 按Pierce的话讲:“类型系统是一个语法方法,它们根据程序计算的值的种类对程序短语进行分类,通过分类结果错误行为进行自动检查。” 21 | 22 | 类型允许你表示函数的定义域和值域。例如,从数学角度看这个定义: 23 | 24 |
 25 | f: R -> N
 26 | 
27 | 28 | 它告诉我们函数“f”是从实数集到自然数集的映射。 29 | 30 | 抽象地说,这就是 _具体_ 类型的准确定义。类型系统给我们提供了一些更强大的方式来表达这些集合。 31 | 32 | 鉴于这些注释,编译器可以 _静态地_ (在编译时)验证程序是 _合理_ 的。也就是说,如果值(在运行时)不符合程序规定的约束,编译将失败。 33 | 34 | 一般说来,类型检查只能保证 _不合理_ 的程序不能编译通过。它不能保证每一个合理的程序都 _可以_ 编译通过。 35 | 36 | 随着类型系统表达能力的提高,我们可以生产更可靠的代码,因为它能够在我们运行程序之前验证程序的不变性(当然是发现类型本身的模型bug!)。学术界一直很努力地提高类型系统的表现力,包括值依赖(value-dependent)类型! 37 | 38 | 需要注意的是,所有的类型信息会在编译时被删去,因为它已不再需要。这就是所谓的擦除。 39 | 40 | h2(#scala). Scala中的类型 41 | 42 | Scala强大的类型系统拥有非常丰富的表现力。其主要特性有: 43 | 44 | * *参数化多态性* 粗略地说,就是泛型编程 45 | * *(局部)类型推断* 粗略地说,就是为什么你不需要这样写代码val i: Int = 12: Int 46 | * *存在量化* 粗略地说,为一些没有名称的类型进行定义 47 | * *视窗* 我们将下周学习这些;粗略地说,就是将一种类型的值“强制转换”为另一种类型 48 | 49 | h2(#parametricpoly). 参数化多态性 50 | 51 | 多态性是在不影响静态类型丰富性的前提下,用来(给不同类型的值)编写通用代码的。 52 | 53 | 例如,如果没有参数化多态性,一个通用的列表数据结构总是看起来像这样(事实上,它看起来很像使用泛型前的Java): 54 | 55 |
 56 | scala> 2 :: 1 :: "bar" :: "foo" :: Nil
 57 | res5: List[Any] = List(2, 1, bar, foo)
 58 | 
59 | 60 | 现在我们无法恢复其中成员的任何类型信息。 61 | 62 |
 63 | scala> res5.head
 64 | res6: Any = 2
 65 | 
66 | 67 | 所以我们的应用程序将会退化为一系列类型转换(“asInstanceOf[]”),并且会缺乏类型安全的保障(因为这些都是动态的)。 68 | 69 | 多态性是通过指定 _类型变量_ 实现的。 70 | 71 |
 72 | scala> def drop1[A](l: List[A]) = l.tail
 73 | drop1: [A](l: List[A])List[A]
 74 | 
 75 | scala> drop1(List(1,2,3))
 76 | res1: List[Int] = List(2, 3)
 77 | 
78 | 79 | h3. Scala有秩1多态性 80 | 81 | 粗略地说,这意味着在Scala中,有一些你想表达的类型概念“过于泛化”以至于编译器无法理解。假设你有一个函数 82 | 83 |
 84 | def toList[A](a: A) = List(a)
 85 | 
86 | 87 | 你希望继续泛型地使用它: 88 | 89 |
 90 | def foo[A, B](f: A => List[A], b: B) = f(b)
 91 | 
92 | 93 | 这段代码不能编译,因为所有的类型变量只有在调用上下文中才被固定。即使你“钉住”了类型B: 94 | 95 |
 96 | def foo[A](f: A => List[A], i: Int) = f(i)
 97 | 
98 | 99 | ...你也会得到一个类型不匹配的错误。 100 | 101 | h2(#inference). 类型推断 102 | 103 | 静态类型的一个传统反对意见是,它有大量的语法开销。Scala通过 _类型推断_ 来缓解这个问题。 104 | 105 | 在函数式编程语言中,类型推断的经典方法是 _Hindley Milner算法_,它最早是实现在ML中的。 106 | 107 | Scala类型推断系统的实现稍有不同,但本质类似:推断约束,并试图统一类型。 108 | 109 | 例如,在Scala中你无法这样做: 110 | 111 |
112 | scala> { x => x }
113 | :7: error: missing parameter type
114 |        { x => x }
115 | 
116 | 117 | 而在OCaml中你可以: 118 | 119 |
120 | # fun x -> x;;
121 | - : 'a -> 'a = 
122 | 
123 | 124 | 在Scala中所有类型推断是 _局部的_ 。Scala一次分析一个表达式。例如: 125 | 126 |
127 | scala> def id[T](x: T) = x
128 | id: [T](x: T)T
129 | 
130 | scala> val x = id(322)
131 | x: Int = 322
132 | 
133 | scala> val x = id("hey")
134 | x: java.lang.String = hey
135 | 
136 | scala> val x = id(Array(1,2,3,4))
137 | x: Array[Int] = Array(1, 2, 3, 4)
138 | 
139 | 140 | 类型信息都保存完好,Scala编译器为我们进行了类型推断。请注意我们并不需要明确指定返回类型。 141 | 142 | h2(#variance). 变性 Variance 143 | 144 | Scala的类型系统必须同时解释类层次和多态性。类层次结构可以表达子类关系。在混合OO和多态性时,一个核心问题是:如果T'T一个子类,Container[T']应该被看做是Container[T]的子类吗?变性(Variance)注解允许你表达类层次结构和多态类型之间的关系: 145 | 146 | | |*含义* | *Scala 标记*| 147 | |*协变covariant* |C[T']是 C[T] 的子类 | [+T]| 148 | |*逆变contravariant* |C[T] 是 C[T']的子类 | [-T]| 149 | |*不变invariant* |C[T] 和 C[T']无关 | [T]| 150 | 151 | 子类型关系的真正含义:对一个给定的类型T,如果T'是其子类型,你能替换它吗? 152 | 153 |
154 | scala> class Covariant[+A]
155 | defined class Covariant
156 | 
157 | scala> val cv: Covariant[AnyRef] = new Covariant[String]
158 | cv: Covariant[AnyRef] = Covariant@4035acf6
159 | 
160 | scala> val cv: Covariant[String] = new Covariant[AnyRef]
161 | :6: error: type mismatch;
162 |  found   : Covariant[AnyRef]
163 |  required: Covariant[String]
164 |        val cv: Covariant[String] = new Covariant[AnyRef]
165 |                                    ^
166 | 
167 | 168 |
169 | scala> class Contravariant[-A]
170 | defined class Contravariant
171 | 
172 | scala> val cv: Contravariant[String] = new Contravariant[AnyRef]
173 | cv: Contravariant[AnyRef] = Contravariant@49fa7ba
174 | 
175 | scala> val fail: Contravariant[AnyRef] = new Contravariant[String]
176 | :6: error: type mismatch;
177 |  found   : Contravariant[String]
178 |  required: Contravariant[AnyRef]
179 |        val fail: Contravariant[AnyRef] = new Contravariant[String]
180 |                                      ^
181 | 
182 | 183 | 逆变似乎很奇怪。什么时候才会用到它呢?令人惊讶的是,函数特质的定义就使用了它! 184 | 185 |
186 | trait Function1 [-T1, +R] extends AnyRef
187 | 
188 | 189 | 如果你仔细从替换的角度思考一下,会发现它是非常合理的。让我们先定义一个简单的类层次结构: 190 | 191 |
192 | scala> class Animal { val sound = "rustle" }
193 | defined class Animal
194 | 
195 | scala> class Bird extends Animal { override val sound = "call" }
196 | defined class Bird
197 | 
198 | scala> class Chicken extends Bird { override val sound = "cluck" }
199 | defined class Chicken
200 | 
201 | 202 | 假设你需要一个以Bird为参数的函数: 203 | 204 |
205 | scala> val getTweet: (Bird => String) = // TODO
206 | 
207 | 208 | 标准动物库有一个函数满足了你的需求,但它的参数是Animal。在大多数情况下,如果你说“我需要一个___,我有一个___的子类”是可以的。但是,在函数参数这里是逆变的。如果你需要一个接受参数类型Bird的函数变量,但却将这个变量指向了接受参数类型为Chicken的函数,那么给它传入一个Duck时就会出错。然而,如果将该变量指向一个接受参数类型为Animal的函数就不会有这种问题: 209 | 210 |
211 | scala> val getTweet: (Bird => String) = ((a: Animal) => a.sound )
212 | getTweet: Bird => String = 
213 | 
214 | 215 | 函数的返回值类型是协变的。如果你需要一个返回Bird的函数,但指向的函数返回类型是Chicken,这当然是可以的。 216 | 217 |
218 | scala> val hatch: (() => Bird) = (() => new Chicken )
219 | hatch: () => Bird = 
220 | 
221 | 222 | h2(#bounds). 边界 223 | 224 | Scala允许你通过 _边界_ 来限制多态变量。这些边界表达了子类型关系。 225 | 226 |
227 | scala> def cacophony[T](things: Seq[T]) = things map (_.sound)
228 | :7: error: value sound is not a member of type parameter T
229 |        def cacophony[T](things: Seq[T]) = things map (_.sound)
230 |                                                         ^
231 | 
232 | scala> def biophony[T <: Animal](things: Seq[T]) = things map (_.sound)
233 | biophony: [T <: Animal](things: Seq[T])Seq[java.lang.String]
234 | 
235 | scala> biophony(Seq(new Chicken, new Bird))
236 | res5: Seq[java.lang.String] = List(cluck, call)
237 | 
238 | 239 | 类型下界也是支持的,这让逆变和巧妙协变的引入得心应手。List[+T]是协变的;一个Bird的列表也是Animal的列表。List定义一个操作::(elem T)返回一个加入了elem的新的List。新的List和原来的列表具有相同的类型: 240 | 241 |
242 | scala> val flock = List(new Bird, new Bird)
243 | flock: List[Bird] = List(Bird@7e1ec70e, Bird@169ea8d2)
244 | 
245 | scala> new Chicken :: flock
246 | res53: List[Bird] = List(Chicken@56fbda05, Bird@7e1ec70e, Bird@169ea8d2)
247 | 
248 | 249 | List _同样_ 定义了::[B >: T](x: B) 来返回一个List[B]。请注意B >: T,这指明了类型B为类型T的超类。这个方法让我们能够做正确地处理在一个List[Bird]前面加一个Animal的操作: 250 | 251 |
252 | scala> new Animal :: flock
253 | res59: List[Animal] = List(Animal@11f8d3a8, Bird@7e1ec70e, Bird@169ea8d2)
254 | 
255 | 256 | 注意返回类型是Animal。 257 | 258 | h2(#quantification). 量化 259 | 260 | 有时候,你并不关心是否能够命名一个类型变量,例如: 261 | 262 |
263 | scala> def count[A](l: List[A]) = l.size
264 | count: [A](List[A])Int
265 | 
266 | 267 | 这时你可以使用“通配符”取而代之: 268 | 269 |
270 | scala> def count(l: List[_]) = l.size
271 | count: (List[_])Int
272 | 
273 | 274 | 这相当于是下面代码的简写: 275 | 276 |
277 | scala> def count(l: List[T forSome { type T }]) = l.size
278 | count: (List[T forSome { type T }])Int
279 | 
280 | 281 | 注意量化会的结果会变得非常难以理解: 282 | 283 |
284 | scala> def drop1(l: List[_]) = l.tail
285 | drop1: (List[_])List[Any]
286 | 
287 | 288 | 突然,我们失去了类型信息!让我们细化代码看看发生了什么: 289 | 290 |
291 | scala> def drop1(l: List[T forSome { type T }]) = l.tail
292 | drop1: (List[T forSome { type T }])List[T forSome { type T }]
293 | 
294 | 295 | 我们不能使用T因为类型不允许这样做。 296 | 297 | 你也可以为通配符类型变量应用边界: 298 | 299 |
300 | scala> def hashcodes(l: Seq[_ <: AnyRef]) = l map (_.hashCode)
301 | hashcodes: (Seq[_ <: AnyRef])Seq[Int]
302 | 
303 | scala> hashcodes(Seq(1,2,3))
304 | :7: error: type mismatch;
305 |  found   : Int(1)
306 |  required: AnyRef
307 | Note: primitive types are not implicitly converted to AnyRef.
308 | You can safely force boxing by casting x.asInstanceOf[AnyRef].
309 |        hashcodes(Seq(1,2,3))
310 |                      ^
311 | 
312 | scala> hashcodes(Seq("one", "two", "three"))
313 | res1: Seq[Int] = List(110182, 115276, 110339486)
314 | 
315 | 316 | 317 | *参考* D. R. MacIver写的Scala中的存在类型 318 | -------------------------------------------------------------------------------- /web/zh_cn/basics.textile: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: 基础 4 | next: basics2.textile 5 | --- 6 | 7 | 课程内容: 8 | * "关于这节课":#overview 9 | * "表达式":#expressions 10 | * "值":#val 11 | * "函数":#functions 12 | * "类":#class 13 | * "继承":#extends 14 | * "特质":#trait 15 | * "类型":#types 16 | 17 | h2(#overview). 关于这节课 18 | 19 | 最初的几个星期将涵盖基本语法和概念,然后我们将通过更多的练习展开这些内容。 20 | 21 | 有一些例子是以解释器交互的形式给出的,另一些则是以源文件的形式给出的。 22 | 23 | 安装一个解释器,可以使探索问题空间变得更容易。 24 | 25 | 26 | h3. 为什么选择 Scala? 27 | 28 | * 表达能力 29 | ** 函数是一等公民 30 | ** 闭包 31 | * 简洁 32 | ** 类型推断 33 | ** 函数创建的文法支持 34 | * Java互操作性 35 | ** 可重用Java库 36 | ** 可重用Java工具 37 | ** 没有性能惩罚 38 | 39 | h3. Scala 如何工作? 40 | 41 | * 编译成Java字节码 42 | * 可在任何标准JVM上运行 43 | ** 甚至是一些不规范的JVM上,如Dalvik 44 | ** Scala编译器是Java编译器的作者写的 45 | 46 | h3. 用 Scala 思考 47 | 48 | Scala不仅仅是更好的Java。你应该用全新的头脑来学习它,你会从这些课程中认识到这一点的。 49 | 50 | h3. 启动解释器 51 | 52 | 使用自带的sbt console启动。 53 | 54 |
 55 | $ sbt console
 56 | 
 57 | [...]
 58 | 
 59 | Welcome to Scala version 2.8.0.final (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_20).
 60 | Type in expressions to have them evaluated.
 61 | Type :help for more information.
 62 | 
 63 | scala>
 64 | 
65 | 66 | 67 | h2(#expressions). 表达式 68 | 69 |
 70 | scala> 1 + 1
 71 | res0: Int = 2
 72 | 
73 | 74 | res0是解释器自动创建的变量名称,用来指代表达式的计算结果。它是Int类型,值为2。 75 | 76 | Scala中(几乎)一切都是表达式。 77 | 78 | h2(#val). 值 79 | 80 | 你可以给一个表达式的结果起个名字赋成一个不变量(val)。 81 | 82 |
 83 | scala> val two = 1 + 1
 84 | two: Int = 2
 85 | 
86 | 87 | 你不能改变这个不变量的值. 88 | 89 | h3. 变量 90 | 91 | 如果你需要修改这个名称和结果的绑定,可以选择使用var。 92 | 93 |
 94 | scala> var name = "steve"
 95 | name: java.lang.String = steve
 96 | 
 97 | scala> name = "marius"
 98 | name: java.lang.String = marius
 99 | 
100 | 101 | h2(#functions). 函数 102 | 103 | 你可以使用def创建函数. 104 | 105 |
106 | scala> def addOne(m: Int): Int = m + 1
107 | addOne: (m: Int)Int
108 | 
109 | 110 | 在Scala中,你需要为函数参数指定类型签名。 111 | 112 |
113 | scala> val three = addOne(2)
114 | three: Int = 3
115 | 
116 | 117 | 如果函数不带参数,你可以不写括号。 118 | 119 |
120 | scala> def three() = 1 + 2
121 | three: ()Int
122 | 
123 | scala> three()
124 | res2: Int = 3
125 | 
126 | scala> three
127 | res3: Int = 3
128 | 
129 | 130 | h3. 匿名函数 131 | 132 | 你可以创建匿名函数。 133 | 134 |
135 | scala> (x: Int) => x + 1
136 | res2: (Int) => Int = 
137 | 
138 | 139 | 这个函数为名为x的Int变量加1。 140 | 141 |
142 | scala> res2(1)
143 | res3: Int = 2
144 | 
145 | 146 | 你可以传递匿名函数,或将其保存成不变量。 147 | 148 |
149 | scala> val addOne = (x: Int) => x + 1
150 | addOne: (Int) => Int = 
151 | 
152 | scala> addOne(1)
153 | res4: Int = 2
154 | 
155 | 156 | 如果你的函数有很多表达式,可以使用{}来格式化代码,使之易读。 157 | 158 |
159 | def timesTwo(i: Int): Int = {
160 |   println("hello world")
161 |   i * 2
162 | }
163 | 
164 | 165 | 对匿名函数也是这样的。 166 | 167 |
168 | scala> { i: Int =>
169 |   println("hello world")
170 |   i * 2
171 | }
172 | res0: (Int) => Int = 
173 | 
174 | 175 | 在将一个匿名函数作为参数进行传递时,这个语法会经常被用到。 176 | 177 | h3. 部分应用(Partial application) 178 | 179 | 你可以使用下划线“_”部分应用一个函数,结果将得到另一个函数。Scala使用下划线表示不同上下文中的不同事物,你通常可以把它看作是一个没有命名的神奇通配符。在{ _ + 2 }的上下文中,它代表一个匿名参数。你可以这样使用它: 180 | 181 |
182 | scala> def adder(m: Int, n: Int) = m + n
183 | adder: (m: Int,n: Int)Int
184 | 
185 | 186 |
187 | scala> val add2 = adder(2, _:Int)
188 | add2: (Int) => Int = 
189 | 
190 | scala> add2(3)
191 | res50: Int = 5
192 | 
193 | 194 | 你可以部分应用参数列表中的任意参数,而不仅仅是最后一个。 195 | 196 | h3. 柯里化函数 197 | 198 | 有时会有这样的需求:允许别人一会在你的函数上应用一些参数,然后又应用另外的一些参数。 199 | 200 | 例如一个乘法函数,在一个场景需要选择乘数,而另一个场景需要选择被乘数。 201 | 202 |
203 | scala> def multiply(m: Int)(n: Int): Int = m * n
204 | multiply: (m: Int)(n: Int)Int
205 | 
206 | 207 | 你可以直接传入两个参数。 208 | 209 |
210 | scala> multiply(2)(3)
211 | res0: Int = 6
212 | 
213 | 214 | 你可以填上第一个参数并且部分应用第二个参数。 215 | 216 |
217 | scala> val timesTwo = multiply(2) _
218 | timesTwo: (Int) => Int = 
219 | 
220 | scala> timesTwo(3)
221 | res1: Int = 6
222 | 
223 | 224 | 你可以对任何多参数函数执行柯里化。例如之前的adder函数 225 | 226 |
227 | scala> (adder _).curried
228 | res1: (Int) => (Int) => Int = 
229 | 
230 | 231 | h3. 可变长度参数 232 | 233 | 这是一个特殊的语法,可以向方法传入任意多个同类型的参数。例如要在多个字符串上执行String的capitalize函数,可以这样写: 234 | 235 |
236 | def capitalizeAll(args: String*) = {
237 |   args.map { arg =>
238 |     arg.capitalize
239 |   }
240 | }
241 | 
242 | scala> capitalizeAll("rarity", "applejack")
243 | res2: Seq[String] = ArrayBuffer(Rarity, Applejack)
244 | 
245 | 246 | h2(#class). 类 247 | 248 |
249 | scala> class Calculator {
250 |      |   val brand: String = "HP"
251 |      |   def add(m: Int, n: Int): Int = m + n
252 |      | }
253 | defined class Calculator
254 | 
255 | scala> val calc = new Calculator
256 | calc: Calculator = Calculator@e75a11
257 | 
258 | scala> calc.add(1, 2)
259 | res1: Int = 3
260 | 
261 | scala> calc.brand
262 | res2: String = "HP"
263 | 
264 | 265 | 上面的例子展示了如何在类中用def定义方法和用val定义字段值。方法就是可以访问类的状态的函数。 266 | 267 | h3. 构造函数 268 | 269 | 构造函数不是特殊的方法,他们是除了类的方法定义之外的代码。让我们扩展计算器的例子,增加一个构造函数参数,并用它来初始化内部状态。 270 | 271 |
272 | class Calculator(brand: String) {
273 |   /**
274 |    * A constructor.
275 |    */
276 |   val color: String = if (brand == "TI") {
277 |     "blue"
278 |   } else if (brand == "HP") {
279 |     "black"
280 |   } else {
281 |     "white"
282 |   }
283 | 
284 |   // An instance method.
285 |   def add(m: Int, n: Int): Int = m + n
286 | }
287 | 
288 | 289 | 注意两种不同风格的注释。 290 | 291 | 你可以使用构造函数来构造一个实例: 292 | 293 |
294 | scala> val calc = new Calculator("HP")
295 | calc: Calculator = Calculator@1e64cc4d
296 | 
297 | scala> calc.color
298 | res0: String = black
299 | 
300 | 301 | h3. 表达式 302 | 303 | 上文的Calculator例子说明了Scala是如何面向表达式的。颜色的值就是绑定在一个if/else表达式上的。Scala是高度面向表达式的:大多数东西都是表达式而非指令。 304 | 305 | h3. 旁白: 函数 vs 方法 306 | 307 | 函数和方法在很大程度上是可以互换的。由于函数和方法是如此的相似,你可能都不知道你调用的东西是一个函数还是一个方法。而当真正碰到的方法和函数之间的差异的时候,你可能会感到困惑。 308 | 309 |
310 | scala> class C {
311 |      |   var acc = 0
312 |      |   def minc = { acc += 1 }
313 |      |   val finc = { () => acc += 1 }
314 |      | }
315 | defined class C
316 | 
317 | scala> val c = new C
318 | c: C = C@1af1bd6
319 | 
320 | scala> c.minc // calls c.minc()
321 | 
322 | scala> c.finc // returns the function as a value:
323 | res2: () => Unit = 
324 | 
325 | 326 | 当你可以调用一个不带括号的“函数”,但是对另一个却必须加上括号的时候,你可能会想哎呀,我还以为自己知道Scala是怎么工作的呢。也许他们有时需要括号?你可能以为自己用的是函数,但实际使用的是方法。 327 | 328 | 在实践中,即使不理解方法和函数上的区别,你也可以用Scala做伟大的事情。如果你是Scala新手,而且在读两者的差异解释,你可能会跟不上。不过这并不意味着你在使用Scala上有麻烦。它只是意味着函数和方法之间的差异是很微妙的,只有深入语言内部才能清楚理解它。 329 | 330 | h2(#extends). 继承 331 | 332 |
333 | class ScientificCalculator(brand: String) extends Calculator(brand) {
334 |   def log(m: Double, base: Double) = math.log(m) / math.log(base)
335 | }
336 | 
337 | 338 | *参考* Effective Scala 指出如果子类与父类实际上没有区别,类型别名是优于继承的。A Tour of Scala 详细介绍了子类化。 339 | 340 | h3. 重载方法 341 | 342 |
343 | class EvenMoreScientificCalculator(brand: String) extends ScientificCalculator(brand) {
344 |   def log(m: Int): Double = log(m, math.exp(1))
345 | }
346 | 
347 | 348 | h3. 抽象类 349 | 350 | 你可以定义一个抽象类,它定义了一些方法但没有实现它们。取而代之是由扩展抽象类的子类定义这些方法。你不能创建抽象类的实例。 351 | 352 |
353 | scala> abstract class Shape {
354 |      |   def getArea():Int    // subclass should define this
355 |      | }
356 | defined class Shape
357 | 
358 | scala> class Circle(r: Int) extends Shape {
359 |      |   def getArea():Int = { r * r * 3 }
360 |      | }
361 | defined class Circle
362 | 
363 | scala> val s = new Shape
364 | :8: error: class Shape is abstract; cannot be instantiated
365 |        val s = new Shape
366 |                ^
367 | 
368 | scala> val c = new Circle(2)
369 | c: Circle = Circle@65c0035b
370 | 
371 | 372 | h2(#trait). 特质(Traits) 373 | 374 | 特质是一些字段和行为的集合,可以扩展或混入(mixin)你的类中。 375 | 376 |
377 | trait Car {
378 |   val brand: String
379 | }
380 | 
381 | trait Shiny {
382 |   val shineRefraction: Int
383 | }
384 | 
385 | 386 |
387 | class BMW extends Car {
388 |   val brand = "BMW"
389 | }
390 | 
391 | 392 | 通过with关键字,一个类可以扩展多个特质: 393 | 394 |
395 | class BMW extends Car with Shiny {
396 |   val brand = "BMW"
397 |   val shineRefraction = 12
398 | }
399 | 
400 | 401 | *参考* Effective Scala 对特质的观点。 402 | 403 | *什么时候应该使用特质而不是抽象类?* 如果你想定义一个类似接口的类型,你可能会在特质和抽象类之间难以取舍。这两种形式都可以让你定义一个类型的一些行为,并要求继承者定义一些其他行为。一些经验法则: 404 | 405 |
    406 |
  • 优先使用特质。一个类扩展多个特质是很方便的,但却只能扩展一个抽象类。 407 |
  • 如果你需要构造函数参数,使用抽象类。因为抽象类可以定义带参数的构造函数,而特质不行。例如,你不能说trait t(i: Int) {},参数i是非法的。 408 |
409 | 410 | 你不是问这个问题的第一人。可以查看更全面的答案: "stackoverflow: Scala特质 vs 抽象类":https://stackoverflow.com/questions/1991042/scala-traits-vs-abstract-classes , "抽象类和特质的区别":https://stackoverflow.com/questions/2005681/difference-between-abstract-class-and-trait, and "Scala编程: 用特质,还是不用特质?":https://www.artima.com/pins1ed/traits.html#12.7 411 | 412 | h2(#types). 类型 413 | 414 | 此前,我们定义了一个函数的参数为Int,表示输入是一个数字类型。其实函数也可以是泛型的,来适用于所有类型。当这种情况发生时,你会看到用方括号语法引入的类型参数。下面的例子展示了一个使用泛型键和值的缓存。 415 | 416 |
417 | trait Cache[K, V] {
418 |   def get(key: K): V
419 |   def put(key: K, value: V)
420 |   def delete(key: K)
421 | }
422 | 
423 | 424 | 方法也可以引入类型参数。 425 | 426 |
427 | def remove[K](key: K)
428 | 
429 | -------------------------------------------------------------------------------- /web/ko/java.textile: -------------------------------------------------------------------------------- 1 | --- 2 | prev: concurrency.textile 3 | next: finagle.textile 4 | title: 자바 + 스칼라 5 | layout: post 6 | --- 7 | 8 | 이번 강좌에서는 자바와 함께 사용하는 법에 대해 다룬다. 9 | 10 | * Javap 11 | * 클래스 12 | * 예외 13 | * 트레잇 14 | * 객체 15 | * 클로져와 함수 16 | * 공변성 17 | 18 | h2. Javap 19 | 20 | javap는 JDK에 따라오는 도구이다.(JRE에는 없다) Javap는 클래스 파일을 역컴파일해서 내부를 보여준다. 사용법도 아주 간단하다. 21 | 22 |
 23 | [local ~/projects/interop/target/scala_2.8.1/classes/com/twitter/interop]$ javap MyTrait
 24 | Compiled from "Scalaisms.scala"
 25 | public interface com.twitter.interop.MyTrait extends scala.ScalaObject{
 26 |     public abstract java.lang.String traitName();
 27 |     public abstract java.lang.String upperTraitName();
 28 | }
 29 | 
30 | 31 | 하드코어 프로그래머라면 직접 바이트코드를 볼 수도 있다. 32 | 33 |
 34 | [local ~/projects/interop/target/scala_2.8.1/classes/com/twitter/interop]$ javap -c MyTrait\$class
 35 | Compiled from "Scalaisms.scala"
 36 | public abstract class com.twitter.interop.MyTrait$class extends java.lang.Object{
 37 | public static java.lang.String upperTraitName(com.twitter.interop.MyTrait);
 38 |   Code:
 39 |    0:  aload_0
 40 |    1:	invokeinterface	#12,  1; //InterfaceMethod com/twitter/interop/MyTrait.traitName:()Ljava/lang/String;
 41 |    6:	invokevirtual	#17; //Method java/lang/String.toUpperCase:()Ljava/lang/String;
 42 |    9:	areturn
 43 | 
 44 | public static void $init$(com.twitter.interop.MyTrait);
 45 |   Code:
 46 |    0:	return
 47 | 
 48 | }
 49 | 
50 | 51 | 자바 세계에서 왜 어떤 코드가 잘 동작하지 않는지 궁금하다면 javap를 한번 써 보라! 52 | 53 | h2. 클래스 54 | 55 | 스칼라 _클래스_ 를 자바에서 사용할 때 고려해야 할 네가지 요소는 다음과 같다. 56 | 57 | * 클래스 매개변수 58 | * 클래스 내의 값(val) 59 | * 클래스 내의 변수(var) 60 | * 예외 61 | 62 | 간단한 스칼라 클래스를 만들고 이런 요소를 모두 다 보여줄 것이다. 63 | 64 | 65 |
 66 | package com.twitter.interop
 67 | 
 68 | import java.io.IOException
 69 | import scala.throws
 70 | import scala.reflect.{BeanProperty, BooleanBeanProperty}
 71 | 
 72 | class SimpleClass(name: String, val acc: String, @BeanProperty var mutable: String) {
 73 |   val foo = "foo"
 74 |   var bar = "bar"
 75 |   @BeanProperty
 76 |   val fooBean = "foobean"
 77 |   @BeanProperty
 78 |   var barBean = "barbean"
 79 |   @BooleanBeanProperty
 80 |   var awesome = true
 81 | 
 82 |   def dangerFoo() = {
 83 |     throw new IOException("SURPRISE!")
 84 |   }
 85 | 
 86 |   @throws(classOf[IOException])
 87 |   def dangerBar() = {
 88 |     throw new IOException("NO SURPRISE!")
 89 |   }
 90 | }
 91 | 
92 | 93 | h3. 클래스 매개변수 94 | 95 | * 기본적으로 클래스 매개변수는 자바 쪽에서는 생성자 인자로 작용한다. 이는 클래스 외부에서 이를 억세스할 방법은 없다는 뜻이다. 96 | * 클래스 매개변수를 val이나 var로 지정하는 것은 아래 코드와 동일하다. 97 | 98 |
 99 | class SimpleClass(acc_: String) {
100 |   val acc = acc_
101 | }
102 | 
103 | 따라서 다른 val이나 var와 마찬가지로 자바 코드에서 억세스가 가능하다. 104 | 105 | h3. 값(val) 106 | 107 | * 모든 값에는 자바에서 억세스할 수 있는 메소드가 있다. 값의 이름이 "foo"였다면 자바 메소드는 "foo()"가 된다. 108 | 109 | h3. 변수(var) 110 | 111 | * 변수에는 _$eq 라는 메소드가 만들어진다. 다음과 같이 호출해 새 값을 할당할 수 있다(당연히 ()라는 메소드로 값을 읽는것도 가능하다). 112 | 113 |
114 | foo$_eq("newfoo");
115 | 
116 | 117 | h3. BeanProperty 118 | 119 | 값이나 변수에 @BeanProperty 애노테이션을 할 수 있다. 그러면 POJO 게터/세터 정의와 마찬가지로 게터와 세터를 만들어준다. isFoo와 같은 형태의 게터/세터를 원하면 BooleanBeanProperty 애노테이션을 붙이도록 하라. 그러면 보기 싫은 foo$_eq가 다음과 같이 된다. 120 | 121 |
122 | setFoo("newfoo");
123 | getFoo();
124 | 
125 | 126 | 127 | h3. 예외 128 | 129 | 스칼라에는 체크드 예외가 없다. 자바에는 있다. 이에 대해서는, 여기서는 설명하지 않겠지만, 철학적인 논쟁이 있어왔다. 하지만, 자바에서 예외를 받으려 한다면 이게 문제가 *된다.* dangerFoo와 dangerBar의 정의는 이를 보여준다. 자바에서는 다음과 같이 할 수 없다. 130 | 131 |
132 |         // exception erasure!
133 |         try {
134 |             s.dangerFoo();
135 |         } catch (IOException e) {
136 |             // UGLY
137 |         }
138 | 
139 | 
140 | 141 | 자바는 s.dangerFoo가 IOException를 던지지 않는다고 오류를 표시할 것이다. 이를 Thorwable을 받는 것으로 처리할 수 있긴 하지만, 구차한 일이다. 142 | 143 | 대신, 착한 스칼라 시민이라면 dangerBar에서처럼 throws 애노테이션을 사용하는 것이 좋다. 그렇게 하면 자바 쪽에서는 체크드 예외로 사용 가능해진다. 144 | 145 | h3. 읽을거리 146 | 147 | 자바와 함께 동작하기 위해 사용할 수 있는 스칼라의 애노테이션 목록이 여기 에 있다. 148 | 149 | h2. 트레잇 150 | 151 | 인터페이스와 구현을 한꺼번에 어떻게 가져올 수 있을까? 간단한 트레잇을 하나 정의해서 들여다 보자. 152 | 153 |
154 | trait MyTrait {
155 |   def traitName:String
156 |   def upperTraitName = traitName.toUpperCase
157 | }
158 | 
159 | 160 | 이 트레잇에는 추상 메소드가 하나(traitName), 구현된 메소드가 하나(upperTraitName) 있다. 스칼라가 만들어내는 것은 무엇일까? MyTrait이라는 이름의 인터페이스와 MyTrait$class라 불리는 짝 구현 클래스를 만든다. 161 | 162 | MyTrait의 구현은 예상을 벗어나지 않는다. 163 | 164 |
165 | [local ~/projects/interop/target/scala_2.8.1/classes/com/twitter/interop]$ javap MyTrait
166 | Compiled from "Scalaisms.scala"
167 | public interface com.twitter.interop.MyTrait extends scala.ScalaObject{
168 |     public abstract java.lang.String traitName();
169 |     public abstract java.lang.String upperTraitName();
170 | }
171 | 
172 | 173 | MyTrait$class 구현이 더 재미있는 부분이다. 174 | 175 |
176 | [local ~/projects/interop/target/scala_2.8.1/classes/com/twitter/interop]$ javap MyTrait\$class
177 | Compiled from "Scalaisms.scala"
178 | public abstract class com.twitter.interop.MyTrait$class extends java.lang.Object{
179 |     public static java.lang.String upperTraitName(com.twitter.interop.MyTrait);
180 |     public static void $init$(com.twitter.interop.MyTrait);
181 | }
182 | 
183 | 184 | MyTrait$class에는 MyTrait의 인스턴스를 받는 정적 메소드만 존재한다. 이 메소드는 자바에서 트레잇을 확장하는 방법을 찾는데 단서가 될 수 있다. 185 | 186 | 맨 처음에는 아마 다음과 같은 시도를 할 것이다. 187 | 188 |
189 | package com.twitter.interop;
190 | 
191 | public class JTraitImpl implements MyTrait {
192 |     private String name = null;
193 | 
194 |     public JTraitImpl(String name) {
195 |         this.name = name;
196 |     }
197 | 
198 |     public String traitName() {
199 |         return name;
200 |     }
201 | }
202 | 
203 | 204 | 이러면 다음과 같은 오류를 보게 된다. 205 | 206 |
207 | [info] Compiling main sources...
208 | [error] /Users/mmcbride/projects/interop/src/main/java/com/twitter/interop/JTraitImpl.java:3: com.twitter.interop.JTraitImpl is not abstract and does not override abstract method upperTraitName() in com.twitter.interop.MyTrait
209 | [error] public class JTraitImpl implements MyTrait {
210 | [error]        ^
211 | 
212 | 213 | 물론 직접 이 메소드를 구현할 수도 _있었을_ 것이다. 하지만, 더 영리한 방법이 있다. 214 |
215 | package com.twitter.interop;
216 | 
217 |     public String upperTraitName() {
218 |         return MyTrait$class.upperTraitName(this);
219 |     }
220 | 
221 | 222 | 단지 스칼라가 만들어 놓은 짝 구현 클래스에 위임하면 끝난다. 원한다면 이를 오버라이드할 수도 있다. 223 | 224 | h2. 객체 225 | 226 | 객체(여기서는 스칼라에서 object로 선언한 싱글턴 객체)는 스칼라에서 정적인 메소드와 싱글턴을 구현하는 방법이다. 자바에서 이를 사용하는 방법은 조금 불편하다. 객체를 사용하는 문법적으로 완벽한 방법은 없다. 하지만 스칼라 2.8의 객체를 자바에서 사용하는 것은 아주 나쁘지는 않다. 227 | 228 | 스칼라 객체는 끝에 "$"가 붙은 클래스로 컴파일된다. 클래스와 짝 객체를 만들어 보자. 229 | 230 |
231 | class TraitImpl(name: String) extends MyTrait {
232 |   def traitName = name
233 | }
234 | 
235 | object TraitImpl {
236 |   def apply = new TraitImpl("foo")
237 |   def apply(name: String) = new TraitImpl(name)
238 | }
239 | 
240 | 241 | 자바에서는 다음과 같이 억세스할 수 있다. 242 | 243 |
244 | MyTrait foo = TraitImpl$.MODULE$.apply("foo");
245 | 
246 | 247 | 아마도 "뭐지?"라고 스스로에게 물었을 것이다. 그게 당연한 반응이다. 실제 TraitImpl$ 안이 어떻게 되어 있는지 살펴보자. 248 | 249 |
250 | local ~/projects/interop/target/scala_2.8.1/classes/com/twitter/interop]$ javap TraitImpl\$
251 | Compiled from "Scalaisms.scala"
252 | public final class com.twitter.interop.TraitImpl$ extends java.lang.Object implements scala.ScalaObject{
253 |     public static final com.twitter.interop.TraitImpl$ MODULE$;
254 |     public static {};
255 |     public com.twitter.interop.TraitImpl apply();
256 |     public com.twitter.interop.TraitImpl apply(java.lang.String);
257 | }
258 | 
259 | 260 | 정적인 메소드는 없다. 대신에 정적인 멤버 MODULE$이 있다. 메소드 구현은 모두 이 멤버에게 위임한다. 이렇게 하는 것은 억세스할 때 보기 안좋다. 하지만, MODULE$를 사용해야 한다는 사실을 아는 한 잘 동작하긴 한다. 261 | 262 | h3. 메소드 전달 263 | 264 | 스칼라 2.8에서는 객체를 다루는 것이 훨씬 쉬워졌다. 짝 객체와 함께 정의된 클래스를 다루는 경우 2.8 컴파일러는 짝 객체로 전달하기 위한 메소드도 생성해준다. 따라서 스칼라 2.8로 빌드한 경우, TraitImpl에 있는 메소드를 다음과 같이도 호출 가능하다. 265 | 266 |
267 | MyTrait foo = TraitImpl.apply("foo");
268 | 
269 | 270 | h2. 클로저 함수 271 | 272 | 스칼라의 가장 중요한 특징 중 하나가 함수를 1등 시민으로 대우한다는 것이다. 함수를 인자로 받는 메소드가 정의된 클래스를 만들자. 273 | 274 |
275 | class ClosureClass {
276 |   def printResult[T](f: => T) = {
277 |     println(f)
278 |   }
279 | 
280 |   def printResult[T](f: String => T) = {
281 |     println(f("HI THERE"))
282 |   }
283 | }
284 | 
285 | 286 | 스칼라에서는 다음과 같이 호출할 수 있다. 287 | 288 |
289 | val cc = new ClosureClass
290 | cc.printResult { "HI MOM" }
291 | 
292 | 293 | 자바에서는 쉽지 않지만, 아주 어려운 것도 아니다. ClosureClass가 컴파일된 결과를 살펴보면 다음과 같다. 294 | 295 | 296 |
297 | [local ~/projects/interop/target/scala_2.8.1/classes/com/twitter/interop]$ javap ClosureClass
298 | Compiled from "Scalaisms.scala"
299 | public class com.twitter.interop.ClosureClass extends java.lang.Object implements scala.ScalaObject{
300 |     public void printResult(scala.Function0);
301 |     public void printResult(scala.Function1);
302 |     public com.twitter.interop.ClosureClass();
303 | }
304 | 
305 | 306 | 그렇게 이해하기 어렵지는 않다. "f: => T"는 "Function0"로, "f: String => T"는 "Function1"로 번역된다. 스칼라는 Function0부터 Function22까지, 최대 22개의 인자를 지원한다. 그정도 갯수면 충분해야 하리라 본다. 307 | 308 | 이제 이를 자바에서는 어떻게 사용할 수 있나 보자. 스칼라는 AbstractFunction0과 AbstractFunction1를 제공한다. 따라서 다음과 같이 넘기는 것이 가능하다. 309 | 310 |
311 |     @Test public void closureTest() {
312 |         ClosureClass c = new ClosureClass();
313 |         c.printResult(new AbstractFunction0() {
314 |                 public String apply() {
315 |                     return "foo";
316 |                 }
317 |             });
318 |         c.printResult(new AbstractFunction1() {
319 |                 public String apply(String arg) {
320 |                     return arg + "foo";
321 |                 }
322 |             });
323 |     }
324 | 
325 | 326 | 인자의 타입을 지정하기 위해 자바의 일반적 함수(제네릭)을 사용했다는 점에 유의하라. 327 | -------------------------------------------------------------------------------- /notes.txt: -------------------------------------------------------------------------------- 1 | Scala School notes. Marius & Steve (Aug 25, 2011) 2 | 3 | notes 4 | 5 | - slim searchbird.tar.gz, make available, put on front page 6 | 7 | lesson 1: 8 | 9 | * cases are introduced too early (in try{}catch{}) (moved to end of lesson 2) 10 | * bootstrap vs. screen width (not sure how to fix) 11 | 12 | lesson 2: 13 | 14 | * you can leave out parenthesis (explained in lesson 1) 15 | * var vs. val (explained in lesson 1) 16 | * why default case, runtime exceptions (fixed) 17 | * currentCount should have parens (fixed) 18 | * introduce traits too early (introduced traits to Lesson 1) 19 | * remove .equals (fixed) 20 | * match … Bottle(thisColor, theseOunces) (fixed) 21 | * don't omit parens for partial application (fixed) 22 | * java.lang.Object in function (switched to use an object extending Function1) 23 | * motivate case classes better - structs (fixed) 24 | * defer case classes until after matching (maybe) (show that it would be painful to do the blue bottle example without case classes) (fixed) 25 | 26 | * introduce blocks? (lesson 1) 27 | * introduce casting 28 | * module system 29 | * didn't explain brackets (TODO: should we have a mini-types explanation earlier?) 30 | 31 | lesson 3: 32 | * todo: introduce variable length arguments (Lesson 1) 33 | * get rid of recap (fixed) 34 | * introduce tuples (already there) 35 | * kill Seq (fixed) 36 | * flatMap (fixed) 37 | * note that these are combinators -- transformations on collections (fixed) 38 | * introduce currying (in lesson #1) 39 | * kill the block in foldLeft (ok, fine. fixed) 40 | * index vs. value is confusing (where is this?) 41 | * Map(1 -> 2) looks like special syntax (fixed) 42 | * find better example for partial application (fixed: add(m)(n)) 43 | * compositional semantics of currying argument lists vs. partial application, vs. … 44 | 45 | lesson 4: 46 | 47 | * don't use whitespace application 48 | * move composition all the way up 49 | * introduce option! 50 | * show case classes after unapply 51 | * introduce options together with collections (collections of 1 or 0) 52 | * PartialFunction != partially applied function! (fixed) 53 | * introduce whitespace applicaton of methods 54 | * change wildcard partial function to a total function 55 | * for partial functions, don’t say ‘closed, say ‘is only defined for certain arguments’ 56 | 57 | * introduce Option earlier 58 | 59 | (Steve’s half took 1.5 hours) 60 | 61 | Feedback: can we show more code? larger examples of matching, for 62 | example. 63 | 64 | Map(1 -> "a") is confusing. special syntax? syntactic sugar? 65 | (Fixed) 66 | 67 | Explain types earlier. 68 | 69 | Font is too light for a projector. Important things should be bold. 70 | Font is too small, border too large. Great for a screen, bad for a 71 | projector. (Toggle a style-sheet?) 72 | 73 | Lesson: Types 74 | 75 | Q: What do we need for finagle? 76 | 77 | Idea: the example functions should be based on searchbird so we can 78 | later stitch together functions we've already built and understand. 79 | 80 | Note from Sam List[Any] example makes you think it'll always resolve 81 | to Any but it will be most common super-type. 82 | 83 | scala> trait F defined trait F 84 | 85 | scala> class A extends F defined class A 86 | 87 | scala> class B extends F defined class B 88 | 89 | scala> List(new A, new B) res55: List[ScalaObject with F] = 90 | List(A@1bac8b63, B@5cf31ec6) 91 | 92 | Feedback: aren't showing how to encode higher-rank polymorphism but 93 | talk about it. why do that? 94 | 95 | Feedback: should we discuss HM since Scala doesn't do it? Can we 96 | explain the ad-hoc rules more easily? 97 | 98 | { x => x } shows {} use but we haven't introduced that yet. 99 | 100 | In Types, we use return type inference heavily but nowhere else. 101 | (should we explain and use return type inference earlier?) 102 | 103 | Feedback: AnyRef has made an appearance but we haven't explained it 104 | yet. 105 | 106 | We show trait Function but haven't explained a trait yet. 107 | 108 | We use the function value syntax but generally only show partially 109 | applied functions earlier so is confusing. 110 | 111 | val fB: (A => B) = (a: A) => new B(a) 112 | 113 | vs 114 | 115 | def fB(a: A): B) = new B(a) 116 | 117 | 118 | Feedback: make it non-mathematical, don't use A, B, C, use Animal, 119 | Cats and Dogs rather than more abstract. 120 | 121 | Invariant isn't covered and yet is the default so people will always 122 | need to understand it. 123 | 124 | note: functions are contravariant on their arguments. 125 | 126 | this would be clearer as animals, with a pound or cage method? 127 | 128 | We often explain things in person that we then explain things in the 129 | talk. (need more practice) 130 | 131 | wildcard not needed for finagle so skip. 132 | 133 | Lesson: Finagle (took 20 minutes to get here) 134 | 135 | we need to add flatMap earlier. 136 | 137 | Finagle lesson uses Java callback as an example: should switch to 138 | Scala. 139 | 140 | we use for-comprehension syntax but haven't explained that yet. with 141 | yield. in the collections/combinators lesson. 142 | 143 | ... looks like an underscore (add spacing) 144 | 145 | def Future[A].flatMap(...) is odd looking syntax. (is for 146 | illustration). put it in a trait: 147 | 148 | trait Future[A] { 149 | def flatMap[B](f: A => Future[B]): Future[B] 150 | } 151 | 152 | Q: "why is it called flatMap?" (haven't described collections or 153 | for-comprehensions so people don't know the contract) 154 | 155 | failures short-circuit sequential composition. 156 | 157 | We haven't explained Unit yet. 158 | 159 | We use dot-less syntax but haven't explained that yet. 160 | 161 | Two uses join methods: one returns Future[Unit], the other doesn't. 162 | 163 | `dispatch(request) join userLimit` is confusing and controversial 164 | because dispatch is always called. 165 | 166 | Not stock _or_ combinator. can write it with flatMap. 167 | 168 | handle is flatMap for errors. 169 | 170 | error-handling, generally, should be made more clear: the fact that 171 | it's threaded everywhere should be called out. 172 | 173 | Thought: get laser pointers. (also bring our own VGA/DVI adapters) 174 | 175 | use function extension 176 | 177 | class Foo extends (A => B) but haven't explained that yet. 178 | 179 | Remove Passbird from Finagle example. 180 | 181 | Feedback: call out the type-safety of authenticatedTimedOutService in 182 | text. 183 | 184 | Filter brings up the question of "what's blocking? can I block here?" 185 | 186 | 187 | Lesson: Searchbird (have 25 minutes left) 188 | 189 | Need a way to hide the examples we want people to fill out. 190 | 191 | sbt transcript 192 | 193 | sbt should be ./sbt (to make sure we use our local sbt) skip update, 194 | don't do that! 195 | 196 | don't make people do a gem install of thrift_client. 197 | 198 | mine fails. 199 | 200 | System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/rubygems/custom_require.rb:31:in 201 | `gem_original_require': no such file to load -- bundler/setup 202 | (LoadError) 203 | from 204 | /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/rubygems/custom_require.rb:31:in 205 | `require' from src/scripts/console:5 206 | 207 | d'oh. 208 | 209 | turn this into a scala client, show people finagle clients. 210 | 211 | ServerConfig is hard to explain. 212 | 213 | "RuntimeEnvironments has command-line flags, and other process 214 | information" 215 | 216 | Future[Void] is in the trait (we still haven't explained traits) 217 | 218 | In the Implementation scala code: 219 | 220 | concurrency bug to not sync around the map. 221 | 222 | Future(value) should be Future.value(value) // d'oh, value twice 223 | 224 | database(key) = value 225 | 226 | motivates explaining update syntax sugar (or else not do that in the 227 | implementation) 228 | 229 | forward/reverse hashmaps use mixins. 230 | 231 | Why is there Future.void but not Future.unit? 232 | 233 | we use a reduceLeftOption and then getOrElse without dots. 234 | 235 | Sub-lesson: "Distributing our service" (have 10 minutes left) 236 | 237 | we use require(!indices.isEmpty)? 238 | 239 | there's an example of finagle clients in here that we can pull out 240 | into an object for a simple repl-based client. 241 | 242 | val Array(_, port) = shards(which).split(":") 243 | 244 | haven't explained the deconstruction syntax here. 245 | 246 | Idea: thumb drives with the code. 247 | 248 | ¶ 249 | 250 | notes from Dave L. re: searchbird lesson 251 | 252 | When you add the search method to the thrift IDL, it would be nice to 253 | highlight just the added line. 254 | 255 | "excercise" -> "exercise" 256 | 257 | Would be cool to have an "Exercises" section at the end for all the 258 | go-getters to work through. 259 | 260 | Am I having a brain fart, or should these be made consistent (meaning 261 | use the 2-argument getOrElse in both places)? 262 | val current = reverse.get(token) getOrElse Set() 263 | vs. 264 | val hits = tokens map { token => reverse.getOrElse(token, Set()) } 265 | 266 | 267 | More ideas for followup exercises: 268 | 269 | - flesh out put() on CompositeIndex to have a scheme for sending put 270 | requests to specific shards (round-robin? some attribute of the 271 | document?) 272 | 273 | - the search() method performs an intersection of the documents 274 | matching the terms (an AND query). implement a union (OR query). 275 | 276 | - the result set size for search() is unbounded. add support for a 277 | default result size, and the ability to paginate through the full 278 | result set. think about the expense of this operation on large result 279 | sets, and how you might optimize the system for this. 280 | 281 | That's all for now... I'd love to go through the full set of material, 282 | but can't get to it tonight. But I'm really looking forward to 283 | incorporating this into NHO. If things go well tomorrow, perhaps I 284 | can try it out on the current crop of newbies? 285 | -------------------------------------------------------------------------------- /web/basics2.textile: -------------------------------------------------------------------------------- 1 | --- 2 | prev: basics.textile 3 | next: collections.textile 4 | title: Basics continued 5 | layout: post 6 | --- 7 | 8 | This lesson covers: 9 | * "apply":#apply 10 | * "objects":#object 11 | * "Functions are Objects":#fnobj 12 | * "packages":#package 13 | * "pattern matching":#match 14 | * "case classes":#caseclass 15 | * "try-catch-finally":#exception 16 | 17 | h2(#apply). apply methods 18 | 19 | apply methods give you a nice syntactic sugar for when a class or object has one main use. 20 | 21 |
 22 | scala> class Foo {}
 23 | defined class Foo
 24 | 
 25 | scala> object FooMaker {
 26 |      |   def apply() = new Foo
 27 |      | }
 28 | defined module FooMaker
 29 | 
 30 | scala> val newFoo = FooMaker()
 31 | newFoo: Foo = Foo@5b83f762
 32 | 
33 | 34 | or 35 | 36 |
 37 | scala> class Bar {
 38 |      |   def apply() = 0
 39 |      | }
 40 | defined class Bar
 41 | 
 42 | scala> val bar = new Bar
 43 | bar: Bar = Bar@47711479
 44 | 
 45 | scala> bar()
 46 | res8: Int = 0
 47 | 
48 | 49 | Here our instance object looks like we're calling a method. More on that later! 50 | 51 | h2(#object). Objects 52 | 53 | Objects are used to hold single instances of a class. Often used for factories. 54 | 55 |
 56 | object Timer {
 57 |   var count = 0
 58 | 
 59 |   def currentCount(): Long = {
 60 |     count += 1
 61 |     count
 62 |   }
 63 | }
 64 | 
65 | 66 | How to use 67 | 68 |
 69 | scala> Timer.currentCount()
 70 | res0: Long = 1
 71 | 
72 | 73 | Classes and Objects can have the same name. The object is called a 'Companion Object'. We commonly use Companion Objects for Factories. 74 | 75 | Here is a trivial example that only serves to remove the need to use 'new' to create an instance. 76 | 77 |
 78 | class Bar(foo: String)
 79 | 
 80 | object Bar {
 81 |   def apply(foo: String) = new Bar(foo)
 82 | }
 83 | 
84 | 85 | 86 | h2(#fnobj). Functions are Objects 87 | 88 | In Scala, we talk about object-functional programming often. What does that mean? What is a Function, really? 89 | 90 | A Function is a set of traits. Specifically, a function that takes one argument is an instance of a Function1 trait. This trait defines the apply() syntactic sugar we learned earlier, allowing you to call an object like you would a function. 91 | 92 |
 93 | scala> object addOne extends Function1[Int, Int] {
 94 |      |   def apply(m: Int): Int = m + 1
 95 |      | }
 96 | defined module addOne
 97 | 
 98 | scala> addOne(1)
 99 | res2: Int = 2
100 | 
101 | 102 | There is Function0 through 22. Why 22? It's an arbitrary magic number. I've never needed a function with more than 22 arguments so it seems to work out. 103 | 104 | The syntactic sugar of apply helps unify the duality of object and functional programming. You can pass classes around and use them as functions and functions are just instances of classes under the covers. 105 | 106 | Does this mean that every time you define a method in your class, you're actually getting an instance of Function*? No, methods in classes are methods. Methods defined standalone in the repl are Function* instances. 107 | 108 | Classes can also extend Function and those instances can be called with (). 109 | 110 |
111 | scala> class AddOne extends Function1[Int, Int] {
112 |      |   def apply(m: Int): Int = m + 1
113 |      | }
114 | defined class AddOne
115 | 
116 | scala> val plusOne = new AddOne()
117 | plusOne: AddOne = 
118 | 
119 | scala> plusOne(1)
120 | res0: Int = 2
121 | 
122 | 123 | A nice short-hand for extends Function1[Int, Int] is extends (Int => Int) 124 | 125 |
126 | class AddOne extends (Int => Int) {
127 |   def apply(m: Int): Int = m + 1
128 | }
129 | 
130 | 131 | h2(#package). Packages 132 | 133 | You can organize your code inside of packages. 134 | 135 |
136 | package com.twitter.example
137 | 
138 | 139 | at the top of a file will declare everything in the file to be in that package. 140 | 141 | Values and functions cannot be outside of a class or object. Objects are a useful tool for organizing static functions. 142 | 143 |
144 | package com.twitter.example
145 | 
146 | object colorHolder {
147 |   val BLUE = "Blue"
148 |   val RED = "Red"
149 | }
150 | 
151 | 152 | Now you can access the members directly 153 | 154 |
155 | println("the color is: " + com.twitter.example.colorHolder.BLUE)
156 | 
157 | 158 | Notice what the scala repl says when you define this object: 159 | 160 |
161 | scala> object colorHolder {
162 |      |   val Blue = "Blue"
163 |      |   val Red = "Red"
164 |      | }
165 | defined module colorHolder
166 | 
167 | 168 | This gives you a small hint that the designers of Scala designed objects to be part of Scala's module system. 169 | 170 | h2(#match). Pattern Matching 171 | 172 | One of the most useful parts of Scala. 173 | 174 | Matching on values 175 | 176 |
177 | val times = 1
178 | 
179 | times match {
180 |   case 1 => "one"
181 |   case 2 => "two"
182 |   case _ => "some other number"
183 | }
184 | 
185 | 186 | Matching with guards 187 | 188 |
189 | times match {
190 |   case i if i == 1 => "one"
191 |   case i if i == 2 => "two"
192 |   case _ => "some other number"
193 | }
194 | 
195 | 196 | Notice how we captured the value in the variable 'i'. 197 | 198 | The _ in the last case statement is a wildcard; it ensures that we can handle any statement. Otherwise you will suffer a runtime error if you pass in a number that doesn't match. We discuss this more later. 199 | 200 | *See Also* Effective Scala has opinions about when to use pattern matching and pattern matching formatting. A Tour of Scala describes Pattern Matching 201 | 202 | h3. Matching on type 203 | 204 | You can use match to handle values of different types differently. 205 | 206 |
207 | def bigger(o: Any): Any = {
208 |   o match {
209 |     case i: Int if i < 0 => i - 1
210 |     case i: Int => i + 1
211 |     case d: Double if d < 0.0 => d - 0.1
212 |     case d: Double => d + 0.1
213 |     case text: String => text + "s"
214 |   }
215 | }
216 | 
217 | 218 | h3. Matching on class members 219 | 220 | Remember our calculator from earlier. 221 | 222 | Let's classify them according to type. 223 | 224 | Here's the painful way first. 225 | 226 |
227 | def calcType(calc: Calculator) = calc match {
228 |   case _ if calc.brand == "HP" && calc.model == "20B" => "financial"
229 |   case _ if calc.brand == "HP" && calc.model == "48G" => "scientific"
230 |   case _ if calc.brand == "HP" && calc.model == "30B" => "business"
231 |   case _ => "unknown"
232 | }
233 | 
234 | 235 | Wow, that's painful. Thankfully Scala provides some nice tools specifically for this. 236 | 237 | h2(#caseclass). Case Classes 238 | 239 | case classes are used to conveniently store and match on the contents of a class. You can construct them without using new. 240 | 241 |
242 | scala> case class Calculator(brand: String, model: String)
243 | defined class Calculator
244 | 
245 | scala> val hp20b = Calculator("HP", "20b")
246 | hp20b: Calculator = Calculator(hp,20b)
247 | 
248 | 
249 | 250 | case classes automatically have equality and nice toString methods based on the constructor arguments. 251 | 252 |
253 | scala> val hp20b = Calculator("HP", "20b")
254 | hp20b: Calculator = Calculator(hp,20b)
255 | 
256 | scala> val hp20B = Calculator("HP", "20b")
257 | hp20B: Calculator = Calculator(hp,20b)
258 | 
259 | scala> hp20b == hp20B
260 | res6: Boolean = true
261 | 
262 | 263 | case classes can have methods just like normal classes. 264 | 265 | h6. Case Classes with pattern matching 266 | 267 | case classes are designed to be used with pattern matching. Let's simplify our calculator classifier example from earlier. 268 | 269 |
270 | val hp20b = Calculator("HP", "20B")
271 | val hp30b = Calculator("HP", "30B")
272 | 
273 | def calcType(calc: Calculator) = calc match {
274 |   case Calculator("HP", "20B") => "financial"
275 |   case Calculator("HP", "48G") => "scientific"
276 |   case Calculator("HP", "30B") => "business"
277 |   case Calculator(ourBrand, ourModel) => "Calculator: %s %s is of unknown type".format(ourBrand, ourModel)
278 | }
279 | 
280 | 281 | Other alternatives for that last match 282 | 283 |
284 |   case Calculator(_, _) => "Calculator of unknown type"
285 | 
286 | 287 | OR we could simply not specify that it's a Calculator at all. 288 | 289 |
290 |   case _ => "Calculator of unknown type"
291 | 
292 | 293 | OR we could re-bind the matched value with another name 294 | 295 |
296 |   case c@Calculator(_, _) => "Calculator: %s of unknown type".format(c)
297 | 
298 | 299 | h2(#exception). Exceptions 300 | 301 | Exceptions are available in Scala via a try-catch-finally syntax that uses pattern matching. 302 | 303 |
304 | try {
305 |   remoteCalculatorService.add(1, 2)
306 | } catch {
307 |   case e: ServerIsDownException => log.error(e, "the remote calculator service is unavailable. should have kept your trusty HP.")
308 | } finally {
309 |   remoteCalculatorService.close()
310 | }
311 | 
312 | 313 | trys are also expression-oriented 314 | 315 |
316 | val result: Int = try {
317 |   remoteCalculatorService.add(1, 2)
318 | } catch {
319 |   case e: ServerIsDownException => {
320 |     log.error(e, "the remote calculator service is unavailable. should have kept your trusty HP.")
321 |     0
322 |   }
323 | } finally {
324 |   remoteCalculatorService.close()
325 | }
326 | 
327 | 328 | This is not an example of excellent programming style, just an example of try-catch-finally resulting in expressions like most everything else in Scala. 329 | 330 | Finally will be called after an exception has been handled and is not part of the expression. 331 | -------------------------------------------------------------------------------- /web/ru/basics2.textile: -------------------------------------------------------------------------------- 1 | --- 2 | prev: basics.textile 3 | next: collections.textile 4 | title: Основы языка. Продолжение 5 | layout: post 6 | --- 7 | 8 | В этом уроке вы узнаете: 9 | * "Метод apply":#apply 10 | * "Объекты":#object 11 | * "Функции, тоже являются Объектами":#fnobj 12 | * "Пакеты":#package 13 | * "Сопоставление с образцом":#match 14 | * "Case классы":#caseclass 15 | * "try-catch-finally":#exception 16 | 17 | h2(#apply). Метод apply 18 | 19 | Метод apply - это синтаксический сахар, который применяется для класса или объекта с единственной целью. 20 | 21 |
 22 | object FooMaker {
 23 |   def apply() = new Foo
 24 | }
 25 | 
 26 | scala> class Bar {
 27 |      |   def apply() = 0
 28 |      | }
 29 | defined class Bar
 30 | 
 31 | scala> val bar = new Bar
 32 | bar: Bar = Bar@47711479
 33 | 
 34 | scala> bar()
 35 | res8: Int = 0
 36 | 
37 | 38 | Здесь наш экземпляр объекта выглядит так, будто мы просто вызываем метод, но это не так. Подробнее об этом позже! 39 | 40 | h2(#object). Объекты 41 | 42 | Объекты используются для хранения одного экземпляра класса. Чаще всего они используются с фабриками объектов. 43 | 44 |
 45 | object Timer {
 46 |   var count = 0
 47 | 
 48 |   def currentCount(): Long = {
 49 |     count += 1
 50 |     count
 51 |   }
 52 | }
 53 | 
54 | 55 | Как можно это использовать? 56 | 57 |
 58 | scala> Timer.currentCount()
 59 | res0: Long = 1
 60 | 
61 | 62 | Классы и Объекты могут иметь похожие имена. В этом случае Объект называется 'Объект-спутник'(Companion Object). Чаще всего мы будем использовать Объекты-спутники с Фабриками объектов. 63 | 64 | Далее представлен простой пример, который показывает, как можно использовать Объект-спутник, для того чтобы исключить необходимость в использовании ключевого слова 'new' для создания экземпляра объекта. 65 | 66 |
 67 | class Bar(foo: String)
 68 | 
 69 | object Bar {
 70 |   def apply(foo: String) = new Bar(foo)
 71 | }
 72 | 
73 | 74 | 75 | h2(#fnobj). Функции, тоже являются Объектами 76 | 77 | В Scala, мы часто говорим об объектно-функциональном стиле. Что это значит? Чем на самом деле является Функция? 78 | 79 | Функция - это набор трейтов. В частности, функция, которая принимает один аргумент является экземпляром трейта Function1. Этот трейт определяет метод apply(), который является синтаксическим сахаром, о нем мы узнали ранее, он позволяет вам вызывать объект, словно он является функцией. 80 | 81 |
 82 | scala> object addOne extends Function1[Int, Int] {
 83 |      |   def apply(m: Int): Int = m + 1
 84 |      | }
 85 | defined module addOne
 86 | 
 87 | scala> addOne(1)
 88 | res2: Int = 2
 89 | 
90 | 91 | Существует функция с 22 аргументами. Почему с 22? Это произвольное магическое число. Я никогда не нуждался в функции с более чем 22 аргументами. 92 | 93 | Синтаксический сахар метода apply объединяет двойственность объектного и функционального стилей программирования. Вы можете передавать классы и использовать их в качестве функций, кроме этого функции могут быть просто экземплярами классов. 94 | 95 | Означает ли это, что каждый раз, когда вы определяете метод в своем классе, вы фактически получаете экземпляр Function*? Нет, методы в классах - это просто методы. Методы-одиночки, определенные в REPL будут экземплярами Function*. 96 | 97 | Классы могут расширять Function* и в этих случаях они могут быть вызваны при помощи (). 98 | 99 |
100 | scala> class AddOne extends Function1[Int, Int] {
101 |      |   def apply(m: Int): Int = m + 1
102 |      | }
103 | defined class AddOne
104 | 
105 | scala> val plusOne = new AddOne()
106 | plusOne: AddOne = 
107 | 
108 | scala> plusOne(1)
109 | res0: Int = 2
110 | 
111 | 112 | Запись extends Function1[Int, Int] мы можем переписать, используя extends (Int => Int) 113 | 114 |
115 | class AddOne extends (Int => Int) {
116 |   def apply(m: Int): Int = m + 1
117 | }
118 | 
119 | 120 | h2(#package). Пакеты 121 | 122 | Вы можете организовывать ваш код, используя пакеты. 123 | 124 |
125 | package com.twitter.example
126 | 
127 | 128 | В верхней части файла объявляется все, что будет в этом пакете. 129 | 130 | Значения и функции не могут быть объявлены за пределами класса или объекта. Объекты представляют собой полезный инструмент для организации статических функций. 131 | 132 |
133 | package com.twitter.example
134 | 
135 | object colorHolder {
136 |   val BLUE = "Blue"
137 |   val RED = "Red"
138 | }
139 | 
140 | 141 | После этого у вас есть доступ к членам пакета напрямую 142 | 143 |
144 | println("the color is: " + com.twitter.example.colorHolder.BLUE)
145 | 
146 | 147 | Обратите внимание, что Scala REPL говорит вам когда вы объявляете объект: 148 | 149 |
150 | scala> object colorHolder {
151 |      |   val Blue = "Blue"
152 |      |   val Red = "Red"
153 |      | }
154 | defined module colorHolder
155 | 
156 | 157 | Это дает вам небольшую подсказку, которую разработчики Scala используют для проектирования объектов, которые станут частью модульной системы Scala. 158 | 159 | h2(#match). Сопоставление с образцом 160 | 161 | Одна из самых часто используемых возможностей Scala. 162 | 163 | Сопоставление значений 164 | 165 |
166 | val times = 1
167 | 
168 | times match {
169 |   case 1 => "one"
170 |   case 2 => "two"
171 |   case _ => "some other number"
172 | }
173 | 
174 | 175 | Сопоставление с использованием условий 176 | 177 |
178 | times match {
179 |   case i if i == 1 => "one"
180 |   case i if i == 2 => "two"
181 |   case _ => "some other number"
182 | }
183 | 
184 | 185 | Заметьте, как мы пытаемся поймать значение переменной 'i'. 186 | 187 | Используемый знак _ в последнем утверждении - это спецсимвол, он 188 | гарантирует, что мы сможем отловить любое значение. В противном случае вы можете получить 189 | ошибку времени выполнения, если вы попадаете в утверждение, которого не существует. Мы обсудим 190 | это чуть позже. 191 | 192 | *Смотрите также:* В Effective Scala описывается когда использовать сопоставление с образцом и правила форматирования сопоставления с образцом. В "Туре по Scala" также описывается Сопоставление с образцом 193 | 194 | h3. Сопоставление типов 195 | 196 | Вы можете использовать match, чтобы управлять значениями типов различными способами. 197 | 198 |
199 | def bigger(o: Any): Any = {
200 |   o match {
201 |     case i: Int if i < 0 => i - 1
202 |     case i: Int => i + 1
203 |     case d: Double if d < 0.0 => d - 0.1
204 |     case d: Double => d + 0.1
205 |     case text: String => text + "s"
206 |   }
207 | }
208 | 
209 | 210 | h3. Сопоставление методов класса 211 | 212 | Вспомните про наш калькулятор, который мы рассматривали ранее. 213 | 214 | Давайте проведем классификацию по типам. 215 | 216 |
217 | def calcType(calc: Calculator) = calc match {
218 |   case calc.brand == "HP" && calc.model == "20B" => "financial"
219 |   case calc.brand == "HP" && calc.model == "48G" => "scientific"
220 |   case calc.brand == "HP" && calc.model == "30B" => "business"
221 |   case _ => "unknown"
222 | }
223 | 
224 | 225 | Ничего себе, как-то все слишком сложно. К счастью, Scala предоставляет некоторые полезные инструменты специально для этого случая. 226 | 227 | h2(#caseclass). Case Классы 228 | 229 | Case классы используются для удобного хранения и поиска соответствий по содержимому класса. Вы можете создавать их без использования 'new'. 230 | 231 |
232 | scala> case class Calculator(brand: String, model: String)
233 | defined class Calculator
234 | 
235 | scala> val hp20b = Calculator("HP", "20b")
236 | hp20b: Calculator = Calculator(hp,20b)
237 | 
238 | 
239 | 240 | У case классов есть метод ToString, работающий автоматически, и который опирается на аргументы конструктора. 241 | 242 |
243 | scala> val hp20b = Calculator("HP", "20b")
244 | hp20b: Calculator = Calculator(hp,20b)
245 | 
246 | scala> val hp20B = Calculator("HP", "20b")
247 | hp20B: Calculator = Calculator(hp,20b)
248 | 
249 | scala> hp20b == hp20B
250 | res6: Boolean = true
251 | 
252 | 253 | case классы могут иметь методы, как и обычные классы. 254 | 255 | h6. Case Классы и сопоставление с образцом 256 | 257 | case классы предназначены для использования вместе с сопоставлением с образцом. Давайте упростим наш классификатор из примера с калькулятором. 258 | 259 |
260 | val hp20b = Calculator("HP", "20B")
261 | val hp30b = Calculator("HP", "30B")
262 | 
263 | def calcType(calc: Calculator) = calc match {
264 |   case Calculator("HP", "20B") => "financial"
265 |   case Calculator("HP", "48G") => "scientific"
266 |   case Calculator("HP", "30B") => "business"
267 |   case Calculator(ourBrand, ourModel) => "Calculator: %s %s is of unknown type".format(ourBrand, ourModel)
268 | }
269 | 
270 | 271 | А это другой способ для последнего сопоставления 272 | 273 |
274 |   case Calculator(_, _) => "Calculator of unknown type"
275 | 
276 | 277 | мы можем не объявлять, что это Calculator совсем. 278 | 279 |
280 |   case _ => "Calculator of unknown type"
281 | 
282 | 283 | Или мы можем связать найденное значение с другим именем 284 | 285 |
286 |   case c@Calculator(_, _) => "Calculator: %s of unknown type".format(c)
287 | 
288 | 289 | h2(#exception). Исключения 290 | 291 | Исключения доступны в Scala при использовании синтаксиса try-catch-finally, который использует сопоставление с образцом. 292 | 293 |
294 | try {
295 |   remoteCalculatorService.add(1, 2)
296 | } catch {
297 |   case e: ServerIsDownException => log.error(e, "the remote calculator service is unavailble. should have kept your trustry HP.")
298 | } finally {
299 |   remoteCalculatorService.close()
300 | }
301 | 
302 | 303 | try тоже ориентирован на выражения 304 | 305 |
306 | val result: Int = try {
307 |   remoteCalculatorService.add(1, 2)
308 | } catch {
309 |   case e: ServerIsDownException => {
310 |     log.error(e, "the remote calculator service is unavailble. should have kept your trustry HP.")
311 |     0
312 |   }
313 | } finally {
314 |   remoteCalculatorService.close()
315 | }
316 | 
317 | 318 | Этот код не является примером прекрасного стиля программирования, а просто пример того, как try-catch-finally вычисляет выражения, подобно всему остальному в Scala. 319 | 320 | Finally будет вызван после того, как исключение будет обработано. 321 | -------------------------------------------------------------------------------- /web/zh_cn/collections.textile: -------------------------------------------------------------------------------- 1 | --- 2 | prev: basics2.textile 3 | next: pattern-matching-and-functional-composition.textile 4 | title: 集合 5 | layout: post 6 | --- 7 | 8 | 课程内容: 9 | 10 | * 基本数据结构 11 | ** "数组 Array":#Arrays 12 | ** "列表 List":#Lists 13 | ** "集合 Set":#Sets 14 | ** "元组 Tuple":#Tuple 15 | ** "映射 Map":#Maps 16 | ** "选项 Option":#Option 17 | * 函数组合子 18 | ** "map":#map 19 | ** "foreach":#foreach 20 | ** "filter":#filter 21 | ** "zip":#zip 22 | ** "partition":#partition 23 | ** "find":#find 24 | ** "drop and dropWhile":#drop 25 | ** "foldRight and foldLeft":#fold 26 | ** "flatten":#flatten 27 | ** "flatMap":#flatMap 28 | ** "扩展函数组合子":#generalized 29 | ** "Map?":#vsMap 30 | 31 | h1. 基本数据结构 32 | 33 | Scala提供了一些不错的集合。 34 | 35 | *参考* Effective Scala 对怎样使用集合的观点。 36 | 37 | h2(#Arrays). 数组 Array 38 | 39 | 数组是有序的,可以包含重复项,并且可变。 40 | 41 |
 42 | scala> val numbers = Array(1, 2, 3, 4, 5, 1, 2, 3, 4, 5)
 43 | numbers: Array[Int] = Array(1, 2, 3, 4, 5, 1, 2, 3, 4, 5)
 44 | 
 45 | scala> numbers(3) = 10
 46 | 
 47 | scala> numbers
 48 | numbers: Array[Int] = Array(1, 2, 3, 10, 5, 1, 2, 3, 4, 5)
 49 | 
50 | 51 | h2(#Lists). 列表 List 52 | 53 | 列表是有序的,可以包含重复项,不可变。 54 | 55 |
 56 | scala> val numbers = List(1, 2, 3, 4, 5, 1, 2, 3, 4, 5)
 57 | numbers: List[Int] = List(1, 2, 3, 4, 5, 1, 2, 3, 4, 5)
 58 | 
 59 | scala> numbers(3) = 10
 60 | :9: error: value update is not a member of List[Int]
 61 |               numbers(3) = 10
 62 | 
63 | 64 | h2(#Sets). 集合 Set 65 | 66 | 集合无序且不可包含重复项。 67 | 68 |
 69 | scala> val numbers = Set(1, 2, 3, 4, 5, 1, 2, 3, 4, 5)
 70 | numbers: scala.collection.immutable.Set[Int] = Set(5, 1, 2, 3, 4)
 71 | 
72 | 73 | h2(#Tuple). 元组 Tuple 74 | 75 | 元组在不使用类的情况下,将元素组合起来形成简单的逻辑集合。 76 | 77 |
 78 | scala> val hostPort = ("localhost", 80)
 79 | hostPort: (String, Int) = (localhost, 80)
 80 | 
81 | 82 | 与样本类不同,元组不能通过名称获取字段,而是使用位置下标来读取对象;而且这个下标基于1,而不是基于0。 83 | 84 |
 85 | scala> hostPort._1
 86 | res0: String = localhost
 87 | 
 88 | scala> hostPort._2
 89 | res1: Int = 80
 90 | 
91 | 92 | 元组可以很好得与模式匹配相结合。 93 | 94 |
 95 | hostPort match {
 96 |   case ("localhost", port) => ...
 97 |   case (host, port) => ...
 98 | }
 99 | 
100 | 101 | 在创建两个元素的元组时,可以使用特殊语法:-> 102 | 103 |
104 | scala> 1 -> 2
105 | res0: (Int, Int) = (1,2)
106 | 
107 | 108 | *参考* Effective Scala 对 解构绑定 (“拆解”一个元组)的观点。 109 | 110 | h2(#Maps). 映射 Map 111 | 112 | 它可以持有基本数据类型。 113 | 114 |
115 | Map(1 -> 2)
116 | Map("foo" -> "bar")
117 | 
118 | 119 | 这看起来像是特殊的语法,不过不要忘了上文讨论的->可以用来创建二元组。 120 | 121 | Map()方法也使用了从第一节课学到的变参列表:Map(1 -> "one", 2 -> "two")将变为 Map((1, "one"), (2, "two")),其中第一个元素是映射的键,第二个元素是映射的值。 122 | 123 | 映射的值可以是映射甚至是函数。 124 | 125 |
126 | Map(1 -> Map("foo" -> "bar"))
127 | 
128 | 129 |
130 | Map("timesTwo" -> { timesTwo(_) })
131 | 
132 | 133 | h2(#Option). 选项 Option 134 | 135 | Option 是一个表示有可能包含值的容器。 136 | 137 | Option基本的接口是这样的: 138 | 139 |
140 | trait Option[T] {
141 |   def isDefined: Boolean
142 |   def get: T
143 |   def getOrElse(t: T): T
144 | }
145 | 
146 | 147 | Option本身是泛型的,并且有两个子类: Some[T]None 148 | 149 | 我们看一个使用Option的例子: 150 | 151 | Map.get 使用 Option 作为其返回值,表示这个方法也许不会返回你请求的值。 152 | 153 |
154 | scala> val numbers = Map("one" -> 1, "two" -> 2)
155 | numbers: scala.collection.immutable.Map[java.lang.String,Int] = Map(one -> 1, two -> 2)
156 | 
157 | scala> numbers.get("two")
158 | res0: Option[Int] = Some(2)
159 | 
160 | scala> numbers.get("three")
161 | res1: Option[Int] = None
162 | 
163 | 164 | 现在我们的数据似乎陷在Option中了,我们怎样获取这个数据呢? 165 | 166 | 直觉上想到的可能是基于isDefined方法进行条件判断。 167 | 168 |
169 | // We want to multiply the number by two, otherwise return 0.
170 | val result = if (res1.isDefined) {
171 |   res1.get * 2
172 | } else {
173 |   0
174 | }
175 | 
176 | 177 | 我们建议使用getOrElse或模式匹配处理这个结果。 178 | 179 | getOrElse 让你轻松地定义一个默认值。 180 | 181 |
182 | val result = res1.getOrElse(0) * 2
183 | 
184 | 185 | 模式匹配能自然地配合Option使用。 186 | 187 |
188 | val result = res1 match {
189 |   case Some(n) => n * 2
190 |   case None => 0
191 | }
192 | 
193 | 194 | *参考* Effective Scala 对使用Options的意见。 195 | 196 | h1(#combinators). 函数组合子(Functional Combinators) 197 | 198 | List(1, 2, 3) map squared对列表中的每一个元素都应用了squared平方函数,并返回一个新的列表List(1, 4, 9)。我们把类似于map的操作称作组合子。 (如果想要更好的定义,你可以看看Stackoverflow上对组合子的说明。)他们常被用在标准的数据结构上。 199 | 200 | h2(#map). map 201 | 202 | map对列表中的每个元素应用一个函数,返回应用后的元素所组成的列表。 203 | 204 |
205 | scala> val numbers = List(1, 2, 3, 4)
206 | numbers: List[Int] = List(1, 2, 3, 4)
207 | 
208 | scala> numbers.map((i: Int) => i * 2)
209 | res0: List[Int] = List(2, 4, 6, 8)
210 | 
211 | 212 | 或传入一个函数 (Scala编译器自动把我们的方法转换为函数) 213 | 214 |
215 | scala> def timesTwo(i: Int): Int = i * 2
216 | timesTwo: (i: Int)Int
217 | 
218 | scala> numbers.map(timesTwo)
219 | res0: List[Int] = List(2, 4, 6, 8)
220 | 
221 | 222 | h2(#foreach). foreach 223 | 224 | foreach很像map,但没有返回值。foreach仅用于有副作用[side-effects]的函数。 225 | 226 |
227 | scala> numbers.foreach((i: Int) => i * 2)
228 | 
229 | 230 | 什么也没有返回。 231 | 232 | 你可以尝试存储返回值,但它会是Unit类型(即void) 233 | 234 |
235 | scala> val doubled = numbers.foreach((i: Int) => i * 2)
236 | doubled: Unit = ()
237 | 
238 | 239 | h2(#filter). filter 240 | 241 | filter移除任何对传入函数计算结果为false的元素。返回一个布尔值的函数通常被称为谓词函数[或判定函数]。 242 | 243 |
244 | scala> numbers.filter((i: Int) => i % 2 == 0)
245 | res0: List[Int] = List(2, 4)
246 | 
247 | 248 |
249 | scala> def isEven(i: Int): Boolean = i % 2 == 0
250 | isEven: (i: Int)Boolean
251 | 
252 | scala> numbers.filter(isEven)
253 | res2: List[Int] = List(2, 4)
254 | 
255 | 256 | h2(#zip). zip 257 | 258 | zip将两个列表的内容聚合到一个对偶列表中。 259 | 260 |
261 | scala> List(1, 2, 3).zip(List("a", "b", "c"))
262 | res0: List[(Int, String)] = List((1,a), (2,b), (3,c))
263 | 
264 | 265 | h2(#partition). partition 266 | 267 | partition将使用给定的谓词函数分割列表。 268 | 269 |
270 | scala> val numbers = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
271 | scala> numbers.partition(_ % 2 == 0)
272 | res0: (List[Int], List[Int]) = (List(2, 4, 6, 8, 10),List(1, 3, 5, 7, 9))
273 | 
274 | 275 | h2(#find). find 276 | 277 | find返回集合中第一个匹配谓词函数的元素。 278 | 279 |
280 | scala> numbers.find((i: Int) => i > 5)
281 | res0: Option[Int] = Some(6)
282 | 
283 | 284 | h2(#drop). drop & dropWhile 285 | 286 | drop 将删除前i个元素 287 | 288 |
289 | scala> numbers.drop(5)
290 | res0: List[Int] = List(6, 7, 8, 9, 10)
291 | 
292 | 293 | dropWhile 将删除匹配谓词函数的第一个元素。例如,如果我们在numbers列表上使用dropWhile函数来去除奇数, 1将被丢弃(但3不会被丢弃,因为他被2“保护”了)。 294 | 295 |
296 | scala> numbers.dropWhile(_ % 2 != 0)
297 | res0: List[Int] = List(2, 3, 4, 5, 6, 7, 8, 9, 10)
298 | 
299 | 300 | h2(#fold). foldLeft 301 | 302 |
303 | scala> numbers.foldLeft(0)((m: Int, n: Int) => m + n)
304 | res0: Int = 55
305 | 
306 | 307 | 0为初始值(记住numbers是List[Int]类型),m作为一个累加器。 308 | 309 | 可视化观察运行过程: 310 | 311 |
312 | scala> numbers.foldLeft(0) { (m: Int, n: Int) => println("m: " + m + " n: " + n); m + n }
313 | m: 0 n: 1
314 | m: 1 n: 2
315 | m: 3 n: 3
316 | m: 6 n: 4
317 | m: 10 n: 5
318 | m: 15 n: 6
319 | m: 21 n: 7
320 | m: 28 n: 8
321 | m: 36 n: 9
322 | m: 45 n: 10
323 | res0: Int = 55
324 | 
325 | 326 | h3. foldRight 327 | 328 | 和foldLeft一样,只是运行过程相反。 329 | 330 |
331 | scala> numbers.foldRight(0) { (m: Int, n: Int) => println("m: " + m + " n: " + n); m + n }
332 | m: 10 n: 0
333 | m: 9 n: 10
334 | m: 8 n: 19
335 | m: 7 n: 27
336 | m: 6 n: 34
337 | m: 5 n: 40
338 | m: 4 n: 45
339 | m: 3 n: 49
340 | m: 2 n: 52
341 | m: 1 n: 54
342 | res0: Int = 55
343 | 
344 | 345 | h2(#flatten). flatten 346 | 347 | flatten将嵌套结构扁平化一个层级。 348 | 349 |
350 | scala> List(List(1, 2), List(3, 4)).flatten
351 | res0: List[Int] = List(1, 2, 3, 4)
352 | 
353 | 354 | h2(#flatMap). flatMap 355 | 356 | flatMap是一种常用的组合子,结合映射[mapping]和扁平化[flattening]。 flatMap需要一个处理嵌套列表的函数,然后将结果串连起来。 357 | 358 |
359 | scala> val nestedNumbers = List(List(1, 2), List(3, 4))
360 | nestedNumbers: List[List[Int]] = List(List(1, 2), List(3, 4))
361 | 
362 | scala> nestedNumbers.flatMap(x => x.map(_ * 2))
363 | res0: List[Int] = List(2, 4, 6, 8)
364 | 
365 | 366 | 可以把它看做是“先映射后扁平化”的快捷操作: 367 | 368 |
369 | scala> nestedNumbers.map((x: List[Int]) => x.map(_ * 2)).flatten
370 | res1: List[Int] = List(2, 4, 6, 8)
371 | 
372 | 373 | 这个例子先调用map,然后调用flatten,这就是“组合子”的特征,也是这些函数的本质。 374 | 375 | *参考* Effective Scala 对flatMap的意见。 376 | 377 | h2(#generalized). 扩展函数组合子 378 | 379 | 现在我们已经学过集合上的一些函数。 380 | 381 | 我们将尝试写自己的函数组合子。 382 | 383 | 有趣的是,上面所展示的每一个函数组合子都可以用fold方法实现。让我们看一些例子。 384 | 385 |
386 | def ourMap(numbers: List[Int], fn: Int => Int): List[Int] = {
387 |   numbers.foldRight(List[Int]()) { (x: Int, xs: List[Int]) =>
388 |     fn(x) :: xs
389 |   }
390 | }
391 | 
392 | scala> ourMap(numbers, timesTwo(_))
393 | res0: List[Int] = List(2, 4, 6, 8, 10, 12, 14, 16, 18, 20)
394 | 
395 | 396 | 为什么是List[Int]()?Scala没有聪明到理解你的目的是将结果积聚在一个空的Int类型的列表中。 397 | 398 | h2(#vsMap). Map? 399 | 400 | 所有展示的函数组合子都可以在Map上使用。Map可以被看作是一个二元组的列表,所以你写的函数要处理一个键和值的二元组。 401 | 402 |
403 | scala> val extensions = Map("steve" -> 100, "bob" -> 101, "joe" -> 201)
404 | extensions: scala.collection.immutable.Map[String,Int] = Map((steve,100), (bob,101), (joe,201))
405 | 
406 | 407 | 现在筛选出电话分机号码低于200的条目。 408 | 409 |
410 | scala> extensions.filter((namePhone: (String, Int)) => namePhone._2 < 200)
411 | res0: scala.collection.immutable.Map[String,Int] = Map((steve,100), (bob,101))
412 | 
413 | 414 | 因为参数是元组,所以你必须使用位置获取器来读取它们的键和值。呃! 415 | 416 | 幸运的是,我们其实可以使用模式匹配更优雅地提取键和值。 417 | 418 |
419 | scala> extensions.filter({case (name, extension) => extension < 200})
420 | res0: scala.collection.immutable.Map[String,Int] = Map((steve,100), (bob,101))
421 | 
422 | 423 | 为什么这个代码可以工作?为什么你可以传递一个部分模式匹配? 424 | 425 | 敬请关注下周的内容! 426 | --------------------------------------------------------------------------------