Показаны сообщения с ярлыком Haskell. Показать все сообщения
Показаны сообщения с ярлыком Haskell. Показать все сообщения

Перечисления в Haskell

Определение класса типов Enum:
class Enum a where
 -- функции движения по перечислительному типу
 succ, pred :: a -> a
 
 -- всем перечислениям может быть сопоставлен некоторый номер
 toEnum :: Int -> a
 fromEnum :: a -> Int

Итерирование по типу:
Prelude> succ 4
5
Prelude> pred 4
3
Prelude> pred 'c'
'b'
Prelude> succ 'z'
'{'
Prelude> fromEnum 'z'
122
Prelude> toEnum 122 :: Char
'z'


Наследование в Haskell

Механизм расширения классов типов слегка похож на наследование в объектно-ориентированных языках. Речь идет не о наследовании реализации, а о наследовании интерфейса, поскольку классы типов являются некоторым эквивалентом интерфейсов. 
{- 
Класс типов Ord параметризован типовым параметром a и имеет в рамках класса типов контекст.
Класс типов Ord расширяет класс типов Eq.
-}
class (Eq a) => Ord a where
 (<), (<=), (>=), (>) :: a -> a -> Bool -- сигнатуры функций сравнения
 max, min :: a -> a -> a
 compare :: a -> a -> Ordering -- более тщательное сравнение двух значений
{- Minimal complete definition: either compare or <= -}
Тип Ordering устроен довольно просто.
Prelude> :i Ordering
data Ordering = LT | EQ | GT  -- Defined in ‘GHC.Types’
instance Bounded Ordering -- Defined in ‘GHC.Enum’
instance Enum Ordering -- Defined in ‘GHC.Enum’
instance Eq Ordering -- Defined in ‘GHC.Classes’
instance Ord Ordering -- Defined in ‘GHC.Classes’
instance Read Ordering -- Defined in ‘GHC.Read’
instance Show Ordering -- Defined in ‘GHC.Show’
instance Monoid Ordering -- Defined in ‘GHC.Base’
В нем определены три конструктора. В типе Bool определены два конструктора True и False. А в типе Ordering определены три конструктора: LT, EQ и GT. Таким образом это перечисление, которое содержит ровно три элемента.

Полиморфизм в Haskell

Про функцию говорят, что она обладает полиморфным поведением, если она может быть вызвана на значениях разных типов. Например, оператор сложения эта функция, которая может быть вызвана на значениях типа Int, возвращая результат типа Int; она также может быть вызвана на значениях типа Double, возвращая результат типа Double. Таким образом сложение это полиморфный оператор. Выделяют два типа полиморфных функций:
  • параметрический полиморфизм - характеризуется тем, что код функций одинаков для всех типов на которых мы можем вызывать эту функцию;
  • специальный полиморфизм - предполагает, что для каждого типа, для которых вызов этой функции допустим имеется своя собственная реализация.
Пример с оператором сложения это как раз пример специального полиморфизма потому что на низком уровне сложение значений целочисленных и сложение значений с плавающей точкой это разные функции и код.

Ввод-вывод в Haskell


Отправка сообщений об ошибке в диагностический поток

Функция error принимает в качестве аргумента строку и выводит эту строку в диагностический поток. 
factorial'' 0 = 1
factorial'' n = if n < 0 then error "arg must be >= 0" else n * factorial'' (n - 1)

Функция undefined всегда прерывает выполнение программы выводя в диагностический поток стандартное сообщение об ошибке.
Prelude> undefined
*** Exception: Prelude.undefined
С точки статической семантики Haskell не завершающаяся рекурсия и прерывание программы из-за ошибки это одно и то же. Считается, что в этом случае возвращаемым значением программы служит специальный символ, который обозначается символом ⊥ и называется по-английски "bottom", но на русский его иногда переводят как "основание". Это значение является элементом любого типа в Haskell. И функция undefined как раз является способом использовать это значение. Функция undefined подходит в качестве выражения любого типа. А это значит, что она может использоваться в любом месте программы. При программировании на Haskell принято использовать значение undefined для того чтобы маркировать еще не написанные части программы. Проверка типов гарантировано пройдет. Иногда функцию undefined используют для того чтобы поместить её в такое место до которого исполнение гарантировано не дойдет. В противном случае используют функцию error, а не undefined, если исполнение программы дойдет до этой точки, то лучше пользователю сообщить содержательную информацию о том что же за ошибка произошла.

Кортежи и списки в Haskell

Кортежи

Кортежем называется упорядоченный набор элементов фиксированной длины. При этом тип элементов может быть произвольный. Первый элемент кортежа может относиться к одному типу, второй к другому, третий элемент к третьему или опять к первому типу.

Структура кода на Haskell

Исходный код на языке Haskell сохраняется в текстовых файлах с расширением .hs. Допустимо использование Unicode в коде.

Отступы 

В Haskell отступы содержательны, поэтому табуляция в Haskell трактуется как 8 пробелов. Отступы задают так называемый двухмерный синтаксис и распознаются компилятором. Отступы измеряются в символах пробела. Увеличение отступа безопасно, а уменьшение отступа может привести к проблемам. Увеличение отступа говорит о том, что мы продолжаем текущее объявление, которое началось на предыдущей строке. Уменьшение отступа может приводить к проблемам, если мы уменьшаем наш отступ настолько, что его текущий отступ меньше, чем тот отступ с которого это объявление начиналось.
module Roots where

roots :: Double 
      -> Double 
      -> Double 
      -> (Double, Double)
roots a b c =
 (
    (-b - sqrt (b ^ 2 - 4 * a * c)) / (2 * a)
 ,
    (-b + sqrt (b ^ 2 - 4 * a * c)) / (2 * a)
 )


Импорт модулей

module DemoImport where

import Data.Char

test = isDigit '7'

Импортировать модуль можно и напрямую из интерпретатора.
Prelude> import Data.Complex
Prelude Data.Complex> 
Для того чтобы выяснить в точности как модуль называется и какая функция в каком модуле присутствует можно воспользоваться стандартной справочной системой Hoogle.

Базовые типы языка программирования Haskell

Haskell - типизированный язык со строгой статической системой типов. Термин "строгий" означает, что отсутствует неявное приведение типов. Термин "статический" означает, что проверка типов происходит не во время исполнения, а во время компиляции. Haskell умеет выводить тип практически любого выражения самостоятельно. Если выражение правильно построено, то тогда его тип выводится, а если в выражении содержится какая-то ошибка, то тогда система вывода типов Haskell возвращает сообщение об ошибке. 

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

В Haskell имеется стандартный набор числовых типов:
  • Int - для целых ограниченного размера;
  • Integer - для целых произвольного размера;
  • Float - числа с плавающей точкой одинарной точности;
  • Double - числа с плавающей точкой двойной точности.
Все эти типы являются представителями одного класса типов - Num. Механизм классов типов используется для того чтобы задать для всех чисел общий интерфейс. Например, чтобы числа относящиеся к разным типам например, Double и Int можно было складывать с помощью одного и того же оператора сложения. 

Операторы в языке программирования Haskell

Операторы в отличие от функций вызываются в инфиксном стиле.
Prelude> 6 + 7
13

Однако это отличие можно убрать. Функции можно вызывать в операторном стиле, а операторы можно вызывать в функциональном стиле.
Prelude> 6 `max` 7
7 Prelude> (+) 6 7 13

Все операторы в Haskell бинарные, т.е. принимают ровно два аргумента. За одним исключением - унарный префиксный минус. Есть и бинарный минус. Из-за того, что в языке присутствует два этих оператора могут возникать коллизии и неудобства. Когда отрицательное число используется в качестве аргумента функции, то его нужно заключать в круглые скобки.
Prelude> - 7
-7
Prelude> (-) 5 3
2
Prelude> max (-5) 5
5

Конструкции и выражения в Haskell


Условное выражение

Prelude> let f x = if x > 0 then 1 else (-1)
Prelude> f 5
1
Prelude> f (-5)
-1

В Haskell отрицательные числа получаются с помощью знака - перед соответствующим литералом и во многих случаях их следует заключать в скобки. Если бы мы использовали синтаксис без скобок, то получили бы выражение f - 5.

Обе ветви then и else должны присутствовать. В ветвях then и else должны стоять выражения одного и того же типа, иначе GHCi вернет сообщение об ошибке, потому что функция определена неверно.

Условное выражение можно использовать в построении более сложных выражений. 
Prelude> let g x = (if x > 0 then 1 else (-1)) + 3
Prelude> g 5
4
Prelude> g (-7)
2

Функция, которая возвращает 1, если ей передано положительное число, (-1), если отрицательное, и 0 в случае, когда передан 0:
sign x = if x > 0 then 1 else if x == 0 then 0 else (-1)


Сопоставление с образцом

Использование условного выражения if then else не всегда удобно. В Haskell существует гораздо более мощный инструмент решающий ту же задачу. Это так называемое сопоставление с образцом. Основная идея заключается в том что мы определяем функцию не с помощью одного уравнения, а с помощью нескольких уравнений. Каждое из этих уравнений описывает одну из возможных ветвей программы.
factorial' 0 = 1
factorial' n = n * factorial' (n - 1)
Если нужно определить в интерпретаторе функцию из нескольких уравнений, то следует писать, например, так:
Prelude> let {factorial' 0 = 1; factorial' n = n * factorial' (n - 1)}
Prelude> factorial' 0
1


Следующие функции, могут привести к расходимости (мы называем выражение расходящимся, если вычисление его значения приводит к бесконечному циклу или аварийному завершению):
grault x 0 = x
grault x y = x
garply = grault 'q'
В определении функции рассматриваются два случая. Несмотря на то, что мы видим, что итоговый результат в обоих случаях одинаковый, программа, тем не менее, должна выбрать, по какому из двух путей пойдет вычисление. Соответственно, чтобы осуществить этот выбор, необходимо вычислить второй аргумент.


Охранные выражения

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

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

factorial''' 0 = 1
factorial''' n | n < 0 = error "arg must be >= 0" 
               | n > 0 = n * factorial''' (n - 1)
      
factorial4 :: Integer -> Integer
factorial4 n | n == 0    = 1
             | n > 0     = n * factorial4 (n - 1)
             | otherwise = error "arg must be >= 0"

Идентификатор otherwise. Это не ключевое слово, а константа, определенная для удобства в стандартной библиотеке:
Prelude> otherwise
True


Выражение let in

Конструкция let in позволяет ликвидировать повторяющиеся выражения.
Prelude> let x = True in (True,x)
(True,True)
Prelude> (let x = 'w' in [x,'o',x]) ++ "!"
"wow!"
Первая часть после ключевого слова let описывает локальное связывание. Здесь некоторое выражение связывается с некоторой переменной. Эта переменная может использоваться во второй части выражения let in, в части in. Значение всего выражения определяется частью in, а часть let носит вспомогательную роль.
roots' a b c =
  let d = sqrt (b ^ 2 - 4 * a * c) in
  ((-b - d) / (2 * a), (-b + d) / (2 * a))

Выражение let in может задавать сразу несколько связываний.
roots'' a b c =
  let {d = sqrt (b ^ 2 - 4 * a * c); x1 = (-b - d) / (2 * a); x2 = (-b + d) / (2 * a)}
  in (x1, x2)

Можно организовывать связываемые выражения с помощью отступов.
roots''' a b c =
  let
    x1 = (-b - d) / aTwice
    x2 = (-b + d) / aTwice
    d = sqrt $ b ^ 2 - 4 * a * c
    aTwice = 2 * a
  in (x1, x2)
Локальные связывания должны иметь один и тот же отступ.

С помощью выражения let in можно также определять локальные функции.
factorial6 n | n >= 0    = let
                   helper acc 0 = acc
                   helper acc n = helper (acc * n) (n - 1)
               in helper 1 n
             | otherwise = error "arg must be >= 0"

Возможно также не только локальное определение функции, но и локальное связывание образцов.
rootsDiff a b c = let
 (x1,x2) = roots a b c
 in x2 - x1


Попытка вычислить следующее выражение приводит к бесконечному циклу.
quux = let x = x in x



Конструкция where

Еще одна конструкция позволяющая обеспечивать локальные связывания. Конструкция where очень похожа на выражение let in, но устроена ровно наоборот. В выражении let in часть в которой находится локальное связывание стоит сначала, а потом фигурирует выражение в котором это локальное связывание используется. В конструкции where сначала идет выражение в котором используются какие-то переменные, а потом внутри выражение where происходит локальное связывание.
roots'''' a b c = (x1, x2) where
    x1 = (-b - d) / aTwice
    x2 = (-b + d) / aTwice
    d = sqrt $ b ^ 2 - 4 * a * c
    aTwice = 2 * a

Между конструкцией where и выражением let in есть одно отличие. Конструкция let in является выражением.
Prelude> let x = 2 in x^2
4
Prelude> (let x = 2 in x^2)^2
16
Конструкция where выражением не является. Конструкция where может использоваться только в определении функции и только на определенном месте, в качестве глобальной части тела этой функции. Это сделано для того чтобы использовать функцию where в некоторых контекстах в которых выражение let in использовать нельзя. Мы можем писать предложение where общее сразу для нескольких уравнений с охранными выражениями.
factorial7 :: Integer -> Integer
factorial7 n | n >= 0    = helper 1 n
             | otherwise = error "arg must be >= 0"
 where
  helper acc 0 = acc
  helper acc n = helper (acc * n) (n - 1)

Функции в Haskell

Синтаксис применения функции

Синтаксис двух последовательных идентификаторов означает применение функции foo к своему аргументу bar:
foo bar

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

Скобки используются для группировки аргументов:
acos (cos pi)

Функция нескольких аргументов:
max 5 42

Операция применения функции ассоциативна влево:
(max 5) 42
Функция max последовательно применяется к двум аргументам.
Компилятор понимает конструкцию f x y как (f x) y, а не наоборот f (x y).

Выражение (max 5) это так называемое частичное применение функции. В общем виде его можно сформулировать следующим образом: если у нас имеется функция N переменных и мы смотрим на неё как на функцию N переменных, то мы можем взглянуть на неё с другой стороны и сказать, что это функция одной переменной возвращающая нам функцию N - 1 переменной.
3 + sin 42
3 + (max 5) 42

Glasgow Haskell Compiler

Средой разработки для Haskell служит Haskell Platform - набор инструментов и библиотек, которые упрощают разработку на этом языке. В состав Haskell Platform входит компилятор Glasgow Haskell Compiler.

GHCi может загружать как откомпилированные для текущей платформы модули (и именно так происходит со стандартными библиотечными модулями), так и работать в режиме интерпретатора (так по умолчанию происходит с исходным кодом, который перед загрузкой преобразуется в байт-код). Буковка i в названии обозначает interactive, но GHCi часто называют интерпретатором.

Библиотека Prelude всегда загружается при запуске GHCi и содержит внутри себя определения наиболее часто используемых стандартных функций, операторов и объектов языка Haskell. 
Prelude> 33 + 3 * 3
42
Prelude> pi
3.141592653589793
Prelude> "ABC" ++ "DE"
"ABCDE"

Приглашение командной строки может быть изменено с помощью команды:
:set prompt "GHCi >"
Подобное переопределение скрывает имя загруженного модуля и не рекомендуется.

История языка программирования Haskell

Язык Haskell назван в честь американского логика и математика Хаскелла Брукса Карри. (Написание Хаскель является в среде русскоязычных пользователей языка общепринятым, хотя и не очень официальным.)

Первая реализация была выпущена в 1990-м году. Текущий стандарт: Haskell 2010.

О языке Haskell

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

Язык Haskell — чистый функциональный язык программирования с «ленивой» семантикой исполнения и полиморфной статической типизацией.

Справку по функциям стандартной библиотеки (и не только) можно получить с помощью онлайн системы Hoogle.