Either là một kiểu hàm biểu diễn một giá trị trong hai biến thể khả dĩ: Left cho lỗi và Right cho thành công. Không giống như ngoại lệ, Either làm cho việc xử lý lỗi trở nên rõ ràng ở cấp độ kiểu và không yêu cầu khối try-catch. Theo Arrow, 2026, Either được sử dụng rộng rãi trong các dự án Kotlin để kết hợp các thao tác có thể thất bại mà không có tác dụng phụ.
Những điểm chính
Either là một kiểu dữ liệu đại số, được vay mượn từ lập trình hàm, biểu diễn chính xác một trong hai kiểu khả dĩ. Trong bối cảnh xử lý lỗi, quy ước là: kiểu Left (Left) chứa mô tả vấn đề, trong khi kiểu Right (Right) chứa kết quả thành công.
Khái niệm Either bắt nguồn từ ngôn ngữ Haskell và phân loại kiểu Either a b, trong đó a là kiểu giá trị trái và b là kiểu giá trị phải. Trong phát triển di động, Either trở nên phổ biến nhờ thư viện Arrow cho Kotlin và các phương pháp hàm trong Swift.
Lợi thế chính của Either so với ngoại lệ là không có đường dẫn thực thi ẩn. Một hàm trả về Either khai báo rõ ràng trong chữ ký của nó rằng nó có thể thất bại. Trình biên dịch kiểm soát rằng cả hai biến thể đều được xử lý.
Không giống như ném ngoại lệ, Either duy trì tính minh bạch của luồng dữ liệu. Gọi một hàm trả về Either không yêu cầu try-catch ở phía người gọi — pattern-matching hoặc fold là đủ. Điều này đặc biệt quan trọng trong các kiến trúc có cách tiếp cận phản ứng, nơi mỗi nguồn dữ liệu trả về Either và chuỗi biến đổi được xây dựng qua map và flatMap.
Either bao gồm hai kiểu con: Left và Right. Một thể hiện Either chỉ có thể là một trong hai vào mỗi thời điểm. Chữ ký điển hình trong Kotlin là Either<E, A>, trong đó E là kiểu lỗi và A là kiểu giá trị thành công.
Left biểu diễn trường hợp thất bại. Không giống ngoại lệ, Left không làm gián đoạn luồng thực thi — nó chỉ trả về một giá trị cần được xử lý. Kiểu lỗi có thể là bất kỳ: String, Int, một sealed class tùy chỉnh hoặc mô hình miền.
Right chứa kết quả chính xác của thao tác. Tên gọi phản ánh sự đúng đắn — theo quy ước, Right có nghĩa là thành công. Các phép biến đổi như map và flatMap được áp dụng trên Right, cho phép xây dựng chuỗi tính toán mà không cần kiểm tra từng bước.
flatMap là cơ chế chính để kết hợp Either. Nếu giá trị hiện tại là Right, flatMap áp dụng hàm được truyền vào và trả về một Either mới. Nếu là Left, flatMap bỏ qua phép biến đổi và chuyển tiếp lỗi. Hành vi này được gọi là đánh giá short-circuit.
Ngoài flatMap, Either còn hỗ trợ mapLeft để biến đổi lỗi, fold để xử lý cả hai biến thể tại một chỗ và getOrElse để trích xuất giá trị với giá trị mặc định. Các hàm này bao phủ mọi tình huống: từ trích xuất đơn giản đến kết hợp phức tạp với các lời gọi bất đồng bộ trong coroutine Kotlin hoặc Combine trong Swift.
fun parseInt(input: String): Either<String, Int> =
input.toIntOrNull()?.let { Right(it) }
?: Left("Không thể chuyển đổi: $input")
fun divide(a: Int, b: Int): Either<String, Int> =
if (b == 0) Left("Chia cho không")
else Right(a / b)
val result = parseInt("10")
.flatMap { divide(it, 2) }
Trong hệ sinh thái Kotlin, Either được triển khai trong thư viện Arrow. Thư viện chuẩn của Kotlin cung cấp Result, nhưng Either mang lại sự linh hoạt hơn: kiểu lỗi tùy ý, kết hợp qua flatMap và hỗ trợ các mẫu hàm.
Arrow là thư viện hàm cho Kotlin bổ sung Either, Option, Validated và các kiểu khác. Arrow.Either là một sealed class với hai lớp con: ArrowCore.Left và ArrowCore.Right. Thư viện cũng cung cấp các phần mở rộng tiện lợi: .getOrElse(), .fold(), .mapLeft().
Hãy xem xét một tình huống thực tế — một yêu cầu mạng trong ứng dụng Android với các lỗi có thể xảy ra: mất mạng, lỗi máy chủ, phản hồi không hợp lệ. Either cho phép kết hợp tất cả các biến thể thành một kiểu trả về duy nhất.
sealed class NetworkError {
data class NoConnection(val message: String): NetworkError()
data class ServerError(val code: Int): NetworkError()
data class ParseError(val detail: String): NetworkError()
}
suspend fun fetchUser(id: String): Either<NetworkError, User> {
return try {
val response = api.getUser(id)
if (response.isSuccessful) {
Right(response.body()!!)
} else {
Left(NetworkError.ServerError(response.code()))
}
} catch (e: IOException) {
Left(NetworkError.NoConnection(e.message ?: ""))
}
}
Trong Swift, từ phiên bản 5.0, kiểu Result tích hợp sẵn có sẵn, về khái niệm tương tự Either nhưng có hạn chế: lỗi phải tuân thủ giao thức Error và giá trị thành công phải là một kiểu duy nhất. Either trong Swift được triển khai thông qua enum với hai tham số generic.
Result<Success, Failure> là kiểu tích hợp của Swift, trong đó Failure: Error. Either không áp đặt ràng buộc về kiểu lỗi, cho phép lưu trữ String, cấu trúc tùy chỉnh hoặc thậm chí nhiều kiểu lỗi thông qua các enum lồng nhau.
enum Either<E, A> {
case left(E)
case right(A)
func map<B>(_ transform: (A) -> B) -> Either<E, B> {
switch self {
case .left(let e): return .left(e)
case .right(let a): return .right(transform(a))
}
}
}
let result: Either<String, Int> = .right(42)
let mapped = result.map { $0 * 2 }
Either là tối ưu cho các tình huống yêu cầu xử lý lỗi rõ ràng và an toàn kiểu mà không có ngoại lệ. Hãy xem xét các trường hợp sử dụng chính trong phát triển di động với Kotlin và Swift.
Đừng sử dụng Either cho các thao tác đơn giản không có tác dụng phụ — giá trị trả về thông thường đáng tin cậy hơn và dễ đọc hơn. Either cũng là quá mức khi thất bại là một tình huống ngoại lệ chứ không phải kịch bản dự kiến.
Either cũng hiệu quả khi làm việc với coroutine trong Kotlin. Một hàm trả về Either có thể được gọi bên trong một coroutine với xử lý lỗi qua fold hoặc mapLeft mà không chặn luồng. Điều này đặc biệt hữu ích trong các ứng dụng Android với kiến trúc MVVM, nơi mỗi kho dữ liệu trả về Either và ViewModel biến đổi kết quả thành UiState.
Các nhà phát triển lần đầu làm quen với Either thường mắc phải những lỗi tương tự. Hãy xem xét những lỗi phổ biến nhất và cách tránh chúng.
Câu hỏi thường gặp
Optional biểu diễn sự hiện diện hoặc vắng mặt của giá trị (Some/None), nhưng không cho biết nguyên nhân vắng mặt. Either cung cấp hai kiểu cụ thể — trái cho lỗi và phải cho thành công — cho phép truyền ngữ cảnh thất bại.
Trong Java không có Either tích hợp sẵn, nhưng các thư viện Vavr và functionaljava cung cấp triển khai. Trong phát triển Android, Either từ Vavr là một lựa chọn thay thế phổ biến cho phong cách hàm với lambda.
Sealed class trong Kotlin tiện hơn khi có nhiều hơn hai biến thể hoặc chúng có cấu trúc khác nhau. Đối với kết quả nhị phân (lỗi/thành công), Either nhỏ gọn hơn và cung cấp các bộ kết hợp hàm có sẵn.
Either là bất biến và an toàn luồng theo mặc định. Trong Kotlin với coroutine, Either kết hợp hoàn hảo: flatMap hoạt động trong phạm vi coroutine và xử lý lỗi không yêu cầu khóa.
Không. Either phù hợp cho các thao tác có lỗi dự kiến (mạng, xác thực, logic nghiệp vụ). Đối với các getter đơn giản và tính toán không có tác dụng phụ, kiểu thông thường dễ đọc hơn và không thêm độ phức tạp không cần thiết.
Tổng kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm