Законы Монад
WelcomeДобро toпожаловать theв finalзаключительную partчасть ofсерии ourо seriesмонадах onв monadsHaskell. inСейчас Haskell!мы Soуже far,знаем we'veбольшишнство learnedидей mostлежащих ofв theоснове concreteзная detailsих you'llтонкости needдля inиспользования orderв to use monads in your programs. But there's still one more abstract concept we should cover in relation to all these structures. This is the notion of structural “laws”программах. TheseНо areесть rulesеще thatабстрактные theseидеи, typeclassesкоторые shouldнам toнужно followизучить, toкоторые meetсвязанны otherсо programmersвсеми expectationsэтими aboutструктурами. ourЭто code.
Eachструктурных of"законов". theЭти structuresправила we've discussed has its own set of laws. If you need to review anything about these structures and theдля typeclass functionsдолжны thatвыполняться compriseчтобы them,пересекаться beс sureожиданиями toдругих review part 1, part 2 and part 3 of this course.программистов.
Жизнь без законов
OnceПомните, you understand all this, you'll be all ready to kick yourчто Haskell learningотражает intoкаждый highабстрактный gear!класс Downloadс our Production checklist for some ideas about libraries you can learn to perform common tasks.
If you've been following along with the series repository, you can take a look at the code for this section in this final module. There's a short example at the bottom where you can play around with QuickCheck and the Functor laws.
LIFE WITHOUT LAWS
Remember Haskell represents each of our abstract classes by aпомощью type class. EachКаждый ofиз these type classes has one or two main functions. So, as long as we implement those functions and it type checks, we have a new functor/applicative/monad, right?
Well not quite. Yes, your program will compile and you'll be able to use the instances. But this doesn't mean your instances follow the mathematical constructs. If they don't, your instances won't fulfill other programmers' expectations. Eachэтих type class hasимеет itsодину ownили “laws”две главные функции. ForПоэтому, instance,каждый let'sраз thinkреализуя backэти toфункции theи GovDirectoryеё проверки типов, мы получаем функтор/аппликатив/монаду, правильно?
Не совсем. Да, ваша программа будет собираться и у вас будет возможность использовать её объекты. Но это не значит, что ваш объект следует математическим конструктам. Если нет, ваш объект не будет полноценным для других программистов. Каждый type weclass createdимеет inсвои theзаконы. functorДля article.примера, Supposeдавайте weвернемся madeк aGovDirectory
differentтипу, functorкоторый instanceмы thanсоздавали weв hadстатье there:про функторы. Предположим мы сделали различные объекты функторов:
data GovDirectory a = GovDirectory {
mayor :: a,
interimMayor :: Maybe a,
cabinet :: Map String a,
councilMembers :: [a]
}
instance Functor GovDirectory where
fmap f oldDirectory = GovDirectory {
mayor = f (mayor oldDirectory),
interimMayor = Nothing,
cabinet = f <$> cabinet oldDirectory,
councilMembers = f <$> councilMembers oldDirectory
}
AsНасколько we'llвидно, see,это thisнарушает wouldодин violateиз oneзаконов ofфунктора. theВ functorэтом laws.случае, Inэто thisбудет caseне itнастоящий wouldфунктор. notЕго beповедение aдолжно trueсмущать functor.любого Itsпрограммиста behaviorпытающегося wouldего confuseиспользовать. anyМы otherдолжны programmerпозаботиться tryingо toтом, useчтобы it.убедиться Weчто shouldнаш takeэкземпляр careимеет toсмысл. makeКак sureтолько, thatвы ourэто instancesпочувствуете makeдля sense. Once you get a feel for these type
, classes,classtheзначит likelihoodвы isсделали thatэкземпляр theпо instancesправилу. you'llНе createпереживайте followесли theвас laws.что-то Soсмущает. don'tЭта sweatстатья itочень ifматематичка, aи fewвы ofне theseсразу areпоймете, confusing.все Thisидеи, articleчто willтут beпредложены. veryВы math-y,можете andпонять weи won'tиспользовать dwellэти tooклассы muchбез onзнания theэтих concepts.законов. YouНу canчто understandже, andокунемся useбез theseсуеты classesв withoutэти knowing these laws by heart. So without further ado, let's dive into the laws!законы.
FUNCTORЗаконы LAWSфункторов
ThereЕсть areдва twoзакона functorфунктороов. laws.Первый The- firstзакон isидентичности. anМы identityпосмотрим law.на We'llнекотороый seeвариант someэтой variationидеи ofдля thisкаждого ideaиз forэтих each of the type
. classes.classRememberВспомните, howкак fmap
"maps"функция aработаетс functionсодержанием. overЕсли ourмы container.применим Ifнашу weфункцию mapидентичности theк identityконтейнеру, functionрезультатом overбудет aтот container,же the result should be the same container object:объект.
fmap id = id
InДругими otherсловами, words,наш ourфунктор functorне shouldдолжен notприменять beкакие-то applyingдополнительные anyпреобразования extraили changesсторонние orэффекты. sideОн effects.должен Itтолько shouldприменять onlyфункцию. applyВторой theзакон function.это Theкомпозиционный. secondОн lawгласит, isчто aреализация compositionнашго law.функтора Itне statesдолжна ourломать functorидею implementationнашей should not break the composition of functions:функции.
fmap (g . f) = fmap g . fmap f
-- For reference, remember the type of the composition operator:
(.) :: (b -> c) -> (a -> b) -> (a -> c)
OnС oneдругой sideстороы, weмы composeможем twoсобрать functions,две andфункции, mapи theобъединить resultingрезультат functionв overфункции ourповерх container.контейнера. OnС theдругой otherстороны, side,мы weприменяем mapпервую theфункцию, firstполучаем function,результат, getи theприменяем result,вторую andфункции mapповерх. theВторой secondзакон functionговорит, overчто it.результаты Theдолжны functorбыть compositionодинаковыми. lawЗвучит statesэто theseсложно. outcomesНо shouldвам beне identical.нужно Thisпереживать. soundsСкорей complex.всего Butелсли youвы don'tсломаете needзакон toкомпозиции worry about it much. If you break the composition law inв Haskell, you'llскорей alsoвсего likelyвы breakсломаете theи identityзакон law.идентичности.
ThoseУ areнас theвсего onlyдва twoзакона, lawsпоэтому forдвинем functors! So let's move on to applicative functors.дальше.
APPLICATIVEАппликативные LAWSзаконы
ApplicativeАппликативные functorsфункторы are- aэто littleнемного moreсложнее complicated.чем Theyкажется. haveОни fourимеют different4 laws.различных Theзакона. firstПервый isдостаточно easyпросто. though.Это It'sеще anotherодин simpleзакон identity law. It says:идентичности:
pure id <*> v = v
OnСлева the- leftобертка side,для weидентичной wrapфункции. theЗатем identityмы function.применяем Thenеё weк applyконтейнеру. itЗакон overаппликативной ourидентичности container.говорит, Theчто applicativeв identityрезультате lawдолжен statesбыть thisтот shouldже resultобъект. inДостаточно an identical object. Simple enough.просто.
TheВторой secondзакон lawэто isзакон theгомоморфизма. homomorphismПредставим, law.мы Supposeоборачиваем weфункцию wrapи aдругие functionобъекты andв anчистые. objectМы inможем pure.затем Weприменить canобернутую thenфункцию applyповерх theнормального wrappedобъекта, functionи overзатем theоберунть wrappedих object.в Ofчистые. course,Закон weгомоморфизма couldговорит, alsoчто applyэти theрезультаты normalдолжны functionбыть over the normal object, and THEN wrap it in pure. The homomorphism law states these results should be the same.одинаковы.
pure f <*> pure x = pure (f x)
WeМы shouldдолжны seeувидеть aчистый distinctшаблон. patternПоверх here.этого Theможно overridingсказать, themeчто ofбольшая almostчасть allэтих theseзаконов lawsгласит, isчто that our type classes are containers. The type class
functionэто shouldконтенеры. notФнукция havetype
anyclasssideне effects.должна Allиметь theyсторонних shouldэффектов. doВсе isони, facilitateчто theони wrapping,должны unwrapping,- andоблегчать transformationобертывание, ofразвертывание data.и проеобразование данных.
TheТретий thirdзакон law- isзакон theобмена. interchangeОн law.по-сложнее. It'sЗакон aговорит, littleчто moreот complicated,порядка soоборачивания don'tничего sweatне itдолжно tooзависеть. much.С Itодной statesстороны, thatмы theприменяем orderлюбой thatаппликтор weнад wrapобернутым thingsв shouldn'tчистую matter.функцию Oneобъектом. onС side,другой we- applyпервое anyмы applicativeприменяем overфункцию aк pureобъекту wrappedкак object.к Onаргументу. theЗатем otherприменяем side,её firstк weпервому wrapаппликативу. aДолжно functionполучиться applyingодно theи objectто as an argument. Then we apply this to the first applicative. These should be the same.же.
u <*> pure y = pure ($ y) <*> u
Последний закон аппликативности, копирует второй закон функтора. The final applicative law mimics the second functor law. It is a composition law. It states that function composition holds across applications within the functor:
pure (.) <*> u <*> v <*> w = u <*> (v <*> w)
The sheer number of laws here can be a little overwhelming. Remember, the instances you make will probably follow the laws! Let's move on to our final example: monads.
MONAD LAWS
Monads have three laws. The first two are simple identity laws, like our other classes have had:
return a >>= f = f
m >>= return = m
These are the left and right identities. They state effectively that the only thing the return function is allowed to do is to wrap the object (sound familiar?). It cannot manipulate the data in any way. Our main takeaway from these is that the following code samples are equivalent:
func1 :: IO String
func1 = do
str <- getLine
return str
func2 :: IO String
func2 = getLine
The third law is a bit more interesting. It tells us that associativity holds within monads:
(m >>= f) >>= g = m >>= (\x -> f x >>= g)
But we see this third law has a parallel structure to the other composition laws. In the first case, we apply two functions in two steps. In the second case, we compose the functions first, and THEN apply the result. These should be the same.
So in summary, there are two main ideas from all the laws. First, identity should be preserve over wrapper functions, like pure and return. Second, function composition should hold across our structures.
CHECKING THE LAWS
As I stated before, most of the instances that you come up with will naturally follow these rules. As you get more experience with the different type classes, this will be even more true. Yet, it also pays to be sure. Haskell has an excellent tool for verifying your instances pass a certain law.
This utility is QuickCheck. It can take any a certain rule, generate many different test cases on the rule, and verify the rule holds. In this section, we'll run a few tests on our GovDirectory functor instance. We'll see how QuickCheck proves its initial failure, and ultimate success. First we need to implement the Arbitrary type class over our type. We can do this a long as the inner type is also Arbitrary, such as a built-in string type. Then we'll use all the other Arbitrary instances that exist over our inner types:
instance Arbitrary a => Arbitrary (GovDirectory a) where
arbitrary = do
m <- arbitrary
im <- arbitrary
cab <- arbitrary
cm <- arbitrary
return $ GovDirectory
{ mayor = m
, interimMayor = im
, cabinet = cab
, councilMembers = cm
}
Once you have done that, you can write a test case over a particular rule. In this case, we check the identity function for functors:
main :: IO ()
main = quickCheck govDirectoryFunctorCheck
govDirectoryFunctorCheck :: GovDirectory String -> Bool
govDirectoryFunctorCheck gd = fmap id gd == gd
Now let's test this on the faulty instance we used above. We can see that a particular test will fail:
*** Failed! Falsifiable (after 2 tests):
GovDirectory {mayor = "", interimMayor = Just "\156", cabinet = fromList [("","")], councilMembers = []}
It specifies for us an arbitrary instance that failed the test. Now suppose we correct the instance:
interimMayor = f <$> (interimMayor oldDirectory),
We'll see the tests pass!
+++ OK, passed 100 tests.
SUMMARY
That wraps up our series on monads! Remember that if any of these concepts are still fuzzy, don't hesitate to go back and check out some of the earlier parts of the series. We started by learning the basics about functors, applicative functors and monads. Then we went more in depth on monads and saw some of the more useful ones like the Reader, Writer, and State monads. Then we learned how to combine these together by using monad transformers.
Now that you understand perhaps the most important concept in Haskell, the sky is your limit! Check out our Production Checklist to learn about the many libraries you can use to supercharge your Haskell!