Знакомство с haskell. компилятор ghc. интерпретатор ghci. среда разработки. выражения. операторы

Minimal installers

What they are

Minimal installers provide centrally the GHC compiler and the Cabal and Stack tools for installing packages. Some may install further build tools (i.e. for parsing and lexing) as well.

What you get

  • Only the core libraries necessary for each platform are included.
  • Cabal or Stack must be used to download and install packages after installation.

Where to get help

  • For help learning Haskell itself, start with the Documentation page on the Haskell Wiki.
  • If you need help with GHC—the Haskell compiler—there is a comprehensive GHC User Manual.
  • For help using Cabal to download or create additional packages (see ), there is the Cabal User Guide.
  • For help using Stack to download or create packages, see the stack documentation .
  • Finally, you can ask questions of other Haskell users and experts on the #haskell IRC channel on the Freenode IRC network.

Open Source

Visual Studio Code is a full featured IDE with several Haskell extensions available, such as Haskero, Haskelly and Haskell Language Server.

Leksah (for Nix)

Leksah is an IDE for Haskell written in Haskell. Leksah is intended as a practical tool to support the Haskell development process. Leksah uses GTK+ as GUI Toolkit with the gtk2hs binding. It is platform independent and should run on any platform where GTK+, gtk2hs and GHC can be installed.

See Vim

See Emacs.

Not an editor per se, but a barebone IDE that will let you know compilation error any time you save a file. Doesn’t integrate with any editor but works well with any of them + a terminal emulator.

This IDE supports many languages. For Haskell it currently supports project management, syntax highlighting, building (with GHC) & executing within the IDE.

Does anyone use functional programming?

Functional programming languages are used in substantial applications.
For example:

  • Software AG, a major German software company, market an expert system (Natural Expert) which is programmed in a functional language. Their users find it easy to develop their applications in this language, through which they gain access to an underlying database system. It all runs on an IBM mainframe.
  • Ericsson have developed a new functional language, Erlang, to use in their future telephony applications. They have already written 130k-line Erlang applications, and find them very much shorter and faster to develop.
  • Amoco ran an experiment in which they re-coded in Miranda, a lazy functional language, a substantial fraction of their main oil-reservoir simulation code, a critical application. The resulting program was vastly shorter, and its production revealed a number of errors in the existing software. Amoco subsequently transcribed the functional program into C++ with encouraging results.
  • A researcher at the MITRE corporation is using Haskell to prototype his digital signal-processing applications.
  • Researchers at Durham University used Miranda, and later Haskell, in a seven-year project to build LOLITA, a 30,000-line program for natural-language understanding.
  • Query is the query language of the O2 object-oriented database system. O2Query is probably the most sophisticated commercially-available object-oriented database query language and it is a functional language.
  • ICAD Inc market a CAD system for mechanical and aeronautical engineers. The language in which the engineers describe their design is functional, and it uses lazy evaluation extensively to avoid recomputing parts of the design which are not currently visible on the screen. This results in substantial performance improvements.
  • An incestuous example: the Glasgow Haskell compiler is written in Haskell: a 100,000-line application.

Some other examples of Haskell in practice.

Haskell Platform

What it is

The Haskell Platform is a self-contained, all-in-one installer. After download, you will have everything necessary to build Haskell programs against a core set of useful libraries. It comes in both core versions with tools but no libraries outside of GHC core, or full versions, which include a broader set of globally installed libraries.

What you get

  • The Glasgow Haskell Compiler
  • The Cabal build system, which can install new packages, and by default fetches from Hackage, the central Haskell package repository.
  • the Stack tool for developing projects
  • Support for profiling and code coverage analysis
  • 35 core & widely-used packages

The Platform is provided as a single installer, and can be downloaded at the links below.

  • Linux
  • OS X
  • Windows

Where to get help

  • You can find a comprehensive list of what the Platform offers.
  • See the general help mentioned , which covers the usage of GHC, as well as the Cabal and Stack tools.

Подвох с унарным минусом

Как можно было заметить, все обсуждаемые операторы – бинарные. Как там насчёт унарного минуса?

В принципе, в Haskell есть унарный минус, но он не может быть определён средствами языка, как прочие бинарные операторы, и, как следствие, может вести себя несколько неожиданно.

В некоторых случаях, всё работает ожидаемо:

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

Здесь компилятор интерпретирует как бинарный оператор “минус” и сообщает, что не может разрешить приоритет операторов ( и имеют одинаковый приоритет).

В общем и целом, любое однозначное подвыражение, начинающееся со знака считается унарным отрицанием и интерпретируется как вызов функции . Унарный минус – единственный унарный оператор в стандартном Haskell. Ключевое слово здесь – “однозначное”. Чтобы сделать выражение выше однозначным, мы должны взять в скобки:

На практике, поскольку унарный минус – синоним (или, точнее, “синтаксический сахар”) для функции , можно использовать функцию непосредственно:

Операторы сравнения

Оператор Смысл Приоритет Ассоциативность
Равенство 4 Нет
Неравенство 4 Нет
Меньше 4 Нет
Больше 4 Нет
Меньше или равно 4 Нет
Больше или равно 4 Нет

Результатом всех перечисленных операций сравнения является булевское значение. Они могут быть использованы в условии , например

Булевские операторы

Булевские значения можно комбинировать при помощи булевских операторов

Оператор Смысл Приоритет Ассоциативность
Конъюнкция 3 Правая
Дизъюнкция 2 Правая

Кроме того, есть функция , соответствующая инверсии.

Булевские литералы имеют вид и .

Секционирование операторов

Как любая функция в Haskell, оператор может быть частично применён. Например,

создаст функцию одного аргумента, умножающую этот аргумент на три.

Такой синтаксис может показаться на первый взгляд странным, но – это функция двух аргументов, частично применённая (к первому аргументу); это функция одного (т.е. оставшегося второго) аргумента. Мы можем записать эквивалентное λ-выражение как

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

но мы можем опустить с обеих сторон по правилу η-редукции:

Это удобно для коммутативных операторов, однако, как быть в случае некоммутативных операторов, например, ?

Запись функции можно упростить (сократить), воспользовавшись т.н. секционированием операторов.

Любой оператор можно частично применить к первому либо ко второму аргументу, записав соответствующую часть выражения в круглых скобках. Например,

Можно записать следующие соотношения эквивалентности:

где символизирует любой бинарный оператор

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

или более коротко то же самое:

тогда второе соотношение можно так же записать в виде

Единственный оператор, с которым этот синтаксис даёт сбой – оператор вычитания , поскольку интерпретируется как унарное отрицание . Чтобы обойти это ограничение, в стандартной библиотеке объявлена функция :

она ведёт себя так же, как :

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

Полиморфизм

Выше упоминались функции, действующие для разных типов. Например, аргумент
может иметь тип

Чтобы отразить это, тип описывается с использованием
ти́повых переменных:

                                                    curry :: ((a, b) -> c) -> a -> b -> c

Ти́повые переменные, в отличие от самих типов, записываются со строчной буквы. Для
них принято использовать короткие имена, чаще однобуквенные.

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

Таким образом, типы, как и функции, имеют параметры. Это называется параметрическим
полиморфизмом.

Теперь можно посмотреть на типы уже упомянутых функций:

                                                    ghci> :type id
                                                    id :: a -> a
                                                    ghci> :type fst
                                                    fst :: (a, b) -> a
                                                    ghci> :type (.)
                                                    (.) :: (b -> c) -> (a -> b) -> a -> c
                                                    ghci> :type zip
                                                    zip ::  ->  -> 

Libraries

The process for creating a Haskell library is almost identical. The differences
are as follows, for the hypothetical «ltree» library:

Hierarchical source

The source should live under a directory path that fits into the
existing module layout guide.
So we would create the following directory structure, for the module
Data.LTree:

   $ mkdir Data
   $ cat > Data/LTree.hs 
   module Data.LTree where

So our Data.LTree module lives in Data/LTree.hs

The Cabal file

Cabal files for libraries list the publically visible modules, and have
no executable section:

   $ cat > ltree.cabal 
   Name:                ltree
   Version:             0.1
   Description:         Lambda tree implementation
   License:             BSD3
   License-file:        LICENSE
   Author:              Don Stewart
   Maintainer:          dons@cse.unsw.edu.au
   Build-Type:          Simple
   Cabal-Version:       >=1.2
   
   Library
     Build-Depends:     base >= 3 && < 5
     Exposed-modules:   Data.LTree
     ghc-options:       -Wall

We can thus build our library:

   $ cabal configure --prefix=$HOME --user
   $ cabal build    
   Preprocessing library ltree-0.1...
   Building ltree-0.1...
    Compiling Data.LTree       ( Data/LTree.hs, dist/build/Data/LTree.o )
   /usr/bin/ar: creating dist/build/libHSltree-0.1.a

and our library has been created as a object archive. Now install it:

   $ cabal install
   Installing: /home/dons/lib/ltree-0.1/ghc-6.6 & /home/dons/bin ltree-0.1...
   Registering ltree-0.1...
   Reading package info from ".installed-pkg-config" ... done.
   Saving old package config file... done.
   Writing new package config file... done.

And we’re done!
To try it out, first make sure that your working directory is anything but the source directory of your library:

   $ cd ..

And then use your new library from, for example, ghci:

   $ ghci -package ltree
   Prelude> :m + Data.LTree
   Prelude Data.LTree> 

The new library is in scope, and ready to go.

More complex build systems

For larger projects, you may want to store source trees in subdirectories. This can be done simply by creating a directory — for example, «src» — into which you will put your src tree.

To have Cabal find this code, you add the following line to your Cabal
file:

   hs-source-dirs: src

Debugging/help

Like most IDEs, we’ll be providing error messages in a separate pane. We’re
also hoping to provide some «humanization» of error messages to make them less
intimidating (with the full error message still available for those who want to
see it). From each error message, we’ll be providing some options, in
particular jumping to the relevant code and a «more information» link which
will go to an FP Complete Wiki page.

The UI isn’t ironed out, but we’ll provide something along the lines of
hovering over an identifier to get its type, along with links to where the type
is defined or online documentation. This very likely will go beyond simply
linking to the Haddocks: we’ll likely have links to cookbooks explaining common
ways to use different types and functions.

Other IDEs and Editors

The list below is incomplete. Please add to it with whatever you think of. This list should be expanded into sections, as above, with more details, with links to the actual documentation of the described features.

  • Vim — PROS: Free. Works on Windows. Works in terminal. Decent alignment support. Tag-based completion and jumps. Very good syntax highlighting, flymake (via Syntastic), Cabal integration, Hoogle. Documentation for symbol at point CONS: Arcane, difficult for new users. Some complain of bad indentation support.
  • Yi — PROS: Written in Haskell. Works in terminal. CONS: Very immature, lacking features. Problems building generally, especially on Windows.
  • See also Editors

Отображение

Рассмотрим функцию . Эта стандартная функция используется для отображения (англ. mapping) функции на элементы списка. Пусть вас не смущает такой термин: отображение функции на элемент фактически означает её применение к этому элементу.

Вот объявление функции :

Вот опять эти маленькие буквы! Помните, я обещал рассказать о них? Рассказываю: малой буквой принято именовать полиморфный (англ. polymorphic) тип. Полиморфизм — это многообразность, многоформенность. В данном случае речь идёт не об указании конкретного типа, а о «типовой заглушке». Мы говорим: «Функция применяется к функции из какого-то типа в какой-то тип и к списку типа , а результат её работы — это другой список типа ». Типовой заглушкой я назвал их потому, что на их место встают конкретные типы, что делает функцию очень гибкой. Например:

Результатом работы этой программы будет строка:

Функция применяется к двум аргументам: к функции и к строке . Функция из стандартного модуля переводит символ типа в верхний регистр:

Вот её объявление:

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

Ага, уже теплее! Мы сделали два новых открытия: во-первых, заглушки и могут быть заняты одним и тем же конкретным типом, а во-вторых, сигнатура позволяет нам тут же понять остальные типы. Подставим их:

А теперь вспомним о природе типа :

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

Так, на первом шаге функция будет применена к элементу , на втором — к элементу , и так далее до последнего элемента . Когда функция бежит по этому списку, результат применения функции к его элементам служит элементами для второго списка, который и будет в конечном итоге возвращён. Так, результатом первого шага будет элемент , результатом второго — элемент , а результатом последнего — элемент . Схема такова:

Вот и получается:

Работа функции выглядит как изменение списка, однако, в виду неизменности последнего, в действительности формируется новый список. Что самое интересное, функция пребывает в полном неведении о том, что ею в конечном итоге изменяют регистр целой строки, она знает лишь об отдельных символах этой строки. То есть функция, являющаяся аргументом функции , ничего не знает о функции , и это очень хорошо! Чем меньше функции знают друг о друге, тем проще и надёжнее использовать их друг с другом.

Рассмотрим другой пример, когда типовые заглушки и замещаются разными типами:

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

Уже знакомая нам стандартная функция переводит свой единственный аргумент в строковый вид:

В данном случае, раз уж мы работаем с числами типа , тип функции такой:

Подставим в сигнатуру функции :

Именно так, как у нас и есть:

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

Разумеется, в качестве аргумента функции мы можем использовать и наши собственные функции:

Результат работы:

Мы передали функции нашу собственную ЛФ, умножающую свой единственный аргумент на

Обратите внимание, мы вновь использовали краткую форму определения функции , опустив имя её аргумента. Раскроем подробнее:

Вы спросите, как же вышло, что оператор применения расположен между двумя аргументами функции ? Разве он не предназначен для применения функции к единственному аргументу? Совершенно верно. Пришло время открыть ещё один секрет Haskell.

Для любопытных

В разделе про диапазоны для списка мы оперировали значениями типа , и . Возникает вопрос: а можно ли использовать значения каких-нибудь других типов? Отвечаю: можно, но с оговоркой. Попробуем проделать это со строкой:

При попытке скомпилировать такой код увидим ошибку:

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

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

Theoretical Foundations

A lot of related work was done by Rittri and Runciman in the late 80’s. Since then Di Cosmo has produced a book on type isomorphisms, which is also related. Unfortunately the implementations that accompanied the earlier works were for functional languages that have since become less popular, and to my knowledge no existing functional programming language has a tool such as Hoogle.

I have given two presentations on type searching in Hoogle:

Similar Tools

I didn’t know of any similar tools before starting development, and no other tool has really influenced this tool (except the first on this list). The follow are provided for comparison.

Stack

What it is

Stack is a cross-platform build tool for Haskell that handles management of the toolchain (including the GHC compiler and MSYS2 on Windows), building and registering libraries, and more.

What you get

  • Once downloaded, it has the capacity to download and install GHC and other core tools.
  • Project development is isolated within sandboxes, including automatic download of the right version of GHC for a given project.
  • It manages all Haskell-related dependencies, ensuring reproducible builds.
  • It fetches from a curated repository of over a thousand packages by default, known to be mutually compatible.
  • It can optionally use Docker to produce standalone deployments.

How to get it

The install and upgrade page describes how to download Stack on various platforms, although the main three are repeated here:

Instructions for other Linux distributions, including Debian, Fedora, Red Hat, Nix OS, and Arch Linux, are also available.

Where to get help

For help with Haskell and GHC in general, see the links mentioned . For Stack itself there are also the following resources:

  • The offers a general overview, and help with installation.
  • There is an in-depth guide to using Stack.
  • Getting started with Stack introduces how to build new projects using Stack.
  • You may post issues and feature requests on its GitHub issue tracker.
  • There is a mailing list for Stack
  • There is a dedicated #haskell-stack IRC channel on the Freenode IRC network.
  • The StackOverflow haskell-stack tag has many stack-specific questions and answers.

What is Haskell?

Haskell is a modern, standard, non-strict, purely-functional
programming language. It provides all the features sketched above,
including polymorphic typing, lazy evaluation and higher-order
functions. It also has an innovative type system which supports a
systematic form of overloading and a module system.

It is specifically designed to handle a wide range of applications,
from numerical through to symbolic. To this end, Haskell has an
expressive syntax, and a rich variety of built-in data types,
including arbitrary-precision integers and rationals, as well as the
more conventional integer, floating-point and boolean types.

See also the History of Haskell.

Features

Statically typed

Every expression in Haskell has a type which is determined at compile time. All the types composed together by function application have to match up. If they don’t, the program will be rejected by the compiler. Types become not only a form of guarantee, but a language for expressing the construction of programs.

All Haskell values have a type:

char = 'a'    :: Char
int = 123     :: Int
fun = isDigit :: Char -> Bool

You have to pass the right type of values to functions, or the compiler will reject the program:

Type error

isDigit 1

You can decode bytes into text:

bytes = Crypto.Hash.SHA1.hash "hello" :: ByteString
text = decodeUtf8 bytes               :: Text

But you cannot decode Text, which is already a vector of Unicode points:

Type error

doubleDecode = decodeUtf8 (decodeUtf8 bytes)

Purely functional

Every function in Haskell is a function in the mathematical sense (i.e., «pure»). Even side-effecting IO operations are but a description of what to do, produced by pure code. There are no statements or instructions, only expressions which cannot mutate variables (local or global) nor access state like time or random numbers.

The following function takes an integer and returns an integer. By the type it cannot do any side-effects whatsoever, it cannot mutate any of its arguments.

square :: Int -> Int
square x = x * x

The following string concatenation is okay:

"Hello: " ++ "World!" 

The following string concatenation is a type error:

Type error

"Name: " ++ getLine

Because has type and not , like is. So by the type system you cannot mix and match purity with impurity.

Type inference

You don’t have to explicitly write out every type in a Haskell program. Types will be inferred by unifying every type bidirectionally. However, you can write out types if you choose, or ask the compiler to write them for you for handy documentation.

This example has a type signature for every binding:

main :: IO ()
main = do line :: String <- getLine
          print (parseDigit line)
  where parseDigit :: String -> Maybe Int
        parseDigit ((c :: Char)  _) =
          if isDigit c
             then Just (ord c  ord '0')
             else Nothing

But you can just write:

main = do line <- getLine
          print (parseDigit line)
  where parseDigit (c  _) =
          if isDigit c
             then Just (ord c  ord '0')
             else Nothing

You can also use inference to avoid wasting time explaining what you want:

do ss <- decode ""
   is <- decode ""
   return (zipWith (\s i -> s ++ " " ++ show (i + 5)) ss is)
 => Just "Hello! 6","World! 7"

Types give a parser specification for free, the following input is not accepted:

do ss <- decode ""
   is <- decode ""
   return (zipWith (\s i -> s ++ " " ++ show (i + 5)) ss is)
 => Nothing

Concurrent

Haskell lends itself well to concurrent programming due to its explicit handling of effects. Its flagship compiler, GHC, comes with a high-performance parallel garbage collector and light-weight concurrency library containing a number of useful concurrency primitives and abstractions.

Easily launch threads and communicate with the standard library:

main = do
  done <- newEmptyMVar
  forkIO (do putStrLn "I'm one thread!"
             putMVar done "Done!")
  second <- forkIO (do threadDelay 100000
                       putStrLn "I'm another thread!")
  killThread second
  msg <- takeMVar done
  putStrLn msg

Use an asynchronous API for threads:

do a1 <- async (getURL url1)
  a2 <- async (getURL url2)
  page1 <- wait a1
  page2 <- wait a2
  ...

Atomic threading with software transactional memory:

transfer :: Account -> Account -> Int -> IO ()
transfer from to amount =
  atomically (do deposit to amount
                 withdraw from amount)

Atomic transactions must be repeatable, so arbitrary IO is disabled in the type system:

Type error

main = atomically (putStrLn "Hello!")

Заключение

Итак, мы узнали о строгой статической типизации и основных типах и классах Haskell.
Преимущества и особенности системы типов вы еще оцените при дальнейшем изучении и
применении языка.

Рассмотренные классы образуют иерархию (рисунок 3).

Рисунок 3. Иерархия классов

Интересные ресурсы для самостоятельного изучения:

  • Библиотека Prelude: http://haskell.org/ghc/docs/latest/html/libraries/base/Prelude.html
    (EN) (Для начала изучите набор функций и их типы.)
  • Hoogle – Web-приложение для поиска функций в стандартных библиотеках, в том
    числе по сигнатуре: http://haskell.org/hoogle/.
  • Команда выводит информацию о данном имени. Попробуйте
    применить ее к функциям, типам, методам и классам.

В следующей
статье мы рассмотрим определение функций и начнем писать первые программы на
Haskell.

Прилагаемые файлы:fph02.lhs

  • Haskell 98 Language and
    Libraries. The Revised Report. 2002. (EN)
  • B. O’Sullivan, D. Stewart,
    J. Goerzen. Real World Haskell. (EN)
  • B.C. Pierce. Types
    and Programming Languages. MIT Press, 2002. (EN)
  • P. Hudak, J. Peterson, J. Fasel.
    A Gentle Introduction to Haskell 98. 2000. (EN)
  • G. Hutton. Programming in
    Haskell. Cambridge University Press, 2007. (EN)
Оцените статью
Рейтинг автора
5
Материал подготовил
Андрей Измаилов
Наш эксперт
Написано статей
116
Добавить комментарий