Если вы видите что-то необычное, просто сообщите мне. Skip to main content

Функторы

WelcomeДобро toпожаловать ourв seriesнашу onсерию monadsстатей. andМонады otherодна abstractиз structuresтех inидей, Haskell!которая Monadsкажется areпричиной oneмножества ofстрахов thoseи conceptsмучений thatсреди seemsмножества toлюдей cause of lot of fear and anguish among people trying to learnпробоющих Haskell. TheЦель aimэтой ofсерии thisпоказать, seriesчто isэто toне showстрашная thatи theyне aren'tсложная aидея, scaryи orможет difficultбыть concept,легко butразобрана canделая beопределенные easily understood by taking the proper steps.

In this first part of the series, we'll start by learning about functors, a simpler abstract structure. If you already know about functors, feel free to move onto part 2, where we discuss applicative functors. If you know about both these concepts and want to dive straight into monads, head to part 3!

As a word of advice, this series will be a lot more beneficial if you can follow along with the code examples. If you've never installed Haskell or written a line of code, download our Beginners Checklist to find out how you can get started.

This series also has a companion Github Repository! You can use this repository to follow along with the code samples in these articles. The complete code for this first part is in the FunctorsComplete module. If you want to fill in some of the examples as you go along, you can also look at the Functors module, which has a couple "TODOs" for you to write yourself!шаги.

AПростой SIMPLE EXAMPLEпример.

Here'sЕсть aпростой simpleпример exampleс toкоторого startмы usначнем onнаш ourпуть. way.Этот Thisкод codeпревращает convertsвходную anстроку inputтипа string like "John Doe 24"24 intoв aкортеж. tuple.Мы Weхотим wantучитывать toвсе considerвходные allварианты, inputsпоэтому though,результатом soбудет the resulting type is a Maybe.Maybe.

tupleFromInputString :: String -> Maybe (String, String, Int)
tupleFromInputString input = if length stringComponents /= 3
  then Nothing
  else Just (stringComponents !! 0, stringComponents !! 1, age)
  where 
    stringComponents = words input
    age = (read (stringComponents !! 2) :: Int)

ThisЭта simpleпростая functionфункция simplyпринимает takesстроку aи stringпреобразует andеё convertsв itпараметры intoдля parametersимени, forфамилии firstи name,возраста. lastПредположим name,у andнас age.есть Supposeдругая weчасть haveпрограммы anotherиспользующая partтип ofданных ourдля programотображение usingчеловека aвместо dataкортежа. typeМы toзахотим representнаписать aфункцию personпреобразователь insteadмежду ofэтими aдвумя tuple.видами. WeМы mightтак writeже aхотим conversionучитывать functionситуацию betweenневозможности theseэтого twoпреобразования. differentПоэтому types.есть Weдругая wantфункция, toкоторая accountобработает forэтот the possibility of failure. So we'll have another function handling that case.случай.

data Person = Person {
  firstName :: String,
  lastName :: String,
  age :: Int
}

personFromTuple :: (String, String, Int) -> Person
personFromTuple (fName, lName, age) = Person fName lName age

convertTuple :: Maybe (String, String, Int) -> Maybe Person
convertTuple Nothing = Nothing
convertTuple (Just t) = Just (personFromTuple t)

AИзменение CHANGE OF FORMATформата

ButНо, imagineпредставьте, ourнаша originalоригинальная programпрограмма changesменяется toв readчасти inчтения aвсего wholeсписка list of names:имен:

listFromInputString :: String -> [(String, String, Int)]
listFromInputString contents = mapMaybe tupleFromInputString (lines contents)

tupleFromInputString :: String -> Maybe (String, String, Int)
...

NowТеперь ifесли weмы passedпередаем thisрезультат resultкоду toиспользуя thePerson codeмы usingдолжны Person,изменить weтип wouldфункции haveconvertTuple. toОна changeбудет theиметь typeпаралельную ofструктуру. theMaybe convertTupleи function.List Itоба wouldдействуют haveкак aхранитель parallelдругих structureзначений. though.Иногда, Maybeнас andне Listзаботит canво bothчто actобернуты asзначения. containersНам forпросто otherхочется values.преобразовать Sometimes,что-то weлежащее don'tпод careсуществующим howзначением. valuesи areзатем wrapped.запустим Weновое justзначение wantв toтой transformже whatever underlying value exists, and then return the new value in the same wrapper.обертке.

INTRODUCTION TO FUNCTORS

With this idea in mind, we can start understanding functors. First and foremost, Functor is a typeclass in Haskell. In order for a data type to be an instance of the Functor typeclass, it must implement a single function: fmap.

fmap :: (a -> b) -> f a -> f b

The fmap function takes two inputs. First, it demands a function between two data types. The second parameter is some container of the first type. The output then is a container of the second type. Now let's look at a few different Functor instances for some familiar types. For lists, fmap is simply defined as the basic map function:

instance Functor [] where
  fmap = map

In fact, fmap is a generalization of mapping. For example, the Map data type is also a functor. It uses its own map function for fmap. Functors simply take this idea of transforming all underlying values and apply it to other types. With this in mind, let's observe how Maybe is a functor:

instance Functor Maybe where
  fmap _ Nothing = Nothing
  fmap f (Just a) = Just (f a)

This looks a lot like our original convertTuple function! If we have no value in the first place, then the result is Nothing. If we do have a value, simply apply the function to the value, and rewrap it in Just. The Either data type can be seen as a Maybe type with more information about how it failed. It has similar behavior:

instance Functor (Either a) where
    fmap _ (Left x) = Left x
    fmap f (Right y) = Right (f y)

Note the first type parameter of this instance is fixed. Only the second parameter of an Either value is changed by fmap. Based on these examples, we can see how to rewrite convertTuple to be more generic:

convertTupleFunctor :: Functor f => f (String, String, Int) -> f Person
convertTupleFunctor = fmap personFromTuple

MAKING OUR OWN FUNCTORS

We can also take our own data type and define an instance of Functor. Suppose we have the following data type representing a directory of local government officials. It is parameterized by the type a. This means we allow different directories using different representations of a person:

data GovDirectory a = GovDirectory {
  mayor :: a,
  interimMayor :: Maybe a,
  cabinet :: Map String a,
  councilMembers :: [a]
}

One part of our application might represent people with tuples. Its type would be GovDirectory (String, String, Int). However, another part could use the type GovDirectory Person. We can define the following Functor instance for GovDirectory by defining fmap. Since our underlying types are mostly functors themselves, this just involves calling fmap on the fields!

instance Functor GovDirectory where
  fmap f oldDirectory = GovDirectory {
    mayor = f (mayor oldDirectory),
    interimMayor = fmap f (interimMayor oldDirectory),
    cabinet = fmap f (cabinet oldDirectory),
    councilMembers = fmap f (councilMembers oldDirectory)
  }

We can also use the infix operator <$> as a synonym for fmap. So you can write this more cleanly as:

instance Functor GovDirectory where
  fmap f oldDirectory = GovDirectory {
    mayor = f (mayor oldDirectory),
    interimMayor = f <$> interimMayor oldDirectory,
    cabinet = f <$> cabinet oldDirectory,
    councilMembers = f <$> councilMembers oldDirectory
  }

Now we have our own functor instance, so transforming the underlying data type of our directory class is easy! We can just use fmap in conjunction with our transformation function, personFromTuple:

oldDirectory :: GovDirectory (String, String, Int)
oldDirectory = GovDirectory
  ("John", "Doe", 46)
  Nothing
  (M.fromList 
    [ ("Treasurer", ("Timothy", "Houston", 51))
    , ("Historian", ("Bill", "Jefferson", 42))
    , ("Sheriff", ("Susan", "Harrison", 49))
    ])
  ([("Sharon", "Stevens", 38), ("Christine", "Washington", 47)])

newDirectory :: GovDirectory Person
newDirectory = personFromTuple <$> oldDirectory

CONCLUSION

Now that you know about functors, it's time to deepen your understanding of these kinds of structures. So move onto part 2 where we'll discuss applicative functors. If you're dying to try out some of these examples but have never tried Haskell before, download our Beginners Checklist to learn how!