Haskell — ленивый язык программирования

Haskell — необыч­ный язык с точ­ки зре­ния тех, кто при­вык к JavaScript, С++, Python или любо­му дру­го­му импе­ра­тив­но­му язы­ку. Всё дело в том, что Haskell — функ­ци­о­наль­ный язык. Мы уже рас­ска­зы­ва­ли, чем отли­ча­ют­ся функ­ци­о­наль­ные язы­ки от осталь­ных, теперь посмот­рим на них в деле.

👉 Haskell — не совсем для обыч­ных вещей, и стан­дарт­ное при­ло­же­ние с кра­си­вым интер­фей­сом на нём сде­лать не полу­чит­ся. А вот сде­лать сер­вер­ную часть, кото­рая возь­мёт на себя все слож­ные вычис­ле­ния, или кон­соль­ную про­грам­му, кото­рая вызы­ва­ет­ся через команд­ную стро­ку, — вполне. Луч­ше все­го Haskell справ­ля­ет­ся с точ­ны­ми вычис­ле­ни­я­ми и рас­чё­та­ми, поэто­му чем луч­ше вы буде­те знать мате­ма­ти­ку — тем луч­ше для кода.

Синтаксис в Haskell

Самая про­стая про­грам­ма на Haskell выгля­дит так:

// Комментарий main = putStrLn "Hello World!"

Давай­те раз­бе­рём, как рабо­та­ет един­ствен­ная стро­ка в про­грам­ме. Main — это как бы глав­ная функ­ция, кото­рая воз­вра­ща­ет какое-то зна­че­ние. В нашем слу­чае оно воз­вра­ща­ет зна­че­ние выво­да на экран стро­ки "Hello World!". Ком­пи­ля­тор зна­ет, что putStrLn — функ­ция выво­да на экран, но если бы он это­го не знал, нам нуж­но было бы напи­сать так:

// Комментарий main :: IO () main = putStrLn "Hello World!"

Теперь мы явно ука­за­ли тип функ­ции main — это ввод и вывод каких-то резуль­та­тов (IO — input/output, ввод-вывод). Ещё в Haskell есть такие базо­вые типы дан­ных:

  • Integer — целое чис­ло
  • Char — сим­вол
  • Double — чис­ло с пла­ва­ю­щей точ­кой или дроб­ное чис­ло

А ещё есть спе­ци­аль­ные кон­струк­ции (), [] и ->, с помо­щью кото­рых мож­но сде­лать свои типы дан­ных на осно­ве базо­вых, напри­мер, спи­сок или мас­сив.

Мате­ма­ти­че­ские опе­ра­ции в Haskell рабо­та­ют и выгля­дят как обыч­но: +, –, *, /.

Функции и их значения

Почти всё в Haskell дела­ет­ся через функ­ции. Зада­ча про­грам­ми­ста — опи­сать функ­цию таким обра­зом, что­бы ком­пи­ля­тор понял:

  • какие пара­мет­ры могут прий­ти в функ­цию,
  • что с ними нуж­но сде­лать,
  • в каком виде нуж­но отдать резуль­тат.

Полу­ча­ет­ся, что функ­ции — это набо­ры пра­вил, по кото­рым обра­ба­ты­ва­ют­ся дан­ные. Напри­мер, если нам нуж­но най­ти фак­то­ри­ал любо­го чис­ла, то на Haskell это мож­но запи­сать так:

// факториал — произведение чисел от единицы до этого числа. 
// к примеру, факториал трёх равен 1 * 2 * 3

fac :: Integer -> Integer

fac 0 = 1

fac n | n > 0 = n * fac (n - 1)

Пер­вая стро­ка после ком­мен­та­ри­ев гово­рит нам, что мы объ­яв­ля­ем новую функ­цию (fac),и рабо­тать с типа­ми дан­ных (::) она будет так: на вход ей посту­пит целое чис­ло (Integer) и на выхо­де функ­ция вер­нёт тоже целое чис­ло (-> Integer).

Вто­рая стро­ка отве­ча­ет за то, что если нуж­но посчи­тать фак­то­ри­ал ноля, то это будет еди­ни­ца.

Послед­няя стро­ка — самая слож­ная и инте­рес­ная. Сле­ди­те за мате­ма­ти­че­ской мыс­лью: фак­то­ри­ал любо­го чис­ла n (fac n), где n боль­ше нуля (| n > 0), равен про­из­ве­де­нию это­го чис­ла (n *) на фак­то­ри­ал преды­ду­ще­го чис­ла (fac (n – 1)).

Если пока непо­нят­но, что тут про­ис­хо­дит, — почи­тай­те про рекур­сию, там мы подроб­но разо­бра­ли этот при­мер.

Мы толь­ко что запи­са­ли стро­гое мате­ма­ти­че­ское опре­де­ле­ние на Haskell и объ­яс­ни­ли функ­ции, как ей най­ти фак­то­ри­ал любо­го чис­ла. Но если мы попро­бу­ем вызвать фак­то­ри­ал чис­ла 4.5, то Haskell выдаст ошиб­ку, пото­му что 4.5 не отно­сит­ся к типу Integer.

👉 Вся про­грам­ма на Haskell — это набор таких функ­ций и пра­вил, кото­рые могут быть свя­за­ны друг с дру­гом. Все дан­ные обра­ба­ты­ва­ют­ся стро­го по этим пра­ви­лам, и на выхо­де полу­ча­ет­ся точ­ный резуль­тат. Мож­но рабо­тать с любы­ми дан­ны­ми, если пере­ве­сти их в понят­ный для язы­ка вид, поэто­му Haskell часто исполь­зу­ют в тех обла­стях, где нуж­на надёж­ность и гаран­ти­ро­ван­ная точ­ность вычис­ле­ний.

Ленивые вычисления

Haskell — язык, кото­рый под­дер­жи­ва­ет лени­вые вычис­ле­ния. Это зна­чит, что он вычис­лит нуж­ные зна­че­ния в любой функ­ции не тогда, когда про­грам­мист это напи­сал, а когда это зна­че­ние дей­стви­тель­но пона­до­бит­ся в про­грам­ме.

Напри­мер, у нас есть функ­ция, кото­рая воз­вра­ща­ет какое-то зна­че­ние после вызо­ва. Если это зна­че­ние пря­мо сей­час не нуж­но или оно не исполь­зу­ет­ся при вызо­ве функ­ции, то Haskell не будет его счи­тать. Он дождёт­ся того момен­та, когда зна­че­ние функ­ции пона­до­бит­ся в дру­гом месте, и толь­ко тогда посчи­та­ет его.

Лени­вые вычис­ле­ния помо­га­ют сокра­тить нагруз­ку на ресур­сы и дела­ют про­грам­мы быст­рее и эффек­тив­нее. Если вы пише­те каль­ку­ля­тор, в кото­ром преду­смат­ри­ва­е­те все мате­ма­ти­че­ские дей­ствия, но поль­зу­е­тесь толь­ко сло­же­ни­ем, то Haskell даже не будет обра­щать вни­ма­ния на осталь­ное. Он будет знать, что у вас есть код, кото­рый, если что, может ещё умно­жать и делить, но делать с ним пока ниче­го не будет.

Для чего нужен Haskell

Обра­бот­ка тек­ста и син­так­си­че­ский ана­лиз. В Haskell лег­ко зало­жить пра­ви­ла любо­го язы­ка, по кото­рым стро­ят­ся язы­ко­вые кон­струк­ции, и научить его ана­ли­зи­ро­вать этот язык. Напри­мер, он может делить пред­ло­же­ния на сло­ва и пред­ло­ги, нахо­дить свя­зи меж­ду ними, смот­реть, всё ли напи­са­но без оши­бок, или нахо­дить непра­виль­ные язы­ко­вые кон­струк­ции. Это рабо­та­ет и для обыч­ных язы­ков (рус­ский или англий­ский), и для язы­ков про­грам­ми­ро­ва­ния.

Ком­пи­ля­то­ры. Бла­го­да­ря тому, что Haskell всё дела­ет стро­го по пра­ви­лам, это отлич­ный инстру­мент для напи­са­ния ком­пи­ля­то­ров. Зада­ча любо­го ком­пи­ля­то­ра — взять текст про­грам­мы, про­ве­рить, нет ли в нём оши­бок, и выпол­нить код. Так как Haskell отлич­но рабо­та­ет с тек­стом и при этом не оши­ба­ет­ся в пра­ви­лах, то он может пре­об­ра­зо­вы­вать коман­ды дру­го­го язы­ка в коман­ды для ком­пью­те­ра.

Финан­со­вые инстру­мен­ты. Основ­ное тре­бо­ва­ния к финан­со­вым инстру­мен­там — гаран­ти­ро­ван­ная точ­ность вычис­ле­ний и отсут­ствие оши­бок, поэто­му Haskell любят исполь­зо­вать кор­по­ра­ции для сво­их внут­рен­них инстру­мен­тов. Это могут быть систе­мы учё­та бан­ков­ских тран­зак­ций, бир­же­вой тор­гов­ли, ана­ли­за рис­ков или инстру­мен­ты финан­со­во­го мони­то­рин­га меж­ду отде­ла­ми.

Про­мыш­лен­ные при­ло­же­ния. Haskell поз­во­ля­ет зада­вать нуж­ные пра­ви­ла и обра­ба­ты­вать дан­ные по этим пра­ви­лам — имен­но это нуж­но пред­при­я­ти­ям, что­бы постро­ить систе­мы под­держ­ки при­ня­тия реше­ний или внут­рен­не­го ауди­та. Это сни­ма­ет нагруз­ку с людей и поз­во­ля­ет алго­рит­мам эффек­тив­нее нахо­дить сла­бые места в ком­па­нии или точ­ки для про­мыш­лен­но­го роста.

Текст:
Миха­ил Поля­нин
Редак­тор:
Миха­ил Поля­нин
Кор­рек­тор:
Ири­на Михе­е­ва
Иллю­стра­ция:
Даня Бер­ков­ский
Вёрст­ка:
Мария Дро­но­ва
Гла­ша­тай:
Вита­лий Вебер
Лени­во вычис­ля­ет:
Роди­он Скря­бин