Отже, сидить собі такий програміст на Haskell. Тут до нього підходить менеджер і заявляє, мовляв, нам потрібно навчитися пекти хліб. Загалом, нічого толком не ясно, але раз треба, так треба:
data Bread = Bread
Пізніше стає відомо, що хліб, виявляється, має дбати в грубці:
data Oven = Oven
data Bread = Bread
createBread. Oven -> Bread
createBread _ = Bread
Як бачите, поки це мало на що впливає. Як і те, що печі бувають різних видів:
data Oven = ElectricOven | GasOven | MicrowaveOven
data Bread = Bread
createBread. Oven -> Bread
createBread _ = Bread
Перше дійсно більш-менш цікаве умова полягає в тому, що газова піч не може піч без газу:
data GasStatus = GasAvailable | GasUnavailable
data Oven = ElectricOven | GasOven | MicrowaveOven
data Bread = Bread
breadCouldBeCreated GasOven GasUnavailable = False
breadCouldBeCreated _ _ = True
createBread oven gas
| breadCouldBeCreated oven gas = Just Bread
| otherwise = Nothing
Тут вводиться тип «стан газу». Газ - він або є, або його немає. Якщо ми використовуємо газові балони, можна зберігати кількість доступного газу в літрах, суть від цього не змінюється. Функція breadCouldBeCreated перевіряє, чи можемо ми що-небудь приготувати при поточних обставинах (наявність газу і тип печі).
Пізніше стає відомо, що крім хліба в печі також можна готувати торти і пиріжки з різною начинкою:
data Stuffing = Meat | Cabbage
data Food = Cake | Bread | Pasty Stuffing
data GasStatus = GasAvailable | GasUnavailable
data Oven = ElectricOven | GasOven | MicrowaveOven
ovenCouldBeUsed GasOven GasUnavailable = False
ovenCouldBeUsed _ _ = True
create food oven gas
| ovenCouldBeUsed oven gas = Just food
| otherwise = Nothing
Вводимо типи «їжа» і «начинка». Функцію breadCouldBeCreated перейменовуємо в ovenCouldBeUsed.
Тепер менеджер хоче, щоб торти, пиріжки та хліб пеклися не просто так, а за різними рецептами. Сказано зроблено:
data Stuffing = Meat | Cabbage
data Food = Cake | Bread | Pasty Stuffing
data GasStatus = GasAvailable | GasUnavailable
data Oven = ElectricOven | GasOven | MicrowaveOven
ovenCouldBeUsed GasOven GasUnavailable = False
ovenCouldBeUsed _ _ = True
create food oven gas
| ovenCouldBeUsed oven gas = Just food
| otherwise = Nothing
breadRecipe = create Bread
cakeRecipe = create Cake
pastyRecipe stuffing = create $ Pasty stuffing
Рецепт зазвичай наказує виконання якихось дій з інгредієнтами, піччю і так далі. Очевидно, що рецепт є функцію вищого порядку.
Нарешті, в печах потрібно обпалювати цеглу. Судячи з формулювання, обпалювати цеглу слід в тих же самих печах, включаючи мікрохвильову (хоча в статті на Хабре вводиться окремий клас Furnace):
data Stuffing = Meat | Cabbage
data Food = Cake | Bread | Pasty Stuffing
data GasStatus = GasAvailable | GasUnavailable
data Oven = ElectricOven | GasOven | MicrowaveOven
data Brick = Brick
ovenCouldBeUsed GasOven GasUnavailable = False
ovenCouldBeUsed _ _ = True
create food oven gas
| ovenCouldBeUsed oven gas = Just food
| otherwise = Nothing
breadRecipe = create Bread
cakeRecipe = create Cake
pastyRecipe stuffing = create $ Pasty stuffing
makeBrick oven gas
| ovenCouldBeUsed oven gas = Just Brick
| otherwise = Nothing
Тут ми просто додали тип «цеглина» і функцію «зробити цегла».
Висновки напрошуються самі собою. Ми отримуємо простий для розуміння і легкий у супроводі код. У процесі його написання ми легко і невимушено будуємо модель предметної області, фактично просто роблячи переклад з російської на Haskell. Без усяких там наслідувань, рефакторингов і UML.
Можливо, в дійсності малося на увазі, що кожна піч виробляє хліба і торти трохи інакше, і нам знадобиться ввести додатковий клас типів (або, якщо хочете, «інтерфейс») з відповідними екземплярами класів. Заодно нам не доведеться вручну кодувати виклик ovenCouldBeUsed всюди, де використовується піч. Але не схоже, що рішення істотно ускладниться від усього цього.
А як ви печете хліб?
Сподобався пост? Поділися з іншими:
(Необхідно включити JS)