// [ C ]
int *numbers = (int *)malloc(sizeof(int) * 42);
if (numbers == NULL) {
// Error handling here
}
// [ C ]
int *numbers = (int *)malloc(sizeof(int) * 42);
// Easily forgets to handle errors
// [ Java ]
static int toInt(String string)
throws FormatException {
...
}
// [ Java ]
toInt("42"); // Success
toInt("Swift"); // Failure
// [ Java ]
String string = ...;
int number = toInt(string); // Compilation error
// [ Java ]
String string = ...;
try {
int number = toInt(string);
...
} catch (FormatException e) {
// Error handling
...
}
// [ Java ]
String string = ...;
try {
int number = toInt(string);
...
} catch (FormatException e) {
// Error handling
throw new Error("Never reaches here.");
}
// [ Java ]
String string = ...;
int number = toInt(string)!; // Ignores exceptions
// This `!` was what we wanted.
// [ Swift ]
func toInt(string: String) -> Int? {
...
}
// [ Swift ]
let string: String = ...
let number: Int = toInt(string) // Compilation error
// [ Swift ]
let string: String = ...
if let number = toInt(string) {
...
} else {
// Error handling
...
}
// [ Swift ]
let string: String = ...
let number: Int = toInt(string)! // Ignores an error
// [ Swift ]
@warn_unused_result
func updateBar(bar: Bar) -> ()? {
...
}
// [ Swift ]
foo.updateBar(bar) // Warning
// [ Swift ]
if let _ = foo.updateBar(bar) {
...
} else {
// Error handling
...
}
// [ Swift ]
_ = foo.updateBar(bar) // Ignores the error
// [ Swift ]
let string: String = ...
let number: Int? = toInt(string)
...
// Errors can be handled lazily
if let number = number {
...
} else {
// Error handling
...
}
// [ Swift ]
let a: Int? = ...
let square = a * a // Compilation error
// [ Swift ]
let a: Int? = ...
let b: Int? = ...
let sum = a + b // Compilation error
// [ Swift ]
let a: Int? = ...
let square: Int?
if let a = a {
square = a * a
} else {
square = nil
}
// [ Swift ]
let a: Int? = ...
let b: Int? = ...
let sum: Int?
if let a = a, b = b {
sum = a + b
} else {
sum - nil
}
// [ Swift ]
let a: Int? = ...
let square: Int? = a.map { $0 * $0 }
// [ Swift ]
let a: Int? = ...
let b: Int? = ...
let sum: Int? = a.flatMap { a in b.map { b in a + b } }
// [ Swift ]
let id: String? = json["id"].string
// [ JSON ]
// The `json` might not be an `Object`
[ "abc" ]
// It might not have a key named `"id"`
{ "foo": "abc" }
// The value might not be a `String`
{ "id": 42 }
// [ Swift ]
struct Person {
let id: String
let firstName: String
let lastName: String
let age: Int
let isAdmin: Bool
}
let id: String? = json["id"].string
let firstName: String? = json["firstName"].string
let lastName: String? = json["lastName"].string
let age: Int? = json["age"].int
let isAdmin: Bool? = json["isAdmin"].bool
// [ Swift ]
let person: Person? = id.flatMap { id in
firstName.flatMap { firstName in
lastName.flatMap { lastName in
age.flatMap { age in
isAdmin.flatMap { isAdmin in
Person(id: id, firstName: firstName,
lastName: lastName, age: age, isAdmin: isAdmin)
}
}
}
}
}
// [ Swift ]
let person: Person?
if let id = id, firstName = firstName,
lastName = lastName, age = age, isAdmin = isAdmin {
person = Person(id: id, firstName: firstName,
lastName: lastName, age: age, isAdmin: isAdmin)
} else {
person = nil
}
// [ Swift ]
let person: Person? = curry(Person.init) <^> id
<*> firstName <*> lastName <*> age <*> isAdmin
// [ Swift ]
//let foo: Optional<Foo> = ...
let foo: Foo? = ...
// let baz: Baz? = foo.flatMap { $0.bar }.flatMap { $0.baz }
let baz: Baz? = foo?.bar?.baz
// let quxOrNil: Qux? = ...
// let qux: Qux
// if let q = quxOrNil {
// qux = q
// } else {
// qux = Qux()
// }
let quxOrNil: Qux? = ...
let qux: Qux = quxOrNil ?? Qux()
Foo? == Optional<Foo>
!
map
, flatMap
<^>
, <*>
(1)foo?.bar?.baz
??
I couldn’t resist the temptation to put in a null reference, simply because it was so easy to implement. – Tony Hoare
// [ Swift ]
let a: Int? = toInt(aString)
let b: Int? = toInt(bString)
let sum: Int? = a.flatMap { a in b.map { b in a + b } }
guard let sum = sum else {
// Which `a` or `b` failed to be parsed?
// What string was the input?
...
}
// [ Swift ]
let isAdmin: Bool
if let admin = json["isAdmin"].bool {
// { "isAdmin": true }
isAdmin = admin
} else {
// 1. [ true ]
// 2. {}
// 3. { "isAdmin": 42 }
isAdmin = ...
}
// [ Swift ]
let isAdmin: Bool
if let admin = json["isAdmin"].bool {
// { "isAdmin": true }
isAdmin = admin
} else {
// 1. [ true ]
// 2. {} => false
// 3. { "isAdmin": 42 }
isAdmin = ...
}
// [ Swift ]
let isAdmin: Bool
if let admin = json["isAdmin"].bool {
// { "isAdmin": true }
isAdmin = admin
} else {
// 1. [ true ] => error
// 2. {} => false
// 3. { "isAdmin": 42 } => error
isAdmin = ...
}
// [ Swift ]
func toInt(string: String) -> (Int?, FormatError?) {
...
}
(value, nil ) // Success
(nil , error) // Failure
(value, error) // ???
(nil , nil ) // ???
// [ Swift ]
func toInt(string: String) -> Int|FormatError {
...
}
// [ Swift ]
switch toInt(...) {
case let value as Int:
...
case let error as FormatError:
// Error handling
...
}
// [ Ceylon ]
Integer? a = 42;
Integer|Null a = 42;
# [ Python ]
def foo() -> Optional[Foo]: ...
def foo() -> Union[Foo, None]: ...
// [ Swift ]
enum Optional<T> {
case Some(T)
case None
}
// [ Swift ]
enum Result<T, E> {
case Success(T)
case Failure(E)
}
// [ Swift ]
enum Optional<T> {
case Some(T)
case None
}
// [ Swift ]
let a: Result<Int, FormatError> = toInt(aString)
let b: Result<Int, FormatError> = toInt(bString)
let sum: Result<Int, FormatError> = a.flatMap { a in b.map { b in a + b } }
switch sum {
case let .Success(sum):
...
case let .Failure(error):
// Get the detailed error information from `error`
...
}
// [ Swift ]
let isAdmin: Bool
switch json["isAdmin"].bool {
case let .Success(admin):
isAdmin = admin
case .Failure(.MissingKey):
// {} => false
isAdmin = false
case .Failure(.TypeMismatch, .NotObject):
// [ true ] => error
// { "isAdmin": 42 } => error
...
}
// [ Swift ]
let foo: Result<Foo, Error> = ...
let baz: Result<Foo, Error>
= foo.flatMap { $0.bar }.flatMap { $0.baz }
// [ Swift ]
let foo: Foo|Error = ...
let baz: Baz|Error = foo?.bar?.baz
// [ Swift ]
let a: Result<Int, ErrorA> = ...
let b: Result<Int, ErrorB> = ...
let sum: Result<Int, ???>
= a.flatMap { a in b.map { b in a + b } }
// [ Swift ]
let a: Result<Int, ErrorA> = ...
let b: Result<Int, ErrorB> = ...
let sum: Result<Int, ErrorA|ErrorB>
= a.flatMap { a in b.map { b in a + b } }
// [ Swift ]
let a: Result<Int, ErrorA> = ...
let b: Result<Int, ErrorB> = ...
let sum: Result<Int, Result<ErrorA, ErrorB>>
= a.flatMap { a in b.map { b in a + b } }
// [ Swift ]
let a: Int|ErrorA = ...
let b: Int|ErrorB = ...
let sum: Int|ErrorA|ErrorB
= a.flatMap { a in b.map { b in a + b } }
// [ Swift ]
let id: String|ErrorA = ...
let firstName: String|ErrorB = ...
let lastName: String|ErrorC = ...
let age: Int|ErrorD = ...
let isAdmin: Bool| ErrorE = ...
let person: Person|(((ErrorA|ErrorB)|ErrorC)|ErrorD)|ErrorE
= curry(Person.init) <^> id <*> firstName
<*> lastName <*> age <*> isAdmin
// [ Swift ]
switch person {
case let .Success(person):
...
case let .Failure(.Success(.Success(.Success(.Success(.Failure(errorA)))))):
...
case let .Failure(.Success(.Success(.Success(.Failure(errorB))))):
...
case let .Failure(.Success(.Success(.Failure(errorC)))):
...
case let .Failure(.Success(.Failure(errorD))):
...
case let .Failure(.Failure(errorD)):
...
}
// [ Swift ]
enum Result<T, E> {
case Success(T)
case Failure(E)
}
// [ Swift ]
enum Result<T> {
case Success(T)
case Failure(ErrorType)
}
// [ Swift ]
downloadJson(url) { json: Result<Json> in
switch json {
case let .Success(json): // success
...
case let .Failure(.Timeout): // timeout
// retry
...
case let .Failure(error): // others
// error
...
}
}
// [ Swift ]
let isAdmin: Bool
switch json["isAdmin"].bool {
case let .Success(admin): // success
isAdmin = admin
case .Failure(.MissingKey): // missing key
// {} => false
isAdmin = false
case let .Failure(error): // others
// [ true ] => error
// { "isAdmin": 42 } => error
...
}
// [ Swift ]
enum Foo {
case Bar(A)
case Baz
case Qux(B)
}
func foo() -> Foo { ... }
switch foo() {
case let Bar(a):
...
case let Baz:
...
case let Qux(b):
...
}
// [ Swift ]
let a: Result<Int> = ... // ErrorA
let b: Result<Int> = ... // ErrorB
let sum: Result<Int> // ErrorA or ErrorB
= a.flatMap { a in b.map { b in a + b } }
// [ Swift ]
let a: Int| = ...
let b: Int| = ...
let sum: Int|
= a.flatMap { a in b.map { b in a + b } }
// [ Swift ]
func toInt(string: String) throws -> Int {
...
}
do {
let number = try toInt(string)
...
} catch let error {
// Error handling here
...
}
// [ Swift ]
// Manual propagation
switch(toInt(string)) {
case let .Success(number):
...
case let .Failure(error): // Handles an error manually
...
}
// Automatic propagation
do {
let number = try toInt(string) // Jumps to `catch` automatically
...
} catch let error {
...
}
// [ Swift ]
// Manual propagation
let a: Result<Int> = toInt(aString)
let b: Result<Int> = toInt(bString)
switch a.flatMap { a in b.map { b in a + b } } {
case let .Success(sum):
...
case let .Failure(error):
...
}
// Automatic propagation
do {
let a: Int = try toInt(aString)
let b: Int = try toInt(bString)
let sum: Int = a + b
...
} catch let error {
...
}
// [ Swift ]
let sum = toInt(aString).flatMap { a in
toInt(bString).flatMap {
.Some(a + b)
}
}
-- [ Haskell ]
sum = do
a <- toInt aString
b <- toInt bString
Just (a + b)
// [ Swift ]
func toInt(string: String) throws FormatError -> Int { // Compilation error
...
}
// [ Java ]
class FormatException extends RuntimeException {
...
}
// [ Java ]
static int toInt(String string) throws FormatException {
...
}
// [ Java ]
String string = ...;
int number = toInt(string); // No compilation error
// [ Java ]
void foo() { // What can `foo` throw?
a(); // May throw an unchecked exception
b(); // May throw an unchecked exception
c(); // May throw an unchecked exception
d(); // May throw an unchecked exception
e(); // May throw an unchecked exception
f(); // May throw an unchecked exception
g(); // May throw an unchecked exception
}
// [ Swift ]
func foo() throws { // What can `foo` throw?
a() // Can throw an error?
b() // Can throw an error?
c() // Can throw an error?
d() // Can throw an error?
e() // Can throw an error?
f() // Can throw an error?
g() // Can throw an error?
}
// [ Swift ]
func foo() throws {
a()
try b() // May throw an error
c()
d()
try e() // May throw an error
f()
g()
}
// [ Java ]
try {
foo();
bar();
baz();
} catch (QuxException e) {
// Where did it come from?
}
// [ Swift ]
do {
foo()
try bar()
baz()
} catch let error {
// Came from `bar()`
}
// [ Swift ]
// Optionals for a simple domain error
func toInt(string: String) -> Int? {
...
}
// Manual propagation
guard let number = toInt(string) {
// Error handling here
...
}
// [ Swift ]
// Manual propagation
let a: Int? = toInt(aString)
let b: Int? = toInt(bString)
if let sum = (a.flatMap { a in b.map { b in a + b } }) {
...
} else {
...
}
// Automatic propagation
do {
let a: Int = try toInt(aString)
let b: Int = try toInt(bString)
let sum: Int = a + b
...
} catch {
...
}
// [ Swift ]
func toInt(string: String) throws -> Int {
...
}
// [ Swift ]
func toInt(string: String) -> Result<Int> {
...
}
// [ Swift ]
do {
let a: Int = try toInt(aString)
let b: Int = try toInt(bString)
let sum: Int = a + b
...
} catch {
...
}
// [ Swift ]
let a: Result<Int> = toInt(aString)
let b: Result<Int> = toInt(bString)
switch a.flatMap { a in b.map { b in a + b } } {
case let .Success(sum):
...
case let .Failure(error):
...
}
// [ Swift ]
let infinite: List<Int> = List { $0 } // [0, 1, 2, 3, 4, ...]
let square: List<Int> = infinite.map { $0 * $0 } // [0, 1, 4, 9, 16, ...]
// [ Swift ]
func toInt(string: String) throws -> Int {
...
}
let strings: List<String> = ... // ["0", "1", "2", ...]
do {
// Never finishes
let numbers: List<Int> = try strings.map(transform: toInt)
} catch let error {
...
}
// [ Swift ]
// By throws
func map<U>(transform: T throws -> U) throws -> List<U>
// By `Result`
func map<U>(transform: T -> Result<U>) -> Result<List<U>>
// [ Swift ]
// By throws
func map<U>(transform: T throws -> U) throws -> List<U>
func map<U>(transform: T throws -> U) -> List<Result<U>>
// By `Result`
func map<U>(transform: T -> Result<U>) -> Result<List<U>>
func map<U>(transform: T -> Result<U>) -> List<Result<U>>
// [ Swift ]
func toInt(string: String) throws -> Int {
...
}
let a: List<String> = ... // ["0", "1", "2", ...]
let b: List<Result<Int>> = strings.map(transform: toInt)
// [Result(0), Result(1), Result(2), ...]
let c: List<Result<Int>> = numbers.take(10)
// [Result(0), Result(1), Result(2), ..., Result(9)]
let d: Result<List<Int>> = sequence(first10)
// Result([0, 1, 2, ..., 9])
do {
let e: List<Int> = try d // [0, 1, 2, ..., 9]
...
} catch let error {
// Handling `FormatError`
...
}
// Swift 2.x
let a = toInt(aString) // Compilation error here
let b = toInt(bString)
let sum = a + b
// Swift with my proposal
let a = toInt(aString)
let b = toInt(bString)
let sum = a + b // Compilation error here
// [ Swift ]
let a: Promise<Int> = asyncGetInt(...)
let b: Promise<Int> = asyncGetInt(...)
let sum: Promise<Int> = a.flatMap { a in b.map { b in a + b } }
// [ Swift ]
let a: Result<Int> = failableGetInt(...)
let b: Result<Int> = failableGetInt(...)
let sum: Result<Int> = a.flatMap { a in b.map { b in a + b } }
// [ C# ]
async Task<int> AsyncGetInt(...) {
...
}
async void PrintSum() {
int a = await AsyncGetInt(...);
int b = await AsyncGetInt(...);
Console.WriteLine(a + b);
}
// [ Swift ]
func asyncGetInt(...) async -> Promise<Int> {
...
}
func printSum() async {
let a: Int = await asyncGetInt(...)
let b: Int = await asyncGetInt(...)
print(a + b)
}
// [ C# ]
async Task<int> AsyncGetInt(...) {
...
}
async void PrintSum() {
int a = await AsyncGetInt(...);
int b = await AsyncGetInt(...);
Console.WriteLine(a + b);
}
// [ Swift ]
func asyncGetInt(...) async -> Promise<Int> {
...
}
func printSum() async {
let a: Int = await asyncGetInt(...)
let b: Int = await asyncGetInt(...)
print(a + b)
}
// [ Swift ]
func asyncGetInt(...) async -> Int { // <- Changed Here
...
}
func printSum() async {
let a: Int = await asyncGetInt(...)
let b: Int = await asyncGetInt(...)
print(a + b)
}
// [ Swift ]
func asyncGetInt(...) async -> Promise<Int> {
...
}
func printSum() async {
let a: Int = await asyncGetInt(...)
let b: Int = await asyncGetInt(...)
print(a + b)
}
// [ Swift ]
func asyncGetInt(...) async -> Int { // async
...
}
func printSum() async { // async
let a: Int = await asyncGetInt(...) // await
let b: Int = await asyncGetInt(...) // await
print(a + b)
}
// [ Swift ]
func failableGetInt(...) throws -> Int { // throws
...
}
func printSum() throws { // throws
let a: Int = try failableGetInt(...) // try
let b: Int = try failableGetInt(...) // try
print(a + b)
}
// [ Swift ]
func asyncGetInt(...) async -> Int { // async
...
}
do {
let a: Int = await asyncGetInt(...) // await
let b: Int = await asyncGetInt(...) // await
print(a + b)
}
// [ Swift ]
func asyncGetInt(...) -> Promise<Int> { // Promise
...
}
do {
let a: Int = try asyncGetInt(...) // try
let b: Int = try asyncGetInt(...) // try
print(a + b)
}
Result<T>
instead of Result<T, E>
Optional
sthrows
and Result
try
for asynchronous operations… and let me know your opinions
“thoughtbot/Runes”, https://github.com/thoughtbot/Runes ↩