├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── banner.jpg
├── build.sbt
├── core
└── src
│ └── main
│ ├── scala-akka-2.3.x
│ └── akka
│ │ └── typedactors
│ │ └── PromiseRef.scala
│ ├── scala-akka-2.4.x
│ └── akka
│ │ └── typedactors
│ │ └── PromiseRef.scala
│ └── scala
│ ├── akka
│ └── typedactors
│ │ └── AskSupport.scala
│ └── de
│ └── knutwalker
│ └── akka
│ └── typed
│ ├── PropsBuilder.scala
│ ├── TypedActor.scala
│ └── package.scala
├── creator
└── src
│ └── main
│ └── scala
│ └── de
│ └── knutwalker
│ └── akka
│ └── typed
│ └── Typed.scala
├── docs
└── src
│ ├── site
│ ├── .gitignore
│ ├── _config.yml
│ ├── _data
│ │ └── more_vert.yml
│ ├── _includes
│ │ ├── foot.html
│ │ ├── head.html
│ │ └── header.html
│ ├── _layouts
│ │ ├── index.html
│ │ ├── page.html
│ │ └── skel.html
│ ├── _sass
│ │ ├── _base.scss
│ │ ├── _layout.scss
│ │ └── _post.scss
│ ├── css
│ │ ├── main.scss
│ │ └── prism.css
│ ├── index.md
│ ├── js
│ │ ├── main.js
│ │ └── prism.js
│ ├── scaladoc.md
│ └── usage.md
│ ├── test
│ └── scala
│ │ └── akka
│ │ ├── LoggingReceive.scala
│ │ └── PrintLogger.scala
│ └── tut
│ ├── comparison.md
│ ├── creator.md
│ ├── implementation.md
│ ├── index.md
│ ├── motivation.md
│ ├── props.md
│ ├── typed-actor.md
│ ├── union.md
│ └── unsafe.md
├── examples
└── src
│ └── test
│ ├── resources
│ └── application.conf
│ └── scala
│ └── org
│ └── example
│ ├── PersistenceExample.scala
│ ├── SimpleExample.scala
│ ├── TypedActorExample.scala
│ ├── TypedCreatorExample.scala
│ ├── UnionExample.scala
│ └── UnionExample2.scala
├── project
├── Build.scala
├── build.properties
└── plugins.sbt
├── tests
└── src
│ └── test
│ ├── scala-akka-2.3.x
│ └── de
│ │ └── knutwalker
│ │ └── akka
│ │ └── typed
│ │ └── Shutdown.scala
│ ├── scala-akka-2.4.x
│ └── de
│ │ └── knutwalker
│ │ └── akka
│ │ └── typed
│ │ └── Shutdown.scala
│ └── scala
│ └── de
│ └── knutwalker
│ └── akka
│ └── typed
│ ├── CreateInbox.scala
│ ├── TypedActorSpec.scala
│ ├── TypedSpec.scala
│ ├── UnionSpec.scala
│ └── UnionTypeSpec.scala
└── version.sbt
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.gitignore.io
2 |
3 | ### OSX ###
4 | .DS_Store
5 | .AppleDouble
6 | .LSOverride
7 |
8 | # Icon must end with two \r
9 | Icon
10 |
11 | # Thumbnails
12 | ._*
13 |
14 | # Files that might appear on external disk
15 | .Spotlight-V100
16 | .Trashes
17 |
18 | # Directories potentially created on remote AFP share
19 | .AppleDB
20 | .AppleDesktop
21 | Network Trash Folder
22 | Temporary Items
23 | .apdisk
24 |
25 |
26 | ### Intellij ###
27 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm
28 |
29 | *.iml
30 |
31 | ## Directory-based project format:
32 | .idea/
33 | # if you remove the above rule, at least ignore the following:
34 |
35 | # User-specific stuff:
36 | # .idea/workspace.xml
37 | # .idea/tasks.xml
38 | # .idea/dictionaries
39 |
40 | # Sensitive or high-churn files:
41 | # .idea/dataSources.ids
42 | # .idea/dataSources.xml
43 | # .idea/sqlDataSources.xml
44 | # .idea/dynamic.xml
45 | # .idea/uiDesigner.xml
46 |
47 | # Gradle:
48 | # .idea/gradle.xml
49 | # .idea/libraries
50 |
51 | # Mongo Explorer plugin:
52 | # .idea/mongoSettings.xml
53 |
54 | ## File-based project format:
55 | *.ipr
56 | *.iws
57 |
58 | ## Plugin-specific files:
59 |
60 | # IntelliJ
61 | out/
62 |
63 | # mpeltonen/sbt-idea plugin
64 | .idea_modules/
65 |
66 | # JIRA plugin
67 | atlassian-ide-plugin.xml
68 |
69 | # Crashlytics plugin (for Android Studio and IntelliJ)
70 | com_crashlytics_export_strings.xml
71 | crashlytics.properties
72 | crashlytics-build.properties
73 |
74 |
75 | ### SublimeText ###
76 | # cache files for sublime text
77 | *.tmlanguage.cache
78 | *.tmPreferences.cache
79 | *.stTheme.cache
80 |
81 | # workspace files are user-specific
82 | *.sublime-workspace
83 |
84 | # project files should be checked into the repository, unless a significant
85 | # proportion of contributors will probably not be using SublimeText
86 | # *.sublime-project
87 |
88 | # sftp configuration file
89 | sftp-config.json
90 |
91 |
92 | ### Java ###
93 | *.class
94 |
95 | # Mobile Tools for Java (J2ME)
96 | .mtj.tmp/
97 |
98 | # Package Files #
99 | *.jar
100 | *.war
101 | *.ear
102 |
103 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
104 | hs_err_pid*
105 |
106 |
107 | ### Scala ###
108 | *.class
109 | *.log
110 |
111 | # sbt specific
112 | .cache
113 | .history
114 | .lib/
115 | dist/*
116 | target/
117 | lib_managed/
118 | src_managed/
119 | project/boot/
120 | project/plugins/project/
121 |
122 | # Scala-IDE specific
123 | .scala_dependencies
124 | .worksheet
125 |
126 |
127 | ### SBT ###
128 | # Simple Build Tool
129 | # http://www.scala-sbt.org/release/docs/Getting-Started/Directories.html#configuring-version-control
130 |
131 | target/
132 | lib_managed/
133 | src_managed/
134 | project/boot/
135 | .history
136 | .cache
137 |
138 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: scala
2 | sudo: false
3 |
4 | jdk:
5 | - oraclejdk8
6 | - oraclejdk7
7 |
8 | scala:
9 | - 2.11.7
10 |
11 | cache:
12 | directories:
13 | - $HOME/.m2/repository
14 | - $HOME/.ivy2/cache
15 | - $HOME/.sbt/boot/
16 | - $HOME/.sbt/launchers/
17 |
18 | script:
19 | - sbt ++$TRAVIS_SCALA_VERSION travis
20 | - find $HOME/.sbt -name "*.lock" | xargs rm
21 | - find $HOME/.ivy2 -name "ivydata-*.properties" | xargs rm
22 |
23 | notifications:
24 | webhooks:
25 | urls:
26 | - https://webhooks.gitter.im/e/860acd787c2ef2f7ff06
27 | on_success: change
28 | on_failure: always
29 | on_start: never
30 |
31 | before_install:
32 | - pip install --user codecov
33 |
34 | after_success:
35 | - codecov
36 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | ## Versioning
4 |
5 | This project tries to follows [Semantic Versioning](http://semver.org/),
6 | adopted for Scala/ABI changes. Concretely, that means:
7 |
8 | - a MAJOR increase breaks source compability
9 | - it introduces, removes and modifies public API
10 | - a MINOR increase breaks binary compability
11 | - it may add new features to the public API
12 | - it may deprecate but never remove parts of the public API
13 | - it may, without warning or deprecation phase, remove and alter private API
14 | - a PATCH increase is binary and source compatible with regards to its accompanying MINOR version
15 | - it may fix bugs, optimise performance
16 | - it may add new features
17 |
18 | In all fairness, claims to binary compability are not guaranteed but rather educated
19 | guesses based on [mima](https://github.com/typesafehub/migration-manager) reports.
20 |
21 |
22 |
23 | ## [Unreleased][unreleased]
24 |
25 |
26 |
27 | ## [1.6.0][1.6.0] - 2016-01-16
28 | **This release is source compatible with the previous release, but not binary compatible.**
29 |
30 | ### Added
31 | - `unionBecome` to change behavior when the actor is of a union type
32 | - `apply` on union receive builders can be inferred
33 |
34 | ### Fixed
35 | - Provers for union type membership were unnecessarily left-biased, see #8
36 |
37 |
38 | ## [1.5.1][1.5.1] - 2015-11-05
39 |
40 | ### Added
41 | - `only` method to union typed actors to gain a view into a specific subcase of the union
42 |
43 |
44 | ## [1.5.0][1.5.0] - 2015-11-04
45 | **This release is source compatible with the previous release, but not binary compatible.**
46 |
47 | ### Fixed
48 | - the dependeny on `akka-actor` is set to the `provided` scope again
49 |
50 | ### Changed
51 | - `TypedActor.Of` no longer requires an implicit classTag to be available.
52 |
53 | ### Added
54 | - Phantom Union types for Typed Actors to support mulitple unrelated messages
55 |
56 |
57 | ## [1.4.0][1.4.0] - 2015-10-16
58 | **This release is source compatible with the previous release, but not binary compatible.**
59 |
60 | ### Added
61 | - New modules for Akka 2.4
62 |
63 | ### Changed
64 | - `TypedActor` can now be extended directly and used as a trait
65 |
66 |
67 | ## [1.3.1][1.3.1] - 2015-10-01
68 | ### Fixed
69 | - Lubbing on `forward` could lead to unchecked messages being send
70 |
71 |
72 | ## [1.3.0][1.3.0] - 2015-09-29
73 | ### Added
74 | - `TypedActor.apply` to quickly create an actor from a total function
75 | - Ask support for typed actors
76 |
77 |
78 | ## [1.2.0][1.2.0] - 2015-09-19
79 | ### Added
80 | - typed `Props` gets all the pretty methods
81 | - `Total` wrapper for usage with `typedBecome` or `typedReceive`
82 | - `Untyped` wrapper for defining a `typedReceive` that can accept messages outside of the required type
83 | - `PropsFor` constructors that can infer the message type from the given `TypedActor`
84 | - `PropsOf` constructors that are type curried and can better infer the message type
85 | - `untyped` and `typed` converters on typed and untyped actors, resp.
86 | - lots of documentation
87 |
88 | ### Changed
89 | - Rename `receiveMsg` to `typedBecome` and deprecate the former
90 |
91 | ### Removed
92 | - `typedBecomeFull` in favor of `typedBecome` and `Total`
93 | - Requirement of `TypedActor` to be a `case class`
94 |
95 | ### Fixed
96 | - Sending a wrong message type the the untyped cast of an typed actor now results in an unhandled message instead of an error
97 |
98 |
99 | ## [1.1.0][1.1.0] - 2015-08-30
100 | ### Added
101 | - `typedBecomeFull` on `TypedActor` for become with total functions
102 |
103 | ### Changed
104 | - `typedBecome` and `receiveMsg` prefer partial functions over total ones
105 |
106 |
107 | ## [1.0.1][1.0.1] - 2015-08-30
108 | ### Added
109 | - `typedBecome` on `TypedActor`
110 |
111 | ### Changed
112 | - `TypedActor` wraps its `typedReceive` in a `LoggingReceive`
113 |
114 | ### Removed
115 | - Support for Scala 2.10
116 |
117 |
118 | ## [1.0.0][1.0.0] - 2015-08-30
119 | ### Added
120 | - Initial release, basic typed actors
121 |
122 |
123 | [unreleased]: https://github.com/knutwalker/typed-actors/compare/v1.6.0...develop
124 | [1.6.0]: https://github.com/knutwalker/typed-actors/compare/v1.5.1...v1.6.0
125 | [1.5.1]: https://github.com/knutwalker/typed-actors/compare/v1.5.0...v1.5.1
126 | [1.5.0]: https://github.com/knutwalker/typed-actors/compare/v1.4.0...v1.5.0
127 | [1.4.0]: https://github.com/knutwalker/typed-actors/compare/v1.3.1...v1.4.0
128 | [1.3.1]: https://github.com/knutwalker/typed-actors/compare/v1.3.0...v1.3.1
129 | [1.3.0]: https://github.com/knutwalker/typed-actors/compare/v1.2.0...v1.3.0
130 | [1.2.0]: https://github.com/knutwalker/typed-actors/compare/v1.1.0...v1.2.0
131 | [1.1.0]: https://github.com/knutwalker/typed-actors/compare/v1.0.1...v1.1.0
132 | [1.0.1]: https://github.com/knutwalker/typed-actors/compare/v1.0.0...v1.0.1
133 | [1.0.0]: https://github.com/knutwalker/typed-actors/compare/9cae71d329e808479e50cd6c10cd1ca4aca2343f...v1.0.0
134 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
--------------------------------------------------------------------------------
/banner.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/knutwalker/typed-actors/65bd6ed11e9e9d2160006d32dbca90e55a167775/banner.jpg
--------------------------------------------------------------------------------
/build.sbt:
--------------------------------------------------------------------------------
1 | import Build.autoImport._ // screw you, IntelliJ
2 |
3 | lazy val core = project settings (name := "typed-actors")
4 |
5 | lazy val creator = project dependsOn core settings (
6 | libraryDependencies += "com.chuusai" %% "shapeless" % "2.2.5")
7 |
8 | lazy val tests = project dependsOn (core, creator) settings (
9 | dontRelease,
10 | libraryDependencies ++= List(
11 | "org.specs2" %% "specs2-core" % "3.6.5" % "test",
12 | "org.specs2" %% "specs2-matcher-extra" % "3.6.5" % "test"))
13 |
14 | lazy val examples = project dependsOn (core, creator, tests % "test->test") settings (
15 | dontRelease,
16 | libraryDependencies += akkaPersistence(akkaActorVersion.value)
17 | )
18 |
19 | lazy val docs = project dependsOn (core, creator) settings (
20 | tutsSettings(core, creator),
21 | libraryDependencies += akkaPersistence(akkaActorVersion.value))
22 |
23 | lazy val parent = project in file(".") dependsOn (core, creator) aggregate (core, creator, tests, examples) settings parentSettings()
24 |
25 | addCommandAlias("travis", ";clean;coverage;testOnly -- timefactor 3;coverageReport;coverageAggregate;docs/makeSite")
26 |
--------------------------------------------------------------------------------
/core/src/main/scala-akka-2.3.x/akka/typedactors/PromiseRef.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 – 2016 Paul Horn
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package akka.typedactors
18 |
19 | import akka.actor.InternalActorRef
20 | import akka.pattern.PromiseActorRef
21 | import akka.util.Timeout
22 | import de.knutwalker.akka.typed.UntypedActorRef
23 |
24 | import scala.reflect.ClassTag
25 |
26 | private[typedactors] object PromiseRef {
27 | def apply[A](ref: InternalActorRef, target: UntypedActorRef, sender: UntypedActorRef, timeout: Timeout, ctA: ClassTag[A]): PromiseActorRef = {
28 | PromiseActorRef(ref.provider, timeout, targetName = target.toString())
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/core/src/main/scala-akka-2.4.x/akka/typedactors/PromiseRef.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 – 2016 Paul Horn
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package akka.typedactors
18 |
19 | import akka.actor.InternalActorRef
20 | import akka.pattern.PromiseActorRef
21 | import akka.util.Timeout
22 | import de.knutwalker.akka.typed.UntypedActorRef
23 |
24 | import scala.reflect.ClassTag
25 |
26 | private[typedactors] object PromiseRef {
27 | def apply[A](ref: InternalActorRef, target: UntypedActorRef, sender: UntypedActorRef, timeout: Timeout, ctA: ClassTag[A]): PromiseActorRef = {
28 | PromiseActorRef(ref.provider, timeout, target, ctA.runtimeClass.getName, sender)
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/core/src/main/scala/akka/typedactors/AskSupport.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 – 2016 Paul Horn
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package akka.typedactors
18 |
19 | import akka.actor.InternalActorRef
20 | import akka.pattern.AskTimeoutException
21 | import akka.util.Timeout
22 | import de.knutwalker.akka.typed._
23 |
24 | import scala.concurrent.Future
25 | import scala.reflect.ClassTag
26 |
27 |
28 | /**
29 | * Support the ask pattern.
30 | * This must live in the `akka.typedactors` package since it requires usage
31 | * of `private[akka]` methods/classes.
32 | *
33 | * This is considered a PRIVATE API.
34 | * @see [[de.knutwalker.akka.typed.ActorRefOps.?]]
35 | */
36 | object AskSupport {
37 | def ask[A, B](ref: ActorRef[A], f: ActorRef[B] ⇒ A, timeout: Timeout, ctA: ClassTag[A], sender: UntypedActorRef): Future[B] =
38 | internalAsk[A, B](ref.untyped, timeout, f.asInstanceOf[UntypedActorRef ⇒ Any], sender, ctA)
39 |
40 | private def internalAsk[A, B](_ref: UntypedActorRef, timeout: Timeout, f: UntypedActorRef ⇒ Any, sender: UntypedActorRef, ctA: ClassTag[A]): Future[B] = _ref match {
41 | case r: InternalActorRef if r.isTerminated ⇒
42 | val msg = f(r.provider.deadLetters)
43 | _ref.tell(msg, sender)
44 | Future.failed[B](new AskTimeoutException(s"Recipient[${_ref}] had already been terminated. Sender[$sender] sent the message of type '${msg.getClass.getName}'."))
45 | case r: InternalActorRef ⇒
46 | if (timeout.duration.length <= 0) {
47 | Future.failed[B](new IllegalArgumentException(s"Timeout length must not be negative, question not sent to [${_ref}]. Sender[$sender] sent the message of type '${ctA.runtimeClass.getName}'."))
48 | } else {
49 | val ref = PromiseRef(r, _ref, sender, timeout, ctA)
50 | val msg = f(ref)
51 | _ref.tell(msg, ref)
52 | ref.result.future.asInstanceOf[Future[B]]
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/core/src/main/scala/de/knutwalker/akka/typed/PropsBuilder.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 – 2016 Paul Horn
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package de.knutwalker.akka.typed
18 |
19 | import _root_.akka.actor.Actor
20 |
21 | import scala.reflect.ClassTag
22 |
23 | /**
24 | * Type-curried creation of `Props[A]` to aid the type inference.
25 | *
26 | * @see [[de.knutwalker.akka.typed.PropsOf]]
27 | * @param ignore dummy parameter as requirement for AnyVal.
28 | * Option[Nothing] has one inhabitant, None.
29 | * Unit is not possible, since it is a AnyVal type itself.
30 | * @tparam A the message type this actor is receiving
31 | */
32 | final class PropsBuilder[A](val ignore: Option[Nothing]) extends AnyVal {
33 |
34 | /**
35 | * Creates a new typed Props that uses the default constructor of the given
36 | * actor type to create new instances of this actor.
37 | *
38 | * Wrapper for `akka.actor.Props[T]`.
39 | *
40 | * @tparam T the actor type
41 | * @return a typed Props to create `ActorRef[A]`s for this actor
42 | */
43 | def apply[T <: Actor : ClassTag]: Props[A] =
44 | Props[A, T]
45 |
46 | /**
47 | * Creates a new typed Props that uses the given creator function to create
48 | * instances of this actor.
49 | *
50 | * CAVEAT: Required mailbox type cannot be detected when using anonymous
51 | * mixin composition when creating the instance. For example, the following
52 | * will not detect the need for `DequeBasedMessageQueueSemantics` as defined
53 | * in `Stash`:
54 | *
55 | * {{{
56 | * 'Props(new Actor with Stash { ... })
57 | * }}}
58 | *
59 | * Instead you must create a named class that mixin the trait, e.g.
60 | * `class MyActor extends Actor with Stash`.
61 | *
62 | * Wrapper for `akka.actor.Props[T](=> T)`.
63 | *
64 | * @param creator the thunk that create the new instance of this actor
65 | * @tparam T the actor type
66 | * @return a typed Props to create `ActorRef[A]`s for this actor
67 | */
68 | def apply[T <: Actor : ClassTag](creator: ⇒ T): Props[A] =
69 | Props[A, T](creator)
70 |
71 | /**
72 | * Creates a new typed Props that uses the given class and constructor
73 | * arguments to create instances of this actor.
74 | *
75 | * Wrapper for `akka.actor.Props[T](Class[T], Any*)`.
76 | *
77 | * @param clazz the class of this actor
78 | * @param args the constructor argumentes of this actor
79 | * @tparam T the actor type
80 | * @return a typed Props to create `ActorRef[A]`s for this actor
81 | */
82 | def apply[T <: Actor](clazz: Class[T], args: Any*): Props[A] =
83 | Props[A, T](clazz, args: _*)
84 | }
85 |
--------------------------------------------------------------------------------
/core/src/main/scala/de/knutwalker/akka/typed/TypedActor.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 – 2016 Paul Horn
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package de.knutwalker.akka.typed
18 |
19 | import _root_.akka.actor.Actor
20 | import _root_.akka.event.LoggingReceive
21 | import akka.actor.Actor.Receive
22 | import de.knutwalker.akka.typed.TypedActor.{ TypedBecomeOnAux, MkTotalUnionReceiveEmpty, MkPartialUnionReceive, Downcast, TypedReceiver }
23 |
24 | import scala.reflect.ClassTag
25 |
26 | /**
27 | * TypedActor base trait that should be extended by to create a typed Actor.
28 | * This actor is designed to only receive one type of message in its lifetime.
29 | * Typically, this is some ADT/sealed trait that defines the protocol for this actor.
30 | *
31 | * The message type is defined by extending [[TypedActor.Of]]:
32 | *
33 | * {{{
34 | * class ExampleActor extends TypeActor.Of[ExampleProtocol] {
35 | * // ...
36 | * }
37 | * }}}
38 | *
39 | * The `TypedActor` is just a regular actor, but if offers some methods that help
40 | * you stay within the defined typed contract.
41 | * [[TypedActor#typedSelf]] is the alternative to [[akka.actor.Actor.self]]
42 | * to get the typed ActorRef for this actor.
43 | * [[TypedActor#typedBecome]] is the alternative to `context.become` to check
44 | * the receiving type while changing behavior.
45 | * You can get exhausitveness checking for your receive block if you use
46 | * [[TypedActor#Total]] as a wrapper around your receive block.
47 | *
48 | * {{{
49 | * // error: match may not be exhaustive. It would fail on the following inputs: None
50 | * class ExampleActor extends TypedActor {
51 | * type Message = Option[String]
52 | * def typedReceive: TypedReceive = Total {
53 | * case Some("foo") ⇒
54 | * }
55 | * }
56 | * }}}
57 | *
58 | * If you must go back to untyped land, use the [[TypedActor#Untyped]] wrapper.
59 | *
60 | * @see [[akka.actor.Actor]] for more about Actors in general.
61 | */
62 | trait TypedActor extends Actor {
63 | type Message
64 | final type TypedReceive = PartialFunction[Message, Unit]
65 |
66 | /** Typed variant of [[self]]. */
67 | final val typedSelf: ActorRef[Message] =
68 | tag(self)
69 |
70 | /** Typed variant of `context.become`. */
71 | final def typedBecome(f: TypedReceive): Unit =
72 | context become untypedFromTyped(f)
73 |
74 | /** `context.become` for a given subtype if this actor is of a union type. */
75 | final def unionBecome(implicit ev: IsUnion[Message]): TypedBecomeOnAux[ev.Out] =
76 | new TypedBecomeOnAux[ev.Out](this)
77 |
78 | /**
79 | * Wraps a total receiver function and returns it as a [[TypedReceive]].
80 | * Use this to get exhaustiveness checking for your receive block.
81 | *
82 | * {{{
83 | * // error: match may not be exhaustive. It would fail on the following inputs: None
84 | * class ExampleActor extends TypedActor.Of[Option[String]] {
85 | * def typedReceive: TypedReceive = Total {
86 | * case Some("foo") ⇒
87 | * }
88 | * }
89 | * }}}
90 | */
91 | final def Total(f: Message ⇒ Unit)(implicit ct: ClassTag[Message]): TypedReceive =
92 | new Downcast[Message](this, ct.runtimeClass.asInstanceOf[Class[Message]])(f)
93 |
94 | /**
95 | * Wraps an untyped receiver and returns it as a [[TypedReceive]].
96 | * Use this to match for messages that are outside of your protocol, e.g. [[akka.actor.Terminated]].
97 | *
98 | * {{{
99 | * class ExampleActor extends TypedActor.Of[ExampleMessage] {
100 | * def typedReceive: TypedReceive = Untyped {
101 | * case Terminated(ref) => println(s"$$ref terminated")
102 | * }
103 | * }
104 | * }}}
105 | */
106 | final def Untyped(f: Receive): TypedReceive =
107 | f // .asInstanceOf[TypedReceive]
108 |
109 | /**
110 | * Builds final receive out of sub-receives if this TypedActor is for a Union message type.
111 | * This mirrors a [[TypedReceive]], i.e. you must not cover all cases.
112 | *
113 | * {{{
114 | * class ExampleActor extends TypedActor.Of[Foo | Bar | Baz] {
115 | * def typedReceive: TypedReceive = Union
116 | * .on[Foo]{ case Foo() => println("foo") }
117 | * .on[Bar]{ case Bar() => println("bar") }
118 | * .apply
119 | * }
120 | * }
121 | * }}}
122 | */
123 | final def Union(implicit ev: IsUnion[Message]): MkPartialUnionReceive[ev.Out, MkPartialUnionReceive.Empty] =
124 | new MkPartialUnionReceive(this, None)
125 |
126 | /**
127 | * Builds final receive out of sub-receives if this TypedActor is for a Union message type.
128 | * This mirrors a [[Total]] receive, i.e. you must provide all cases.
129 | *
130 | * {{{
131 | * class ExampleActor extends TypedActor.Of[Foo | Bar | Baz] {
132 | * def typedReceive: TypedReceive = TotalUnion
133 | * .on[Foo]{ case Foo() => println("foo") }
134 | * .on[Bar]{ case Bar() => println("bar") }
135 | * .on[Bax]{ case Bax() => println("bax") }
136 | * .apply
137 | * }
138 | * }
139 | * }}}
140 | */
141 | final def TotalUnion(implicit ev: IsUnion[Message]): MkTotalUnionReceiveEmpty[ev.Out] =
142 | new MkTotalUnionReceiveEmpty(None)
143 |
144 | /**
145 | * `TypedActor`s delegate to [[typedReceive]].
146 | * @see [[akka.actor.Actor#receive]]
147 | */
148 | def receive: Receive =
149 | untypedFromTyped(typedReceive)
150 |
151 | /**
152 | * Defines the actors behavior. Unlike [[akka.actor.Actor#receive]], this one
153 | * is typed in its first parameter.
154 | */
155 | def typedReceive: TypedReceive
156 |
157 | /**
158 | * Wraps a typed receive and returns it as an untyped receive.
159 | * Use this only if you have to mix with other traits that override receive,
160 | * where you need to repeat the implementation of this typed actors default receive method.
161 | */
162 | final protected def untypedFromTyped(f: TypedReceive): Receive =
163 | LoggingReceive(new TypedReceiver(f))
164 | }
165 | object TypedActor {
166 | /**
167 | * Abstract class to extend from in order to get a [[TypedActor]].
168 | * If you want to have the message type provided as a type parameter,
169 | * you have to add a context bound for [[scala.reflect.ClassTag]].
170 | *
171 | * {{{
172 | * class ExampleActor extends TypeActor.Of[ExampleProtocol] {
173 | * // ...
174 | * }
175 | * }}}
176 | *
177 | * @tparam A the message type this actor is receiving
178 | */
179 | abstract class Of[A] extends TypedActor {
180 | final type Message = A
181 | }
182 |
183 | /**
184 | * Creates a new typed actor from a total function, forfeiting the
185 | * functionality of changing behavior.
186 | *
187 | * @param f the actors behavior
188 | * @tparam A the message type
189 | */
190 | def apply[A: ClassTag](f: A ⇒ Unit): Props[A] =
191 | PropsFor(new TypedActor.Of[A] {def typedReceive = Total(f)})
192 |
193 | /**
194 | * Builder API for creating total union matchers.
195 | *
196 | * @see [[TypedActor.Union]]
197 | * @param finalPf the resulting receive function.
198 | * @tparam U the union type that provided the possible sub cases
199 | * @tparam S phantom type to ensure at least one case is provided
200 | */
201 | final class MkPartialUnionReceive[U <: Union, S <: MkPartialUnionReceive.State] private[TypedActor] (self: Actor, val finalPf: Option[PartialFunction[U, Unit]]) {
202 |
203 | /** Adds a case to the final receive function */
204 | def on[A](f: PartialFunction[A, Unit])(implicit ev: A isPartOf U): MkPartialUnionReceive[U, MkPartialUnionReceive.NonEmpty] = {
205 | val pf = new TypedReceiver[A](f).asInstanceOf[PartialFunction[U, Unit]]
206 | new MkPartialUnionReceive[U, MkPartialUnionReceive.NonEmpty](self, Some(finalPf.fold(pf)(_ orElse pf)))
207 | }
208 | def total[A](f: A => Unit)(implicit ev: A isPartOf U, ct: ClassTag[A]): MkPartialUnionReceive[U, MkPartialUnionReceive.NonEmpty] =
209 | on[A](new Downcast[A](self, ct.runtimeClass.asInstanceOf[Class[A]])(f))
210 |
211 | /** Returns the final receive function */
212 | def apply(implicit ev: S =:= MkPartialUnionReceive.NonEmpty): PartialFunction[U, Unit] =
213 | finalPf.get
214 | }
215 | object MkPartialUnionReceive {
216 | sealed trait State extends Any
217 | sealed trait Empty extends State
218 | sealed trait NonEmpty extends State
219 |
220 | implicit def buildReceive[U <: Union, S <: State](mk: MkPartialUnionReceive[U, S])(implicit ev: S =:= NonEmpty): PartialFunction[U, Unit] =
221 | mk.apply
222 | }
223 |
224 | /**
225 | * Builder API for creating total union matchers.
226 | * This is step 1 when no sub case was given.
227 | *
228 | * @see [[TypedActor.TotalUnion]]
229 | * @tparam U the union type that provided the possible sub cases
230 | */
231 | final class MkTotalUnionReceiveEmpty[U <: Union] private[TypedActor] (val ignore: Option[Nothing]) extends AnyVal {
232 |
233 | /** Adds a case to the final receive function */
234 | def on[A](f: PartialFunction[A, Unit])(implicit ev: A isPartOf U): MkTotalUnionReceiveHalfEmpty[U, A] =
235 | new MkTotalUnionReceiveHalfEmpty[U, A](new TypedReceiver[A](f).asInstanceOf[PartialFunction[U, Unit]])
236 | def total[A](f: A => Unit)(implicit ev: A isPartOf U): MkTotalUnionReceiveHalfEmpty[U, A] = on(PartialFunction(f))
237 | }
238 |
239 | /**
240 | * Builder API for creating total union matchers.
241 | * This is step 2 when the first sub case was given.
242 | * Cannot be an `AnyVal` as this would expose the final receive function.
243 | *
244 | * @see [[TypedActor.TotalUnion]]
245 | * @param finalPf the resulting receive function.
246 | * @tparam U the union type that provided the possible sub cases
247 | * @tparam B the type of the first sub case
248 | */
249 | final class MkTotalUnionReceiveHalfEmpty[U <: Union, B](finalPf: PartialFunction[U, Unit]) {
250 |
251 | /** Adds a case to the final receive function */
252 | def on[A](f: PartialFunction[A, Unit])(implicit ev: A isPartOf U): MkTotalUnionReceive[U, B | A] = {
253 | val pf = new TypedReceiver[A](f).asInstanceOf[PartialFunction[U, Unit]]
254 | new MkTotalUnionReceive[U, B | A](finalPf orElse pf)
255 | }
256 | def total[A](f: A => Unit)(implicit ev: A isPartOf U): MkTotalUnionReceive[U, B | A] = on[A](PartialFunction(f))
257 | }
258 |
259 | /**
260 | * Builder API for creating total union matchers.
261 | * This is the final step, when a covered union is given.
262 | * Cannot be an `AnyVal` as this would expose the final receive function.
263 | *
264 | * @see [[TypedActor.TotalUnion]]
265 | * @param finalPf the resulting receive function.
266 | * @tparam U the union type that provided the possible sub cases
267 | * @tparam T the union type of all the alrady given cases
268 | */
269 | final class MkTotalUnionReceive[U <: Union, T <: Union] private[TypedActor] (finalPf: PartialFunction[U, Unit]) {
270 |
271 | /** Adds a case to the final receive function */
272 | def on[A](f: PartialFunction[A, Unit])(implicit ev: A isPartOf U): MkTotalUnionReceive[U, T | A] = {
273 | val pf = new TypedReceiver[A](f).asInstanceOf[PartialFunction[U, Unit]]
274 | new MkTotalUnionReceive[U, T | A](finalPf orElse pf)
275 | }
276 | def total[A](f: A => Unit)(implicit ev: A isPartOf U): MkTotalUnionReceive[U, T | A] = on(PartialFunction(f))
277 |
278 | /** Returns the final receive function */
279 | def apply(implicit ev: T containsAllOf U): PartialFunction[U, Unit] =
280 | finalPf
281 | }
282 | object MkTotalUnionReceive {
283 | implicit def buildReceive[U <: Union, T <: Union](mk: MkTotalUnionReceive[U, T])(implicit ev: T containsAllOf U): PartialFunction[U, Unit] =
284 | mk.apply
285 | }
286 |
287 | /** Helper to define a new become behavior for a union sub type */
288 | final class TypedBecomeOnAux[U <: Union] private[TypedActor] (val self: Actor) extends AnyVal {
289 |
290 | /** become this new partial behavior */
291 | def on[A](f: PartialFunction[A, Unit])(implicit ev: A isPartOf U): Unit =
292 | self.context become LoggingReceive(new TypedReceiver(f))(self.context)
293 |
294 | /** become this new total behavior */
295 | def total[A](f: A ⇒ Unit)(implicit ev: A isPartOf U, ct: ClassTag[A]): Unit =
296 | on[A](new Downcast[A](self, ct.runtimeClass.asInstanceOf[Class[A]])(f))
297 | }
298 |
299 | private class Downcast[A](actor: Actor, cls: Class[A])(f: A ⇒ Unit) extends Receive {
300 | def isDefinedAt(x: Any): Boolean = cls.isInstance(x)
301 | def apply(v1: Any): Unit = try f(cls.cast(v1)) catch {
302 | case _: MatchError ⇒ actor.unhandled(v1)
303 | }
304 | }
305 |
306 | private class TypedReceiver[A](f: PartialFunction[A, Unit]) extends Receive {
307 | private[this] val receive: Receive =
308 | f.asInstanceOf[Receive]
309 |
310 | def isDefinedAt(x: Any): Boolean = try {
311 | receive.isDefinedAt(x)
312 | } catch {
313 | case _: ClassCastException ⇒ false
314 | }
315 |
316 | def apply(x: Any): Unit =
317 | receive(x)
318 | }
319 | }
320 |
--------------------------------------------------------------------------------
/core/src/main/scala/de/knutwalker/akka/typed/package.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 – 2016 Paul Horn
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package de.knutwalker.akka
18 |
19 | import _root_.akka.actor.{ Actor, ActorContext, ActorPath, ActorRefFactory, Deploy }
20 | import _root_.akka.routing.RouterConfig
21 | import akka.typedactors.AskSupport
22 | import akka.util.Timeout
23 |
24 | import scala.annotation.implicitNotFound
25 | import scala.concurrent.Future
26 | import scala.reflect.ClassTag
27 |
28 | /**
29 | * Compile-time wrapper for akka.actor.ActorRef to allow for same degree of
30 | * type-safety on actors without requiring too much changes to the underlying
31 | * system.
32 | *
33 | * Example:
34 | * {{{
35 | * import akka.actor._
36 | * import de.knutwalker.akka.typed._
37 | *
38 | * case class Ping(replyTo: ActorRef[Pong])
39 | * case class Pong(replyTo: ActorRef[Ping])
40 | *
41 | * implicit val system = ActorSystem()
42 | *
43 | * val ping: ActorRef[Ping] = ActorOf(PropsOf[Ping](new Actor {
44 | * def receive: Receive = {
45 | * case Ping(replyTo) ⇒ replyTo ! Pong(self.typed)
46 | * }
47 | * }))
48 | * val pong: ActorRef[Pong] = ActorOf(PropsOf[Pong](new Actor {
49 | * def receive: Receive = {
50 | * case Pong(replyTo) ⇒ replyTo ! Ping(self.typed)
51 | * }
52 | * }))
53 | *
54 | * ping ! Ping(pong)
55 | *
56 | *
57 | * system.shutdown()
58 | * }}}
59 | */
60 | package object typed {
61 |
62 | type UntypedActorRef = akka.actor.ActorRef
63 | type UntypedProps = akka.actor.Props
64 | val UntypedProps = akka.actor.Props
65 |
66 | type ActorRef[A] = Tagged[UntypedActorRef, A]
67 | type Props[A] = Tagged[UntypedProps, A]
68 |
69 | /**
70 | * Creates a new typed Props that uses the default constructor of the given
71 | * actor type to create new instances of this actor.
72 | *
73 | * Wrapper for `akka.actor.Props[T]`
74 | *
75 | * @tparam A the message type this actor is receiving
76 | * @tparam T the actor type
77 | * @return a typed Props to create `ActorRef[A]`s for this actor
78 | */
79 | def Props[A, T <: Actor : ClassTag]: Props[A] =
80 | Props[A](akka.actor.Props[T])
81 |
82 | /**
83 | * Creates a new typed Props that uses the given creator function to create
84 | * instances of this actor.
85 | *
86 | * CAVEAT: Required mailbox type cannot be detected when using anonymous
87 | * mixin composition when creating the instance. For example, the following
88 | * will not detect the need for `DequeBasedMessageQueueSemantics` as defined
89 | * in `Stash`:
90 | *
91 | * {{{
92 | * Props(new Actor with Stash { ... })
93 | * }}}
94 | *
95 | * Instead you must create a named class that mixin the trait, e.g.
96 | *
97 | * {{{
98 | * class MyActor extends Actor with Stash
99 | * }}}
100 | *
101 | * Wrapper for `akka.actor.Props[T](=> T)`.
102 | *
103 | * @param creator the thunk that create the new instance of this actor
104 | * @tparam A the message type this actor is receiving
105 | * @tparam T the actor type
106 | * @return a typed Props to create `ActorRef[A]`s for this actor
107 | */
108 | def Props[A, T <: Actor : ClassTag](creator: ⇒ T): Props[A] =
109 | Props[A](akka.actor.Props[T](creator))
110 |
111 | /**
112 | * Creates a new typed Props that uses the given class and constructor
113 | * arguments to create instances of this actor.
114 | *
115 | * Wrapper for `akka.actor.Props[T](Class[T], Any*)`.
116 | *
117 | * @param clazz the class of this actor
118 | * @param args the constructor argumentes of this actor
119 | * @tparam A the message type this actor is receiving
120 | * @tparam T the actor type
121 | * @return a typed Props to create `ActorRef[A]`s for this actor
122 | */
123 | def Props[A, T <: Actor](clazz: Class[T], args: Any*): Props[A] =
124 | Props[A](akka.actor.Props(clazz, args: _*))
125 |
126 | /**
127 | * Creates a new typed Props that uses the given [[akka.actor.Props]] to
128 | * create instances of this actor.
129 | *
130 | * @param p the Props to use
131 | * @tparam A the message type this actor is receiving
132 | * @return a typed Props to create `ActorRef[A]`s for this actor
133 | */
134 | def Props[A](p: UntypedProps): Props[A] =
135 | tag(p)
136 |
137 | /**
138 | * Creates a new typed Props that uses the default constructor of the given
139 | * actor to create new instances of this typed actor.
140 | *
141 | * The message type is derived from the given actor.
142 | *
143 | * @tparam T the typed actor
144 | * @return a typed Props to create `ActorRef[A]`s for this actor
145 | */
146 | def PropsFor[T <: TypedActor : ClassTag]: Props[T#Message] =
147 | Props[T#Message, T]
148 |
149 | /**
150 | * Creates a new typed Props that uses the given creator function to create
151 | * instances of this typed actor.
152 | *
153 | * The message type is derived from the given actor.
154 | *
155 | * @see `Props` for some caveat about this constructor.
156 | * @param creator the thunk that create the new instance of this typed actor
157 | * @tparam T the typed actor
158 | * @return a typed Props to create `ActorRef[A]`s for this actor
159 | */
160 | def PropsFor[T <: TypedActor : ClassTag](creator: ⇒ T): Props[T#Message] =
161 | Props[T#Message, T](creator)
162 |
163 | /**
164 | * Creates a new typed Props that uses the given class and constructor
165 | * arguments to create instances of this typed actor.
166 | *
167 | * The message type is derived from the given actor.
168 | *
169 | * @param clazz the class of this typed actor
170 | * @param args the constructor argumentes of this actor
171 | * @tparam T the typed actor
172 | * @return a typed Props to create `ActorRef[A]`s for this actor
173 | */
174 | def PropsFor[T <: TypedActor](clazz: Class[T], args: Any*): Props[T#Message] =
175 | Props[T#Message, T](clazz, args: _*)
176 |
177 | /**
178 | * Type-curried creation of `Props[A]` to aid type inference.
179 | *
180 | * @example
181 | * {{{
182 | * // message gets inferred as Nothing
183 | * Props(new MyActor): Props[Nothing]
184 | *
185 | *
186 | * // too verbose
187 | * Props[MyMessage, MyActor](new MyActor): Props[MyMessage]
188 | *
189 | *
190 | * // guided inference
191 | * PropsOf[MyMessage](new MyActor): Props[MyMessage]
192 | * }}}
193 | * @tparam A the message type this actor is receiving
194 | * @return a class that created typed Props using one of the above methods
195 | */
196 | def PropsOf[A]: PropsBuilder[A] =
197 | new PropsBuilder[A](None)
198 |
199 | /**
200 | * Creates a new typed actor with the given name as a child of the
201 | * implicit [[akka.actor.ActorRefFactory]].
202 | *
203 | * @param p see [[Props]] for details on how to obtain a `Props` object
204 | * @param name the name of the actor.
205 | * must not be null, empty or start with “$”.
206 | * If the given name is already in use, an
207 | * `InvalidActorNameException` is thrown.
208 | * @param factory the factory to create the actor. Within an actor itself,
209 | * this is its `context`. For a toplevel actor, you need to
210 | * put the `ActorSystem` into the implicit scope.
211 | * @throws akka.actor.InvalidActorNameException if the given name is
212 | * invalid or already in use
213 | * @throws akka.ConfigurationException if deployment, dispatcher or
214 | * mailbox configuration is wrong
215 | * @tparam A the message type this actor is receiving
216 | * @return the typed ActorRef[A] for this actor
217 | */
218 | def ActorOf[A](p: Props[A], name: String)(implicit factory: ActorRefFactory): ActorRef[A] =
219 | tag(factory.actorOf(untag(p), name))
220 |
221 | /**
222 | * Creates a new typed actor as a child of the implicit
223 | * [[akka.actor.ActorRefFactory]] and give it an automatically generated name.
224 | *
225 | * @param p see [[Props]] for details on how to obtain a `Props` object
226 | * @param factory the factory to create the actor. Within an actor itself,
227 | * this is its `context`. For a toplevel actor, you need to
228 | * put the `ActorSystem` into the implicit scope.
229 | * @throws akka.ConfigurationException if deployment, dispatcher or
230 | * mailbox configuration is wrong
231 | * @tparam A the message type this actor is receiving
232 | * @return the typed ActorRef[A] for this actor
233 | */
234 | def ActorOf[A](p: Props[A])(implicit factory: ActorRefFactory): ActorRef[A] =
235 | tag(factory.actorOf(untag(p)))
236 |
237 |
238 | implicit final class PropsOps[A](val props: Props[A]) extends AnyVal {
239 |
240 | /** @see [[akka.actor.Props#dispatcher]] */
241 | def dispatcher: String =
242 | untyped.dispatcher
243 |
244 | /** @see [[akka.actor.Props#mailbox]] */
245 | def mailbox: String =
246 | untyped.mailbox
247 |
248 | /** @see [[akka.actor.Props#routerConfig]] */
249 | def routerConfig: RouterConfig =
250 | untyped.routerConfig
251 |
252 | /** @see [[akka.actor.Props#withDispatcher]] */
253 | def withDispatcher(d: String): Props[A] =
254 | tag(untyped.withDispatcher(d))
255 |
256 | /** @see [[akka.actor.Props#withMailbox]] */
257 | def withMailbox(m: String): Props[A] =
258 | tag(untyped.withMailbox(m))
259 |
260 | /** @see [[akka.actor.Props#withRouter]] */
261 | def withRouter(r: RouterConfig): Props[A] =
262 | tag(untyped.withRouter(r))
263 |
264 | /** @see [[akka.actor.Props#withDeploy]] */
265 | def withDeploy(d: Deploy): Props[A] =
266 | tag(untyped.withDeploy(d))
267 |
268 | /** @see [[akka.actor.Props#actorClass]] */
269 | def actorClass(): Class[_ <: Actor] =
270 | untyped.actorClass()
271 |
272 | /**
273 | * @return this typed Props as an untyped [[akka.actor.Props]].
274 | * The returned instance is `eq` to `this`.
275 | */
276 | def untyped: UntypedProps =
277 | untag(props)
278 |
279 | /**
280 | * Build a union typed Props out of this Props.
281 | * The resulting props may accept either `A` or `B` as message.
282 | */
283 | def or[B]: Props[A | B] =
284 | retag(props)
285 | }
286 |
287 | implicit final class ActorRefOps[A](private val ref: ActorRef[A]) extends AnyVal {
288 |
289 | /**
290 | * Sends a typed message asynchronously.
291 | *
292 | * @see [[akka.actor.ActorRef#tell]]
293 | */
294 | def !(msg: A)(implicit sender: UntypedActorRef = Actor.noSender): Unit =
295 | untyped ! msg
296 |
297 | /**
298 | * Ask a typed question asynchronously.
299 | * This signature enforces the `replyTo` pattern for keeping type safety.
300 | *
301 | * Instead of sending a message of `Any` and replying to an untyped `sender()`,
302 | * you supply a function that, given a typed sender, will return the message.
303 | * This is typically done with a second parameter list of a case class.
304 | *
305 | * {{{
306 | * case class MyMessage(payload: String)(val replyTo: ActorRef[MyResponse])
307 | *
308 | * class MyActor extends Actor {
309 | * def receive = {
310 | * case m@MyMessage(payload) => m.replyTo ! MyResponse(payload)
311 | * }
312 | * }
313 | * }}}
314 | */
315 | def ?[B](f: ActorRef[B] ⇒ A)(implicit timeout: Timeout, ctA: ClassTag[A], sender: UntypedActorRef = Actor.noSender): Future[B] =
316 | AskSupport.ask[A, B](ref, f, timeout, ctA, sender)
317 |
318 | /** @see [[akka.actor.ActorRef#path]] */
319 | def path: ActorPath =
320 | untyped.path
321 |
322 | /** @see [[akka.actor.ActorRef#forward]] */
323 | def forward(msg: A)(implicit context: ActorContext): Unit =
324 | untyped.forward(msg)
325 |
326 | /**
327 | * Sends any message asynchronously, e.g. [[akka.actor.PoisonPill]].
328 | * This is the same as using tell on a untyped actor.
329 | *
330 | * @see [[akka.actor.ActorRef#tell]]
331 | */
332 | def unsafeTell(msg: Any)(implicit sender: UntypedActorRef = Actor.noSender): Unit =
333 | untyped ! msg
334 |
335 | /**
336 | * Returns this typed ActorRef as an untyped [[akka.actor.ActorRef]].
337 | * The returned instance is `eq` to `this`.
338 | */
339 | def untyped: UntypedActorRef =
340 | untag(ref)
341 |
342 | /**
343 | * Build a union typed ActorRef out of this ActorRef.
344 | * The resulting actor may accept either `A` or `B` as message.
345 | */
346 | def or[B]: ActorRef[A | B] =
347 | retag(ref)
348 | }
349 |
350 | implicit final class ActorRefUnionedOps[U <: Union](private val ref: ActorRef[U]) extends AnyVal {
351 |
352 | /**
353 | * Sends a typed message asynchronously.
354 | *
355 | * @see [[akka.actor.ActorRef#tell]]
356 | */
357 | def (implicit ev: A isPartOf U, sender: UntypedActorRef = Actor.noSender): Unit =
358 | untag(ref) ! msg
359 |
360 | /**
361 | * Ask a typed question asynchronously.
362 | * This signature enforces the `replyTo` pattern for keeping type safety.
363 | *
364 | * Instead of sending a message of `Any` and replying to an untyped `sender()`,
365 | * you supply a function that, given a typed sender, will return the message.
366 | * This is typically done with a second parameter list of a case class.
367 | *
368 | * {{{
369 | * case class MyMessage(payload: String)(val replyTo: ActorRef[MyResponse])
370 | *
371 | * class MyActor extends Actor {
372 | * def receive = {
373 | * case m@MyMessage(payload) => m.replyTo ! MyResponse(payload)
374 | * }
375 | * }
376 | * }}}
377 | */
378 | def ?[A, B](f: ActorRef[B] ⇒ A)(implicit ev: A isPartOf U, timeout: Timeout, ctA: ClassTag[A], sender: UntypedActorRef = Actor.noSender): Future[B] =
379 | AskSupport.ask[A, B](retag(ref), f, timeout, ctA, sender)
380 |
381 | /** Returns an ActorRef that only handles a subpart of the given union type. */
382 | def only[A](implicit ev: A isPartOf U): ActorRef[A] =
383 | retag(ref)
384 | }
385 |
386 | implicit final class UntypedPropsOps(private val untyped: UntypedProps) extends AnyVal {
387 |
388 | /**
389 | * Returns this Props as a typed [[Props]].
390 | * The returned instance is `eq` to `this`.
391 | * @tparam A the message type this actor will be receiving
392 | * @return the typed Props
393 | */
394 | def typed[A]: Props[A] =
395 | tag(untyped)
396 | }
397 |
398 | implicit final class UntypedActorRefOps(private val untyped: UntypedActorRef) extends AnyVal {
399 |
400 | /**
401 | * Returns this Props as a typed [[ActorRef]].
402 | * The returned instance is `eq` to `this`.
403 | * @tparam A the message type this actor is receiving
404 | * @return the typed ActorRef
405 | */
406 | def typed[A]: ActorRef[A] =
407 | tag(untyped)
408 | }
409 |
410 | private type Tagged[A, T] = {
411 | type Message = T
412 | type Self = A
413 | }
414 |
415 | @inline private[typed] def tag[A, T](a: A): Tagged[A, T] =
416 | a.asInstanceOf[Tagged[A, T]]
417 |
418 | @inline private[typed] def untag[A, T](t: Tagged[A, T]): A =
419 | t.asInstanceOf[A]
420 |
421 | @inline private[this] def retag[A, B, T](a: Tagged[T, A]): Tagged[T, B] =
422 | a.asInstanceOf[Tagged[T, B]]
423 | }
424 |
425 | /**
426 | * Everything in here is is considered an INTERNAL API!
427 | *
428 | * Union type implementation follows. The only thing concerning the user is the
429 | * `|` type which should be fairly self explanatory. The rest are type classes
430 | * and provers to implement the type-level constraints for the union types.
431 | */
432 | package typed {
433 |
434 | sealed trait Union
435 | sealed trait |[+A, +B] extends Union
436 |
437 | @implicitNotFound("Cannot prove that ${A} is a union type.")
438 | sealed trait IsUnion[-A] {
439 | type Out <: Union
440 | }
441 |
442 | object IsUnion {
443 | type Aux[-A0, U0 <: Union] = IsUnion[A0] { type Out = U0 }
444 | implicit def isUnion[A <: Union]: Aux[A, A] =
445 | new IsUnion[A] {
446 | type Out = A
447 | }
448 | }
449 |
450 | @implicitNotFound("Cannot prove that message of type ${A} is a member of ${U}.")
451 | sealed trait isPartOf[A, +U <: Union]
452 | object isPartOf extends IsPartOf0 {
453 | implicit def leftPart[A](implicit ev: A isNotA Union): isPartOf[A, A | Nothing] =
454 | null
455 |
456 | implicit def rightPart[A](implicit ev: A isNotA Union): isPartOf[A, Nothing | A] =
457 | null
458 | }
459 | sealed trait IsPartOf0 {
460 | implicit def tailPart1[A, U <: Union](implicit partOfTl: A isPartOf U): isPartOf[A, U | Nothing] =
461 | null
462 |
463 | implicit def tailPart2[A, U <: Union](implicit partOfTl: A isPartOf U): isPartOf[A, Nothing | U] =
464 | null
465 | }
466 |
467 | @implicitNotFound("Cannot prove that ${U} contains some members of ${T}.")
468 | sealed trait containsSomeOf[U <: Union, T <: Union]
469 | object containsSomeOf extends ContainsSomeOf0 {
470 | implicit def headPart[A, B, T <: Union](implicit evA: A isPartOf T, evB: B isPartOf T): containsSomeOf[A | B, T] =
471 | null
472 | }
473 | sealed trait ContainsSomeOf0 {
474 | implicit def tailPart0[A, U <: Union, T <: Union](implicit ev: A isPartOf T, tailAligns: U containsSomeOf T): containsSomeOf[U | A, T] =
475 | null
476 |
477 | implicit def tailPart1[A, U <: Union, T <: Union](implicit ev: A isPartOf T, tailAligns: U containsSomeOf T): containsSomeOf[A | U, T] =
478 | null
479 | }
480 |
481 | @implicitNotFound("Cannot prove that ${U} contains the same members as ${T}.")
482 | sealed trait containsAllOf[U <: Union, T <: Union]
483 | object containsAllOf {
484 | implicit def uContainsAllOfT[U <: Union, T <: Union](implicit evA: U containsSomeOf T, evB: T containsSomeOf U): containsAllOf[U, T] =
485 | null
486 | }
487 |
488 | // @annotation.implicitAmbiguous("${A} must not be <: ${B}")
489 | sealed trait isNotA[A, B]
490 | object isNotA {
491 | implicit def nsub[A, B]: A isNotA B = null
492 | // $COVERAGE-OFF$Code only exists to prove non-equality and is expected to never execute
493 | implicit def nsubAmbig1[A, B >: A]: A isNotA B = sys.error("Unexpected invocation")
494 | implicit def nsubAmbig2[A, B >: A]: A isNotA B = sys.error("Unexpected invocation")
495 | // $COVERAGE-ON$
496 | }
497 | }
498 |
--------------------------------------------------------------------------------
/creator/src/main/scala/de/knutwalker/akka/typed/Typed.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 – 2016 Paul Horn
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package de.knutwalker.akka.typed
18 |
19 | import _root_.akka.actor.{ ActorRefFactory, Actor }
20 | import _root_.shapeless.{ Generic, ProductArgs }
21 |
22 | import scala.reflect.ClassTag
23 |
24 | /**
25 | * A shapeless-powered, typed variant of the actor/props creation.
26 | * This is an alternative over constructors, that use the `Class[A], Any*`
27 | * overload to create actors.
28 | *
29 | * You must use `TypedActor`s and these actors must be `case class`es for
30 | * this to work.
31 | *
32 | * Example:
33 | * {{{
34 | * case class ExampleActor(foo: Option[String]) extends TypedActor.Of[String] {
35 | * def typedReceive: TypedReceive = ???
36 | * }
37 | *
38 | * // runtime error (IllegalArgumentException: no matching constructor found):
39 | * val runtimeError: ActorRef[String] =
40 | * ActorOf(PropsOf[String](classOf[ExampleActor1], "wrong type"))
41 | *
42 | * // compiletime error:
43 | * // found : String("wrong type") :: HNil
44 | * // required: Option[String] :: HNil
45 | * val compiletimeError: ActorRef[String] =
46 | * Typed[ExampleActor2].create("wrong type")
47 | * }}}
48 | *
49 | * === Usage ===
50 | *
51 | * Use apply with the `TypedActor` as the type parameter and then call either
52 | * `create` or `props` with the appropriate constructor parameters to create
53 | * either an [[ActorRef]] or a [[Props]].
54 | */
55 | object Typed {
56 |
57 | def apply[T <: TypedActor](implicit gen: Generic[T], ct: ClassTag[T]) =
58 | new MkProps[T#Message, T, gen.Repr]()(gen, ct)
59 |
60 | final class MkProps[A, T <: Actor, L](implicit gen: Generic.Aux[T, L], ct: ClassTag[T]) extends ProductArgs {
61 |
62 | def propsProduct(args: L): Props[A] =
63 | Props[A](UntypedProps[T](gen.from(args)))
64 |
65 | def createProduct(args: L)(implicit ref: ActorRefFactory): ActorRef[A] =
66 | ActorOf(propsProduct(args))
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/docs/src/site/.gitignore:
--------------------------------------------------------------------------------
1 | _site/
2 | .sass-cache/
3 |
--------------------------------------------------------------------------------
/docs/src/site/_config.yml:
--------------------------------------------------------------------------------
1 | title: Typed Actors
2 | description: compile-time typechecked akka actors
3 | baseurl: "/typed-actors"
4 | url: "https://knutwalker.github.io/"
5 | markdown: kramdown
6 | highlighter: pygments
7 | kramdown:
8 | input: GFM
9 |
--------------------------------------------------------------------------------
/docs/src/site/_data/more_vert.yml:
--------------------------------------------------------------------------------
1 | - name: Code
2 | url: https://github.com/knutwalker/typed-actors
3 | - name: Travis
4 | url: https://travis-ci.org/knutwalker/typed-actors
5 | - name: Gitter
6 | url: https://gitter.im/knutwalker/typed-actors
7 | - name: knutwalker
8 | url: https://twitter.com/knutwalker
9 | icon: twitter
10 | - name: knutwalker
11 | url: https://github.com/knutwalker
12 | icon: github
13 |
--------------------------------------------------------------------------------
/docs/src/site/_includes/foot.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/docs/src/site/_includes/head.html:
--------------------------------------------------------------------------------
1 |
9 | {% include foot.html %}
10 |
11 |
12 |
--------------------------------------------------------------------------------
/docs/src/site/_sass/_base.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * Set `margin-bottom` to maintain vertical rhythm
3 | */
4 | h1, h2, h3, h4, h5, h6,
5 | p, blockquote, pre,
6 | ul, ol, dl, figure,
7 | %vertical-rhythm {
8 | margin-bottom: $spacing-unit / 2;
9 | }
10 |
11 | body {
12 | background-color: #F5F2F0;
13 | }
14 |
15 | /**
16 | * Blockquotes
17 | */
18 | blockquote {
19 | color: $grey-color;
20 | border-left: 4px solid $grey-color-light;
21 | padding-left: $spacing-unit / 2;
22 | font-size: 18px;
23 | letter-spacing: -1px;
24 | font-style: italic;
25 |
26 | > :last-child {
27 | margin-bottom: 0;
28 | }
29 | }
30 |
31 | /**
32 | * Code formatting
33 | */
34 | pre,
35 | code {
36 | border: 1px solid $grey-color-light;
37 | border-radius: 3px;
38 | }
39 |
40 | code {
41 | padding: 1px 5px;
42 | }
43 |
44 | pre {
45 | padding: 8px 12px;
46 | overflow-x: scroll;
47 |
48 | > code {
49 | border: 0;
50 | padding-right: 0;
51 | padding-left: 0;
52 | }
53 | }
54 |
55 | /**
56 | * Clearfix
57 | */
58 | %clearfix {
59 |
60 | &:after {
61 | content: "";
62 | display: table;
63 | clear: both;
64 | }
65 | }
66 |
67 | /**
68 | * Icons
69 | */
70 | .icon {
71 | &.fa {
72 | font-size: 1.3em;
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/docs/src/site/_sass/_layout.scss:
--------------------------------------------------------------------------------
1 | //
2 | // Generals styles for material design light elements
3 | //
4 |
5 | // Page container
6 |
7 | .page-content{
8 | max-width: 1200px;
9 | margin: 0 auto;
10 |
11 | &.page-section {
12 | margin-top: 25px;
13 | border-radius: 2px;
14 | padding: 80px 56px;
15 | margin-bottom: 80px;
16 | }
17 | }
18 |
19 | h4[id] {
20 | text-decoration: underline;
21 | text-decoration-color: transparent;
22 | transition: text-decoration-color 0.2s ease-in;
23 | }
24 |
25 | h4[id]::after {
26 | font-family: 'Material Icons';
27 | font-weight: normal;
28 | font-style: normal;
29 | font-size: 24px;
30 | line-height: 1;
31 | letter-spacing: normal;
32 | text-transform: none;
33 | display: inline-block;
34 | word-wrap: normal;
35 | transition: opacity 0.2s ease-in;
36 | -moz-font-feature-settings: 'liga';
37 | -moz-osx-font-smoothing: grayscale;
38 | vertical-align: text-top;
39 | margin-left: 0.3em;
40 | opacity: 0;
41 | content: "link";
42 | }
43 |
44 | h4[id]:hover {
45 | text-decoration-color: #424242;
46 | cursor: pointer;
47 | }
48 |
49 | h4[id]:hover::after {
50 | font-family: 'Material Icons';
51 | font-weight: normal;
52 | font-style: normal;
53 | font-size: 24px;
54 | line-height: 1;
55 | letter-spacing: normal;
56 | text-transform: none;
57 | cursor: pointer;
58 | display: inline-block;
59 | word-wrap: normal;
60 | transition: opacity 0.2s ease-in;
61 | -moz-font-feature-settings: 'liga';
62 | -moz-osx-font-smoothing: grayscale;
63 | vertical-align: text-top;
64 | margin-left: 0.3em;
65 | opacity: 1;
66 | content: "link";
67 | }
68 |
69 | // Cards styles
70 |
71 | .mdl-card {
72 | min-height: 160px;
73 | }
74 |
75 | .mdl-card__title {
76 | color: #000;
77 | height: 64px;
78 | background: transparent;
79 |
80 | a {
81 | text-decoration: none;
82 | font-weight: inherit;
83 | }
84 | }
85 |
86 | .mdl-card__menu {
87 | color: #fff;
88 | }
89 |
90 | .post-button{
91 | position: absolute;
92 | right: 15px;
93 | bottom: 25px;
94 | }
95 |
--------------------------------------------------------------------------------
/docs/src/site/_sass/_post.scss:
--------------------------------------------------------------------------------
1 | //
2 | // Styles for post
3 | //
4 |
5 | .post-ribbon {
6 | width: 100%;
7 | height: 40vh;
8 | flex-shrink: 0;
9 | }
10 |
11 | .post-main {
12 | margin-top: -35vh;
13 | flex-shrink: 0;
14 |
15 | .post-container {
16 | max-width: 1600px;
17 | width: calc(100% - 16px);
18 | margin: 0 auto;
19 | }
20 |
21 | .post-section {
22 | border-radius: 2px;
23 | padding: 56px 56px;
24 | margin: 0 auto 80px;
25 | max-width: 1200px;
26 |
27 | h3 {
28 | margin-top: 28px;
29 | margin-bottom: 28px;
30 | }
31 | }
32 | }
33 |
34 | #post-content {
35 | p {
36 | font-size: 1.2em;
37 | }
38 | pre {
39 | font-size: 1.1em;
40 | margin-bottom: 2em;
41 | }
42 | }
43 |
44 |
--------------------------------------------------------------------------------
/docs/src/site/css/main.scss:
--------------------------------------------------------------------------------
1 | ---
2 | ---
3 |
4 | //
5 | // Main sass file, to import all styles
6 | //
7 |
8 | @charset "utf-8";
9 |
10 | $spacing-unit: 30px;
11 | $text-color: #111;
12 | $background-color: #fdfdfd;
13 | $brand-color: #bada55;
14 | $grey-color: #828282;
15 | $grey-color-light: lighten($grey-color, 40%);
16 | $grey-color-dark: darken($grey-color, 25%);
17 |
18 |
19 | // Import partials from `sass_dir` (defaults to `_sass`)
20 | @import
21 | "base",
22 | "layout",
23 | "post"
24 | ;
25 |
--------------------------------------------------------------------------------
/docs/src/site/css/prism.css:
--------------------------------------------------------------------------------
1 | /* http://prismjs.com/download.html?themes=prism&languages=clike+java+scala */
2 | /**
3 | * prism.js default theme for JavaScript, CSS and HTML
4 | * Based on dabblet (http://dabblet.com)
5 | * @author Lea Verou
6 | */
7 |
8 | code[class*="language-"],
9 | pre[class*="language-"] {
10 | color: black;
11 | text-shadow: 0 1px white;
12 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
13 | direction: ltr;
14 | text-align: left;
15 | white-space: pre;
16 | word-spacing: normal;
17 | word-break: normal;
18 | word-wrap: normal;
19 | line-height: 1.5;
20 |
21 | -moz-tab-size: 4;
22 | -o-tab-size: 4;
23 | tab-size: 4;
24 |
25 | -webkit-hyphens: none;
26 | -moz-hyphens: none;
27 | -ms-hyphens: none;
28 | hyphens: none;
29 | }
30 |
31 | pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection,
32 | code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
33 | text-shadow: none;
34 | background: #b3d4fc;
35 | }
36 |
37 | pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
38 | code[class*="language-"]::selection, code[class*="language-"] ::selection {
39 | text-shadow: none;
40 | background: #b3d4fc;
41 | }
42 |
43 | @media print {
44 | code[class*="language-"],
45 | pre[class*="language-"] {
46 | text-shadow: none;
47 | }
48 | }
49 |
50 | /* Code blocks */
51 | pre[class*="language-"] {
52 | padding: 1em;
53 | margin: .5em 0;
54 | overflow: auto;
55 | }
56 |
57 | :not(pre) > code[class*="language-"],
58 | pre[class*="language-"] {
59 | background: #f5f2f0;
60 | }
61 |
62 | /* Inline code */
63 | :not(pre) > code[class*="language-"] {
64 | padding: .1em;
65 | border-radius: .3em;
66 | }
67 |
68 | .token.comment,
69 | .token.prolog,
70 | .token.doctype,
71 | .token.cdata {
72 | color: slategray;
73 | }
74 |
75 | .token.punctuation {
76 | color: #999;
77 | }
78 |
79 | .namespace {
80 | opacity: .7;
81 | }
82 |
83 | .token.property,
84 | .token.tag,
85 | .token.boolean,
86 | .token.number,
87 | .token.constant,
88 | .token.symbol,
89 | .token.deleted {
90 | color: #905;
91 | }
92 |
93 | .token.selector,
94 | .token.attr-name,
95 | .token.string,
96 | .token.char,
97 | .token.builtin,
98 | .token.inserted {
99 | color: #690;
100 | }
101 |
102 | .token.operator,
103 | .token.entity,
104 | .token.url,
105 | .language-css .token.string,
106 | .style .token.string {
107 | color: #a67f59;
108 | background: hsla(0, 0%, 100%, .5);
109 | }
110 |
111 | .token.atrule,
112 | .token.attr-value,
113 | .token.keyword {
114 | color: #07a;
115 | }
116 |
117 | .token.function {
118 | color: #DD4A68;
119 | }
120 |
121 | .token.regex,
122 | .token.important,
123 | .token.variable {
124 | color: #e90;
125 | }
126 |
127 | .token.important,
128 | .token.bold {
129 | font-weight: bold;
130 | }
131 | .token.italic {
132 | font-style: italic;
133 | }
134 |
135 | .token.entity {
136 | cursor: help;
137 | }
138 |
139 |
--------------------------------------------------------------------------------
/docs/src/site/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: index
3 | ---
4 |
5 | ## {{ site.title }}
6 |
7 | {% highlight scala %}
8 | libraryDependencies ++= List({% for module in site.data.modules %}
9 | "{{ module.organization }}" %% "{{ module.name }}" % "{{ module.version }}"{% unless forloop.last %},{% endunless %}{% endfor %})
10 | {% endhighlight %}
11 |
--------------------------------------------------------------------------------
/docs/src/site/js/main.js:
--------------------------------------------------------------------------------
1 | document.addEventListener('DOMContentLoaded', function() {
2 | "use strict";
3 | var postContent = document.getElementById('post-content');
4 | if (postContent !== undefined) {
5 | postContent.addEventListener('click', function(e) {
6 | var target = e.target;
7 | if (target.tagName === 'H4' && target.id) {
8 | window.location.hash = target.id;
9 | }
10 | });
11 | }
12 | });
13 |
--------------------------------------------------------------------------------
/docs/src/site/js/prism.js:
--------------------------------------------------------------------------------
1 | /* http://prismjs.com/download.html?themes=prism-okaidia&languages=clike+java+scala */
2 | var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(){var e=/\blang(?:uage)?-(?!\*)(\w+)\b/i,t=_self.Prism={util:{encode:function(e){return e instanceof n?new n(e.type,t.util.encode(e.content),e.alias):"Array"===t.util.type(e)?e.map(t.util.encode):e.replace(/&/g,"&").replace(/e.length)break e;if(!(d instanceof a)){u.lastIndex=0;var m=u.exec(d);if(m){c&&(f=m[1].length);var y=m.index-1+f,m=m[0].slice(f),v=m.length,k=y+v,b=d.slice(0,y+1),w=d.slice(k+1),N=[p,1];b&&N.push(b);var O=new a(l,g?t.tokenize(m,g):m,h);N.push(O),w&&N.push(w),Array.prototype.splice.apply(r,N)}}}}}return r},hooks:{all:{},add:function(e,n){var a=t.hooks.all;a[e]=a[e]||[],a[e].push(n)},run:function(e,n){var a=t.hooks.all[e];if(a&&a.length)for(var r,i=0;r=a[i++];)r(n)}}},n=t.Token=function(e,t,n){this.type=e,this.content=t,this.alias=n};if(n.stringify=function(e,a,r){if("string"==typeof e)return e;if("Array"===t.util.type(e))return e.map(function(t){return n.stringify(t,a,e)}).join("");var i={type:e.type,content:n.stringify(e.content,a,r),tag:"span",classes:["token",e.type],attributes:{},language:a,parent:r};if("comment"==i.type&&(i.attributes.spellcheck="true"),e.alias){var l="Array"===t.util.type(e.alias)?e.alias:[e.alias];Array.prototype.push.apply(i.classes,l)}t.hooks.run("wrap",i);var o="";for(var s in i.attributes)o+=(o?" ":"")+s+'="'+(i.attributes[s]||"")+'"';return"<"+i.tag+' class="'+i.classes.join(" ")+'" '+o+">"+i.content+""+i.tag+">"},!_self.document)return _self.addEventListener?(_self.addEventListener("message",function(e){var n=JSON.parse(e.data),a=n.language,r=n.code,i=n.immediateClose;_self.postMessage(JSON.stringify(t.util.encode(t.tokenize(r,t.languages[a])))),i&&_self.close()},!1),_self.Prism):_self.Prism;var a=document.getElementsByTagName("script");return a=a[a.length-1],a&&(t.filename=a.src,document.addEventListener&&!a.hasAttribute("data-manual")&&document.addEventListener("DOMContentLoaded",t.highlightAll)),_self.Prism}();"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism);
3 | Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\w\W]*?\*\//,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0}],string:/("|')(\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,"class-name":{pattern:/((?:\b(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[a-z0-9_\.\\]+/i,lookbehind:!0,inside:{punctuation:/(\.|\\)/}},keyword:/\b(if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,"boolean":/\b(true|false)\b/,"function":/[a-z0-9_]+(?=\()/i,number:/\b-?(?:0x[\da-f]+|\d*\.?\d+(?:e[+-]?\d+)?)\b/i,operator:/--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&?|\|\|?|\?|\*|\/|~|\^|%/,punctuation:/[{}[\];(),.:]/};
4 | Prism.languages.java=Prism.languages.extend("clike",{keyword:/\b(abstract|continue|for|new|switch|assert|default|goto|package|synchronized|boolean|do|if|private|this|break|double|implements|protected|throw|byte|else|import|public|throws|case|enum|instanceof|return|transient|catch|extends|int|short|try|char|final|interface|static|void|class|finally|long|strictfp|volatile|const|float|native|super|while)\b/,number:/\b0b[01]+\b|\b0x[\da-f]*\.?[\da-fp\-]+\b|\b\d*\.?\d+(?:e[+-]?\d+)?[df]?\b/i,operator:{pattern:/(^|[^.])(?:\+[+=]?|-[-=]?|!=?|<=?|>>?>?=?|==?|&[&=]?|\|[|=]?|\*=?|\/=?|%=?|\^=?|[?:~])/m,lookbehind:!0}});
5 | Prism.languages.scala=Prism.languages.extend("java",{keyword:/<-|=>|\b(?:abstract|case|catch|class|def|do|else|extends|final|finally|for|forSome|if|implicit|import|lazy|match|new|null|object|override|package|private|protected|return|sealed|self|super|this|throw|trait|try|type|val|var|while|with|yield)\b/,string:/"""[\W\w]*?"""|"(?:[^"\\\r\n]|\\.)*"|'(?:[^\\\r\n']|\\.[^\\']*)'/,builtin:/\b(?:String|Int|Long|Short|Byte|Boolean|Double|Float|Char|Any|AnyRef|AnyVal|Unit|Nothing)\b/,number:/\b(?:0x[\da-f]*\.?[\da-f]+|\d*\.?\d+e?\d*[dfl]?)\b/i,symbol:/'[^\d\s\\]\w*/}),delete Prism.languages.scala["class-name"],delete Prism.languages.scala["function"];
6 |
--------------------------------------------------------------------------------
/docs/src/site/scaladoc.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: index
3 | title: API Documentation
4 | permalink: /___page--api/
5 | link: /api/#de.knutwalker.akka.typed.package
6 | index: 1
7 | ---
8 | Browse the API documentation.
9 |
--------------------------------------------------------------------------------
/docs/src/site/usage.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: index
3 | title: Library Usage
4 | permalink: /___page--tut/
5 | link: /tut/
6 | index: 0
7 | ---
8 | Learn more about the library.
9 |
--------------------------------------------------------------------------------
/docs/src/test/scala/akka/LoggingReceive.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 – 2016 Paul Horn
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package akka
18 |
19 | import akka.actor.Actor.Receive
20 | import akka.actor.ActorContext
21 |
22 | /**
23 | * Synchronous copy of [[akka.event.LoggingReceive]].
24 | * Just for tutorials.
25 | */
26 | object LoggingReceive {
27 |
28 | def apply(r: Receive)(implicit context: ActorContext): Receive = r match {
29 | case _: LoggingReceive ⇒ r
30 | case _ ⇒ if (context.system.settings.AddLoggingReceive) new LoggingReceive(r) else r
31 | }
32 | }
33 |
34 | class LoggingReceive(r: Receive)(implicit context: ActorContext) extends Receive {
35 | def isDefinedAt(o: Any): Boolean = {
36 | val handled = r.isDefinedAt(o)
37 | val msg = s"received ${if (handled) "handled" else "unhandled"} message $o"
38 | println(s"[DEBUG] $msg")
39 | handled
40 | }
41 | def apply(o: Any): Unit = r(o)
42 | }
43 |
--------------------------------------------------------------------------------
/docs/src/test/scala/akka/PrintLogger.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 – 2016 Paul Horn
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package akka
18 |
19 | import akka.actor._
20 | import akka.event.Logging.{ LogEvent, Debug, Error, Info, InitializeLogger, LoggerInitialized, Warning }
21 |
22 | /**
23 | * Used for tut as a simple logger that only shows the message and the loglevel.
24 | * Not meant to be used in actual production code.
25 | */
26 | class PrintLogger extends Actor {
27 | override def receive: Receive = {
28 | case InitializeLogger(_) ⇒ sender() ! LoggerInitialized
29 | case event: LogEvent if event.message.toString.startsWith("shutting down:") ⇒
30 | case event: Error ⇒ println(s"[ERROR] ${event.message }")
31 | case event: Warning ⇒ println(s"[WARN ] ${event.message }")
32 | case event: Info ⇒ println(s"[INFO ] ${event.message }")
33 | case event: Debug ⇒ println(s"[DEBUG] ${event.message }")
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/docs/src/tut/comparison.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: page
3 | title: Comparison with Akka Typed
4 | tut: 108
5 | ---
6 |
7 | The [`Akka Typed`](http://doc.akka.io/docs/akka/snapshot/scala/typed.html) project is a module of Akka (as of 2.4) which aims to provide typesafe actors as well.
8 | Akka typed takes a completely different approach, mirroring most of the untyped API and ultimately offering a completely new API to define your actors behavior. Currently, this implementation sits on top of untyped They are currently actors.
9 | Let me add that I really like Akka Typed and having worked with it for some time lead me to think about how to bring type safety to the rest of Akka.
10 |
11 | `Akka Typed` is not only about a typed `ActorRef[A]`, there's much more that's changed and is reason to use `Akka Typed`, both in general and over `Typed Actors`. It separates the behavior of your actors from its execution model, making them really easy to test; You can just use a synchronous stub execution model and you get to test just the behavior without concerning yourself about the how-to-test-this-async-thingy. The new behavior API is not just a convoluted `PartialFunction[A, Unit]` but allows you to split your behavior into nice little pieces and have them composed together. `Akka Typed`'s getting rid of some old (and bad) habits as well; `sender()` is gone, as are lifecycle methods that have to be overridden, even the `Actor` trait itself is gone. It's messages and behavior all the way down!
12 |
13 | Those are all concerns that `Typed Actor` will never deal with, this is one important difference: `Typed Actors` is a possibility to add some compile-time checking while `Akka Typed` is a completely new API. Understandingly, `Akka Typed` is better at hiding their untyped implementation, nothing in the public API leads to the fact that something like an untyped actor could even exist.
14 |
15 | On the other hand, having `Akka Typed` as a separate module means it is difficult to use the typed API with other modules. Most APIs expect an `akka.actor.ActorRef` and you can't get one from a akka-typed actor (well, you can, but it's dirty). This also applies to things like `ActorLogging` and `Stash`.
16 | `Typed Actors` doesn't try to prevent you from going untyped and as there is no different runtime representation, it can be easily used with all existing akka modules.
17 | However, if you mix typed/untyped code too much, you run into unhandled messages or maybe even runtime class cast exceptions or match errors (which ought to be bugs then, really).
18 |
19 | `Typed Actors` makes it easy to deal with multiple types of messages, not just one `A` thanks to its [Union type](union.html) support. Joining multiple behavior requires them to be of the same type, although you can get far with a little bit of type-fu. Basically, you can take advantage of the covariant nature of `ActorRef[-A]` (in `Typed Actors`, ActorRef is actually invariant) and create phantom intersection types (`A with B`) and upcast at tellsite. It is, however, something different whether you as the library user has to know how to fu or I as the library author know so you don't have to.
20 |
21 | Also, `Akka Typed` is concerned with Java interop, which `Typed Actors` is not.
22 | Nevertheless, `Akka Typed` is a – in my opinion – really nice project and its new API is a major improvement over the default `Actor`. The resulting patterns, like `replyTo` are a good idea to use with `Typed Actor`s as well.
23 |
24 | That concludes the Usage Guide. I guess the only thing left is to go on hAkking!
25 |
--------------------------------------------------------------------------------
/docs/src/tut/creator.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: page
3 | title: Typed Creator
4 | tut: 106
5 | ---
6 |
7 | ```tut:invisible
8 | import akka.actor._
9 | import de.knutwalker.akka.typed._
10 | sealed trait MyMessage
11 | case class Foo(foo: String) extends MyMessage
12 | case class Bar(bar: String) extends MyMessage
13 | case object SomeOtherMessage
14 | implicit val system = ActorSystem("foo", config=Some(com.typesafe.config.ConfigFactory.parseString("""
15 | akka.stdout-loglevel=OFF
16 | akka.loggers=["akka.PrintLogger"]
17 | """)))
18 | ```
19 |
20 | Now that we covered all ways to [« Create Props](props.html), let's look closer at one more API that is unsafe.
21 | When creating a `Props`, the preferred way is to use the `(Class[_], Any*)` overload, since this one does not create a closure.
22 | If you create a props from within an Actor using the `(=> Actor)` overload, you accidentally close over the `ActorContext`, that's shared state you don't want.
23 | The problem with the constructor using `Class`, you don't get any help from the compiler. If you change one parameter, there is nothing telling you to change the Props constructor but the eventual runtime error (from your tests, hopefully).
24 |
25 | Using shapeless, we can try to fix this issue.
26 |
27 | #### Using the creator module
28 |
29 | The types creator lives in a [separate module](http://search.maven.org/#search%7Cga%7C1%7Cg:%22de.knutwalker%22%20AND%20a:typed-actors-creator*) that you have to include first.
30 |
31 | ```scala
32 | libraryDependencies += "de.knutwalker" %% "typed-actors-creator" % "{{ site.data.version.version }}"
33 | ```
34 |
35 | Next, you _have_ to use the [`TypedActor`](typed-actor.html) trait and you _have_ to make your actor a `case class`.
36 | This is necessary, so that shapeless' generic machinery can pick up the required constructor parameters.
37 |
38 | ```tut:silent
39 | case class MyActor(param: String) extends TypedActor.Of[MyMessage] {
40 | def typedReceive = {
41 | case Foo(foo) => println(s"$param - received a Foo: $foo")
42 | case Bar(bar) => println(s"$param - received a Bar: $bar")
43 | }
44 | }
45 | ```
46 |
47 | Next, use the `Typed` constructor. It takes one type parameter, which is supposed to be your `TypedActor`.
48 | Now you can use two methods, `props` and `create`. Both accept the same arguments as the constructor of your `TypedActor` and will either return a typed `Props` or typed `ActorRef`, respectively (thanks to some shapeless magic).
49 |
50 | ```tut
51 | Typed[MyActor].props("Bernd")
52 | Typed[MyActor].create("Bernd")
53 | ActorOf(Typed[MyActor].props("Bernd"), "typed-bernd")
54 | ```
55 |
56 | Wrong invocations are greeted with a compile error instead of a runtime error!
57 |
58 | ```tut:fail
59 | Typed[MyActor].create()
60 | Typed[MyActor].create("Bernd", "Ralf")
61 | Typed[MyActor].create(42)
62 | ```
63 |
64 | Hooray, Benefit!
65 |
66 | As you can see, shapeless leaks in the error messages, but you can still easily see what parameters are wrong.
67 | This technique uses whitebox macros under the hood, which means that support from IDEs such as IntelliJ will be meager, so prepare for red, squiggly lines.
68 | If you open autocomplete on a `Typed[MyActor]`, you won't see the `create` or `props` methods but `createProduct` and `propsProduct`. This is a leaky implementation as well, better just ignore it and type against those IDE errors.
69 |
70 |
71 | The next bits are about the internals and some good pratices..
72 |
73 | ##### [» Implementation Notes](implementation.html)
74 |
75 |
76 | ```tut:invisible
77 | system.shutdown()
78 | ```
79 |
--------------------------------------------------------------------------------
/docs/src/tut/implementation.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: page
3 | title: Implementation Notes
4 | tut: 107
5 | ---
6 |
7 | ```tut:invisible
8 | import akka.actor._
9 | import de.knutwalker.akka.typed._
10 | implicit val system = ActorSystem("foo", config=Some(com.typesafe.config.ConfigFactory.parseString("""
11 | akka.loglevel=DEBUG
12 | akka.stdout-loglevel=OFF
13 | akka.loggers=["akka.PrintLogger"]
14 | akka.actor.debug.receive=off
15 | """)))
16 | ```
17 |
18 | Typed Actors are implemented as a type tag, a structural type refinement.
19 | This is very similar to [`scalaz.@@`](https://github.com/scalaz/scalaz/blob/81e68e845e91b54450a4542b19c1378f06aea861/core/src/main/scala/scalaz/package.scala#L90-L101) and a little bit to [`shapeless.tag.@@`](https://github.com/milessabin/shapeless/blob/6c659d253ba004baf74e20d5d815729552677303/core/src/main/scala/shapeless/typeoperators.scala#L28-L29)
20 | The message type is put together with the surrounding type (`ActorRef` or `Props`) into a special type, that exists only at compile time.
21 | It carries enough type information for the compiler reject certain calls to tell while not requiring any wrappers at runtime.
22 |
23 | The actual methods are provided by an implicit ops wrapper that extends AnyVal, so that there is no runtime overhead as well.
24 |
25 | The union type is inspired by shapeless' `HNil` or `Coproduct`. The main differences are: 1) There is no runtime, value-level representation and as such, there is no Inr/Inl/:: constructor, it's just the type `|` (instead of `::` or `:+:` for HList and Coproduct, respectively). 2) It doesn't have an end type, a base case like `HNil` or `CNil`. Other than that, the operations around the union type are similar to what you would write if you'd define a function for an HList: There is a typeclass representing the function and some implicit induction steps that recurse on the type.
26 | There are some other union type implementations out there, including the one that is offered by shapeless itself but they often just focus on offering membership testing as functionality, while `Typed Actors` also includes a union set comparison to check whether two union types cover the same elements without them being defined in the same order.
27 |
28 | #### Good Practices
29 |
30 | Typed Actors does not try to prevent you from doing fancy things and shooting yourself in the foot, it rather wants to give you a way so you can help yourself in keeping your sanity.
31 | That is, you can always switch between untyped and typed actors, even if the type information is not actually corresponding to the actors implementation. It is up to you to decide how much safety you want to trade in for flexibility.
32 | That being said, you get the most benefit by using the [TypedActor](typed-actor.html) with the [Typed Creator](creator.html) and only on the `typedReceive` and `typedBecome` methods with the `Total` wrapper. Depending on the situation, you can fairly fine-tune the amount of untypedness you want to have.
33 |
34 | One other thing that is frequently causing trouble is `sender()`.
35 | For one, it's not referentially transparent, return the sender of whatever message the Actor is currently processing. This is causing trouble when the `sender()` call happens for example in a callback attached to a `Future`.
36 | The other thing is, it's always an untyped actor and knowledge about the protocol has to be implicitly kept in the head of the developer.
37 | For that reasons, it is a good idea to always provide a `replyTo: ActorRef[A]` field in the message itself and refrain from using `sender()`, ideally ever.
38 |
39 | An example of how this could look like. First, the counter example using `sender()` as a quasi status quo.
40 | To have a sensible `sender()` available, we're gonna use `akka.actor.Inbox`.
41 |
42 | ```tut:silent
43 | import akka.actor.ActorDSL._
44 | val box = inbox()
45 | ```
46 |
47 | This is a typical request reply cycle using `sender()`.
48 |
49 | ```tut:silent
50 | case class MyMessage(payload: Int)
51 | case class MyResponse(payload: String)
52 | case class MyActor() extends TypedActor.Of[MyMessage] {
53 | def typedReceive = {
54 | case MyMessage(payload) => sender() ! payload.toString
55 | }
56 | }
57 | ```
58 |
59 | ```tut
60 | val ref = Typed[MyActor].create()
61 | box.send(ref.untyped, MyMessage(42))
62 | ```
63 |
64 | Note that there already is a bug, as the return message was not wrapped in `MyResponse`.
65 |
66 | ```tut:fail
67 | val MyResponse(response) = box.receive()
68 | ```
69 |
70 | Here's how that looks using the `replyTo` pattern.
71 |
72 | ```tut:silent
73 | case class MyResponse(payload: String)
74 | case class MyMessage(payload: Int)(val replyTo: ActorRef[MyResponse])
75 | case class MyActor() extends TypedActor.Of[MyMessage] {
76 | def typedReceive = {
77 | case m@MyMessage(payload) => m.replyTo ! MyResponse(payload.toString)
78 | }
79 | }
80 | ```
81 |
82 | ```tut
83 | val ref = Typed[MyActor].create()
84 | ref ! MyMessage(42)(box.receiver.typed)
85 | val MyResponse(response) = box.receive()
86 | ```
87 |
88 | Let's try to reproduce the bug from earlier.
89 |
90 | ```tut:fail
91 | case class MyActor() extends TypedActor.Of[MyMessage] {
92 | def typedReceive = {
93 | case m@MyMessage(payload) => m.replyTo ! payload.toString
94 | }
95 | }
96 | ```
97 |
98 | Now the compiler has caught the bug, benefit!
99 |
100 | The `replyTo` pattern is also important in [Akka Typed](http://doc.akka.io/docs/akka/snapshot/scala/typed.html).
101 |
102 | ##### [» Comparision with Akka Typed](comparison.html)
103 |
104 | ```tut:invisible
105 | system.shutdown()
106 | ```
107 |
--------------------------------------------------------------------------------
/docs/src/tut/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: page
3 | title: Basic Usage
4 | tut: 101
5 | ---
6 |
7 | To use **Typed Actors**, import the following:
8 |
9 | ```tut:silent
10 | import de.knutwalker.akka.typed._
11 | ```
12 |
13 | The underscore/wildcard import is important to bring some implicit classes into scope.
14 | These classes enable the actual syntax to use typed actors.
15 | Also, _Typed Actors_ shadows some names from `akka.actor`, so you need to make sure, that you add this import **after** your akka imports.
16 |
17 | ```tut:silent
18 | import akka.actor._
19 | import de.knutwalker.akka.typed._
20 | ```
21 |
22 | #### Actor Definition
23 |
24 | Using Typed Actors is, at first, similar to regular actors.
25 | It is always a good idea to define your message protocol.
26 |
27 | ```tut:silent
28 | sealed trait MyMessage
29 | case class Foo(foo: String) extends MyMessage
30 | case class Bar(bar: String) extends MyMessage
31 |
32 | case object SomeOtherMessage
33 | ```
34 |
35 | With that, define a regular actor.
36 |
37 | ```tut:silent
38 | class MyActor extends Actor {
39 | def receive = {
40 | case Foo(foo) => println(s"received a Foo: $foo")
41 | case Bar(bar) => println(s"received a Bar: $bar")
42 | }
43 | }
44 | ```
45 |
46 | #### Actor Creation
47 |
48 | Now, use `Props` and `ActorOf`. These are now the ones from `de.knutwalker.akka.typed`, not from `akka.actor`.
49 |
50 | ```tut
51 | implicit val system = ActorSystem("foo")
52 | val props = Props[MyMessage, MyActor]
53 | val ref = ActorOf(props, name = "my-actor")
54 | ```
55 |
56 | This will give you an `ActorRef[MyMessage]`.
57 |
58 | There are three possible ways to create a `Props`, mirroring the constructors from `akka.actor.Props`.
59 |
60 | ```tut
61 | val props = Props[MyMessage, MyActor]
62 | val props = Props[MyMessage, MyActor](new MyActor)
63 | val props = Props[MyMessage, MyActor](classOf[MyActor])
64 | ```
65 |
66 | #### Sending messages
67 |
68 | Sending messages to a typed actor is the same as sending messages to an untyped on, you use `!`.
69 |
70 | ```tut
71 | ref ! Foo("foo")
72 | ref ! Bar("bar")
73 | ```
74 |
75 | If you try to send a message from a different protocol, you will get a compile error. Hooray, benefit!
76 |
77 | ```tut:fail
78 | ref ! SomeOtherMessage
79 | ```
80 |
81 | #### Ask pattern
82 |
83 | Typed actors support the ask pattern, `?`, without imports and the returned Future is properly typed.
84 | In order to achieve this, instead of sending an already instantiated type, you send a function that, given the properly typed sender, will return the message.
85 | This is usually achieved with a separate parameter list on a case class (message), typically called `replyTo`.
86 |
87 | ```tut:silent
88 | case class MyResponse(payload: String)
89 | case class MyMessage(payload: String)(val replyTo: ActorRef[MyResponse])
90 | ```
91 |
92 | If you define your messages this way, you can left out the last parameter list and will get the required function.
93 | To respond, use `message.replyTo` instead of `sender()` to get the properly typed sender. Although, to be fair, `sender()` will be the same actor, it's just the untyped version.
94 | Finally, `?` requires an `implicit Timeout`, just like the regular, untyped ask.
95 |
96 | ```tut:silent
97 | import scala.concurrent.duration._
98 | import akka.util.Timeout
99 |
100 | class MyActor extends Actor {
101 | def receive = {
102 | case m@MyMessage(payload) => m.replyTo ! MyResponse(payload)
103 | }
104 | }
105 | implicit val timeout: Timeout = 1.second
106 | ```
107 |
108 | ```tut
109 | val ref = ActorOf(Props[MyMessage, MyActor])
110 | val future = ref ? MyMessage("foo")
111 | val response = scala.concurrent.Await.result(future, 1.second)
112 | ```
113 |
114 | Next up, learn how to mix multiple unrelated messages into the checked type.
115 |
116 | ##### [» Union Types](union.html)
117 |
118 |
119 | ```tut:invisible
120 | system.shutdown()
121 | ```
122 |
--------------------------------------------------------------------------------
/docs/src/tut/motivation.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: page
3 | title: Motivation
4 | tut: 100
5 | ---
6 |
7 | One critique of Akka, that [comes](https://groups.google.com/d/msg/akka-user/rLKk7-D_jHQ/M_Anx7vRNhcJ) up [every](http://noelwelsh.com/programming/2013/03/04/why-i-dont-like-akka-actors/#akkas-actors-are-not-usefully-typed) [now](http://stew.vireo.org/posts/I-hate-akka/) and [then](http://stackoverflow.com/q/5547947/2996265) is the lack of type safety. Actors essentially represent a `PartialFunction[Any, Unit]` which is, from a type point of view, something of the worst you can have. It tells you nothing useful; Anything can go in, it might or might not be processed and if so, anything anywhere anytime can happen. It forgoes all the benefits of a statically typed language.
8 | There are many reasons for this though, amongst others: location transparency and `context.become`. While its true that only `Any` allows us to model _everything_ that _can_ happen, it doesn't mean that everything _will always_ happen. Not every actor gets moved around between different nodes and changes its behavior to something completely unrelated over and over again.
9 |
10 | So, why not tell the compiler that we know something about certain actors and have it help us? We're in a statically typed language after all. We're used to compiler support when it comes to refactoring, design and composition. Why forgo this for the sake of a feature I don't want to use.
11 |
12 | Hence, `Typed Actors`!
13 |
14 | Akka underwent some experiments itself, for example from [typed-channels](http://doc.akka.io/docs/akka/2.2.0/scala/typed-channels.html) and [typed-actors](http://doc.akka.io/docs/akka/2.3.0/scala/typed-actors.html) to [akka-typed](http://doc.akka.io/docs/akka/2.4.0/scala/typed.html).
15 | Especially the last approach, `Akka Typed` is really nice and the benefit of having an `ActorRef[A]` lead to the creation of this library.
16 |
17 | `Typed Actors` has the following goals:
18 |
19 | - add a compile-time layer to existing `ActorRef`s with minimal runtime overhead
20 | - be compatible with all of the existing Akka modules, traits, and extensions in terms of composition and behavior
21 |
22 | and the following non-goals:
23 |
24 | - enforce an impenetrable mantle of types, don't fight the users knowledge about the actor system, those *are* dynamic after all
25 | - support Java
26 |
27 | So, let's dive in.
28 |
29 | ##### [» Basic Usage](index.html)
30 |
--------------------------------------------------------------------------------
/docs/src/tut/props.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: page
3 | title: Building Props
4 | tut: 105
5 | ---
6 |
7 | With the [« Typed Actor](typed-actor.html) in place, we can now look at more ways to define a `Props`.
8 |
9 | ```tut:invisible
10 | import akka.actor._
11 | import de.knutwalker.akka.typed._
12 | sealed trait MyMessage
13 | case class Foo(foo: String) extends MyMessage
14 | case class Bar(bar: String) extends MyMessage
15 | case object SomeOtherMessage
16 | implicit val system = ActorSystem("foo", config=Some(com.typesafe.config.ConfigFactory.parseString("""
17 | akka.stdout-loglevel=OFF
18 | akka.loggers=["akka.PrintLogger"]
19 | """)))
20 | ```
21 |
22 | #### Message Type Derivation
23 |
24 | When creating a props for a `TypeActor`, we can derive the message type and thus reduce the amount of type annotation we have to write.
25 | This is done with `PropsFor`.
26 |
27 | Consider this typed actor.
28 |
29 | ```tut:silent
30 | class MyActor extends TypedActor.Of[MyMessage] {
31 | def typedReceive = {
32 | case Foo(foo) => println(s"received a Foo: $foo")
33 | }
34 | }
35 | ```
36 |
37 | Using `Props` we have to repeat the information, that this actor only accepts messages of type `MyMessage`, although the compiler knows about this.
38 |
39 | ```tut
40 | Props[MyMessage, MyActor] // MyMessage is repetitive
41 | Props(new MyActor) // message type derives as Nothing
42 | Props[MyMessage, MyActor](new MyActor) // MyMessage and MyActor are repetitive
43 | Props(classOf[MyActor]) // message type derives as Nothing
44 | Props[MyMessage, MyActor](classOf[MyActor]) // MyMessage and MyActor are repetitive
45 | ```
46 |
47 | When you have a `TypedActor`, you can use `PropsFor` instead of `Props` to use the type information embedded in `TypedActor#Message`.
48 |
49 | ```tut
50 | PropsFor[MyActor]
51 | PropsFor(new MyActor)
52 | PropsFor(classOf[MyActor])
53 | ```
54 |
55 | Of course, some of these cases can also be mitigated by using type ascription on the result type.
56 |
57 | ```tut
58 | val props: Props[MyMessage] = Props(new MyActor)
59 | val props: Props[MyMessage] = Props(classOf[MyActor])
60 | val props: Props[MyMessage] = PropsFor[MyActor]
61 | val props: Props[MyMessage] = PropsFor(new MyActor)
62 | val props: Props[MyMessage] = PropsFor(classOf[MyActor])
63 | ```
64 |
65 | #### Type Currying for Props
66 |
67 | `PropsFor` only works with a `TypedActor`. There is yet another way to create a `Props`, that has the type information curried, `PropsOf`.
68 | With `PropsOf`, you apply once with the message type and then use one of the three ways to create a `Props`. This works for all actors
69 |
70 | ```tut
71 | PropsOf[MyMessage][MyActor]
72 | PropsOf[MyMessage](new MyActor)
73 | PropsOf[MyMessage](classOf[MyActor])
74 | ```
75 |
76 | Next, look at how you can improve type safety even further.
77 |
78 | ##### [» Typed Creator](creator.html)
79 |
80 | ```tut:invisible
81 | system.shutdown()
82 | ```
83 |
--------------------------------------------------------------------------------
/docs/src/tut/typed-actor.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: page
3 | title: TypedActor
4 | tut: 104
5 | ---
6 |
7 | We will reuse the definitions and actors from the [« Unsafe Usage](unsafe.html).
8 |
9 | ```tut:invisible
10 | import akka.actor._
11 | import de.knutwalker.akka.typed._
12 | sealed trait MyMessage
13 | case class Foo(foo: String) extends MyMessage
14 | case class Bar(bar: String) extends MyMessage
15 | case object SomeOtherMessage
16 | implicit val system = ActorSystem("foo", config=Some(com.typesafe.config.ConfigFactory.parseString("""
17 | akka.loglevel=DEBUG
18 | akka.stdout-loglevel=OFF
19 | akka.loggers=["akka.PrintLogger"]
20 | akka.actor.debug.receive=off
21 | """)))
22 | ```
23 |
24 | Having a typed reference to an actor is one thing, but how can we improve type-safety within the actor itself?
25 | **Typed Actors** offers a `trait` called `TypedActor` which you can extend from instead of `Actor`.
26 | `TypedActor` itself extends `Actor` but contains an abstract type member and typed receive method
27 | instead of just an untyped receive method.
28 | In order to use the `TypedActor`, you have to extend `TypedActor.Of[_]` and provide your message type via type parameter.
29 |
30 | ```tut
31 | class MyActor extends TypedActor.Of[MyMessage] {
32 | def typedReceive = {
33 | case Foo(foo) => println(s"received a Foo: $foo")
34 | case Bar(bar) => println(s"received a Bar: $bar")
35 | }
36 | }
37 | val ref = ActorOf(Props[MyMessage, MyActor], name = "my-actor")
38 | ref ! Foo("foo")
39 | ref ! Bar("bar")
40 | ```
41 |
42 | If you match on messages from a different type, you will get a compile error.
43 |
44 | ```tut:fail
45 | class MyActor extends TypedActor.Of[MyMessage] {
46 | def typedReceive = {
47 | case SomeOtherMessage => println("received some other message")
48 | }
49 | }
50 | ```
51 |
52 |
53 | #### Divergence
54 |
55 | Similar to the untyped actor, `context.become` is not hidden and can still lead to diverging actors.
56 |
57 | ```tut:invisible
58 | import akka.LoggingReceive
59 | system.shutdown()
60 | implicit val system = ActorSystem("foo", config=Some(com.typesafe.config.ConfigFactory.parseString("""
61 | akka.loglevel=DEBUG
62 | akka.stdout-loglevel=OFF
63 | akka.loggers=["akka.PrintLogger"]
64 | akka.actor.debug.receive=on
65 | """)))
66 | ```
67 |
68 | ```tut
69 | class MyOtherActor extends TypedActor.Of[MyMessage] {
70 | def typedReceive = {
71 | case Foo(foo) => println(s"received a Foo: $foo")
72 | case Bar(bar) => context become LoggingReceive {
73 | case SomeOtherMessage => println("received some other message")
74 | }
75 | }
76 | }
77 | val otherRef = ActorOf(Props[MyMessage, MyOtherActor], "my-other-actor")
78 |
79 | otherRef ! Foo("foo")
80 | otherRef ! Bar("bar")
81 | otherRef ! Foo("baz")
82 | otherRef.untyped ! SomeOtherMessage
83 | ```
84 |
85 | #### More Typing
86 |
87 | The `TypedActor` offers some more methods that ought to help with keeping within the defined type bound.
88 | There is `typedSelf`, which is the typed version of the regular `self`.
89 | Then there is `typedBecome`, the typed version of `context.become`. It takes a partial receive function, much like `typedReceive`.
90 |
91 | Using `typedBecome`, diverging from the type bound is no longer possible
92 |
93 | ```tut:fail
94 | class MyOtherActor extends TypedActor.Of[MyMessage] {
95 | def typedReceive = {
96 | case Foo(foo) => println(s"received a Foo: $foo")
97 | case Bar(bar) => typedBecome {
98 | case SomeOtherMessage => println("received some other message")
99 | }
100 | }
101 | }
102 | ```
103 |
104 | You can event get exhaustiveness checks from the compiler by using the `Total` wrapper.
105 |
106 | ```tut
107 | class MyOtherActor extends TypedActor.Of[MyMessage] {
108 | def typedReceive = Total {
109 | case Foo(foo) => println(s"received a Foo: $foo")
110 | }
111 | }
112 | ```
113 |
114 | Please be aware of a ~~bug~~ feature that wouldn't fail on non-exhaustive checks.
115 | If you use guards in your matchers, the complete pattern is optimistically treated as exhaustive; See [SI-5365](https://issues.scala-lang.org/browse/SI-5365), [SI-7631](https://issues.scala-lang.org/browse/SI-7631), and [SI-9232](https://issues.scala-lang.org/browse/SI-9232). Note the missing non-exhaustiveness warning in the next example.
116 |
117 | ```tut
118 | val False = false
119 | class MyOtherActor extends TypedActor.Of[MyMessage] {
120 | def typedReceive = Total {
121 | case Foo(foo) if False =>
122 | }
123 | }
124 | ```
125 |
126 | Unfortunately, this cannot be worked around by library code. Even worse, this would not result in a unhandled message but in a runtime match error.
127 |
128 | #### Working with Union Types
129 |
130 | Union typed [before](union.html) were declared on an already existing `Props` or `ActorRef` but how can we use union types together with `TypedActor`?
131 |
132 | ```tut:silent
133 | case class Foo(foo: String)
134 | case class Bar(bar: String)
135 | case class Baz(baz: String)
136 | case object SomeOtherMessage
137 | ```
138 |
139 | (We're shadowing the previous definition of `Foo` and `Bar` here, they are reverted after this chapter).
140 |
141 | Since union types are implemented at the type-level, there is no runtime value possible that would allow us to discriminate between those subtypes when running the receive block.
142 |
143 | ```tut:fail
144 | class MyActor extends TypedActor.Of[Foo | Bar | Baz] {
145 | def typedReceive: TypedReceive = {
146 | case Foo(foo) => println(s"received a Foo: $foo")
147 | case Bar(bar) => println(s"received a Bar: $bar")
148 | case Baz(baz) => println(s"received a Baz: $baz")
149 | }
150 | }
151 | ```
152 |
153 | We have to do this discrimination at type-level as well. Don't worry, it's less complicated as that sound. As a side note, sum types like `Either` are sometimes referred to as tagged union, the tag being the thing that would help us to discrimite at runtime – our union type is an untagged union instead.
154 |
155 | The basics stay the same, you still extends `TypedActor.Of` and implement `typedReceive` but this time using either `Union` or `TotalUnion`. Use `Union` if you only cover some of the union types cases and `TotalUnion` if you want to cover _all_ cases. The compiler can perform exhaustiveness checks on the latter.
156 | Both methods return a builder-style object that has an `on` method that must be used to enumerate the individual subcases of the union type and you close with a call to `apply`.
157 |
158 | ```tut
159 | class MyActor extends TypedActor.Of[Foo | Bar | Baz] {
160 | def typedReceive: TypedReceive = Union
161 | .on[Foo]{ case Foo(foo) => println(s"received a Foo: $foo") }
162 | .on[Bar]{ case Bar(bar) => println(s"received a Bar: $bar") }
163 | .on[Baz]{ case Baz(baz) => println(s"received a Baz: $baz") }
164 | .apply
165 | }
166 | ```
167 |
168 | Or if you have a total function for the cases, there is a shortcut:
169 |
170 | ```tut
171 | class MyActor extends TypedActor.Of[Foo | Bar | Baz] {
172 | def typedReceive: TypedReceive = Union
173 | .total[Foo]{ foo ⇒ println(s"received a Foo: $foo.foo") }
174 | .total[Bar]{ bar ⇒ println(s"received a Bar: $bar.bar") }
175 | .total[Baz]{ baz ⇒ println(s"received a Baz: $baz.baz") }
176 | .apply
177 | }
178 | ```
179 |
180 | You have to provide at least one case, you cannot define an empty behavior.
181 |
182 | ```tut:fail
183 | class MyActor extends TypedActor.Of[Foo | Bar | Baz] {
184 | def typedReceive: TypedReceive = Union
185 | .apply
186 | }
187 | ```
188 |
189 |
190 | If you remove one of those cases it still compiles, since `Union` does not check for exhaustiveness.
191 |
192 | ```tut
193 | class MyActor extends TypedActor.Of[Foo | Bar | Baz] {
194 | def typedReceive: TypedReceive = Union
195 | .on[Foo]{ case Foo(foo) => println(s"received a Foo: $foo") }
196 | .on[Baz]{ case Baz(baz) => println(s"received a Baz: $baz") }
197 | .apply
198 | }
199 | ```
200 |
201 | If you switch to `TotalUnion` you can see the compiler message telling that something is missing. Unfortunately it doesn't tell you _which_ case is missing exactly, although that might change in the future.
202 |
203 | ```tut:fail
204 | class MyActor extends TypedActor.Of[Foo | Bar | Baz] {
205 | def typedReceive: TypedReceive = TotalUnion
206 | .on[Foo]{ case Foo(foo) => println(s"received a Foo: $foo") }
207 | .on[Baz]{ case Baz(baz) => println(s"received a Baz: $baz") }
208 | .apply
209 | }
210 | ```
211 |
212 | You can even leave out the call to `apply`.
213 |
214 | ```tut
215 | class MyActor extends TypedActor.Of[Foo | Bar | Baz] {
216 | def typedReceive: TypedReceive = Union
217 | .on[Foo]{ case Foo(foo) ⇒ println(s"received a Foo: $foo") }
218 | .on[Baz]{ case Baz(baz) ⇒ println(s"received a Baz: $baz") }
219 | }
220 | ```
221 |
222 | Which is true for `TotalUnion` as well.
223 |
224 | ```tut
225 | class MyActor extends TypedActor.Of[Foo | Bar | Baz] {
226 | def typedReceive: TypedReceive = TotalUnion
227 | .on[Foo]{ case Foo(foo) ⇒ println(s"received a Foo: $foo") }
228 | .on[Bar]{ case Bar(bar) ⇒ println(s"received a Bar: $bar") }
229 | .on[Baz]{ case Baz(baz) ⇒ println(s"received a Baz: $baz") }
230 | }
231 | ```
232 |
233 | As you can see, you basically provide a receive block for all relevant subtypes of the union. One such receive block is typed in its input, though you cannot use the `Total` helper as this one is fixed on the complete message type, the union type itself in this case.
234 |
235 | ```tut:fail
236 | class MyActor extends TypedActor.Of[Foo | Bar | Baz] {
237 | def typedReceive: TypedReceive = Union
238 | .on[Foo](Total { case Foo(foo) => println(s"received a Foo: $foo") })
239 | .apply
240 | }
241 | ```
242 |
243 | At any rate, the `Props` and `ActorRef` from this `TypedActor` are union typed as well.
244 |
245 | ```tut
246 | val props = PropsFor[MyActor]
247 | val ref = ActorOf(props)
248 |
249 | ref ! Foo("foo")
250 | ref ! Bar("bar")
251 | ref ! Baz("baz")
252 | ```
253 |
254 | ```tut:fail
255 | ref ! SomeOtherMessage
256 | ```
257 |
258 |
259 | If you want to `context.become` with a union type there are some options.
260 |
261 | 1. You can use the `Union`/`TotalUnion` helper as described earlier.
262 | 2. You can use `unionBecome` if you only want to cover _one_ particular case.
263 | It is a shortcut for `typedBecome(Union.on[Msg]{ case ... }.apply)`
264 |
265 |
266 | ```tut
267 | class MyActor extends TypedActor.Of[Foo | Bar | Baz] {
268 | def typedReceive: TypedReceive = Union
269 | .on[Foo]{
270 | case Foo(foo) =>
271 | unionBecome.on[Bar] {
272 | case Bar(bar) => println(s"received a Boo: $bar")
273 | }
274 | }
275 | .apply
276 | }
277 | ```
278 |
279 |
280 | #### Stateless actor from a total function
281 |
282 | ```tut:invisible
283 | sealed trait MyMessage
284 | case class Foo(foo: String) extends MyMessage
285 | case class Bar(bar: String) extends MyMessage
286 | ```
287 |
288 | The companion object `TypedActor` has an `apply` method that wraps a total function in an actor and returns a prop for this actor.
289 |
290 | ```tut
291 | val ref = ActorOf(TypedActor[MyMessage] {
292 | case Foo(foo) => println(s"received a Foo: $foo")
293 | case Bar(bar) => println(s"received a Bar: $bar")
294 | })
295 | ```
296 |
297 |
298 | #### Low-level TypedActor
299 |
300 | You can also directly extend `TypedActor`, in which case you have to implement the abstract type `Message`. The `Of` constructor just does this for you by getting all information from the defined type parameter.
301 | You want to use this you need the `TypedActor` as a trait, for example when mixing it together with other Actor traits, like `PersistenActor`.
302 | For normal use-case, extending `TypedActor.Of[_]` is encouraged.
303 |
304 |
305 | ```tut
306 | import scala.reflect.classTag
307 | class MyTypedActor extends TypedActor {
308 | type Message = MyMessage
309 |
310 | def typedReceive = {
311 | case Foo(foo) =>
312 | }
313 | }
314 | ```
315 |
316 | You can even override the `receive` method, if you have to, using the `untypedFromTyped` method.
317 |
318 | ```tut
319 | class MyTypedActor extends TypedActor {
320 | type Message = MyMessage
321 |
322 | override def receive =
323 | untypedFromTyped(typedReceive)
324 |
325 | def typedReceive = {
326 | case Foo(foo) =>
327 | }
328 | }
329 | ```
330 |
331 | Using this, you can mix a `TypedActor` and a `PersistentActor` together.
332 |
333 | ```tut
334 | import akka.persistence.PersistentActor
335 |
336 | class TypedPersistentActor extends TypedActor with PersistentActor with ActorLogging {
337 | type Message = MyMessage
338 |
339 | def persistenceId: String = "typed-persistent-id"
340 |
341 | val receiveRecover: Receive = akka.actor.Actor.emptyBehavior
342 |
343 | val typedReceive: TypedReceive = {
344 | case foo: Foo =>
345 | persist(foo)(f => context.system.eventStream.publish(foo))
346 | }
347 |
348 | val receiveCommand: Receive =
349 | untypedFromTyped(typedReceive)
350 |
351 | override def receive: Receive =
352 | receiveCommand
353 | }
354 | ```
355 |
356 |
357 | #### Going back to untyped land
358 |
359 | Sometimes you have to receive messages that are outside of your protocol. A typical case is `Terminated`, but other modules and patterns have those messages as well.
360 | You can use `Untyped` to specify a regular untyped receive block, just as if `receive` were actually the way to go. `Untyped` also works with union types without any special syntax.
361 |
362 |
363 | ```tut
364 | class MyOtherActor extends TypedActor.Of[MyMessage] {
365 | def typedReceive = Untyped {
366 | case Terminated(ref) => println(s"$ref terminated")
367 | case Foo(foo) => println(s"received a Foo: $foo")
368 | }
369 | }
370 | ```
371 |
372 | With `Untyped`, you won't get any compiler support, it is meant as an escape hatch; If you find yourself using `Untyped` all over the place, consider just using a regular `Actor` instead.
373 |
374 | Next, learn more ways to create `Props`.
375 |
376 | ##### [» Building Props](props.html)
377 |
378 |
379 | ```tut:invisible
380 | system.shutdown()
381 | ```
382 |
--------------------------------------------------------------------------------
/docs/src/tut/union.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: page
3 | title: Union typed actors
4 | tut: 102
5 | ---
6 |
7 | We will assume imports and the actor system from the [« Basic Usage](index.html).
8 |
9 | ```tut:invisible
10 | import akka.actor._
11 | import akka.LoggingReceive
12 | import de.knutwalker.akka.typed._
13 | implicit val system = ActorSystem("foo", config=Some(com.typesafe.config.ConfigFactory.parseString("""
14 | akka.loglevel=DEBUG
15 | akka.stdout-loglevel=OFF
16 | akka.loggers=["akka.PrintLogger"]
17 | akka.actor.debug.receive=on
18 | """)))
19 | ```
20 |
21 |
22 | #### Unrelated Messages
23 |
24 | The actor messages [before](index.html#actor-definition) were defined as a sealed trait so that the actor can deal with all subclasses of this trait. That way the actor can deal with multiple types of messages.
25 | This works great if you're in control of the messages, unfortunately this is not always the case. Sometimes you have to write an actor that receives multiple messages that are not part of the same sealed trait, possibly because you don't own said messages.
26 | To still use `Typed Actors`, you could use `Any`, which is just as bad as using untyped actors directly.
27 | Alternatively, you could use a [sum type](https://en.wikipedia.org/wiki/Sum_type) like `Either`, define the actor as `ActorRef[Either[A, B]]` and pattern match on the either in the receive block. This has some drawbacks though.
28 | First, listing more than 2 different messages with Either gets very tedious and you'll probably start writing specific custom sum types for each different set of messages and end up with sealed traits that do nothing but wrap other messages and are thus just noisy boilerplate.
29 | Second, there is a runtime overhead involved of wrapping and unwrapping the message in the sum type, i.e. you have to `apply` and `unapply` the `Left`/`Right` instances.
30 | Third, and probably the most disruptive one, you cannot send any of the summed types directly but have to wrap them at tellsite, coupling the actor to the chosen sum type. This also means, that you cannot write proxy-like actors that sit in-between other actors because you have to change the messages.
31 |
32 | `Typed Actors` offer an easy alternative, that solves all the aforementioned problems: **Union Types**.
33 | Both, `ActorRef[A]` and `Props[A]`, have a `or[B]` method, that turns those types into an `ActorRef[A | B]` or a `Props[A | B]`, respectively.
34 | `A | B` is a so called [union type](http://ktoso.github.io/scala-types-of-types/#union-type-span-style-color-red-span) (also sometimes called a disjoint or discriminated union) meaning it is either `A` or `B`. In this regard, it serves the same purpose as `Either[A, B]`, but it is a pure type-level construct. There is no runtime value possible for `A | B`, it is intended to be used a [phantom type](http://ktoso.github.io/scala-types-of-types/#phantom-type) to allow the compiler to apply specific constraints on certain methods.
35 | You, as a library user, needn't worry about this; just read `A | B` as `A or B`.
36 | As a side note, the implementation is different than the one provided by Miles, referenced in the link above and, dare I say, simpler; it's not based on Curry-Howard isomorphism and doesn't require unicode to type.
37 |
38 | You can call `or` multiple times, creating an ever-growing union type. For example `ActorRef[A].or[B].or[C].or[D]` yields `ActorRef[A | B | C | D]`, which just reads `A or B or C or D`. There is no restriction on the length (certainly not at 22), although compile times will suffer for very large union types.
39 | This solves the first problem, enumerating many types just works naturally. To be fair, this is mainly due to the infix notation. You could write `A Either B Either C` as well, but that's just weird while `A | B | C` comes naturally.
40 | As mentioned before, `|` is a pure typelevel construct—there is no runtime value, not event a simple wrapper. This fact solves both, the aforementioned second and third issue. Since there is not even a valid runtime representation, there can be no overhead and there is no wrapping required at tellsite.
41 | Okay, enough theory – lets see union types in action.
42 |
43 | #### Union types
44 |
45 | First, let's define some unrelated messages. Note that these are not part of a sealed trait hierarchy.
46 |
47 | ```tut:silent
48 | case class Foo(foo: String)
49 | case class Bar(bar: String)
50 | case class Baz(baz: String)
51 | case object SomeOtherMessage
52 | ```
53 |
54 | Now, let's define an actor that receives all of these messages.
55 |
56 | ```tut:silent
57 | class MyActor extends Actor {
58 | def receive = {
59 | case Foo(foo) => println(s"received a Foo: $foo")
60 | case Bar(bar) => println(s"received a Bar: $bar")
61 | case Baz(baz) => println(s"received a Baz: $baz")
62 | }
63 | }
64 | ```
65 |
66 | Define a `Props` for one of those messages.
67 |
68 | ```tut
69 | val props: Props[Foo] = Props[Foo, MyActor]
70 | ```
71 |
72 | Now just list the other message types using `or`, either on the `Props` or on a created `ActorRef`.
73 |
74 | ```tut
75 | val props2: Props[Foo | Bar] = props.or[Bar]
76 | val ref2: ActorRef[Foo | Bar] = ActorOf(props2, name = "my-actor")
77 | val ref: ActorRef[Foo | Bar | Baz] = ref2.or[Baz]
78 | ```
79 |
80 | Now you can send either one of the messages that are listed in the union type.
81 |
82 | ```tut
83 | ref ! Foo("foo")
84 | ref ! Bar("bar")
85 | ref ! Baz("baz")
86 | ```
87 |
88 | And if you try to send a message that is not part of the type union, you will get a compile error.
89 |
90 | ```tut:fail
91 | ref ! SomeOtherMessage
92 | ```
93 |
94 | As you can see, there are no wrappers involved. When you send the message, the compiler checks that the message you want to send is part of the union and if this checks succeeds, the compiler will allow the call to `!` (by not failing to compile).
95 | Since there can be no runtime value of the union type, there is a clear distinction for the dispatch to the check if the message itself is the specified type or a subtype thereof and the check if the message is part of the specified union type.
96 |
97 | You can turn an actor that accepts an union type into of its subcases with `only`:
98 |
99 | ```tut
100 | ref.only[Foo]
101 | ref.only[Bar]
102 | ref.only[Baz]
103 | ```
104 |
105 | Which checks the untion type as well.
106 |
107 | ```tut:fail
108 | ref.only[SomeOtherMessage]
109 | ```
110 |
111 |
112 | Union types will return later; for now, the next part is to learn how to interact with the less safer parts of Akka.
113 |
114 | ##### [» Unsafe Usage](unsafe.html)
115 |
116 |
117 | ```tut:invisible
118 | system.shutdown()
119 | ```
120 |
--------------------------------------------------------------------------------
/docs/src/tut/unsafe.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: page
3 | title: Unsafe Usage
4 | tut: 103
5 | ---
6 |
7 | We will reuse the definitions and actors from the [« Basic Usage](index.html).
8 |
9 | ```tut:invisible
10 | import akka.actor._
11 | import akka.LoggingReceive
12 | import de.knutwalker.akka.typed._
13 | sealed trait MyMessage
14 | case class Foo(foo: String) extends MyMessage
15 | case class Bar(bar: String) extends MyMessage
16 | case object SomeOtherMessage
17 | class MyActor extends Actor {
18 | def receive = {
19 | case Foo(foo) => println(s"received a Foo: $foo")
20 | case Bar(bar) => println(s"received a Bar: $bar")
21 | }
22 | }
23 | implicit val system = ActorSystem("foo", config=Some(com.typesafe.config.ConfigFactory.parseString("""
24 | akka.loglevel=DEBUG
25 | akka.stdout-loglevel=OFF
26 | akka.loggers=["akka.PrintLogger"]
27 | akka.actor.debug.receive=on
28 | """)))
29 | val props = Props[MyMessage, MyActor]
30 | ```
31 |
32 | ```tut
33 | val typedRef = ActorOf[MyMessage](props, name = "my-actor")
34 | ```
35 |
36 | #### Autoreceived Messages
37 |
38 | Some messages are automatically handled by some actors and need or cannot be provided in the actors type.
39 | One example is `PoisonPill`. To sent those kind of messages anyway, use `unsafeTell`.
40 |
41 | ```tut
42 | typedRef.unsafeTell(PoisonPill)
43 | ```
44 |
45 | #### Switch Between Typed and Untyped
46 |
47 | Also, some Akka APIs require you to pass an untyped ActorRef (the regular ActorRef).
48 | You can easily turn your typed actor into an untyped one bu using `untyped`.
49 |
50 | ```tut
51 | val untypedRef = typedRef.untyped
52 | ```
53 |
54 | For convenience, `akka.actor.ActorRef` is type aliased as `de.knutwalker.akka.typed.UntypedActorRef`.
55 | Similarly, you can turn any untyped ref into a typed one using `typed`.
56 |
57 | ```tut
58 | val typedAgain = untypedRef.typed[MyMessage]
59 | ```
60 |
61 | As scala tends to infer `Nothing` as the most specific bottom type, you want to make sure to always provide a useful type.
62 |
63 | ```tut
64 | untypedRef.typed
65 | ```
66 |
67 | #### Compiletime only
68 |
69 | There are no compiler checks to make sure, that the given actually is able to receive that kind of message.
70 | This signifies the point, that **Typed Actors** are really just a compile-time wrapper and do not carry any kind of runtime information.
71 | To further demonstrate this, you can see that both instances are actually the very same (despite the scalac warning).
72 |
73 | ```tut
74 | typedRef eq untypedRef
75 | ```
76 |
77 | #### Divergence
78 |
79 | This also means, that it is possible to diverge from the specified type with `context.become`.
80 |
81 | ```tut
82 | class MyOtherActor extends Actor {
83 | def receive = LoggingReceive {
84 | case Foo(foo) => println(s"received a Foo: $foo")
85 | case Bar(bar) => context become LoggingReceive {
86 | case SomeOtherMessage => println("received some other message")
87 | }
88 | }
89 | }
90 | val otherRef = ActorOf(Props[MyMessage, MyOtherActor], "my-other-actor")
91 |
92 | otherRef ! Foo("foo")
93 | otherRef ! Bar("bar")
94 | otherRef ! Foo("baz")
95 | otherRef.untyped ! SomeOtherMessage
96 | ```
97 |
98 | Making sure, that this cannot happen is outside of the scope of **Typed Actors**.
99 | There is, however, a `TypedActor` trait which tries to provide _some_ help. Learn about it next.
100 |
101 | ##### [» Typed Actor](typed-actor.html)
102 |
103 |
104 | ```tut:invisible
105 | system.shutdown()
106 | ```
107 |
--------------------------------------------------------------------------------
/examples/src/test/resources/application.conf:
--------------------------------------------------------------------------------
1 | # Copyright 2015 – 2016 Paul Horn
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | akka.persistence{
16 | snapshot-store.local.dir = "target/snapshots"
17 | journal.leveldb {
18 | native = off
19 | dir = "target/journal"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/examples/src/test/scala/org/example/PersistenceExample.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 – 2016 Paul Horn
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package org.example
18 |
19 | import akka.actor.{ ActorLogging, ActorSystem }
20 | import akka.persistence.{ SnapshotOffer, PersistentActor }
21 | import de.knutwalker.akka.typed._
22 |
23 | import scala.concurrent.duration._
24 |
25 | object PersistenceExample extends App {
26 |
27 | case class Ping(replyTo: ActorRef[Pong])
28 | case class Pong(replyTo: ActorRef[Ping])
29 |
30 | case class Evt(data: String)
31 |
32 | case class ExampleState(events: List[String] = Nil) {
33 | def updated(evt: Evt): ExampleState = copy(evt.data :: events)
34 | def size: Int = events.length
35 | override def toString: String = events.reverse.toString
36 | }
37 |
38 | class TypedPersistentPingActor extends TypedActor with PersistentActor with ActorLogging {
39 | type Message = Ping
40 |
41 | def persistenceId: String = "typed-persistent-ping-id"
42 |
43 | var state = ExampleState()
44 |
45 | def updateState(event: Evt): Unit =
46 | state = state.updated(event)
47 |
48 | def numEvents =
49 | state.size
50 |
51 | val receiveRecover: Receive = {
52 | case evt: Evt => updateState(evt)
53 | case SnapshotOffer(_, snapshot: ExampleState) => state = snapshot
54 | }
55 |
56 | val typedReceive: TypedReceive = {
57 | case Ping(replyTo) ⇒
58 | persist(Evt(s"$numEvents"))(updateState)
59 | persist(Evt(s"${numEvents + 1}")) { event =>
60 | updateState(event)
61 | replyTo ! Pong(typedSelf)
62 | }
63 | }
64 |
65 | val receiveCommand: Receive =
66 | untypedFromTyped(typedReceive).orElse {
67 | case "snap" => saveSnapshot(state)
68 | case "print" => println(state)
69 | }
70 |
71 | override def receive: Receive =
72 | receiveCommand
73 |
74 | override def postStop(): Unit = {
75 | log.info(s"state = $state")
76 | super.postStop()
77 | }
78 | }
79 |
80 | class TypedPongActor extends TypedActor.Of[Pong] with ActorLogging {
81 | private[this] var count = 0
82 | val typedReceive: TypedReceive = {
83 | case Pong(replyTo) ⇒
84 | count += 1
85 | replyTo ! Ping(typedSelf)
86 | }
87 |
88 | override def postStop(): Unit = {
89 | log.info(s"pings: $count")
90 | super.postStop()
91 | }
92 |
93 | override def preStart(): Unit = {
94 | import context.dispatcher
95 | super.preStart()
96 | context.system.scheduler.scheduleOnce(600.millis)(Shutdown(system))
97 | ()
98 | }
99 | }
100 |
101 |
102 |
103 | implicit val system = ActorSystem()
104 | val ping = ActorOf(PropsFor[TypedPersistentPingActor], "ping")
105 | val pong = ActorOf(PropsFor[TypedPongActor], "pong")
106 |
107 | ping ! Ping(pong)
108 | }
109 |
--------------------------------------------------------------------------------
/examples/src/test/scala/org/example/SimpleExample.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 – 2016 Paul Horn
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package org.example
18 |
19 | import akka.actor.{ Actor, ActorLogging, ActorSystem }
20 | import de.knutwalker.akka.typed._
21 |
22 | object SimpleExample extends App {
23 |
24 | case class Ping(replyTo: ActorRef[Pong])
25 | case class Pong(replyTo: ActorRef[Ping])
26 |
27 | implicit val system = ActorSystem()
28 | val ping = ActorOf(PropsOf[Ping](new Actor with ActorLogging {
29 | private[this] var count = 0
30 | def receive: Receive = {
31 | case Ping(replyTo) ⇒
32 | count += 1
33 | replyTo ! Pong(self.typed)
34 | }
35 |
36 | override def postStop(): Unit = {
37 | log.info(s"pings: $count")
38 | }
39 | }))
40 | val pong = ActorOf(PropsOf[Pong](new Actor with ActorLogging {
41 | private[this] var count = 0
42 |
43 | def receive: Receive = {
44 | case Pong(replyTo) ⇒
45 | count += 1
46 | replyTo ! Ping(self.typed)
47 | }
48 |
49 | override def postStop(): Unit = {
50 | log.info(s"pongs: $count")
51 | }
52 | }))
53 |
54 | ping ! Ping(pong)
55 |
56 | Thread.sleep(1000)
57 |
58 | Shutdown(system)
59 | }
60 |
--------------------------------------------------------------------------------
/examples/src/test/scala/org/example/TypedActorExample.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 – 2016 Paul Horn
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package org.example
18 |
19 | import akka.actor.{ ActorLogging, ActorSystem }
20 | import de.knutwalker.akka.typed._
21 |
22 | object TypedActorExample extends App {
23 |
24 | case class Ping(replyTo: ActorRef[Pong])
25 | case class Pong(replyTo: ActorRef[Ping])
26 |
27 | implicit val system = ActorSystem()
28 |
29 | class PingActor extends TypedActor.Of[Ping] with ActorLogging {
30 | private[this] var count = 0
31 |
32 | def typedReceive: TypedReceive = Total {
33 | case Ping(replyTo) ⇒
34 | count += 1
35 | replyTo ! Pong(typedSelf)
36 | }
37 |
38 | override def postStop(): Unit = {
39 | log.info(s"pings: $count")
40 | }
41 | }
42 |
43 | class PongActor extends TypedActor.Of[Pong] with ActorLogging {
44 | private[this] var count = 0
45 |
46 | def typedReceive: TypedReceive = Total {
47 | case Pong(replyTo) ⇒
48 | count += 1
49 | replyTo ! Ping(typedSelf)
50 | }
51 |
52 | override def postStop(): Unit = {
53 | log.info(s"pongs: $count")
54 | }
55 | }
56 |
57 | val ping = ActorOf(PropsOf[Ping](new PingActor))
58 | val pong = ActorOf(PropsOf[Pong](new PongActor))
59 |
60 | ping ! Ping(pong)
61 |
62 | Thread.sleep(1000)
63 |
64 | Shutdown(system)
65 | }
66 |
--------------------------------------------------------------------------------
/examples/src/test/scala/org/example/TypedCreatorExample.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 – 2016 Paul Horn
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package org.example
18 |
19 | import akka.actor.{ ActorLogging, ActorSystem }
20 | import de.knutwalker.akka.typed._
21 |
22 | object TypedCreatorExample extends App {
23 |
24 | case class Ping(replyTo: ActorRef[Pong])
25 | case class Pong(replyTo: ActorRef[Ping])
26 |
27 | implicit val system = ActorSystem()
28 | case class PingActor() extends TypedActor.Of[Ping] with ActorLogging {
29 | private[this] var count = 0
30 |
31 | def typedReceive: TypedReceive = Total {
32 | case Ping(replyTo) ⇒
33 | count += 1
34 | replyTo ! Pong(typedSelf)
35 | }
36 |
37 | override def postStop(): Unit = {
38 | log.info(s"pings: $count")
39 | }
40 | }
41 |
42 | case class PongActor() extends TypedActor.Of[Pong] with ActorLogging {
43 | private[this] var count = 0
44 |
45 | def typedReceive: TypedReceive = Total {
46 | case Pong(replyTo) ⇒
47 | count += 1
48 | replyTo ! Ping(typedSelf)
49 | }
50 |
51 | override def postStop(): Unit = {
52 | log.info(s"pongs: $count")
53 | }
54 | }
55 |
56 | val ping = Typed[PingActor].create()
57 | val pong = Typed[PongActor].create()
58 |
59 | ping ! Ping(pong)
60 |
61 | Thread.sleep(1000)
62 |
63 | Shutdown(system)
64 | }
65 |
--------------------------------------------------------------------------------
/examples/src/test/scala/org/example/UnionExample.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 – 2016 Paul Horn
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package org.example
18 |
19 | import akka.actor.{ Actor, ActorSystem }
20 | import de.knutwalker.akka.typed._
21 |
22 |
23 | object UnionExample extends App {
24 |
25 | case class Foo()
26 | case class Bar()
27 | case class Baz()
28 |
29 | class UnionedActor extends Actor {
30 | def receive = {
31 | case x ⇒ println(x)
32 | }
33 | }
34 |
35 | implicit val system = ActorSystem()
36 |
37 | val props1: Props[Foo] = Props(new UnionedActor)
38 | val props0: Props[Foo | Bar] = props1.or[Bar]
39 | val ref0 : ActorRef[Foo | Bar] = ActorOf(props0, "union")
40 | val ref : ActorRef[Foo | Bar | Baz] = ref0.or[Baz]
41 |
42 | ref ! Foo()
43 | ref ! Bar()
44 | ref ! Baz()
45 |
46 | // [error] UnionExample.scala:49:
47 | // Cannot prove that message of type org.example.UnionExample.Foo.type is a member of org.example.UnionExample.ref.Message.
48 | // ref ! Foo
49 |
50 | Shutdown(system)
51 | }
52 |
--------------------------------------------------------------------------------
/examples/src/test/scala/org/example/UnionExample2.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 – 2016 Paul Horn
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package org.example
18 |
19 | import akka.actor.ActorSystem
20 | import de.knutwalker.akka.typed._
21 |
22 |
23 | object UnionExample2 extends App {
24 |
25 | case class Foo()
26 | case class Bar()
27 | case class Baz()
28 |
29 | class UnionedActor extends TypedActor.Of[Foo | Bar | Baz] {
30 | def typedReceive: TypedReceive = Union
31 | .on[Foo] { case Foo() ⇒ println("foo") }
32 | .on[Bar] { case Bar() ⇒ println("bar") }
33 | .on[Baz] { case Baz() ⇒ println("baz") }
34 | .apply
35 | }
36 |
37 | implicit val system = ActorSystem()
38 |
39 | val props: Props[Foo | Bar | Baz] = PropsFor(new UnionedActor)
40 | val ref : ActorRef[Foo | Bar | Baz] = ActorOf(props, "union")
41 |
42 | ref ! Foo()
43 | ref ! Bar()
44 | ref ! Baz()
45 |
46 | // [error] UnionExample2.scala:48:
47 | // Cannot prove that message of type org.example.UnionExample2.Foo.type is a member of org.example.UnionExample2.ref.Message.
48 | // ref ! Foo
49 |
50 | Shutdown(system)
51 | }
52 |
--------------------------------------------------------------------------------
/project/Build.scala:
--------------------------------------------------------------------------------
1 | import de.knutwalker.sbt._
2 | import de.knutwalker.sbt.KSbtKeys._
3 | import de.knutwalker.sbt.KReleaseSteps.{inquireVersions => _, setReleaseVersion => _, _}
4 | import sbt.Keys._
5 | import sbt._
6 | import sbtrelease.ReleasePlugin.autoImport._
7 | import sbtrelease.ReleaseStateTransformations._
8 |
9 |
10 | object Build extends AutoPlugin {
11 | override def trigger = allRequirements
12 | override def requires = KSbtPlugin
13 |
14 | object autoImport {
15 | lazy val akkaActorVersion = settingKey[String]("Version of akka-actor.")
16 | lazy val isAkka24 = settingKey[Boolean]("Whether the build is compiled against Akka 2.4.x.")
17 | lazy val akkaPersistence = akkaPersistenceDependency
18 | }
19 | import autoImport._
20 |
21 | override lazy val projectSettings = Seq(
22 | projectName := "typed-actors",
23 | organization := "de.knutwalker",
24 | startYear := Some(2015),
25 | maintainer := "Paul Horn",
26 | githubProject := Github("knutwalker", "typed-actors"),
27 | description := "Compile time wrapper for more type safe actors",
28 | scalaVersion := "2.11.7",
29 | akkaActorVersion := "2.3.14",
30 | isAkka24 := akkaActorVersion.value.startsWith("2.4"),
31 | libraryDependencies += "com.typesafe.akka" %% "akka-actor" % akkaActorVersion.value % "provided",
32 | javaVersion := JavaVersion.Java17,
33 | apiMappings ++= mapAkkaJar((externalDependencyClasspath in Compile).value, scalaBinaryVersion.value, akkaActorVersion.value),
34 | releaseProcess := getReleaseSteps(isAkka24.value),
35 | unmanagedSourceDirectories in Compile ++= List(
36 | if (isAkka24.value) (sourceDirectory in Compile).value / s"scala-akka-2.4.x"
37 | else (sourceDirectory in Compile).value / s"scala-akka-2.3.x",
38 | if (isAkka24.value) (sourceDirectory in (Test, test)).value / s"scala-akka-2.4.x"
39 | else (sourceDirectory in (Test, test)).value / s"scala-akka-2.3.x"
40 | )
41 | )
42 |
43 | val akkaPersistenceDependency = (_: String) match {
44 | case x if x.startsWith("2.4") ⇒ "com.typesafe.akka" %% "akka-persistence" % x % "provided"
45 | case otherwise ⇒ "com.typesafe.akka" %% "akka-persistence-experimental" % otherwise % "provided"
46 | }
47 |
48 | def getReleaseSteps(isAkka24: Boolean) = {
49 | val always = List(
50 | checkSnapshotDependencies,
51 | inquireVersions,
52 | runClean,
53 | runTest,
54 | setReleaseVersion,
55 | commitReleaseVersion,
56 | tagRelease,
57 | publishSignedArtifacts,
58 | releaseToCentral
59 | )
60 | val versionSpecific =
61 | if (isAkka24) Nil else List(
62 | pushGithubPages,
63 | commitTheReadme,
64 | setNextVersion,
65 | commitNextVersion
66 | )
67 | val after = List(pushChanges)
68 | always ++ versionSpecific ++ after
69 | }
70 |
71 | def mapAkkaJar(cp: Seq[Attributed[File]], crossVersion: String, akkaVersion: String): Map[File, URL] =
72 | cp.collect {
73 | case file if file.data.toPath.endsWith(s"akka-actor_$crossVersion-$akkaVersion.jar") ⇒
74 | (file.data, url(s"http://doc.akka.io/api/akka/$akkaVersion/"))
75 | }.toMap
76 | }
77 |
--------------------------------------------------------------------------------
/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=0.13.9
2 |
--------------------------------------------------------------------------------
/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | libraryDependencies += "org.slf4j" % "slf4j-nop" % "1.7.13"
2 | addSbtPlugin("de.knutwalker" % "sbt-knutwalker" % "0.3.1")
3 |
--------------------------------------------------------------------------------
/tests/src/test/scala-akka-2.3.x/de/knutwalker/akka/typed/Shutdown.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 – 2016 Paul Horn
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package de.knutwalker.akka.typed
18 |
19 | import akka.actor.ActorSystem
20 | import akka.util.Timeout
21 |
22 | import scala.reflect.ClassTag
23 |
24 | object Shutdown {
25 | def apply(system: ActorSystem): Unit =
26 | system.shutdown()
27 | }
28 |
29 | object TimeoutMessage {
30 | def apply[A](ref: ActorRef[A])(implicit ct: ClassTag[A], timeout: Timeout): String = {
31 | s"Ask timed out on [$ref] after [${timeout.duration.toMillis} ms]"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/tests/src/test/scala-akka-2.4.x/de/knutwalker/akka/typed/Shutdown.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 – 2016 Paul Horn
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package de.knutwalker.akka.typed
18 |
19 | import akka.actor.ActorSystem
20 | import akka.util.Timeout
21 |
22 | import scala.concurrent.Await
23 | import scala.concurrent.duration.Duration
24 | import scala.reflect.ClassTag
25 |
26 | object Shutdown {
27 | def apply(system: ActorSystem): Unit = {
28 | Await.result(system.terminate(), Duration.Inf)
29 | ()
30 | }
31 | }
32 |
33 | object TimeoutMessage {
34 | def apply[A](ref: ActorRef[A])(implicit ct: ClassTag[A], timeout: Timeout): String = {
35 | s"""Ask timed out on [$ref] after [${timeout.duration.toMillis} ms]. Sender[null] sent message of type "${ct.runtimeClass.getName}"."""
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/tests/src/test/scala/de/knutwalker/akka/typed/CreateInbox.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 – 2016 Paul Horn
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package de.knutwalker.akka.typed
18 |
19 | import akka.actor.{ Inbox, ActorSystem }
20 |
21 | import scala.annotation.tailrec
22 |
23 | /**
24 | * Workaround akka bug as long as 2.3 is supported
25 | * https://github.com/akka/akka/issues/15409
26 | */
27 | object CreateInbox {
28 |
29 | def apply()(implicit system: ActorSystem): Inbox =
30 | createInbox(system)
31 |
32 | @tailrec
33 | private def createInbox(sys: ActorSystem): Inbox = {
34 | try Inbox.create(sys) catch {
35 | case cee: ClassCastException ⇒ createInbox(sys)
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/tests/src/test/scala/de/knutwalker/akka/typed/TypedActorSpec.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 – 2016 Paul Horn
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package de.knutwalker.akka.typed
18 |
19 | import akka.actor.{ UnhandledMessage, ActorSystem }
20 | import org.specs2.concurrent.ExecutionEnv
21 | import org.specs2.mutable.Specification
22 | import org.specs2.specification.AfterAll
23 |
24 | import scala.concurrent.TimeoutException
25 | import scala.concurrent.duration.Duration
26 |
27 | import java.util.concurrent.TimeUnit
28 |
29 | object TypedActorSpec extends Specification with AfterAll {
30 | sequential
31 |
32 | sealed trait MyFoo
33 | case object Foo extends MyFoo
34 | case object Bar extends MyFoo
35 | case object Qux
36 |
37 | implicit val system = ActorSystem("foo")
38 | val inbox = CreateInbox()
39 | system.eventStream.subscribe(inbox.getRef(), classOf[UnhandledMessage])
40 |
41 | "The TypedActor" should {
42 | "have typed partial receive" >> { implicit ee: ExecutionEnv ⇒
43 | class MyActor extends TypedActor.Of[MyFoo] {
44 | def typedReceive = {
45 | case Foo => inbox.getRef() ! "received a foo"
46 | }
47 | }
48 | val ref = ActorOf(PropsFor(new MyActor))
49 |
50 | ref ! Foo
51 | expectMsg("received a foo")
52 |
53 | ref ! Bar
54 | expectUnhandled(Bar, ref)
55 |
56 | ref.untyped ! Qux
57 | expectUnhandled(Qux, ref)
58 |
59 | ref.untyped ! Nil
60 | expectUnhandled(Nil, ref)
61 | }
62 |
63 | "have typed total receive" >> { implicit ee: ExecutionEnv ⇒
64 | class MyActor extends TypedActor.Of[MyFoo] {
65 | def typedReceive = Total {
66 | case Foo => inbox.getRef() ! "received a foo"
67 | case Bar => inbox.getRef() ! "received a bar"
68 | }
69 | }
70 | val ref = ActorOf(PropsFor(new MyActor))
71 |
72 | ref ! Foo
73 | expectMsg("received a foo")
74 |
75 | ref ! Bar
76 | expectMsg("received a bar")
77 |
78 | ref.untyped ! Qux
79 | expectUnhandled(Qux, ref)
80 |
81 | ref.untyped ! Nil
82 | expectUnhandled(Nil, ref)
83 | }
84 |
85 | "have untyped partial receive" >> { implicit ee: ExecutionEnv ⇒
86 | class MyActor extends TypedActor.Of[MyFoo] {
87 | def typedReceive = Untyped {
88 | case Foo => inbox.getRef() ! "received a foo"
89 | case Bar => inbox.getRef() ! "received a bar"
90 | case Qux => inbox.getRef() ! "receive a qux"
91 | }
92 | }
93 | val ref = ActorOf(PropsFor(new MyActor))
94 |
95 | ref ! Foo
96 | expectMsg("received a foo")
97 |
98 | ref ! Bar
99 | expectMsg("received a bar")
100 |
101 | ref.untyped ! Qux
102 | expectMsg("receive a qux")
103 |
104 | ref.untyped ! Nil
105 | expectUnhandled(Nil, ref)
106 | }
107 |
108 | "have a quick apply method for total functions" >> { implicit ee: ExecutionEnv ⇒
109 | val ref = ActorOf(TypedActor[MyFoo] {
110 | case Foo => inbox.getRef() ! "received a foo"
111 | case Bar => inbox.getRef() ! "received a bar"
112 | })
113 |
114 | ref ! Foo
115 | expectMsg("received a foo")
116 |
117 | ref ! Bar
118 | expectMsg("received a bar")
119 |
120 | ref.untyped ! Qux
121 | expectUnhandled(Qux, ref)
122 |
123 | ref.untyped ! Nil
124 | expectUnhandled(Nil, ref)
125 | }
126 |
127 | "have a typed become" >> { implicit ee: ExecutionEnv ⇒
128 | class MyActor extends TypedActor.Of[MyFoo] {
129 | def typedReceive = {
130 | case Foo ⇒ typedBecome {
131 | case Bar ⇒ inbox.getRef() ! "received a bar"
132 | }
133 | }
134 | }
135 | val ref = ActorOf(PropsFor(new MyActor))
136 |
137 | ref ! Foo
138 | expectUnhandled(Foo, ref) must throwA[TimeoutException]
139 |
140 | ref ! Bar
141 | expectMsg("received a bar")
142 |
143 | ref.untyped ! Qux
144 | expectUnhandled(Qux, ref)
145 |
146 | ref.untyped ! Nil
147 | expectUnhandled(Nil, ref)
148 | }
149 | }
150 |
151 | def expectUnhandled(message: Any, ref: ActorRef[_])(implicit ee: ExecutionEnv) = {
152 | val receiveTimeout = Duration(100L * ee.timeFactor.toLong, TimeUnit.MILLISECONDS)
153 | inbox.receive(receiveTimeout) === UnhandledMessage(message, system.deadLetters, ref.untyped)
154 | }
155 |
156 | def expectMsg(expected: Any)(implicit ee: ExecutionEnv) = {
157 | val receiveTimeout = Duration(100L * ee.timeFactor.toLong, TimeUnit.MILLISECONDS)
158 | inbox.receive(receiveTimeout) === expected
159 | }
160 |
161 | def afterAll(): Unit = Shutdown(system)
162 | }
163 |
--------------------------------------------------------------------------------
/tests/src/test/scala/de/knutwalker/akka/typed/TypedSpec.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 – 2016 Paul Horn
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package de.knutwalker.akka.typed
18 |
19 | import akka.actor.{ Actor, Deploy, Terminated, PoisonPill, ActorSystem }
20 | import akka.pattern.AskTimeoutException
21 | import akka.routing.NoRouter
22 | import akka.util.Timeout
23 | import org.specs2.concurrent.ExecutionEnv
24 | import org.specs2.execute._
25 | import org.specs2.execute.Typecheck._
26 | import org.specs2.matcher.TypecheckMatchers._
27 | import org.specs2.mutable.Specification
28 | import org.specs2.specification.AfterAll
29 |
30 | import scala.concurrent.duration._
31 |
32 | import java.util.regex.Pattern
33 |
34 | object TypedSpec extends Specification with AfterAll {
35 | sequential
36 |
37 | sealed trait TestMessage
38 | case class Foo(msg: String) extends TestMessage
39 | case object Bar extends TestMessage
40 | case class Baz(msg: String)(val replyTo: ActorRef[SomeOtherMessage]) extends TestMessage
41 |
42 | case class SomeOtherMessage(msg: String)
43 |
44 | implicit val system = ActorSystem("test")
45 | val inbox = CreateInbox()
46 | val inboxRef = inbox.getRef()
47 |
48 | case class MyActor(name: String) extends TypedActor.Of[TestMessage] {
49 | def typedReceive: TypedReceive = {
50 | case Foo(msg) ⇒ inboxRef ! Foo(s"$name: $msg")
51 | case Bar ⇒ inboxRef ! Bar
52 | case m@Baz(msg) ⇒ m.replyTo ! SomeOtherMessage(s"$name: $msg")
53 | }
54 | }
55 |
56 | class TestActor extends TypedActor.Of[TestMessage] {
57 | def typedReceive: TypedReceive = Total(_ ⇒ ())
58 | }
59 |
60 | class AnotherActor extends Actor {
61 | def receive = {
62 | case Foo(msg) ⇒ self.typed[SomeOtherMessage].forward(SomeOtherMessage(msg))
63 | case SomeOtherMessage(msg) ⇒ inboxRef ! SomeOtherMessage(msg)
64 | }
65 | }
66 |
67 | "A compile-time wrapper around actors" should {
68 | val ref: ActorRef[TestMessage] = Typed[MyActor].create("Bernd")
69 |
70 | "only accept the defined message" >> {
71 | ref ! Foo("foo")
72 | inbox.receive(1.second) === Foo("Bernd: foo")
73 | }
74 |
75 | "fail to compile if the wrong message type is sent" >> {
76 | typecheck {
77 | """ ref ! "some other message" """
78 | } must not succeed
79 | }
80 |
81 | "have the same runtime representation as regular actors" >> {
82 | ref must beAnInstanceOf[akka.actor.ActorRef]
83 | ref.untyped must beTheSameAs (ref.asInstanceOf[akka.actor.ActorRef])
84 | ref must beTheSameAs (ref.untyped.typed[TestMessage])
85 | }
86 | }
87 |
88 | "ask support of typed actors" should {
89 |
90 | "ask the question and the return a future" >> { implicit ee: ExecutionEnv ⇒
91 | implicit val timeout: Timeout = (100 * ee.timeFactor).millis
92 | val ref = ActorOf(TypedActor[Baz](m ⇒ m.replyTo ! SomeOtherMessage(m.msg)))
93 | (ref ? Baz("foo")) must be_==(SomeOtherMessage("foo")).await
94 | }
95 |
96 | "respect the timeout" >> { implicit ee: ExecutionEnv ⇒
97 | implicit val timeout: Timeout = (100 * ee.timeFactor).millis
98 | val ref = ActorOf(TypedActor[Baz](_ ⇒ ()), "discarder")
99 | val expectedMessage = TimeoutMessage(ref)
100 | val matchMessage = s"^${Pattern.quote(expectedMessage)}$$"
101 | (ref ? Baz("foo")) must throwAn[AskTimeoutException](message = matchMessage).await
102 | }
103 |
104 | "fail with an invalid timeout" >> { implicit ee: ExecutionEnv ⇒
105 | implicit val timeout: Timeout = -100.millis
106 | val ref = ActorOf(TypedActor[Baz](m ⇒ m.replyTo ! SomeOtherMessage(m.msg)))
107 | (ref ? Baz("foo")) must throwAn[IllegalArgumentException].like {
108 | case e ⇒ e.getMessage must startWith("Timeout length must not be negative, question not sent")
109 | }.await
110 | }
111 |
112 | "fail when the target is already terminated" >> { implicit ee: ExecutionEnv ⇒
113 | implicit val timeout: Timeout = (100 * ee.timeFactor).millis
114 | val ref = kill(ActorOf(TypedActor[Baz](m ⇒ m.replyTo ! SomeOtherMessage(m.msg))))
115 | val expectedMessage = s"Recipient[$ref] had already been terminated."
116 | val matchMessage = s"^${Pattern.quote(expectedMessage)}.*$$"
117 | (ref ? Baz("foo")) must throwAn[AskTimeoutException](message = matchMessage).await
118 | }
119 | }
120 |
121 | "further ops and syntax of typed actors" >> {
122 | val ref = ActorOf(Props[Foo, AnotherActor])
123 |
124 | "forward" >> {
125 | ref ! Foo("foo")
126 | inbox.receive(1.second) === SomeOtherMessage("foo")
127 | }
128 |
129 | "unsafeTell" >> {
130 | ref.unsafeTell(SomeOtherMessage("foo"))
131 | inbox.receive(1.second) === SomeOtherMessage("foo")
132 | }
133 |
134 | "path" >> {
135 | ref.path must beTheSameAs (ref.untyped.path)
136 | }
137 | }
138 |
139 | "Props builders" should {
140 |
141 | "simple props apply for zero-arg constructors" >> {
142 |
143 | val byProps = Props[TestMessage, TestActor]
144 | val byPropsFor = PropsFor[TestActor]
145 | val byPropsOf = PropsOf[TestMessage][TestActor]
146 | val byAkka = UntypedProps[TestActor]
147 |
148 | byProps must be_===(byPropsOf)
149 | byProps must be_===(byPropsFor)
150 | byProps must be_==(byAkka)
151 | }
152 |
153 | "props apply with closure" >> {
154 |
155 | def inner(creator: ⇒ MyActor) = {
156 | val byProps = Props[TestMessage, MyActor](creator)
157 | val byPropsFor = PropsFor[MyActor](creator)
158 | val byPropsOf = PropsOf[TestMessage](creator)
159 | val byAkka = UntypedProps(creator)
160 |
161 | byProps must be_===(byPropsOf)
162 | byProps must be_===(byPropsFor)
163 | byProps must be_==(byAkka)
164 | }
165 |
166 | inner(new MyActor("Bernd"))
167 | }
168 |
169 | "reflection based props apply" >> {
170 |
171 | val byProps = Props[TestMessage, MyActor](classOf[MyActor], "Bernd")
172 | val byPropsFor = PropsFor[MyActor](classOf[MyActor], "Bernd")
173 | val byPropsOf = PropsOf[TestMessage](classOf[MyActor], "Bernd")
174 | val byAkka = UntypedProps(classOf[MyActor], "Bernd")
175 |
176 | byProps must be_===(byPropsOf)
177 | byProps must be_===(byPropsFor)
178 | byProps must be_==(byAkka)
179 | }
180 | }
181 |
182 | "Ops syntax for typed props" should {
183 | val props: Props[TestMessage] = Typed[MyActor].props("Bernd")
184 | val untyped = props.untyped
185 |
186 | "dispatcher" >> {
187 | props.dispatcher must beTheSameAs (untyped.dispatcher)
188 | }
189 |
190 | "mailbox" >> {
191 | props.mailbox must beTheSameAs (untyped.mailbox)
192 | }
193 |
194 | "routerConfig" >> {
195 | props.routerConfig must beTheSameAs (untyped.routerConfig)
196 | }
197 |
198 | "actorClass()" >> {
199 | props.actorClass() must beTheSameAs (untyped.actorClass())
200 | }
201 |
202 | "withDispatcher()" >> {
203 | props.withDispatcher("foo").untyped must be_=== (untyped.withDispatcher("foo"))
204 | props.withDispatcher("foo") must be_=== (untyped.withDispatcher("foo").typed)
205 | }
206 |
207 | "withMailbox()" >> {
208 | props.withMailbox("foo").untyped must be_=== (untyped.withMailbox("foo"))
209 | props.withMailbox("foo") must be_=== (untyped.withMailbox("foo").typed)
210 | }
211 |
212 | "withRouter()" >> {
213 | props.withRouter(NoRouter).untyped must be_=== (untyped.withRouter(NoRouter))
214 | props.withRouter(NoRouter) must be_=== (untyped.withRouter(NoRouter).typed)
215 | }
216 |
217 | "withDeploy()" >> {
218 | props.withDeploy(Deploy("foo")).untyped must be_=== (untyped.withDeploy(Deploy("foo")))
219 | props.withDeploy(Deploy("foo")) must be_=== (untyped.withDeploy(Deploy("foo")).typed)
220 | }
221 | }
222 |
223 | def kill(ref: ActorRef[_]): ref.type = {
224 | val untyped = ref.untyped
225 | inbox.watch(untyped)
226 | untyped ! PoisonPill
227 | val Terminated(`untyped`) = inbox.receive(100.millis)
228 | ref
229 | }
230 |
231 | def afterAll(): Unit = Shutdown(system)
232 | }
233 |
--------------------------------------------------------------------------------
/tests/src/test/scala/de/knutwalker/akka/typed/UnionSpec.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 – 2016 Paul Horn
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package de.knutwalker.akka.typed
18 |
19 | import akka.actor.{ UnhandledMessage, ActorSystem }
20 | import akka.util.Timeout
21 | import org.specs2.concurrent.ExecutionEnv
22 | import org.specs2.execute._
23 | import org.specs2.execute.Typecheck._
24 | import org.specs2.matcher.TypecheckMatchers._
25 | import org.specs2.mutable.Specification
26 | import org.specs2.specification.AfterAll
27 |
28 | import scala.concurrent.duration._
29 | import scala.reflect.ClassTag
30 | import scala.util.matching.Regex
31 | import java.util.concurrent.TimeUnit
32 |
33 |
34 | object UnionSpec extends Specification with AfterAll {
35 | sequential
36 |
37 | case class Foo(msg: String)
38 | case object Bar
39 | case class Baz(msg: String)(val replyTo: ActorRef[SomeOtherMessage])
40 |
41 | case class SomeOtherMessage(msg: String)
42 |
43 | implicit val system = ActorSystem("test")
44 | val inbox = CreateInbox()
45 | val inboxRef = inbox.getRef()
46 | system.eventStream.subscribe(inboxRef, classOf[UnhandledMessage])
47 |
48 | "Type unions on actors" should {
49 | class MyActor(name: String) extends TypedActor.Of[Foo] {
50 | def typedReceive = Untyped {
51 | case Foo(msg) ⇒ inboxRef ! Foo(s"$name: $msg")
52 | case Bar ⇒ inboxRef ! Bar
53 | case m: Baz ⇒ m.replyTo ! SomeOtherMessage(m.msg)
54 | }
55 | }
56 |
57 | val fooProps: Props[Foo] = PropsFor(new MyActor("Bernd"))
58 | val fooOrBarProps: Props[Foo | Bar.type] = fooProps.or[Bar.type]
59 | val fooOrBarRef: ActorRef[Foo | Bar.type] = ActorOf(fooOrBarProps)
60 | val ref: ActorRef[Foo | Bar.type | Baz] = fooOrBarRef.or[Baz]
61 |
62 | "accept a foo message" >> {
63 | ref ! Foo("foo")
64 | inbox.receive(1.second) === Foo("Bernd: foo")
65 | }
66 |
67 | "accept a bar message" >> {
68 | ref ! Bar
69 | inbox.receive(1.second) === Bar
70 | }
71 |
72 | "accept a baz message" >> {
73 | ref ! Baz("baz")(inboxRef.typed)
74 | inbox.receive(1.second) === SomeOtherMessage("baz")
75 | }
76 |
77 | "fail to compile if the wrong message type is sent" >> {
78 | typecheck {
79 | """ ref ! SomeOtherMessage("some other message") """
80 | } must not succeed
81 | }
82 |
83 | "support ask" >> { implicit ee: ExecutionEnv ⇒
84 | implicit val timeout: Timeout = (100 * ee.timeFactor).millis
85 | (ref ? Baz("foo")) must be_==(SomeOtherMessage("foo")).await
86 | }
87 |
88 | "support retagging to a different type" >> {
89 |
90 | "if it is a subtype of the defined union" >> {
91 | ref.only[Foo] ! Foo("foo")
92 | inbox.receive(1.second) === Foo("Bernd: foo")
93 | }
94 |
95 | "which disables all other subcases" >> {
96 | typecheck {
97 | """ ref.only[Foo] ! Bar """
98 | } must not succeed
99 | }
100 |
101 | "fails if the type is unrelated" >> {
102 | typecheck {
103 | """ ref.only[SomeOtherMessage] ! Bar """
104 | } must not succeed
105 | }
106 | }
107 | }
108 |
109 | "A unioned TypedActor" should {
110 | "the Union helper" should {
111 |
112 | class MyActor(name: String) extends TypedActor.Of[Foo | Bar.type | Baz] {
113 | def typedReceive: TypedReceive = Union
114 | .on[Foo]{ case Foo(msg) ⇒ inboxRef ! Foo(s"$name: $msg") }
115 | .on[Bar.type]{ case Bar ⇒ inboxRef ! Bar }
116 | .on[Baz]{ case m: Baz ⇒ m.replyTo ! SomeOtherMessage(m.msg) }
117 | .apply
118 | }
119 |
120 | val props: Props[Foo | Bar.type | Baz] = PropsFor(new MyActor("Bernd"))
121 | val ref: ActorRef[Foo | Bar.type | Baz] = ActorOf(props)
122 |
123 | "allow partial definition" >> {
124 | typecheck {
125 | """
126 | class MyActor(name: String) extends TypedActor.Of[Foo | Bar.type | Baz] {
127 | def typedReceive: TypedReceive = Union
128 | .on[Foo]{ case Foo(msg) ⇒ inboxRef ! Foo(s"$name: $msg") }
129 | .apply
130 | }
131 | """
132 | } must succeed
133 | }
134 |
135 | "allow using total handler" >> {
136 | val ct = implicitly[ClassTag[Foo]]
137 | typecheck {
138 | """
139 | class MyActor(name: String) extends TypedActor.Of[Foo | Bar.type | Baz] {
140 | def typedReceive: TypedReceive = Union
141 | .total[Foo]{ foo ⇒ inboxRef ! Foo(s"$name: $foo.msg") }(implicitly, ct)
142 | .apply
143 | }
144 | """
145 | } must succeed
146 | }
147 |
148 | "allow using two total handlers" >> {
149 | val ctFoo = implicitly[ClassTag[Foo]]
150 | val ctBar = implicitly[ClassTag[Bar.type]]
151 | typecheck {
152 | """
153 | class MyActor(name: String) extends TypedActor.Of[Foo | Bar.type | Baz] {
154 | def typedReceive: TypedReceive = Union
155 | .total[Foo]{ foo ⇒ inboxRef ! Foo(s"$name: $foo.msg") }(implicitly, ctFoo)
156 | .total[Bar.type] ( inboxRef ! _ )(implicitly, ctBar)
157 | .apply
158 | }
159 | """
160 | } must succeed
161 | }
162 |
163 | "require at least one sub path" >> {
164 | typecheck {
165 | """
166 | class MyActor(name: String) extends TypedActor.Of[Foo | Bar.type | Baz] {
167 | def typedReceive: TypedReceive = Union
168 | .apply
169 | }
170 | """
171 | } must failWith(Regex.quote("Cannot prove that de.knutwalker.akka.typed.TypedActor.MkPartialUnionReceive.Empty =:= de.knutwalker.akka.typed.TypedActor.MkPartialUnionReceive.NonEmpty."))
172 | }
173 |
174 | "only allow defined parts" >> {
175 | typecheck {
176 | """
177 | class MyActor(name: String) extends TypedActor.Of[Foo | Bar.type | Baz] {
178 | def typedReceive: TypedReceive = Union
179 | .on[String]{ case msg ⇒ inboxRef ! Foo(s"$name: $msg") }
180 | .apply
181 | }
182 | """
183 | } must failWith(Regex.quote("Cannot prove that message of type String is a member of de.knutwalker.akka.typed.|[de.knutwalker.akka.typed.|[de.knutwalker.akka.typed.UnionSpec.Foo,de.knutwalker.akka.typed.UnionSpec.Bar.type],de.knutwalker.akka.typed.UnionSpec.Baz]."))
184 | }
185 |
186 | "not require apply" >> {
187 | class MyActor(name: String) extends TypedActor.Of[Foo | Bar.type | Baz] {
188 | val typedReceive: TypedReceive = Union
189 | .on[Foo]{ case Foo(msg) ⇒ inboxRef ! Foo(s"$name: $msg") }
190 | }
191 | ActorOf(PropsFor(new MyActor("Bernd"))) must not beNull
192 | }
193 |
194 | "not infer apply when not all requirement are met" >> {
195 | typecheck {
196 | """
197 | class MyActor(name: String) extends TypedActor.Of[Foo | Bar.type | Baz] {
198 | def typedReceive: TypedReceive = Union
199 | }
200 | """
201 | } must failWith("required: .*TypedReceive")
202 | }
203 |
204 | "accept a foo message" >> {
205 | ref ! Foo("foo")
206 | inbox.receive(1.second) === Foo("Bernd: foo")
207 | }
208 |
209 | "accept a bar message" >> {
210 | ref ! Bar
211 | inbox.receive(1.second) === Bar
212 | }
213 |
214 | "accept a baz message" >> {
215 | ref ! Baz("baz")(inboxRef.typed)
216 | inbox.receive(1.second) === SomeOtherMessage("baz")
217 | }
218 |
219 | "fail to compile if the wrong message type is sent" >> {
220 | typecheck {
221 | """ ref ! SomeOtherMessage("some other message") """
222 | } must not succeed
223 | }
224 |
225 | "support ask" >> { implicit ee: ExecutionEnv ⇒
226 | implicit val timeout: Timeout = (100 * ee.timeFactor).millis
227 | (ref ? Baz("foo")) must be_==(SomeOtherMessage("foo")).await
228 | }
229 |
230 | "fail to compile when total handler is not exhaustive" >> {
231 | val ct = implicitly[ClassTag[Option[Foo]]]
232 | typecheck {
233 | """
234 | class MyActor extends TypedActor.Of[Option[Foo] | Bar.type] {
235 | def typedReceive: TypedReceive = Union
236 | .total[Option[Foo]] {
237 | case Some(Foo("foo")) => ()
238 | }
239 | }
240 | """
241 | } must failWith(Regex.quote("match may not be exhaustive.\nIt would fail on the following inputs: None, Some(_)"))
242 | }.pendingUntilFixed("succeeds... possibly missing fatal warnings or similar")
243 |
244 | "not fail when the total doesn't match everything" >> {implicit ee: ExecutionEnv ⇒
245 | class MyActor extends TypedActor.Of[Foo | Bar.type] {
246 | def typedReceive: TypedReceive = Union.total[Foo] {
247 | case Foo("foo") ⇒ inboxRef ! Foo("foo")
248 | }.apply
249 | }
250 | val ref = ActorOf(PropsFor(new MyActor))
251 |
252 | ref ! Foo("foo")
253 | expectMsg(Foo("foo"))
254 |
255 | ref ! Foo("baz")
256 | expectUnhandled(Foo("baz"), ref)
257 | }
258 | }
259 |
260 | "the TotalUnion helper" should {
261 |
262 | class MyActor(name: String) extends TypedActor.Of[Foo | Bar.type | Baz] {
263 | def typedReceive: TypedReceive = TotalUnion
264 | .on[Foo]{ case Foo(msg) ⇒ inboxRef ! Foo(s"$name: $msg") }
265 | .on[Bar.type]{ case Bar ⇒ inboxRef ! Bar }
266 | .on[Baz]{ case m: Baz ⇒ m.replyTo ! SomeOtherMessage(m.msg) }
267 | .apply
268 | }
269 |
270 | val props: Props[Foo | Bar.type | Baz] = PropsFor(new MyActor("Bernd"))
271 | val ref: ActorRef[Foo | Bar.type | Baz] = ActorOf(props)
272 |
273 | "allow total definition" >> {
274 | typecheck {
275 | """
276 | class MyActor(name: String) extends TypedActor.Of[Foo | Bar.type | Baz] {
277 | def typedReceive: TypedReceive = TotalUnion
278 | .on[Foo]{ case Foo(msg) ⇒ inboxRef ! Foo(s"$name: $msg") }
279 | .on[Bar.type]{ case Bar ⇒ inboxRef ! Bar }
280 | .on[Baz]{ case m: Baz ⇒ m.replyTo ! SomeOtherMessage(m.msg) }
281 | .apply
282 | }
283 | """
284 | } must succeed
285 | }
286 |
287 | "allow one total handler" >> {
288 | typecheck {
289 | """
290 | class MyActor(name: String) extends TypedActor.Of[Foo | Bar.type | Baz] {
291 | def typedReceive: TypedReceive = TotalUnion
292 | .on[Foo]{ case Foo(msg) ⇒ inboxRef ! Foo(s"$name: $msg") }
293 | .total[Bar.type]{ inboxRef ! _ }
294 | .on[Baz]{ case m: Baz ⇒ m.replyTo ! SomeOtherMessage(m.msg) }
295 | .apply
296 | }
297 | """
298 | } must succeed
299 | }
300 |
301 | "allow all total handlers" >> {
302 | typecheck {
303 | """
304 | class MyActor(name: String) extends TypedActor.Of[Foo | Bar.type | Baz] {
305 | def typedReceive: TypedReceive = TotalUnion
306 | .total[Foo]{ case Foo(msg) => inboxRef ! Foo(s"$name: foo.msg") }
307 | .total[Bar.type]( inboxRef ! _ )
308 | .total[Baz]{ m => m.replyTo ! SomeOtherMessage(m.msg) }
309 | .apply
310 | }
311 | """
312 | } must succeed
313 | }
314 |
315 | "fail without any parts" >> {
316 | typecheck {
317 | """
318 | class MyActor(name: String) extends TypedActor.Of[Foo | Bar.type | Baz] {
319 | def typedReceive: TypedReceive = TotalUnion
320 | .apply
321 | }
322 | """
323 | } must failWith(Regex.quote("value apply is not a member of de.knutwalker.akka.typed.TypedActor.MkTotalUnionReceiveEmpty[de.knutwalker.akka.typed.|[de.knutwalker.akka.typed.|[de.knutwalker.akka.typed.UnionSpec.Foo,de.knutwalker.akka.typed.UnionSpec.Bar.type],de.knutwalker.akka.typed.UnionSpec.Baz]]"))
324 | }
325 |
326 | "fail with just one part" >> {
327 | typecheck {
328 | """
329 | class MyActor(name: String) extends TypedActor.Of[Foo | Bar.type | Baz] {
330 | def typedReceive: TypedReceive = TotalUnion
331 | .on[Foo]{ case Foo(msg) ⇒ inboxRef ! Foo(s"$name: $msg") }
332 | .apply
333 | }
334 | """
335 | } must failWith(Regex.quote("value apply is not a member of de.knutwalker.akka.typed.TypedActor.MkTotalUnionReceiveHalfEmpty[de.knutwalker.akka.typed.|[de.knutwalker.akka.typed.|[de.knutwalker.akka.typed.UnionSpec.Foo,de.knutwalker.akka.typed.UnionSpec.Bar.type],de.knutwalker.akka.typed.UnionSpec.Baz],de.knutwalker.akka.typed.UnionSpec.Foo]"))
336 | }
337 |
338 | "require all definitions" >> {
339 | typecheck {
340 | """
341 | class MyActor(name: String) extends TypedActor.Of[Foo | Bar.type | Baz] {
342 | def typedReceive: TypedReceive = TotalUnion
343 | .on[Foo]{ case Foo(msg) ⇒ inboxRef ! Foo(s"$name: $msg") }
344 | .on[Bar.type]{ case Bar ⇒ inboxRef ! Bar }
345 | .apply
346 | }
347 | """
348 | } must failWith(Regex.quote("Cannot prove that de.knutwalker.akka.typed.|[de.knutwalker.akka.typed.UnionSpec.Foo,de.knutwalker.akka.typed.UnionSpec.Bar.type] contains the same members as de.knutwalker.akka.typed.|[de.knutwalker.akka.typed.|[de.knutwalker.akka.typed.UnionSpec.Foo,de.knutwalker.akka.typed.UnionSpec.Bar.type],de.knutwalker.akka.typed.UnionSpec.Baz]."))
349 | }
350 |
351 | "only allow defined parts" >> {
352 | typecheck {
353 | """
354 | class MyActor(name: String) extends TypedActor.Of[Foo | Bar.type | Baz] {
355 | def typedReceive: TypedReceive = TotalUnion
356 | .on[String]{ case msg ⇒ inboxRef ! Foo(s"$name: $msg") }
357 | .apply
358 | }
359 | """
360 | } must failWith(Regex.quote("Cannot prove that message of type String is a member of de.knutwalker.akka.typed.|[de.knutwalker.akka.typed.|[de.knutwalker.akka.typed.UnionSpec.Foo,de.knutwalker.akka.typed.UnionSpec.Bar.type],de.knutwalker.akka.typed.UnionSpec.Baz]."))
361 | }
362 |
363 | "not require apply" >> {
364 | class MyActor(name: String) extends TypedActor.Of[Foo | Bar.type | Baz] {
365 | val typedReceive: TypedReceive = TotalUnion
366 | .on[Foo]{ case Foo(msg) ⇒ inboxRef ! Foo(s"$name: $msg") }
367 | .on[Bar.type]{ case Bar ⇒ inboxRef ! Bar }
368 | .on[Baz]{ case m: Baz ⇒ m.replyTo ! SomeOtherMessage(m.msg) }
369 | }
370 | ActorOf(PropsFor(new MyActor("Bernd"))) must not beNull
371 | }
372 |
373 | "not infer apply when not all requirement are met" >> {
374 | typecheck {
375 | """
376 | class MyActor(name: String) extends TypedActor.Of[Foo | Bar.type | Baz] {
377 | def typedReceive: TypedReceive = TotalUnion
378 | .on[Foo]{ case Foo(msg) ⇒ inboxRef ! Foo(s"$name: $msg") }
379 | .on[Bar.type]{ case Bar ⇒ inboxRef ! Bar }
380 | }
381 | """
382 | } must failWith("required: .*TypedReceive")
383 | }
384 |
385 | "accept a foo message" >> {
386 | ref ! Foo("foo")
387 | inbox.receive(1.second) === Foo("Bernd: foo")
388 | }
389 |
390 | "accept a bar message" >> {
391 | ref ! Bar
392 | inbox.receive(1.second) === Bar
393 | }
394 |
395 | "accept a baz message" >> {
396 | ref ! Baz("baz")(inboxRef.typed)
397 | inbox.receive(1.second) === SomeOtherMessage("baz")
398 | }
399 |
400 | "fail to compile if the wrong message type is sent" >> {
401 | typecheck {
402 | """ ref ! SomeOtherMessage("some other message") """
403 | } must not succeed
404 | }
405 |
406 | "support ask" >> { implicit ee: ExecutionEnv ⇒
407 | implicit val timeout: Timeout = (100 * ee.timeFactor).millis
408 | (ref ? Baz("foo")) must be_==(SomeOtherMessage("foo")).await
409 | }
410 | }
411 |
412 | "unionBecome for subcase of union types" should {
413 |
414 | class MyActor extends TypedActor.Of[Foo | Bar.type | Baz] {
415 | def typedReceive: TypedReceive = Union.on[Foo]{
416 | case Foo(msg) ⇒
417 | inboxRef ! s"foo: $msg"
418 | unionBecome.on[Bar.type] {
419 | case Bar ⇒
420 | inboxRef ! Bar
421 | unionBecome.total[Baz] {
422 | baz ⇒ baz.replyTo ! SomeOtherMessage(baz.msg)
423 | }
424 | }
425 | }.apply
426 | }
427 | val ref = ActorOf(PropsFor(new MyActor))
428 |
429 | "allow to change behavior" >> {implicit ee: ExecutionEnv ⇒
430 | val bazMsg = Baz("baz")(inboxRef.typed)
431 |
432 | ref ! Bar
433 | expectUnhandled(Bar, ref)
434 |
435 | ref ! bazMsg
436 | expectUnhandled(bazMsg, ref)
437 |
438 | ref ! Foo("foo")
439 | expectMsg("foo: foo")
440 |
441 | // first become
442 |
443 | ref ! Foo("foo")
444 | expectUnhandled(Foo("foo"), ref)
445 |
446 | ref ! bazMsg
447 | expectUnhandled(bazMsg, ref)
448 |
449 | ref ! Bar
450 | expectMsg(Bar)
451 |
452 | // second become
453 |
454 | ref ! Foo("foo")
455 | expectUnhandled(Foo("foo"), ref)
456 |
457 | ref ! Bar
458 | expectUnhandled(Bar, ref)
459 |
460 | ref ! bazMsg
461 | expectMsg(SomeOtherMessage("baz"))
462 | }
463 |
464 | "fail to compile when message is not part of the union" >> {
465 | typecheck {
466 | """
467 | class MyActor extends TypedActor.Of[Foo | Bar.type | Baz] {
468 | def typedReceive: TypedReceive = Union.on[Foo]{
469 | case Foo(msg) ⇒ unionBecome.on[SomeOtherMessage] {
470 | case x => ()
471 | }
472 | }.apply
473 | }
474 | """
475 | } must failWith(Regex.quote("Cannot prove that message of type de.knutwalker.akka.typed.UnionSpec.SomeOtherMessage is a member of de.knutwalker.akka.typed.|[de.knutwalker.akka.typed.|[de.knutwalker.akka.typed.UnionSpec.Foo,de.knutwalker.akka.typed.UnionSpec.Bar.type],de.knutwalker.akka.typed.UnionSpec.Baz]."))
476 | }
477 |
478 | "fail to compile when total handler is not exhaustive" >> {
479 | val ct = implicitly[ClassTag[Option[Foo]]]
480 | typecheck {
481 | """
482 | class MyActor extends TypedActor.Of[Option[Foo] | Bar.type] {
483 | def typedReceive: TypedReceive = Union.on[Bar.type]{
484 | case Bar ⇒ unionBecome.total[Option[Foo]]({
485 | case Some(Foo("foo")) => ()
486 | })(implicitly, ct)
487 | }.apply
488 | }
489 | """
490 | } must failWith(Regex.quote("match may not be exhaustive.\nIt would fail on the following inputs: None, Some(_)"))
491 | }.pendingUntilFixed("succeeds... possibly missing fatal warnings or similar")
492 |
493 | "not fail when the unoinBecome.total doesnt match everything" >> {implicit ee: ExecutionEnv ⇒
494 | class MyActor extends TypedActor.Of[Foo | Bar.type] {
495 | def typedReceive: TypedReceive = Union.on[Bar.type] {
496 | case Bar ⇒
497 | inboxRef ! Bar
498 | unionBecome.total[Foo] {
499 | case Foo("foo") ⇒ inboxRef ! Foo("foo")
500 | }
501 | }.apply
502 | }
503 | val ref = ActorOf(PropsFor(new MyActor))
504 |
505 | ref ! Bar
506 | expectMsg(Bar)
507 |
508 | ref ! Foo("baz")
509 | expectUnhandled(Foo("baz"), ref)
510 | }
511 | }
512 | }
513 |
514 | def expectUnhandled(message: Any, ref: ActorRef[_])(implicit ee: ExecutionEnv) = {
515 | val receiveTimeout = Duration(100L * ee.timeFactor.toLong, TimeUnit.MILLISECONDS)
516 | inbox.receive(receiveTimeout) === UnhandledMessage(message, system.deadLetters, ref.untyped)
517 | }
518 |
519 | def expectMsg(expected: Any)(implicit ee: ExecutionEnv) = {
520 | val receiveTimeout = Duration(100L * ee.timeFactor.toLong, TimeUnit.MILLISECONDS)
521 | inbox.receive(receiveTimeout) === expected
522 | }
523 |
524 | def afterAll(): Unit = Shutdown(system)
525 | }
526 |
--------------------------------------------------------------------------------
/tests/src/test/scala/de/knutwalker/akka/typed/UnionTypeSpec.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 – 2016 Paul Horn
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package de.knutwalker.akka.typed
18 |
19 | import org.specs2.execute._
20 | import org.specs2.execute.Typecheck._
21 | import org.specs2.matcher.TypecheckMatchers._
22 | import org.specs2.mutable.Specification
23 |
24 |
25 | object UnionTypeSpec extends Specification {
26 |
27 | type IS = Int | String
28 |
29 | type ISB = IS | Boolean
30 | type BIS = Boolean | IS
31 |
32 | type ISBL = ISB | Long
33 | type LISB = Long | ISB
34 | type BISL = BIS | Long
35 | type LBIS = Long | BIS
36 |
37 | "is part of (positive)" should {
38 |
39 | "infer IS" >> typecheck {
40 | """
41 | implicitly[Int isPartOf IS]
42 | implicitly[String isPartOf IS]
43 | """
44 | }
45 |
46 | "infer ISB" >> typecheck {
47 | """
48 | implicitly[Int isPartOf ISB]
49 | implicitly[String isPartOf ISB]
50 | implicitly[Boolean isPartOf ISB]
51 | """
52 | }
53 |
54 | "infer BIS" >> typecheck {
55 | """
56 | implicitly[Int isPartOf BIS]
57 | implicitly[String isPartOf BIS]
58 | implicitly[Boolean isPartOf BIS]
59 | """
60 | }
61 |
62 | "infer ISBL" >> typecheck {
63 | """
64 | implicitly[Int isPartOf ISBL]
65 | implicitly[String isPartOf ISBL]
66 | implicitly[Boolean isPartOf ISBL]
67 | implicitly[Long isPartOf ISBL]
68 | """
69 | }
70 |
71 | "infer LISB" >> typecheck {
72 | """
73 | implicitly[Int isPartOf LISB]
74 | implicitly[String isPartOf LISB]
75 | implicitly[Boolean isPartOf LISB]
76 | implicitly[Long isPartOf LISB]
77 | """
78 | }
79 |
80 | "infer BISL" >> typecheck {
81 | """
82 | implicitly[Int isPartOf BISL]
83 | implicitly[String isPartOf BISL]
84 | implicitly[Boolean isPartOf BISL]
85 | implicitly[Long isPartOf BISL]
86 | """
87 | }
88 |
89 | "infer LBIS" >> typecheck {
90 | """
91 | implicitly[Int isPartOf LBIS]
92 | implicitly[String isPartOf LBIS]
93 | implicitly[Boolean isPartOf LBIS]
94 | implicitly[Long isPartOf LBIS]
95 | """
96 | }
97 | }
98 |
99 | "is part of (negative)" should {
100 |
101 | "not infer IS" >> {
102 | typecheck {
103 | """
104 | implicitly[Float isPartOf IS]
105 | """
106 | } must not succeed
107 | }
108 |
109 | "not infer ISB" >> {
110 | typecheck {
111 | """
112 | implicitly[Float isPartOf ISB]
113 | """
114 | } must not succeed
115 | }
116 |
117 | "not infer BIS" >> {
118 | typecheck {
119 | """
120 | implicitly[Float isPartOf BIS]
121 | """
122 | } must not succeed
123 | }
124 |
125 | "not infer ISBL" >> {
126 | typecheck {
127 | """
128 | implicitly[Float isPartOf ISBL]
129 | """
130 | } must not succeed
131 | }
132 |
133 | "not infer LISB" >> {
134 | typecheck {
135 | """
136 | implicitly[Float isPartOf LISB]
137 | """
138 | } must not succeed
139 | }
140 |
141 | "not infer BISL" >> {
142 | typecheck {
143 | """
144 | implicitly[Float isPartOf BISL]
145 | """
146 | } must not succeed
147 | }
148 |
149 | "not infer LBIS" >> {
150 | typecheck {
151 | """
152 | implicitly[Float isPartOf LBIS]
153 | """
154 | } must not succeed
155 | }
156 | }
157 |
158 | "contains some of (positive)" should {
159 |
160 | "infer IS" >> typecheck {
161 | """
162 | implicitly[(Int | String) containsSomeOf IS]
163 | implicitly[(String | Int) containsSomeOf IS]
164 | """
165 | }
166 |
167 | "infer ISB" >> typecheck {
168 | """
169 | implicitly[(Int | String) containsSomeOf ISB]
170 | implicitly[(String | Int) containsSomeOf ISB]
171 | implicitly[(Int | Boolean) containsSomeOf ISB]
172 | implicitly[(Boolean | Int) containsSomeOf ISB]
173 | implicitly[(String | Boolean) containsSomeOf ISB]
174 | implicitly[(Boolean | String) containsSomeOf ISB]
175 | implicitly[(Int | String | Boolean) containsSomeOf ISB]
176 | implicitly[(Int | Boolean | String) containsSomeOf ISB]
177 | implicitly[(String | Int | Boolean) containsSomeOf ISB]
178 | implicitly[(String | Boolean | Int) containsSomeOf ISB]
179 | implicitly[(Boolean | Int | String) containsSomeOf ISB]
180 | implicitly[(Boolean | String | Int) containsSomeOf ISB]
181 | implicitly[(Int | (String | Boolean)) containsSomeOf ISB]
182 | implicitly[(Int | (Boolean | String)) containsSomeOf ISB]
183 | implicitly[(String | (Int | Boolean)) containsSomeOf ISB]
184 | implicitly[(String | (Boolean | Int)) containsSomeOf ISB]
185 | implicitly[(Boolean | (Int | String)) containsSomeOf ISB]
186 | implicitly[(Boolean | (String | Int)) containsSomeOf ISB]
187 | """
188 | }
189 |
190 | "infer BIS" >> typecheck {
191 | """
192 | implicitly[(Int | String) containsSomeOf BIS]
193 | implicitly[(String | Int) containsSomeOf BIS]
194 | implicitly[(Int | Boolean) containsSomeOf BIS]
195 | implicitly[(Boolean | Int) containsSomeOf BIS]
196 | implicitly[(String | Boolean) containsSomeOf BIS]
197 | implicitly[(Boolean | String) containsSomeOf BIS]
198 | implicitly[(Int | String | Boolean) containsSomeOf BIS]
199 | implicitly[(Int | Boolean | String) containsSomeOf BIS]
200 | implicitly[(String | Int | Boolean) containsSomeOf BIS]
201 | implicitly[(String | Boolean | Int) containsSomeOf BIS]
202 | implicitly[(Boolean | Int | String) containsSomeOf BIS]
203 | implicitly[(Boolean | String | Int) containsSomeOf BIS]
204 | implicitly[(Int | (String | Boolean)) containsSomeOf BIS]
205 | implicitly[(Int | (Boolean | String)) containsSomeOf BIS]
206 | implicitly[(String | (Int | Boolean)) containsSomeOf BIS]
207 | implicitly[(String | (Boolean | Int)) containsSomeOf BIS]
208 | implicitly[(Boolean | (Int | String)) containsSomeOf BIS]
209 | implicitly[(Boolean | (String | Int)) containsSomeOf BIS]
210 | """
211 | }
212 | }
213 |
214 | "contains some of (negative)" should {
215 |
216 | "not infer unrelated type 1" >> {
217 | typecheck {
218 | """
219 | implicitly[(Float | String) containsSomeOf IS]
220 | """
221 | } must not succeed
222 | }
223 |
224 | "not infer unrelated type 2" >> {
225 | typecheck {
226 | """
227 | implicitly[(String | Float) containsSomeOf IS]
228 | """
229 | } must not succeed
230 | }
231 |
232 | "not infer unrelated type 3" >> {
233 | typecheck {
234 | """
235 | implicitly[(Float | Int) containsSomeOf IS]
236 | """
237 | } must not succeed
238 | }
239 |
240 | "not infer unrelated type 4" >> {
241 | typecheck {
242 | """
243 | implicitly[(Int | Float) containsSomeOf IS]
244 | """
245 | } must not succeed
246 | }
247 |
248 | "not infer non union 1" >> {
249 | typecheck {
250 | """
251 | implicitly[Int containsSomeOf IS]
252 | implicitly[String containsSomeOf IS]
253 | """
254 | } must not succeed
255 | }
256 |
257 | "not infer non union 2" >> {
258 | typecheck {
259 | """
260 | implicitly[Int containsSomeOf IS]
261 | implicitly[String containsSomeOf IS]
262 | """
263 | } must not succeed
264 | }
265 | }
266 |
267 | "contains all of (positive)" should {
268 |
269 | "infer ISB" >> typecheck {
270 | """
271 | implicitly[ISB containsAllOf ISB]
272 | implicitly[BIS containsAllOf ISB]
273 | implicitly[(Int | (String | Boolean)) containsAllOf ISB]
274 | implicitly[(Int | (Boolean | String)) containsAllOf ISB]
275 | implicitly[(String | (Int | Boolean)) containsAllOf ISB]
276 | implicitly[(String | (Boolean | Int)) containsAllOf ISB]
277 | implicitly[(Boolean | (Int | String)) containsAllOf ISB]
278 | implicitly[(Boolean | (String | Int)) containsAllOf ISB]
279 | implicitly[((Int | String) | Boolean) containsAllOf ISB]
280 | implicitly[((Int | Boolean) | String) containsAllOf ISB]
281 | implicitly[((String | Int) | Boolean) containsAllOf ISB]
282 | implicitly[((String | Boolean) | Int) containsAllOf ISB]
283 | implicitly[((Boolean | Int) | String) containsAllOf ISB]
284 | implicitly[((Boolean | String) | Int) containsAllOf ISB]
285 | """
286 | }
287 | }
288 |
289 | "contains all of (negative)" should {
290 |
291 | "not infer subunion 1" >> {
292 | typecheck {
293 | """
294 | implicitly[(Int | String) containsAllOf ISB]
295 | """
296 | } must not succeed
297 | }
298 |
299 | "not infer subunion 2" >> {
300 | typecheck {
301 | """
302 | implicitly[(String | Int) containsAllOf ISB]
303 | """
304 | } must not succeed
305 | }
306 |
307 | "not infer subunion 3" >> {
308 | typecheck {
309 | """
310 | implicitly[(Int | Boolean) containsAllOf ISB]
311 | """
312 | } must not succeed
313 | }
314 |
315 | "not infer subunion 4" >> {
316 | typecheck {
317 | """
318 | implicitly[(Boolean | Int) containsAllOf ISB]
319 | """
320 | } must not succeed
321 | }
322 |
323 | "not infer subunion 5" >> {
324 | typecheck {
325 | """
326 | implicitly[(Boolean | String) containsAllOf ISB]
327 | """
328 | } must not succeed
329 | }
330 |
331 | "not infer subunion 6" >> {
332 | typecheck {
333 | """
334 | implicitly[(String | Boolean) containsAllOf ISB]
335 | """
336 | } must not succeed
337 | }
338 | }
339 |
340 | }
341 |
--------------------------------------------------------------------------------
/version.sbt:
--------------------------------------------------------------------------------
1 | version in ThisBuild := "1.6.1-SNAPSHOT"
--------------------------------------------------------------------------------