Поиск…


замечания

Логотип Haskell

Haskell - это усовершенствованный чисто функциональный язык программирования.

Особенности:

  • Статически типизировано: каждое выражение в Haskell имеет тип, который определяется во время компиляции. Проверка статического типа - это процесс проверки безопасности типа программы на основе анализа текста программы (исходного кода). Если программа проходит проверку статического типа, то гарантируется, что программа удовлетворит некоторый набор свойств безопасности типа для всех возможных входов.
  • Чисто функциональный : каждая функция в Haskell является функцией в математическом смысле. Нет инструкций или инструкций, только выражений, которые не могут мутировать переменные (локальные или глобальные), а не состояния доступа, такие как временные или случайные числа.
  • Параллельно: его флагманский компилятор GHC поставляется с высокопроизводительным параллельным сборщиком мусора и небольшой библиотекой параллелизма, содержащим множество полезных примитивов параллелизма и абстракций.
  • Ленькая оценка: функции не оценивают свои аргументы. Задерживает оценку выражения до тех пор, пока не потребуется его значение.
  • Универсальный: Haskell построен для использования во всех контекстах и ​​средах.
  • Пакеты: вклад с открытым исходным кодом в Haskell очень активен с широким спектром пакетов, доступных на серверах общих пакетов.

Последний стандарт Haskell - Haskell 2010. По состоянию на май 2016 года группа работает над следующей версией Haskell 2020.

Официальная документация Haskell также является исчерпывающим и полезным ресурсом. Отличное место для поиска книг, курсов, учебных пособий, руководств, руководств и т. Д.

Версии

Версия Дата выхода
Haskell 2010 2012-07-10
Haskell 98 2002-12-01

Привет, мир!

Базовый «Привет, мир!» программа в Haskell может быть выражена кратко только в одной или двух строках:

main :: IO ()
main = putStrLn "Hello, World!"

Первая строка представляет собой необязательную аннотацию типа, указывающую, что main является значением типа IO () , представляющим собой операцию ввода-вывода, которая «вычисляет» значение типа () (читает «единица», пустой кортеж не передает никакой информации) помимо выполнения некоторых побочных эффектов для внешнего мира (здесь, печатая строку на терминале). Аннотации этого типа обычно опущены для main потому что это его единственный возможный тип.

Поместите это в файл helloworld.hs и скомпилируйте его с помощью компилятора Haskell, такого как GHC:

ghc helloworld.hs

Выполнение скомпилированного файла приведет к выводу "Hello, World!" печатается на экране:

./helloworld
Hello, World!

В качестве альтернативы runhaskell или runghc позволяют запускать программу в интерпретируемом режиме без необходимости ее компиляции:

runhaskell helloworld.hs

Интерактивный REPL также может использоваться вместо компиляции. Он поставляется с большинством сред Haskell, таких как ghci который поставляется вместе с компилятором GHC:

ghci> putStrLn "Hello World!"
Hello, World!
ghci> 

Кроме того, загрузите скрипты в ghci из файла с помощью load (или :l ):

ghci> :load helloworld

:reload (или :r ) перезагружает все в ghci:

Prelude> :l helloworld.hs 
[1 of 1] Compiling Main             ( helloworld.hs, interpreted )

<some time later after some edits>

*Main> :r
Ok, modules loaded: Main.

Объяснение:

Эта первая строка является сигнатурой типа, объявляющей тип main :

main :: IO ()

Значения типа IO () описывают действия, которые могут взаимодействовать с внешним миром.

Поскольку Haskell имеет полноценную систему типа Hindley-Milner, которая позволяет автоматически вводить тип, титровые подписи технически необязательны: если вы просто опустите main :: IO () , компилятор сможет вывести тип самостоятельно анализируя определение main . Тем не менее, очень часто считается, что плохой стиль не записывать сигнатуры типов для определений верхнего уровня. Причины включают:

  • Типовые подписи в Haskell - очень полезная часть документации, потому что система типов настолько выразительна, что вы часто можете видеть, какая функция удобна для просто, глядя на ее тип. Эту «документацию» можно легко получить с помощью таких инструментов, как GHCi. И в отличие от обычной документации, контролер типа компилятора убедится, что он действительно соответствует определению функции!

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

Эта вторая строка выполняет фактическую работу:

main = putStrLn "Hello, World!"

Если вы исходите из императивного языка, может быть полезно отметить, что это определение также можно записать в виде:

main = do {
   putStrLn "Hello, World!" ;
   return ()
   }

Или, что то же самое, (Haskell имеет разводку на основе макета, но будьте осторожны, смешивая вкладки и пробелы непоследовательно, что будет путать этот механизм):

main = do
    putStrLn "Hello, World!"
    return ()

Каждая строка в блоке do представляет собой некоторое монадическое (здесь, I / O) вычисление , так что весь блок do представляет общее действие, состоящее из этих подэтапов, путем комбинирования их способом, определенным для данной монады (для ввода / вывода это означает просто выполнение их один за другим).

Синтаксис do сам по себе является синтаксическим сахаром для монад, например IO , и return - это действие no-op, создающее его аргумент без каких-либо побочных эффектов или дополнительных вычислений, которые могут быть частью определенного определения монады.

Вышеупомянутое то же самое, что и определение main = putStrLn "Hello, World!" , потому что значение putStrLn "Hello, World!" уже имеет тип IO () . Рассматривается как «заявление», putStrLn "Hello, World!" можно рассматривать как полную программу, и вы просто определяете main ссылку на эту программу.

Вы можете посмотреть подпись putStrLn онлайн :

putStrLn :: String -> IO ()
-- thus,
putStrLn (v :: String) :: IO ()

putStrLn - это функция, которая принимает строку в качестве аргумента и выводит действие ввода-вывода (то есть значение, представляющее программу, которую может выполнять среда выполнения). Время выполнения всегда выполняет действие с именем main , поэтому нам просто нужно определить его равным putStrLn "Hello, World!" ,

Факториал

Факториальная функция - Haskell «Hello World!» (и для функционального программирования в целом) в том смысле, что он лаконично демонстрирует основные принципы языка.

Вариант 1

fac :: (Integral a) => a -> a
fac n = product [1..n]

Демо-версия

  • Integral - это класс целочисленных типов чисел. Примеры включают Int и Integer .
  • (Integral a) => помещает ограничение на тип a находящийся в указанном классе
  • fac :: a -> a говорит, что fac - это функция, которая принимает a и возвращает a
  • product - это функция, которая накапливает все числа в списке, умножая их вместе.
  • [1..n] - это специальное обозначение, которое desugars для enumFromTo 1 n и является диапазоном чисел 1 ≤ x ≤ n .

Вариант 2

fac :: (Integral a) => a -> a
fac 0 = 1
fac n = n * fac (n - 1)

Демо-версия

Эта вариация использует сопоставление образцов, чтобы разбить определение функции на отдельные случаи. Первое определение вызывается, если аргумент равен 0 (иногда это называется условием останова), а второе определение - иначе (порядок определений значителен). Это также иллюстрирует рекурсию, поскольку fac ссылается на себя.


Стоит отметить, что из-за правил перезаписи обе версии fac будут скомпилированы с идентичным машинным кодом при использовании GHC с активированными оптимизациями. Таким образом, с точки зрения эффективности эти два будут эквивалентны.

Фибоначчи, используя ленивую оценку

Lazy оценка означает, что Haskell будет оценивать только элементы списка, значения которых необходимы.

Основным рекурсивным определением является:

f (0)  <-  0
f (1)  <-  1
f (n)  <-  f (n-1) + f (n-2)

Если оценивать напрямую, это будет очень медленно. Но, представьте, у нас есть список, который записывает все результаты,

fibs !! n  <-  f (n) 

затем

                  ┌──────┐   ┌──────┐   ┌──────┐
                  │ f(0) │   │ f(1) │   │ f(2) │
fibs  ->  0 : 1 : │  +   │ : │  +   │ : │  +   │ :  .....
                  │ f(1) │   │ f(2) │   │ f(3) │
                  └──────┘   └──────┘   └──────┘

                  ┌────────────────────────────────────────┐
                  │ f(0)   :   f(1)   :   f(2)   :  .....  │ 
                  └────────────────────────────────────────┘
      ->  0 : 1 :               +
                  ┌────────────────────────────────────────┐
                  │ f(1)   :   f(2)   :   f(3)   :  .....  │
                  └────────────────────────────────────────┘

Это кодируется как:

fibn n = fibs !! n
    where
    fibs = 0 : 1 : map f [2..]
    f n = fibs !! (n-1) + fibs !! (n-2)

Или даже как

GHCi> let fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
GHCi> take 10 fibs
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

zipWith создает список, применяя данную двоичную функцию к соответствующим элементам из двух перечисленных ему списков, поэтому zipWith (+) [x1, x2, ...] [y1, y2, ...] равно [x1 + y1, x2 + y2, ...] .

Другой способ написания fibs это с scanl функции :

GHCi> let fibs = 0 : scanl (+) 1 fibs
GHCi> take 10 fibs
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

scanl создает список частичных результатов, которые foldl бы foldl , работая слева направо по списку ввода. То есть scanl f z0 [x1, x2, ...] равно [z0, z1, z2, ...] where z1 = f z0 x1; z2 = f z1 x2; ...

Благодаря ленивой оценке обе функции определяют бесконечные списки, не вычисляя их полностью. То есть, мы можем написать функцию fib , извлекая n-й элемент неограниченной последовательности Фибоначчи:

GHCi> let fib n = fibs !! n  -- (!!) being the list subscript operator
-- or in point-free style:
GHCi> let fib = (fibs !!)
GHCi> fib 9
34

Начиная

Онлайн REPL

Самый простой способ начать писать Haskell - это, вероятно, переход на веб-сайт Haskell или Try Haskell и использование онлайн-REPL (read-eval-print-loop) на главной странице. Онлайновый REPL поддерживает большинство базовых функций и даже некоторых IO. Существует также базовый учебник доступен , который может быть запущен, набрав команду help . Идеальный инструмент, чтобы начать изучать основы Haskell и попробовать некоторые вещи.

GHC (я)

Для программистов, которые готовы заняться немного больше, есть GHCi , интерактивная среда, которая поставляется вместе с компилятором Glorious / Glasgow Haskell . GHC можно установить отдельно, но это только компилятор. Чтобы иметь возможность устанавливать новые библиотеки, необходимо также установить такие инструменты, как Cabal и Stack . Если вы используете Unix-подобную операционную систему, проще всего установить Stack, используя:

curl -sSL https://get.haskellstack.org/ | sh

Это устанавливает GHC, изолированный от остальной части вашей системы, поэтому его легко удалить. Однако всем командам должен предшествовать stack . Еще один простой подход - установить платформу Haskell . Платформа существует в двух вариантах:

  1. Минимальное распределение содержит только GHC (для компиляции) и Cabal / Stack (для установки и сборки пакетов)
  2. Полный дистрибутив дополнительно содержит инструменты для разработки проектов, профилирования и анализа охвата. Также включен дополнительный набор широко используемых пакетов.

Эти платформы можно установить, загрузив установщик и следуя инструкциям или используя диспетчер пакетов вашего дистрибутива (обратите внимание, что эта версия не гарантируется как актуальная):

  • Ubuntu, Debian, Mint:

    sudo apt-get install haskell-platform
    
  • Fedora:

    sudo dnf install haskell-platform
    
  • Красная шляпа:

    sudo yum install haskell-platform
    
  • Arch Linux:

    sudo pacman -S ghc cabal-install haskell-haddock-api \
                   haskell-haddock-library happy alex
    
  • Gentoo:

    sudo layman -a haskell
    sudo emerge haskell-platform
    
  • OSX с Homebrew:

    brew cask install haskell-platform
    
  • OSX с MacPorts:

    sudo port install haskell-platform
    

После установки должно быть возможно запустить GHCi , вызвав команду ghci любом месте терминала. Если установка прошла успешно, консоль должна выглядеть примерно так:

me@notebook:~$ ghci
GHCi, version 6.12.1: http://www.haskell.org/ghc/  :? for help
Prelude> 

возможно, с дополнительной информацией о том, какие библиотеки были загружены перед Prelude> . Теперь консоль стала Haskell REPL, и вы можете выполнить код Haskell, как в онлайн-REPL. Чтобы выйти из этой интерактивной среды, можно ввести :q или :quit . Для получения дополнительной информации о том , что команды доступны в GHCi, типа :? как показано на начальном экране.

Поскольку писать одни и те же вещи снова и снова на одной строке не всегда так практически, может быть хорошей идеей написать код Haskell в файлах. Обычно эти файлы имеют .hs для расширения и могут быть загружены в REPL с помощью :l или :load .

Как упоминалось ранее, GHCi является частью GHC , который на самом деле является компилятором. Этот компилятор может быть использован для преобразования файла .hs с кодом Haskell в запущенную программу. Поскольку файл .hs может содержать множество функций, в файле должна быть определена main функция. Это будет отправной точкой для программы. Файл test.hs можно скомпилировать с помощью команды

ghc test.hs

это создаст объектные файлы и исполняемый файл, если ошибок не было, и main функция была определена правильно.

Дополнительные инструменты

  1. Это уже упоминалось ранее как диспетчер пакетов, но стек может быть полезным инструментом для разработки Haskell совершенно по-разному. После установки он способен

    • установка (несколько версий) GHC
    • создание проекта и строительные леса
    • управление зависимостями
    • проекты по строительству и тестированию
    • бенчмаркинг
  2. IHaskell - это ядро haskell для IPython и позволяет комбинировать (runnable) код с уценкой и математической нотацией.

Штрихи

Несколько наиболее важных вариантов:

Ниже 100

import Data.List ( (\\) )

ps100 = ((([2..100] \\ [4,6..100]) \\ [6,9..100]) \\ [10,15..100]) \\ [14,21..100]

   -- = (((2:[3,5..100]) \\ [9,15..100]) \\ [25,35..100]) \\ [49,63..100]

   -- = (2:[3,5..100]) \\ ([9,15..100] ++ [25,35..100] ++ [49,63..100])

неограниченный

Сито Эратосфена, использующее пакет данных :

import qualified Data.List.Ordered

ps   = 2 : _Y ((3:) . minus [5,7..] . unionAll . map (\p -> [p*p, p*p+2*p..]))

_Y g = g (_Y g)   -- = g (g (_Y g)) = g (g (g (g (...)))) = g . g . g . g . ...

традиционный

(субоптимальное пробное деление)

ps = sieve [2..]
     where
     sieve (x:xs) = [x] ++ sieve [y | y <- xs, rem y x > 0]

-- = map head ( iterate (\(x:xs) -> filter ((> 0).(`rem` x)) xs) [2..] )

Оптимальное пробное деление

ps = 2 : [n | n <- [3..], all ((> 0).rem n) $ takeWhile ((<= n).(^2)) ps]

-- = 2 : [n | n <- [3..], foldr (\p r-> p*p > n || (rem n p > 0 && r)) True ps]

переходный

От пробного деления до сита Эратосфена:

[n | n <- [2..], []==[i | i <- [2..n-1], j <- [0,i..n], j==n]]

Самый короткий код

nubBy (((>1).).gcd) [2..]          -- i.e., nubBy (\a b -> gcd a b > 1) [2..]

nubBy также из Data.List , например (\\) .

Объявление значений

Мы можем объявить серию выражений в REPL следующим образом:

Prelude> let x = 5
Prelude> let y = 2 * 5 + x
Prelude> let result = y * 10
Prelude> x
5
Prelude> y
15
Prelude> result
150

Чтобы объявить те же значения в файле, мы пишем следующее:

-- demo.hs

module Demo where
-- We declare the name of our module so 
-- it can be imported by name in a project.

x = 5

y = 2 * 5 + x

result = y * 10

Имена модулей капитализируются, в отличие от имен переменных.



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow