В коя година се появи хаскел. — И кой го направи? Характеристики на функционалните езици

Синтаксисът за два последователни идентификатора означава прилагане на функцията foo към нейната лента с аргументи:

В Haskell извикването на функция не изисква скоби около аргумента.

Скобите се използват за групиране на аргументи:

акос (кос пи)

Функция с множество аргументи:

максимум 5 42

Операцията по прилагане на функция е асоциативна отляво:

(макс. 5) 42

Функция макссе прилага последователно към два аргумента.
Компилаторът разбира конструкцията f x yкак (f x) y, а не обратното f (x y).

Изразяване (макс. 5)това е така нареченото приложение за частична функция. V общ изгледможе да се формулира по следния начин: ако имаме функция от N променливи и я разглеждаме като функция на N променливи, тогава можем да я погледнем от другата страна и да кажем, че тя е функция на една променлива, която ни връща функция от N - 1 променливи.

3 + грях 42

3 + (макс. 5) 42

Синтаксис на декларация на персонализирана функция

Функция, която сумира квадратите на двата аргумента, предадени към нея:

SumSquares x y = x ^ 2 + y ^ 2 rock "n" roll = 42


Името на функцията и имената на формалните параметри трябва да започват с малки букви. А главните букви се използват за дефиниране на типове данни.

Функция с три аргумента, която изчислява дължината на 3D вектор:

LenVec3 x y z = sqrt (x ^ 2 + y ^ 2 + z ^ 2)


За да дефинираме функция в интерпретатора на GHCi, трябва да използваме ключовата дума let.

Нека sumSquares x y = x ^ 2 + y ^ 2

Свойство чистота на функцията

Важна характеристика, която отличава функционалните езици от императивните езици, е свойството на чистота на функциите. Всички функции в Haskell са чисти. Това означава, че стойността на функцията се определя изцяло от стойностите на предадените й аргументи. Никакви други източници на данни не могат да повлияят на резултата, върнат от функцията. Ако имате нужда от функция за вземане на данни от някъде, тогава трябва да й предадете този източник на данни като аргумент.

Функция, която не приема аргументи, е константа. Такава функция връща една и съща стойност през цялото време, независимо от обстоятелствата.

Prelude> нека fortyTwo = 39 + 3 Prelude> fortyTwo 42

В Haskell не можете да дефинирате функция, която не приема аргументи и връща различни стойности за различни извиквания.

Механизъм за дефиниране на функции чрез частично приложение

Можем да разглеждаме всяка функция като функция на един аргумент, който връща някаква функция.

Prelude> нека max5 x = max 5 x Prelude> max5 4 5 Prelude> max5 42 42


Алтернативен синтаксис за дефиниране на функция:

Prelude> нека max5 "= max 5 Prelude> max5" 4 5 Prelude> max5 "42 42

Съкратихме допълнителния аргумент x отляво и отдясно. И те написаха, че функцията max5 "е само частично приложена функция max. По този начин можете да дефинирате функция, без да посочвате всички аргументи. Съответният стил на програмиране се нарича безсмислен.

Често дизайнът на функциите в Haskell е настроен по такъв начин, че частичното приложение да е удобно;

Prelude> нека лимит на отстъпката proc sum = ако сума> = ограничение, тогава сума * (100 - proc) / 100 else sum Prelude> нека standardDiscount = отстъпка 1000 5 Prelude> standardDiscount 2000 1900.0 Prelude> standardDiscount 900 900.0

Ограничението и параметрите на процедурата рядко се променят. Параметърът на сумата обаче се променя често. Всъщност всеки път, когато се извиква тази функция.

Да предположим, че разработваме интерфейс на системата за превод за естествени езици в Haskell. Той трябва да съдържа функция за превод с параметри text, languageFrom и languageTo. За да бъде удобно изпълнението на следните функции:

  • превеждам от испански на руски,
  • преведете от английски на руски
  • и преведете на руски
трябва да подредите параметрите в следния ред: translate languageTo languageFrom text.

Въведете оператор ->

За да напишете типа на функция, трябва да напишете типа на нейния аргумент и вида на резултата от тази функция. В Haskell, за да опише типа на функция, операторът -> е двоичен оператор, в който левият операнд е типът на аргумента, а десният операнд е типът на резултата. Стрелката е между левия и десния операнд t, k, това е инфиксен оператор.

Prelude>: t not not :: Bool -> Bool Prelude> (&&) False True False Prelude> (&&) False) Вярно False

Типът на последния израз може да се запише по следния начин:
Bool -> (Bool -> Bool)
Операторът на типа се счита за дясно асоциативен. Следователно Bool -> (Bool -> Bool) може да бъде пренаписан като Bool -> Bool -> Bool

Prelude>: t (&&) (&&) :: Bool -> Bool -> Bool

Нека си спомним функцията за отстъпка, която се върна обща сумапокупки с възможна отстъпка. Като параметри е подадена сумата без сумата на отстъпката, процента на процедурата за отстъпка и отстъпката е начислена, ако преведената сума надвишава лимита. Всички тези параметри, както и връщаната стойност, могат да се съхраняват в тип Double. Типът на функцията може да бъде посочен в изходния файл заедно с нейната дефиниция:

отстъпка :: Двойна -> Двойна -> Двойна -> Двойно ограничение на отстъпката proc sum = if sum> = limit then sum * (100 - proc) / 100 else sum

Обърнете внимание, че декларацията на типа не е задължителна, въпреки че често се препоръчва за целите на документацията. Обикновено се поставя преди дефиницията на функцията, въпреки че тази декларация Най-високо нивоможе да се намира навсякъде в изходния файл.

Стандартна отстъпка :: Двойна -> Двойна стандартнаОтстъпка = отстъпка 1000 5

Помислете за функцията twoDigits2Int, която приема два знака и връща число, съставено от тези знаци, ако и двата знака са числови, а 100 е в противен случай... (Първият знак се третира като брой десетки, вторият като брой единици.)

Импортиране на данни.Char twoDigits2Int :: Char -> Char -> Int twoDigits2Int x y = ако isDigit x && isDigit y then digitToInt x * 10 + digitToInt y else 100


GHCi> twoDigits2Int "4" "2" 42

Рекурсия

В императивните езици цикълът е основният елемент на повтарящите се изчисления. Във функционалните езици циклите не са много смислени. Тъй като функционалните езици нямат концепцията за променлива променлива, няма начин да се разграничи една итерация на цикъл от друга. За извършване на повтарящи се изчисления на функционални езици се използва рекурсия. За да предотвратите зацикляне на рекурсивна функция, тя трябва да отговаря на следните изисквания:
  • извикванията на функции от дясната страна трябва да се извършват на стойности на параметри, различни от формален параметърфункции;
  • рекурсивните повиквания трябва да бъдат прекъснати някъде (трябва да има така нареченото условие за прекратяване).

Факториален

Факториал n = ако n == 0, тогава 1, иначе n * факториел (n - 1)


Изпълнение на изчисляване на факториала с помощта на натрупващия параметър:

Факториал5 n | n> = 0 = помощник 1 n | в противен случай = грешка "arg трябва да бъде> = 0" помощник acc 0 = acc помощник acc n = помощник (acc * n) (n - 1)

Подобна реализация в случай на факториал не носи никакви допълнителни ползи, но много често подобни реализации могат да подобрят ефективността на рекурсивните функции. В много случаи, в рекурсивните функции, чрез директна дефиниция на рекурсивна функция по отношение на самата себе си, може да се получи квадратична или по-висока полиномна асимптотика. И в случай на използване на спомагателни функции, много често е възможно да се коригира асимптотиката, като се сведе до линейна.

Двоен факториал

Помислете за функция, която изчислява двоен факториал, тоест продукт на естествени числа, които не надвишават дадено число и имат същата четност. Например: 7 !! = 7⋅5⋅3⋅1, 8 !! = 8⋅6⋅4⋅2. Предполага се, че аргументът на функцията може да приема само неотрицателни стойности.

DoubleFact :: Integer -> Integer doubleFact n = ако n<= 0 then 1 else n * doubleFact (n - 2 )

Числова последователност на Фибоначи

В Haskell тази дефиниция се дава от следната функция:

Фибоначи :: Цело число -> Цело число фибоначи n | n == 0 = 0 | n == 1 = 1 | n> 1 = фибоначи (n - 1) + фибоначи (n - 2) | н< 0 = fibonacci (n + 2 ) - fibonacci (n + 1 ) | otherwise = undefined

Реализацията на функция за изчисляване на числото на Фибоначи, базирана на директна рекурсивна дефиниция, е изключително неефективна – броят на извикванията на функция нараства експоненциално с увеличаване на стойността на аргумента. GHCi ви позволява да проследявате използването на паметта и времето, прекарано за оценка на израз, като изпълните командата: set + s:

* Фибоначи>: набор + s * Фибоначи> фибоначи 30 832040 (16,78 секунди, 409, 318, 904 байта)

Използвайки акумулаторния механизъм, можете да напишете по-ефективна реализация, която има линейна сложност (по броя на рекурсивните извиквания):

Fibonacci ":: Integer -> Integer fibonacci" n = помощник n 0 1 помощник n a b | n == 0 = a | n> 0 = помощник (n - 1) b (a + b) | н< 0 = helper (n + 1 ) b (a - b) | otherwise = undefined


Функции от по-висок порядък

Функция от по-висок ред е функция, която приема друга функция като аргумент. Функциите от по-висок порядък са рядкост в императивните езици. Пример е функцията за сортиране от стандартната библиотека на C. Като първи аргумент, двоичен предикат за сравнение се предава на функцията за сортиране, с помощта на която се предава масив като втори аргумент и се сортира. На функционални езици и в Haskell функциипо-високи порядки се използват много често.

Prelude>: t ($) ($) :: (a -> b) -> a -> b

Той е полиморфен тип. Операторът долар е функция от два аргумента. Неговият ляв операнд или първи аргумент (a -> b) е функция. Неговият десен операнд a е произволна стойност. Операторът долар просто прилага първия си аргумент (a -> b) към втория си аргумент a. Следователно тук е необходимо типовете да са последователни. Типът на втория аргумент към долар оператора трябва да съвпада с типа на параметъра на функцията, който се предава в първия аргумент. Освен това, резултатът от изпълнението на оператора dollar е резултатът от функцията, подадена като първи аргумент. Тъй като типът на резултата е b, резултатът от извлечението за долар е тип b.

Следното използване е валидно само когато a и b са еднакви.

Prelude> нека apply2 fx = f (fx) Prelude>: t apply2 apply2 :: (t -> t) -> t -> t Prelude> apply2 (+ 5) 22 32 Prelude> apply2 (++ "AB") " CD "" CDABAB "

Първият аргумент наистина е функция, но аргументът и връщаната стойност са от един и същи тип. И вторият аргумент е стойността на този тип, а връщаната стойност също е стойността на този тип. По този начин функцията apply2 може да се приложи към по-ограничен набор от функции от долара. Ако доларът е полиморфен по аргумент и връщана стойност, тогава apply2 е полиморфен само в един параметър.

Функцията flip от стандартната библиотека се дефинира, както следва: flip f y x = f x y.

Prelude> flip (/) 4 2 0.5 Prelude> (/) 4 2 2.0 Prelude> flip const 5 True True Prelude>: t flip flip :: (a -> b -> c) -> b -> a -> c Prelude>: t flip const flip const :: b -> c -> c

(- Полезна функция е дефинирана в модула Data.Function по-висок порядък -} на :: (b -> b -> c) -> (a -> b) -> a -> a -> c на op f x y = f x `op` f y (- Необходими са четири аргумента: 1) двоичен оператор със същия тип аргументи (тип b), 2) функция f :: a -> b, която връща стойност от тип b, 3,4) и две стойности от тип а. Функцията on прилага f два пъти към две стойности от тип a и предава резултата на двоичен оператор. Използвайки on, можете например да напишете функцията за сумиране на квадратите на аргументите по този начин :-) sumSquares = (+) „включено“ (^ 2) (- Функцията multSecond, която умножава вторите елементи на двойки, се реализира по следния начин -) multSecond = g `on` h g = (*) h = snd

Анонимни функции

В Haskell, както и в математиката, функциите обикновено се назовават. Когато трябва да извикаме функция, ние се позоваваме на нея по име. Има обаче алтернативен подход, наречен анонимни функции.

Прелюдия> (\ x -> 2 * x + 7) 10 27 Прелюдия> нека f "= (\ x -> 2 * x + 7) Прелюдия> f" 10 27

то анонимна функцияили ламбда функция.

Има синтактична захар за опростяване на нотацията.

Prelude> let lenVec xy = sqrt $ x ^ 2 + y ^ 2 Prelude> let lenVec x = \ y -> sqrt $ x ^ 2 + y ^ 2 Prelude> let lenVec = \ x -> \ y -> sqrt $ x ^ 2 + y ^ 2 Prelude> lenVec 3 4 5.0 Prelude> let lenVec = \ xy -> sqrt $ x ^ 2 + y ^ 2 Prelude> lenVec 3 4 5.0


При използване на функции от по-висок ред се използват анонимни функции.

(- Функция on3, има подобна семантика на on, но приема функция с три места като първи аргумент -) on3 :: (b -> b -> b -> c) -> (a -> b) -> a -> a -> a -> c on3 op f x y z = op (f x) (f y) (f z) (- Сборът от квадратите на три числа може да се запише с помощта на on3 така -) sum3squares = (\ x y z -> x + y + z) `on3` (^ 2)

Функции с къри и без кари

Със синтаксиса за извикване на функции в Haskell можете да изброите не всички аргументи, а само част от тях. Първите няколко аргумента на функция могат да бъдат посочени, а останалите могат да бъдат отхвърлени. Тази идея за частично прилагане на функции е измислена от Хаскел Къри и в негова чест такива функции с аргументи, предавани един по един, се наричат ​​curried. В Haskell не всички функции са къри. В Haskell можете да дефинирате функции върху кортежи. В този случай синтаксисът за извикване на функции ще изглежда по същия начин като в обикновените езици:
име_на функция (първи_аргумент, втори_аргумент)

Прелюдия> първи (1, 2) 1


Кърирането е процедура за преминаване от функции, които не са с кари, към функции, които приемат аргументи един по един. Представете си, че имаме функция от по-висок ред, като комбинатора on, той очаква 2 от 4-те си аргумента да бъдат функция. Първият аргумент е функция с два аргумента, която е къри. Haskell има специален къри комбинатор, който преминава от функция без кари към къри. В следващия пример curry превръща функцията, посочена в двойка, в стандартна къри функция от два аргумента.

* Демо>: t on on :: (b -> b -> c) -> (a -> b) -> a -> a -> c * Demo>: t curry fst `on` (^ 2) curry fst `on` (^ 2) :: Num b => b -> b -> b


Друг пример, неизчерпана функция avg:

Avg :: (Двойно, Double) -> Double avg p = (fst p + snd p) / 2

Функцията curry avg `on` (^ 2) е функция, която изчислява средната стойност на квадратите на двете стойности, предадени на нея.

Функцията къри е:

Prelude> let cur fxy = f (x, y) Prelude>: t cur cur :: ((t1, t2) -> t) -> t1 -> t2 -> t Prelude>: t curry curry :: ((a , б) -> в) -> a -> b -> в

Той приема функция без кариери като първи аргумент, т.е. функция над двойка, но я превръща като връщана стойност в функция с къри, в която аргументите се предават последователно.

Има обратна функциянекари:

Prelude>: t uncurry uncurry :: (a -> Prelude>: t uncurry (flip const) uncurry (flip const) :: (b, c) -> c Prelude>: t snd snd :: (a, b) - > б

Модулът Data.Tuple на стандартната библиотека дефинира функцията swap :: (a, b) -> (b, a), която разменя елементите на двойка:

GHCi> замяна (1, "A") ("A", 1)

Тази функция може да се изрази като:

Prelude> нека swap = uncurry (flip (,)) Prelude> swap (1, "A") ("A", 1)

Строги и слаби функции

Мързеливата оценка води до факта, че в много ситуации е възможно да се премахне непълнотата на програмата.

Const42 :: a -> Int const42 = const 42

Функцията const42 напълно игнорира стойността на аргумента, следователно, в рамките на модела на мързеливо изчисление, ако й предадете някакво сложно изчисление като аргумент, тогава това изчисление никога няма да се случи. Това означава, че всяка програма, включително програма без край, може да бъде предадена като аргумент на const42.

* Демо> const42 True 42 * Демо> const42 123 42 * Демо> const42 (1 + 3) 42 * Демо> const42 undefined 42

Функции като const42 се наричат ​​слаби функции. Ако дивергентно изчисление се предаде на функция като аргумент и резултатът е някаква стойност, която не е дивергентна, тогава такава функция се нарича нестрога. Функцията се нарича строга, така че ако й предадем дивергентен аргумент, тогава стойността на тази функция е задължително дивергентна. Функцията с два аргумента може да бъде строга или нестрога за втория аргумент, в зависимост от стойността на първия й аргумент. Необходим е анализ на тежестта, за да се направи една програма на Haskell много ефективна.

Мисля, че всички редовни читатели на моя блог знаят, че съм умерено пламенен последовател. Не че не искам да чувам нищо за всичко останало – не, просто винаги се застъпвам за възможности за избор, дискусия и постоянно търсененай-доброто, което в крайна сметка означава за някакъв вид еволюция на методологиите за разработка. Култът към личността ми е също толкова неприятен, колкото и култът към отделна методология за програмиране, защото в дългосрочен план е еднозначен път към застой и лудост.

Продължавайки тази образователна работа, бих искал да се съсредоточа днес върху - един прекрасен функционален език за програмиране. Вече три пъти ми беше изпратен същият въпрос: откъде да започна (продължа) да науча Haskell?

Може би е време да дадем кратък отговор, съвет. Тук общ алгоритъмнавлизане в тази тема от мен.

Етап 0 - Въведение. Haskell? Чове глупости?

Добре известен парадокс сред наемателите на програмисти, често наричан "", и е формулиран по следния начин:

Ако една компания избере някакъв по-рядко срещан езотеричен език за програмиране като основен език за програмиране, тогава такава компания има най-големи шансове да получи най-добрите програмисти на пазара. Защо? Факт е, че онези програмисти, за които научаването на нови неща не е проблем, ще искат да бъдат наети в такава „странна компания“; тези, за които малко познато и трудно достъпно не е пречка; и накрая, тези, които имат достатъчно високо самочувствие, за да се предложат в толкова прословуто тежки условия.

И най-важното е, че има два типа програмисти: тези, които се учат да получават Добра работаи винаги избират мейнстриймзащото това значително увеличава шансовете им за намиране на работа; и тези - които просто обичат да научават нещо ново, да се развиват и винаги избират най-доброто, което често е далеч от най-печелившатакакто правят техните колеги кариеристи. Така, Парадоксът на питонатвърди, че като започнете разработката върху напредналата екзотика, вие като прахосмукачка ще привлечете втората категория програмисти (противното е вярно, когато става въпрос за фирми, предлагащи работа).

Мога да цитирам като абстрактен пример за напълно подобно скрито таргетиране на фокус групи с дадени имоти, история от скорошната му младост. Когато още учех, имахме "странно" отношение, което демонстративно, при представяне матаанализникога не е обръщал внимание правилната странапублика. Тоест имаше два реда в публиката - ляв и десен - и сега той изнася лекция, обяснява нещо, но в същото време НИКОГА не поглежда в десния ред - цялото внимание е само към студентите от левия ред . Също и с отговорите на въпросите - десният ред за него не съществуваше. Оттам не чува нищо.

В резултат на това в течение на дълго време забелязах интересно нещо: в нашата група имаше автоматична самоидентификация и фрагментация на тези, които смятаненеобходими и интересни (и те веднага заеха места в лявата лента, след като приеха правилата на тази странна игра), и тези, които не се нуждаят от това, и се втурнаха да заемат места вдясно, защото тук бяха оставени на себе си . Това сортиране се случи от само себе си, без извънредни усилия отвън. За учителя, както разбрах мотивацията му, беше много удобно - той не си хаби енергията за по същество аутсайдери, а веднага създаде условия за съсредоточаване на силата и вниманието си върху тези, които имат нужда от знанията му. За учениците от C клас това също беше печеливша ситуация.

3. Етап – търсене на дълбочина и усещане за нов език

Третият етап вече копае по-дълбоко. Тук предлагам да разчупя малко традиционните шаблони (и о, как обичам да правя това) и да премина към коренно различен формат за представяне на информация: това не само разнообразява задачата за изучаване на език, но и включва нови, неактивирани досега области на мозъка ви (особено ще напрегнем програмиращото ви ляво полукълбо). Имам предвид отлична видео лекция за Haskell от много умен човек с английски корени.

Ето нейния изход:

Курсът по функционално програмиране с Haskell
(Английски език)
35 часа | 1280 × 720 | XviD - 1326Kbps
25.00 кадъра в секунда | Mp3 - 96Kbps | 20,06 GB

Можете да изтеглите тази страхотна видео лекция. Съветвам ви да побързате, докато възмутените притежатели на авторски права дотичат и заковат този рядък архив на nafig ( актуализиране: да, заковах го: амин. Приканвам всички желаещи да използват тази мега-връзка).

4. Последният етап – практика

Намирането на най-подходящата и вдъхновяваща практическа задача за вас е тема на отделна публикация, където бих искал да дам няколко ярки примера от живота на известни отчаяни програмисти, така че засега ще отложим обясненията ми по този въпрос последния етапза малко настрана.

Ето, нещо подобно, отчасти очевидно за мнозина, исках да дам на всички, които искат да научат Haskell, и не само. Направи го!

И в заключение, за привържениците на други езици за програмиране:

Haskell е свещен език за програмиране, даден на шаманите Бубенландтяхното върховно божество Комонадакак универсален лекза комуникация и духовно прочистване, подходящ както за божествени същества, така и за (някои) простосмъртни, които са се възстановили от тежки етапи на интелекта. Поради произхода си езикът винаги е бил функционално чист. Средно изучаването на Haskell започва на 10-12 години. Започването навреме ще гарантира, че ще достигнете третото ниво на Мощност до 75-годишна възраст. Не бива да отлагате за следващия живот това, което можете да направите понезапочнете от този.

Матю Грифин

След като изучавах Haskell дълго време, натрупах достатъчно опит, за да ви дам няколко съвета сега. Освен това бих искал да науча някои принципи за себе си, преди да продължа по-нататък.

И въпреки че понякога прибягвам до Помощ за Python, по-голямата част от работата ми в мрежата сега се извършва в Haskell.

Първо данните

Мислех да премина от динамичен език към статичен, а в Haskell структурата от данни, с която работите, е ясно описана в декларацията. В Python през повечето време кодът изпълнява тази задача.

Когато за първи път видях функции в Haskell, се зачудих: „Какво са данните? Тази функция взема ли нещо на входа и дава ли нещо на изхода?" И когато работех с Python, имах въпрос: "КАКВО КАЗВА КОДЪТ?"

Структурата на данни на Haskell оформи моя начин на мислене, който внесох в работата си в Python. Моят код е забележимо по-добър. И въпреки че много често ми се струваше, че формата на описаните от мен данни се променя без причина, всъщност всичко стана ясно с малко проучване на въпроса. Ограниченията върху свободата за промяна на данни също правят кода по-малко сложен и по-лесен за разбиране.

Четимост на кода

Python ме привлече със способността да пиша наистина четим код... Примерите за код на Haskell изглеждаха просто ужасно, с изключение на някои фрагменти, които изглежда бяха избрани нарочно, за да не плашат начинаещите. И въпреки че някои части от кода изглеждаха много хубаво, повечето от изходния код беше изпълнен с нещо страшно. И зад това „ужасно” се криеше цялата сила на езика.

Определено не исках да пиша "интелигентен" код - такъв, който може да демонстрира впечатляващите възможности на езика, но е напълно неудобен.

Въпреки това, аз оцених четливостта на Haskell в сравнение с други популярни езици за програмиране. Сякаш оценявах Китайскикато роден английски език.

Разбрах, че Haskell не е "умен", а хитър език. Със сигурност е възможно да се напише "интелигентен" код в Haskell, но това не е често срещан случай. В същото време силата на интелигентния код е ограничена до силно писане. Ако функцията трябва да върне Int, тя ще върне Int. Е, или хвърля грешка в компилацията в краен случай.

А по-мощните абстракции в Haskell напомнят за някаква магия, която се опитах да избегна при работа с Python.

Сериозен съм за четливостта

Първо, повярвайте и се убедете, че да, хората четат обяви на този нов език за вас и го правят бързо и редовно. След като най-накрая се справих с повечето от известните нюанси, започнах спокойно да се отнасям към кода в Haskell.

Коментари.Те заемат горна линияв една от главите на нашата "книга".

Тази глава описва как Томи отиде до магазина и купи патица.

глава :: Томи -> Пазар -> Пате

Функцииот друга, намалена функция, в голямата картина, намалете кода до максимум.

Краткост.Нямате нужда от много код, за да осъществите идеите си.

Вмъкване на символи

Исках също да спомена функциите на плъгините, които са често срещани в езика Haskell. Функциите за вмъкване (или операторите) са тези функции, които използват междуза два аргумента вместо преди. Прост пример е "+".

В Haskell имаме няколко символа за допълване по подразбиране: $,<$>, <-, ->, и т.н. Може да ви объркат малко в началото на пътуването.

Не се безпокой! След като се научите как да ги използвате, ще разберете колко са полезни и прости. Преброих около 5 символа, които използвам редовно.

Не препоръчвам да използвате библиотеката за обективи веднага, тъй като тя съдържа много такива символи. Тази библиотека е много полезна, но в началото трябва да можете да постигнете голям успех без нея. Изчакайте, докато можете безопасно да пишете умерено сложни програми в Haskell, след което започнете да използвате обектив.

Трябва напълно да обновите знанията си

Докато изучавате Haskell, по пътя ще срещнете нови термини, като напр функторили монада.

Може да ви е трудно да запомните и научите тези думи. Когато започнете да се запознавате с популярен език за програмиране, много от термините са ви ясни и познати от езиците, които сте научили.

Ние помним информацията, като я свързваме с друга информация. Аз например нямам асоциации с думата функтор, така че няма да ми е лесно да науча този термин.

Стратегията ми за научаване на такива думи ми дойде на ум съвсем наскоро и започнах да я използвам всеки път, когато видях термин, който не разбирам. Състои се в подбора на синоними за тези думи, които не разбирате и не сте запознати. След известно време се научих как да подбирам тези синоними.

Например, функтор.

В Haskell много се показва на вашия екран. Например списъкът е функтор. Това означава, че в списъка се показва функция за показване, която използва друга функция. След това създаде нов списъкс резултатите.

Карта (+1) - резултати в

Затова дадох името на това - mapa. Думата mapa е много лесна за запомняне. Списъкът е функтор. Списъкът е карта.

Моята система за проверка на грешки

Когато писах на Python, моят инструмент за отстраняване на грешки беше инструкции за печат.

В Haskell използвах систематични инструменти.

Но! Можете да приложите Debug.Trace. Тази техника е подобна на тази в Функция на Pythonпечатането е независимо от Haskell IO. И този модулможе да е от полза в началото. Когато за първи път започнахте да работите с Haskell, мислехте ли колко много бихте го използвали?

Ако не използвате изрази за проследяване в кода си, след като сте проверили за грешки, веднага ще забележите, че кодът ви изглежда по-зле в Haskell, отколкото в Python.

Най-добър урок за монада

Всеки път, когато чуете за успеха на програмист на Haskell, се чудите: „Как този човек научи монадите до края?“ И така, всичко е наред.

Трябва да направя разбор. Знаех нещо или две за това, когато писах на Python. Но поради моята неопитност в тази област сега е доста трудно да се направи анализ.

Добре, сега ще ти кажа повече. Ще обясня в Haskell.

Намерих видео в YouTube, "Parsing Stuff in Haskell", което описва как да направите JSON синтактичен анализ в Haskell с помощта на библиотеката Parsec.

Това видео ми помогна по невнимание да разбера как мога да използвам монади и апликативни функтори, за да създам това, от което имам нужда. Разбрах как техните функции (har, har) са свързани една с друга.

След писане синтактичен анализс помощта на видеото започнах да разбирам целия код. Започнах също да разбирам цялата му „природа“. Но в началния етап това няма да е полезно.

Така че Parsec си върши работата доста добре, така че моята неопитност при писане на анализ е практически невидима. Както всеки друг начинаещ в Haskell, аз можех лесно да правя добри програми и операции още в началото на моето запознаване с този език.

Възползвайте се от знанията си

Haskell е моят основен език поради няколко причини:

  1. Изборът на технологии, които ще използвам.
  2. Мога да пиша програмите си по-бързо и през повечето време продавам тези програми.
  3. Не е нужно да се справяте с малки бъгове.
  4. Дори когато се сблъскам с няколко грешки, бързо ги разрешавам, тъй като те са повече или по-малко ясни.
  5. Python не се фокусира върху скоростта. Haskell прави същото, но изборът е мой.
  6. Рефакторингът всъщност е доста ветровит. В Python понякога се карам много, когато забравям да поправя малки грешки в кода, които по-късно създават огромни трудности.
  7. Страхотни библиотеки. Основната характеристика на Haskell е високо качествобиблиотеки.
  8. Общността, готова да помогне.
  9. Лесно мащабиране на кода до желаното ядро.
  10. Haskell се актуализира често. Миналата година, когато GHC (компилаторът за Haskell) беше актуализиран до версия 7.8, което направи кодирането два пъти по-удобно, много уеб сървъри бяха ускорени.

Накрая искам да кажа, че Haskell ме забавлява много. Беше най-доброто изживяванепрез целия ми живот.

Много пъти съм ги питал. Аз отговарям.

— Какъв е този твой Haskell?

Haskell е чисто функционален език за програмиране с общо предназначение, може да се използва за решаване на широк спектър от проблеми. Компилиран, но може да се държи и като скриптиран. Кръстосана платформа. Мързелив, силно статично набран. И не е като другите езици. Изобщо.

— Това някакъв нов език ли е?

Въобще не. Историята на Haskell датира от 1987 г. Този език е роден в математическите среди, когато група хора решават да създадат най-добрия функционален език за програмиране. През 1990 г. излиза първата версия на езика, кръстен на известния американски математик Хаскел Къри. През 1998 г. езикът е стандартизиран и от 2000-те започва бавно да навлиза в света. практическо програмиране... С годините езикът се подобрява и през 2010 г. светът видя своя актуализиран стандарт. Така че имаме работа с език, който е по-стар от Java.

— И кой го направи?

Haskell е създаден от много хора. Най-известната реализация на езика е въплътена в GHC (The Glasgow Haskell Compiler), който е роден през 1989 г. в Университета в Глазгоу. Компилаторът имаше няколко основни разработчици, от които двама са най-известните, Саймън Пейтън Джоунс и Саймън Марлоу. Впоследствие значителен принос за развитието на GHC имат още няколкостотин души. Източник GHC компилаторът е отворен. Между другото, самият компилатор е 82% написан на Haskell.

За любопитните: за изчерпателен преглед на историята на Haskell и GHC, прочетете.

„Има ли библиотеки за Haskell?“

О да! Няма дори стотици – има хиляди. Докато четете, ще се запознаете с много от тях.

„И какво, вече е възможно в производство?“

Вече е в производство. След пускането на първия стандарт езикът се подобри, неговата екосистема се разви, появиха се нови библиотеки и бяха публикувани книги. Haskell е напълно готов за сериозно търговска употреба, както свидетелстват историите за успешно внедряване на Haskell в бизнеса, включително големи.

„Висок ли е прагът за влизане в Haskell?“

Да и не. Овладяването на Haskell е трудно преди всичко поради несходството му с други езици, така че хората с опит с други езици ще трябва да си счупят мозъците. За да ги разбиете, а не просто да ги преместите: Haskell ви кара да гледате по различен начин дори на познати неща. От друга страна, Haskell е по-прост от много известни езици. Не ми вярвайте на думата, скоро ще се убедите сами. И знайте: много хора, научили вкуса на Haskell, категорично не искат да се връщат към други езици. Предупредих те.

"И аз също чух за някои монади ..."

Да, има такова нещо. Някои неща от света на Haskell нямат преки двойници в други езици за програмиране и това обърква начинаещите. Но не се притеснявайте: аз самият преминах през този ступор и ви разбирам добре. Запомнете: новото изглежда само страшно.

"И ако го сравните с C ++ / Python / Scala ..."

Сравняването на Haskell с други езици е извън обхвата на тази книга. Няколко пъти ще срещнете парчета код на други езици тук, но аз ги представям единствено, за да подчертая разликата от Haskell, а не изобщо за сравнение в контекста на "по-добро / по-лошо". И като цяло ще се опитам да не хваля много Haskell, просто искам да ви кажа истината за него. Вече направих своето заключение за този език и вие трябва да направите своето заключение за него.

    Видове

    Програмите на Haskell са изрази, които се оценяват до стойности. Всяка стойност има тип. Интуитивно един тип може да се разбира просто като набор приемливи стойностиизрази. За да разберете вида на даден израз, можете да използвате командата на интерпретатора: type (или: t). Като алтернатива можете да издадете командата: set + t, за да накара интерпретатора автоматично да отпечата типа на всеки изчислен резултат.
    Основните типове на езика Haskell са:
    Типовете Integer и Int се използват за представяне на цели числа и стойностите от тип Integerне са ограничени по дължина.
    Типове плувкии Double се използва за представяне на реални числа.
    Типът Bool съдържа две стойности, True и False, и е предназначен да представя резултата от булеви изрази.
    Типът Char се използва за представяне на знаци. Имената на типове Haskell винаги започват с главна буква.
    Езикът Haskell е силно въведен език за програмиране. В повечето случаи обаче от програмиста не се изисква да декларира какви типове са въведените от тях променливи. Самият интерпретатор е в състояние да изведе типовете променливи, използвани от потребителя.
    Въпреки това, ако все пак за някаква цел е необходимо да се декларира, че някаква стойност принадлежи на определен тип, се използва конструкция от вида: променлива :: Тип. Ако опцията за интерпретатор + t е активирана, тя отпечатва стойностите в същия формат.
    По-долу е даден пример за протокола на сесията за работа с интерпретатора. Очаква се текстът след подканата Prelude> да бъде въведен от потребителя, а следващият текст е отговорът на системата.

    Прелюдия>: набор + t
    Прелюдия> 1
    1 :: цяло число
    Прелюдия> 1.2
    1.2 :: Двойно
    прелюдия> 'а'
    'A' :: Char
    Прелюдия> Вярно
    Вярно :: Бул

    От на този протоколможем да заключим, че стойностите от тип Integer, Double и Char се задават по същите правила като в езика C.
    Добре развитата система за типове и силното въвеждане правят програмите на Haskell безопасни за типа. Гарантирано е, че в правилната програмавсички типове се използват правилно в Haskell. На практика това означава, че програма на Haskell не може да причини нарушение на достъпа, когато се изпълнява. Също така е гарантирано, че неинициализираните променливи не могат да бъдат използвани в програмата. По този начин много грешки в програмата се проследяват при компилация, а не при изпълнение.

    Аритметика

    Преводачът Hugs може да се използва за оценка на аритметични изрази. В този случай можете да използвате операторите +, -, *, / (събиране, изваждане, умножение и деление) с обичайните правила за приоритет.
    Като алтернатива можете да използвате оператора ^ (експоненцииране). Така сесията може да изглежда така:

    Прелюдия> 2 * 2
    4 :: цяло число
    Прелюдия> 4 * 5 + 1
    21 :: Цело число
    Прелюдия> 2 ^ 3
    8 :: Цело число
    Като алтернатива можете да използвате стандарта математически функции sqrt ( Корен квадратен), sin, cos, exp и др. За разлика от много други езици за програмиране, Haskell не трябва да затваря аргумента в скоби при извикване на функция. Така че човек може просто да напише sqrt 2, а не sqrt (2). пример:

    Прелюдия> sqrt 2
    1.4142135623731 :: Двойна
    Прелюдия> 1 + sqrt 2
    2.4142135623731 :: Двойна
    Прелюдия> sqrt 2 + 1
    2.4142135623731 :: Двойна
    Prelude> sqrt (2 + 1)
    1.73205080756888 :: Двойна

    От този примерможем да заключим, че извикването на функцията има по-висок приоритет от аритметични операциитака че изразът sqrt 2 + 1 се интерпретира като (sqrt 2) + 1, а не sqrt (2 + 1). Използвайте скоби, за да посочите точния ред на оценка, както в последния пример. (Всъщност извикването на функция има предимство пред всеки двоичен оператор.)
    Трябва също да се отбележи, че за разлика от повечето други езици за програмиране, целочислените изрази в Haskell се оценяват с неограничен брой цифри (Опитайте се да оцените израза 2 ^ 5000.) За разлика от C, където максималната възможна стойност на тип int е ограничено от капацитета на машината (на съвременни персонални компютритова е 231-1 = 2147483647), типът Haskell Integer може да съхранява цели числа с произволна дължина.

    Кортежи
    В допълнение към простите типове, изброени по-горе, можете да дефинирате стойности в Haskell композитни видове... Например, за да дефинирате точка в равнина, са ви необходими две числа, съответстващи на нейните координати. В Haskell двойка може да бъде определена чрез изброяване на компонентите, разделени със запетаи и ограждането им в скоби: (5,3). Компонентите на двойка не е задължително да са от един и същи тип: можете да направите двойка, чийто първи елемент е низ, вторият е число и т.н.
    Като цяло, ако a и b са някои произволни типовев Haskell типът на двойка, в която първият елемент е от тип a, а вторият е от тип b, се обозначава като (a, b). Например двойката (5,3) е от тип (Integer, Integer); двойката (1, 'a') е от тип (Integer, Char). Можете да донесете още сложен пример: двойката ((1, 'a'), 1.2) е от тип ((Integer, Char), Double). Проверете го с преводач. Трябва да се отбележи, че въпреки че конструкции като (1,2) и (Integer, Integer) изглеждат сходни, в Haskell те означават напълно различни обекти. Първият е стойност, докато вторият е тип. За работа с двойки на езика Haskell има стандартни функции fst и snd, които връщат съответно първия и втория елемент от двойката (имената на тези функции произлизат от английските думи "first" и "second"). Така че те могат да се използват по този начин

    Prelude> fst (5, True)
    5 :: цяло число
    Prelude> snd (5, True)
    Вярно :: Бул
    Освен двойки по същия начин могат да се дефинират тройки, четворки и т.н. Техните видове са написани по подобен начин.
    Прелюдия> (1,2,3)
    (1,2,3) :: (Цяло число, цяло число, цяло число)
    Прелюдия> (1,2,3,4)
    (1,2,3,4) :: (Цяло число, цяло число, цяло число, цяло число)
    Тази структура от данни се нарича кортеж. Кортежът може да съхранява фиксирано количество хетерогенни данни. Функциите fst и snd са дефинирани само за двойки и не работят за други кортежи. Ако се опитате да ги използвате, например, за тризнаци, интерпретаторът дава съобщение за грешка. Елемент от кортеж може да бъде стойност от всякакъв тип, включително друг кортеж. Комбинация от функции fst и snd може да се използва за достъп до елементите на сдвоени кортежи. Следващият пример демонстрира извличане на елемент 'a' от кортеж
    (1, ('a', 23.12)):
    Prelude> fst (snd (1, ('a', 23.12)))
    'A' :: цяло число

    Списъци
    За разлика от кортежите, списъкът може да съхранява произволен брой елементи. За да дефинирате списък в Haskell, трябва квадратни скобиизбройте неговите елементи, разделени със запетаи. Всички тези елементи трябва да са от един и същи тип. Тип списък с елементи, принадлежащи към тип a се обозначава като [a].

    Прелюдия>
    ::
    Прелюдия> ['1', '2', '3']
    [’1’,’2’,’3’] ::
    Може да няма елементи в списъка. Празен списък се обозначава като.
    Оператор: (двоеточие) се използва за добавяне на елемент в началото на списък. Левият му аргумент трябва да бъде елемент, а десният му аргумент трябва да бъде списък:
    Прелюдия> 1:
    ::
    Прелюдия> '5': ['1', '2', '3', '4', '5']
    [’5’,’1’,’2’,’3’,’4’,’5’] ::
    Prelude> False:
    ::
    Всеки списък може да бъде изграден с помощта на оператора (:) и празен списък:
    Прелюдия> 1: (2: (3 :))
    :: Цело число
    Операторът (:) е дясно асоциативен, така че можете да пропуснете скобите в горния израз:
    Прелюдия> 1: 2: 3:
    :: Цело число
    Елементите на списъка могат да бъдат всякакви стойности - числа, символи, кортежи, други списъци и т.н.
    Прелюдия> [(1, 'a'), (2, 'b')]
    [(1, 'a'), (2, 'b')] :: [(Цяло число, символ)]
    Прелюдия> [,]
    [,] :: []
    За работа със списъци в Haskell има голям бройфункции. В това лабораторна работаще разгледаме само някои от тях.
    Функцията head връща първия елемент от списъка.
    Функцията tail връща списък без първия елемент.
    Функцията за дължина връща дължината на списъка.
    Главните и опашните функции са дефинирани за непразни списъци. Когато се опитвате да ги приложите към празен списъкпреводачът съобщава за грешка. Примери за работа с посочените функции:
    Прелюдия> глава
    1 :: цяло число
    Прелюдия> опашка
    ::
    Прелюдия> опашка
    :: Цело число
    Прелюдия> дължина
    3 :: Int
    Имайте предвид, че резултатът от функцията за дължина принадлежи към Тип Intвместо Integer.
    Haskell дефинира оператора ++ за свързване (свързване) на списъци.
    Прелюдия> ++
    :: Цело число

    Струни
    Стойностите на низове в Haskell, както и в C, са посочени в двойни кавички... Те са от тип String.
    Prelude> "здравей" "здравей" :: String
    Низовете всъщност са списъци със знаци; по този начин изразите "здравей", ["h", "e", "l", "l", "o"] и

    'H': 'e': 'l': 'l': 'o': означава едно и също нещо, а String е синоним на. Всички функции за работа със списъци могат да се използват при работа с низове:
    Prelude> глава "здравей"
    'H' :: Char
    Prelude> опашка "здравей"
    "Здравейте" ::
    Prelude> дължина "здравей"
    5 :: Int
    Прелюдия> "здравей" ++ ", свят"
    "Здравей свят" ::
    Превръщам числови стойностикъм редове и обратно, има функции за четене и показване:
    Прелюдия> шоу 1
    "1" ::
    Prelude> "Формула" ++ шоу 1
    "Формула 1" ::
    Prelude> 1 + прочетете "12"
    13 :: цяло число
    Ако функцията show не може да преобразува низа в число, тя ще съобщи за грешка.

    Функции
    Досега използвахме вградените функции на езика Haskell. Сега е време да се научите как да дефинирате собствени функции... За да направим това, трябва да проучим още няколко команди на интерпретатора (припомнете си, че тези команди могат да бъдат съкратени до една буква):
    Командата: load зарежда програмата Haskell, съдържаща се в посочения файл, в интерпретатора.
    Командата: edit стартира процеса на редактиране на последния качен файл.
    Командата: reload препрочита последния зареден файл. Определения персонализирани функциитрябва да е във файла, за да бъде зареден в интерпретатора на Hugs с помощта на командата: load.
    За да редактирате заредената програма, можете да използвате командата: edit. Той стартира външен редактор (Notepad по подразбиране), за да редактира файла. След края на сесията за редактиране редакторът трябва да бъде затворен; преводачът Hugs ще препрочете съдържанието на променения файл. Въпреки това, файлът може да бъде редактиран директно от обвивката на Windows. В този случай, за да може интерпретаторът да прочете отново файла, е необходимо изрично да извикате командата: reload.
    Нека да разгледаме един пример. Създайте lab1.hs файл в някаква директория. Нека бъде пълен пъткъм този файл - c: \ labs \ lab1.hs (това е само пример, вашите файлове може да са наречени различно). В преводача Hugs изпълнете следните команди:

    Prelude>: заредете "c: \\ labs \\ lab1.hs"
    Ако изтеглянето е успешно, подканата на интерпретатора се променя на Main>. Факт е, че ако името на модула не е посочено, то се счита за равно на Main.
    Главно>: редактиране
    Тук трябва да се отвори прозорец на редактор, в който можете да въведете текста на програмата. Въведете:
    х =
    Запазете файла и затворете редактора. Преводачът Hugs ще зареди файла
    c: \ labs \ lab1.hs и сега ще бъде определена стойността на променливата x:
    Главно> x
    ::
    Имайте предвид, че символите \ се дублират при запис на името на файла в аргумента на командата: load. Точно както в езика C, в Haskell символът \ служи като индикатор за началото на служебен символ ('\ n' и т.н.) ...
    Сега можете да преминете към дефиниране на функции. Създайте, в съответствие с процеса, описан по-горе, файл и напишете следния текст в него:

    квадрат :: цяло число -> цяло число
    квадрат x = x * x

    Първият ред (квадрат :: Integer -> Integer) декларира, че дефинираме функционален квадрат, който приема целочислен параметър и връща целочислен резултат. Вторият ред (квадрат x = x * x) е самата дефиниция на функцията. Функцията квадрат приема един аргумент и връща своя квадрат. Функциите в Haskell са стойности от "първи клас". Това означава, че те са "равни" със стойности като цели числа и реални числа, знаци, низове, списъци и др. Функциите могат да бъдат предавани като аргументи на други функции, връщани от функции и т.н. Както всички стойности в Haskell, функциите са от тип. Типът на функция, която приема стойности от тип a и връща стойности от тип b, се обозначава като a-> b.
    Заредете генерирания файл в интерпретатор и изпълнете следните команди:

    Main>: тип квадрат
    квадрат :: цяло число -> цяло число
    Главен> квадрат 2
    4 :: цяло число
    Имайте предвид, че по принцип декларацията на типа на квадратната функция не беше необходима: самият интерпретатор може да изведе необходимата информацияза типа на функцията от нейната дефиниция. Въпреки това, първо, изведеният тип би бил по-общ от Integer -> Integer, и второ, изричното уточняване на типа на функцията е "добра практика" при програмиране в Haskell, тъй като декларацията на типа служи като вид документация за функцията и помага за идентифициране на програмни грешки.
    Имената на дефинираните от потребителя функции и променливи трябва да започват с малка латинска буква. Останалите знаци в името могат да бъдат главни или малки с латински букви, цифри или символи _ и ' (долно черти и апостроф). По този начин, по-долу са примери за валидни имена на променливи:

    вар
    вар1
    име на променлива
    име на променлива
    var '

    Условни изрази

    Можете да използвате условни изрази в дефиниция на функция в Haskell. Нека напишем функцията signum, която изчислява знака на предадения й аргумент:

    signum :: цяло число -> цяло число
    signum x = ако x> 0, тогава 1
    иначе ако х е 0

    Условният израз се записва като:
    ако условие след това изразяванедруго израз. Имайте предвид, че въпреки че този израз изглежда като съответния оператор в C или Pascal, условният израз в Haskell трябва да има както частта then, така и другата част. Изрази в then-part и else-part условен оператортрябва да са от същия тип. Условие в дефиниция на условен израз е всеки израз от тип Bool. Сравненията са примери за такива изрази. Следните оператори могат да се използват за сравнение:
    , = - тези оператори имат същото значение като в езика C (по-малко от, по-голямо от, по-малко от или равно, по-голямо от или равно).
    == е оператор за равенство.
    / = - оператор на проверка за неравенство.
    Изрази като Bool могат да се комбинират с помощта на common логически оператори&& и || (И и ИЛИ), а функциите за отрицание не.
    Примери за валидни условия:
    x> = 0 && x x> 3 && x / = 10
    (x> 10 || x Разбира се, можете да дефинирате свои собствени функции, които връщат Bool стойности и да ги използвате като условия. Например, можете да дефинирате функцията isPositive, която връща True, ако аргументът й е неотрицателен и False в противен случай:
    isPositive :: Integer -> Bool
    isPositive x = ако x> 0 тогава True else False

    Функцията signum вече може да бъде дефинирана, както следва:
    signum :: цяло число -> цяло число
    signum x = ако е положителен x, тогава 1
    иначе ако х е 0
    Имайте предвид, че функцията isPositive може да бъде дефинирана по по-опростен начин:
    е положителен x = x> 0

    Информацията е взета от: http://sguprog.narod.ru/