Immutability
Immutability atau ketidakdapatubahan adalah konsep penting dalam Functional Programming.
Immutability berarti sebuah data tidak diubah secara langsung setelah data tersebut dibuat.
Ketika kita membutuhkan hasil yang berbeda, pendekatan immutable lebih mendorong kita untuk membuat data baru berdasarkan data sebelumnya.
Secara sederhana:
Data asli
│
↓
Function
│
↓
Data baru
Bukan:
Data asli
│
↓
Diubah langsung
│
↓
Data asli berubah
Konsep ini membantu mengurangi perubahan state yang tidak terduga dan membuat alur data lebih mudah dipahami.
Mutable dan Immutable
Sebelum memahami immutability dalam Functional Programming, kita perlu membedakan mutable object dan immutable object.
Mutable
Object mutable adalah object yang isinya dapat diubah setelah object dibuat.
Beberapa contoh tipe data mutable di Python:
list
dict
set
Contoh:
numbers = [1, 2, 3]
numbers.append(4)
print(numbers)
Output:
[1, 2, 3, 4]
List yang sama telah berubah.
Sebelum:
[1, 2, 3]
Setelah:
[1, 2, 3, 4]
Immutable
Object immutable adalah object yang tidak dapat diubah setelah dibuat.
Beberapa contoh tipe data immutable di Python:
int
float
bool
str
tuple
frozenset
Contohnya:
name = "Budi"
Kita tidak dapat mengubah karakter tertentu secara langsung:
name[0] = "A"
Kode tersebut akan menghasilkan error karena string bersifat immutable.
Jika kita membutuhkan string yang berbeda, kita membuat nilai baru:
name = "Andi"
Dalam hal ini, kita tidak mengubah karakter pada string lama, tetapi membuat binding name merujuk pada nilai string yang baru.
Immutability Tidak Sama dengan const
Python tidak memiliki keyword const seperti yang tersedia pada beberapa bahasa pemrograman.
Ketika kita menulis:
number = 10
kita masih dapat melakukan:
number = 20
Ini tidak berarti integer 10 diubah menjadi 20.
Yang terjadi adalah nama number sekarang merujuk pada object integer yang berbeda.
Secara konsep:
Sebelum:
number ──→ 10
Setelah:
number ──→ 20
Object integer 10 tidak diubah menjadi 20.
Contoh Immutable dengan Integer
Perhatikan:
number = 10
number = number + 5
print(number)
Output:
15
Secara sederhana kita dapat menganggap prosesnya:
number ──→ 10
↓ + 5
number ──→ 15
Integer merupakan immutable object.
Operasi:
number + 5
menghasilkan nilai baru.
Contoh Mutable dengan List
Sekarang bandingkan dengan list:
numbers = [1, 2, 3]
numbers.append(4)
print(numbers)
Output:
[1, 2, 3, 4]
Method append() memodifikasi list yang sama.
Secara konsep:
numbers
│
↓
[1, 2, 3]
↓ append(4)
[1, 2, 3, 4]
Inilah salah satu contoh mutation.
Mutation
Mutation adalah perubahan terhadap isi sebuah object setelah object tersebut dibuat.
Contoh:
numbers = [1, 2, 3]
numbers.append(4)
numbers.append(5)
print(numbers)
Output:
[1, 2, 3, 4, 5]
List mengalami mutation karena isinya berubah.
Dalam Functional Programming, mutation sebisa mungkin dikurangi, terutama ketika mutation tersebut dapat menyebabkan perubahan state yang sulit dilacak.
Mengapa Mutation Dapat Menjadi Masalah?
Perubahan object secara langsung dapat menjadi masalah ketika object tersebut digunakan oleh beberapa bagian program.
Contoh:
numbers = [1, 2, 3]
def add_number(numbers):
numbers.append(4)
add_number(numbers)
print(numbers)
Output:
[1, 2, 3, 4]
Function add_number() tidak mengembalikan list baru.
Sebaliknya, function tersebut mengubah list yang diberikan sebagai argument.
Hal ini merupakan side effect.
Mutation dan Shared State
Masalah dapat menjadi lebih sulit ketika satu object digunakan oleh beberapa bagian program.
Contoh:
numbers = [1, 2, 3]
numbers_copy = numbers
numbers_copy.append(4)
print(numbers)
Output:
[1, 2, 3, 4]
Mengapa numbers ikut berubah?
Karena:
numbers_copy = numbers
tidak membuat list baru.
Keduanya merujuk pada object list yang sama.
Secara konsep:
numbers ───────┐
↓
[1, 2, 3]
↑
│
numbers_copy ──┘
Ketika numbers_copy dimodifikasi:
numbers_copy.append(4)
object yang sama berubah.
Membuat Salinan Data
Jika kita ingin membuat list baru, kita dapat menggunakan beberapa pendekatan.
Contohnya:
numbers = [1, 2, 3]
numbers_copy = numbers.copy()
numbers_copy.append(4)
print(numbers)
print(numbers_copy)
Output:
[1, 2, 3]
[1, 2, 3, 4]
Sekarang kedua variable merujuk pada object yang berbeda.
Secara konsep:
numbers
↓
[1, 2, 3]
numbers_copy
↓
[1, 2, 3, 4]
Pendekatan Functional: Menghasilkan Data Baru
Dalam Functional Programming, kita dapat membuat function yang menghasilkan collection baru daripada mengubah collection yang diberikan.
Contoh:
def add_number(numbers):
return numbers + [4]
numbers = [1, 2, 3]
new_numbers = add_number(numbers)
print(numbers)
print(new_numbers)
Output:
[1, 2, 3]
[1, 2, 3, 4]
List asli tetap:
[1, 2, 3]
Sedangkan function menghasilkan list baru:
[1, 2, 3, 4]
Pendekatan ini lebih dekat dengan prinsip immutability.
Immutability dengan List Comprehension
List comprehension juga dapat digunakan untuk menghasilkan list baru.
Misalnya:
numbers = [1, 2, 3, 4]
new_numbers = [
number * 2
for number in numbers
]
print(numbers)
print(new_numbers)
Output:
[1, 2, 3, 4]
[2, 4, 6, 8]
List asli tidak diubah.
Function atau expression menghasilkan collection baru.
Immutability dan Pure Function
Immutability memiliki hubungan erat dengan Pure Function.
Pure function berusaha:
- Menghasilkan output berdasarkan input.
- Tidak mengubah data di luar function.
- Tidak menghasilkan side effect yang tidak diperlukan.
Contoh:
def multiply_by_two(numbers):
return [
number * 2
for number in numbers
]
Kemudian:
numbers = [1, 2, 3]
result = multiply_by_two(numbers)
print(numbers)
print(result)
Output:
[1, 2, 3]
[2, 4, 6]
Function tidak mengubah numbers.
Sebaliknya, function menghasilkan list baru.
Contoh Function yang Mengubah Data
Bandingkan dengan pendekatan berikut:
def multiply_by_two(numbers):
for index in range(len(numbers)):
numbers[index] = numbers[index] * 2
return numbers
Kemudian:
numbers = [1, 2, 3]
result = multiply_by_two(numbers)
print(numbers)
print(result)
Output:
[2, 4, 6]
[2, 4, 6]
List asli telah dimodifikasi.
Pendekatan tersebut menggunakan mutation.
Perbandingan Kedua Pendekatan
Mutation
def multiply_by_two(numbers):
for index in range(len(numbers)):
numbers[index] *= 2
return numbers
Alurnya:
List asli
↓
Dimodifikasi
↓
List asli berubah
Immutable Approach
def multiply_by_two(numbers):
return [
number * 2
for number in numbers
]
Alurnya:
List asli
↓
Diproses
↓
List baru
Pendekatan kedua lebih sesuai dengan prinsip Functional Programming.
Immutability pada Dictionary
Dictionary merupakan mutable object.
Contoh mutation:
user = {
"name": "Budi",
"age": 25
}
user["age"] = 26
print(user)
Output:
{
"name": "Budi",
"age": 26
}
Dictionary yang sama telah dimodifikasi.
Jika ingin menggunakan pendekatan yang tidak memodifikasi dictionary asli, kita dapat membuat dictionary baru.
Contoh:
user = {
"name": "Budi",
"age": 25
}
new_user = {
**user,
"age": 26
}
print(user)
print(new_user)
Output:
{
"name": "Budi",
"age": 25
}
{
"name": "Budi",
"age": 26
}
Dictionary asli tetap tidak berubah.
Immutability pada Set
Set juga merupakan mutable object.
Contoh:
numbers = {1, 2, 3}
numbers.add(4)
print(numbers)
Output:
{1, 2, 3, 4}
Set telah dimodifikasi.
Python menyediakan frozenset sebagai versi set yang immutable.
Contoh:
numbers = frozenset([1, 2, 3])
print(numbers)
Kita tidak dapat menggunakan:
numbers.add(4)
karena frozenset tidak menyediakan operasi mutation tersebut.
Immutability pada Tuple
Tuple merupakan immutable object.
Contoh:
numbers = (1, 2, 3)
Kita tidak dapat mengubah elemen secara langsung:
numbers[0] = 10
Kode tersebut menghasilkan:
TypeError
Jika membutuhkan tuple berbeda, kita membuat tuple baru:
numbers = (10, 2, 3)
Hati-Hati dengan Nested Object
Immutability dapat menjadi lebih kompleks ketika sebuah immutable object berisi mutable object.
Contohnya:
data = (
"Budi",
[1, 2, 3]
)
Tuple tersebut immutable, tetapi list di dalamnya mutable.
Kita masih dapat melakukan:
data[1].append(4)
print(data)
Output:
("Budi", [1, 2, 3, 4])
Tuple-nya sendiri tidak diganti, tetapi object list yang berada di dalamnya mengalami mutation.
Hal ini penting ketika membahas immutability pada struktur data yang kompleks.
Shallow Copy dan Deep Copy
Ketika bekerja dengan data yang memiliki struktur bertingkat, membuat salinan tidak selalu berarti seluruh object di dalamnya ikut disalin.
Contoh:
users = [
{
"name": "Budi"
}
]
Jika kita menggunakan shallow copy:
users_copy = users.copy()
list bagian luar memang dibuat baru, tetapi dictionary di dalamnya masih dapat merujuk pada object yang sama.
Untuk struktur data bertingkat, Python menyediakan module copy.
Contohnya:
import copy
users = [
{
"name": "Budi"
}
]
users_copy = copy.deepcopy(users)
users_copy[0]["name"] = "Andi"
print(users)
print(users_copy)
Output:
[{"name": "Budi"}]
[{"name": "Andi"}]
Dengan deepcopy(), object yang berada di dalam struktur juga disalin secara rekursif.
Immutability Bukan Berarti Semua Data Harus Immutable
Dalam Python, kita tidak harus membuat seluruh aplikasi menggunakan immutable object.
Python sendiri menyediakan banyak mutable object seperti:
list
dict
set
Mutation juga dapat berguna dalam kondisi tertentu.
Yang penting dalam Functional Programming adalah memahami kapan mutation diperlukan dan kapan sebaiknya dihindari.
Manfaat Immutability
Mengurangi mutation dapat memberikan beberapa manfaat.
Predictability
Data lebih mudah diprediksi karena tidak berubah secara tiba-tiba.
Easier Testing
Function yang tidak mengubah data luar lebih mudah diuji.
Easier Debugging
Perubahan state yang lebih sedikit membuat bug lebih mudah dilacak.
Reduced Side Effects
Mutation yang tidak diperlukan dapat dikurangi.
Safer Shared Data
Data yang tidak dimodifikasi secara langsung lebih aman ketika digunakan oleh beberapa bagian program.
Immutability dan State
Salah satu alasan immutability penting dalam Functional Programming adalah untuk mengurangi kompleksitas state.
Dengan mutation:
State
↓
Berubah
↓
Berubah lagi
↓
Berubah lagi
Semakin banyak bagian program yang dapat mengubah state, semakin sulit melacak kondisi data.
Dengan pendekatan immutable:
State 1
↓
Function
↓
State 2
↓
Function
↓
State 3
Setiap tahap menghasilkan data baru.
Pendekatan ini membuat perubahan state lebih eksplisit.
Immutability Bukan Hanya Tentang Performa
Immutability terkadang dianggap hanya berkaitan dengan membuat copy object.
Padahal tujuan utamanya dalam Functional Programming lebih luas.
Immutability membantu kita membuat alur program:
lebih predictable
lebih mudah diuji
lebih mudah dipahami
lebih mudah dilacak
Jadi fokus utamanya bukan sekadar:
"Jangan pernah mengubah object."
Tetapi:
"Hindari perubahan state yang tidak diperlukan dan tidak terkontrol."
Kapan Mutation Masih Tepat?
Mutation masih dapat digunakan ketika memang memberikan manfaat dan tidak menimbulkan kompleksitas yang tidak perlu.
Misalnya untuk proses internal yang sederhana:
numbers = []
for number in range(5):
numbers.append(number)
print(numbers)
Penggunaan append() di sini tidak otomatis membuat kode menjadi buruk.
Functional Programming merupakan pendekatan desain, bukan larangan mutlak terhadap mutation.
Prinsip Praktis
Ketika menulis program Python dengan pendekatan Functional Programming, kita dapat menggunakan beberapa prinsip:
1. Jangan mengubah data jika tidak diperlukan.
2. Utamakan menghasilkan data baru.
3. Hindari perubahan state global.
4. Pisahkan pure function dari side effect.
5. Gunakan object immutable jika sesuai kebutuhan.
6. Pahami apakah sebuah object mutable atau immutable.
Ringkasan Mutable dan Immutable
| Tipe Data | Mutable / Immutable |
|---|---|
int | Immutable |
float | Immutable |
bool | Immutable |
str | Immutable |
tuple | Immutable |
frozenset | Immutable |
list | Mutable |
dict | Mutable |
set | Mutable |
Kesimpulan
Immutability adalah konsep untuk mempertahankan data agar tidak dimodifikasi secara langsung setelah dibuat.
Python memiliki object mutable dan immutable.
Contoh immutable:
int
float
bool
str
tuple
frozenset
Contoh mutable:
list
dict
set
Dalam Functional Programming, kita cenderung mengurangi mutation dan lebih sering menggunakan pendekatan:
Data lama
↓
Function
↓
Data baru
Contohnya:
def multiply_by_two(numbers):
return [
number * 2
for number in numbers
]
numbers = [1, 2, 3]
result = multiply_by_two(numbers)
print(numbers)
print(result)
Output:
[1, 2, 3]
[2, 4, 6]
Data asli tetap dipertahankan, sedangkan function menghasilkan data baru.
Dengan memahami immutability, kita memiliki fondasi penting untuk memahami konsep Functional Programming berikutnya, terutama First-Class Functions, Higher-Order Functions, map(), filter(), reduce(), dan Function Composition.