haskel

Описание: Разработка и отладка приложений. Упор на 3D-графику.

dyvniy M
Автор темы, Администратор
Администратор
Аватара
dyvniy M
Автор темы, Администратор
Администратор
Возраст: 41
Репутация: 1
Лояльность: 1
Сообщения: 3579
Зарегистрирован: Ср, 10 октября 2012
С нами: 11 лет 6 месяцев
Профессия: Программист
Откуда: Россия, Москва
ICQ Сайт Skype ВКонтакте

#1 dyvniy » Пн, 30 марта 2015, 12:36:49

https://lurkmore.co/Haskell
http://habrahabr.ru/post/152889/
http://learnyouahaskell.com/
http://www.realworldhaskell.org/

Установка haskell
http://eax.me/haskell-install/

Зачем нужны функторы и монады
http://habrahabr.ru/post/212955/
В Хаскеле все данные неизменяемые, поэтому функции инкремента, равно как и циклов (и много чего ещё) нет.
Спойлер
ачем нужны все эти функторы и монады?
Функциональное программирование*, Haskell*, Программирование*
Очень часто в статьях про Хаскель сплошь и рядом встречаются функторы и особенно монады.
Так часто, что порой не реже встречаются комментарии «сколько можно про какие-то новые монады» и «пишите о чём-либо полезном».
На мой взгляд это свидетельствует о том, что люди порой не понимают зачем же нужны все эти функторы и монады.

Это статья попытка показать, что сила функциональных языков и в первую очередь Хаскеля — это в том числе и силе функторов и монад.


Чистые данные

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

Термин «чистый» перенагружено в программировании.
Например, фразу «Руби — чисто объектный язык» мы понимаем как «Руби — язык, где всё — объекты».
А вот фразу «Хаскель — это чистый функциональный язык» следует понимать как «Хаскель — функциональный язык без побочных эффектов».
В этой статье мы будем использовать термин «чистый» ещё в одном контексте.
«Чистые данные» — это данные, которые я хочу получить.
В основном примитивные типы — это числа, строки, иногда более сложные, например — картинка или несколько значений.
Соответственно, «грязные данные» — это данные, которые содержат, помимо того что я хочу, дополнительную информацию.

Вот сочиняем программку:
module Main where

foo = undefined -- функция неопределена

main :: IO ()
main = do
putStrLn "Input a: "
a <- getLine -- получаем 1 линию на вход
putStrLn "Input b: "
b <- getLine -- получаем 2 линию на вход
print (foo a b) -- печатаем вычисленное значение

Программа проста до безобразия — мы просим ввести пользователю 2 строчки, а после выводим результат вычисления.
Видим, что наша функция foo ещё не определена (она всегда вызывает падение программы), хотя Хаскель уже может откомпилировать наш код.

Теперь перепишем более детально нашу функцию, используя только «чистые» данные:
pure2args :: Int -> Int -> Int
pure2args = (+) -- функция сложения

pure1arg :: Int -> Int
pure1arg = (+ 1) -- функция добавления единицы

unsafe2args :: Int -> Int -> Int
unsafe2args = div -- фунция целочисленного деления

foo :: String -> String -> Int
foo a b = unsafe2args extraUnsafeE unsafeC -- фарш из сложения и деления
where
unsafeA :: Int
unsafeA = read a -- функция перевода строк в числа

unsafeB :: Int
unsafeB = read b -- функция перевода строк в числа

unsafeC :: Int
unsafeC = pure1arg unsafeB

reallyUnsafeD :: Int
reallyUnsafeD = pure2args unsafeA unsafeC

extraUnsafeE :: Int
extraUnsafeE = unsafe2args unsafeA reallyUnsafeD

Как видим, тут тоже понятно, функция foo по сути является [неважно какой] смесью целочисленного деления и сумм.
Большинство функциональных языков программирования позволяют легко и просто создавать функции, основанные на чистых данных.

Казалось бы всё замечательно — простая и элегантная программка. Но нетушки!
Результат функции намного сложнее, чем нам того хотелось бы.
Как мы понимаем, на 0 делить нельзя, да и пользователь может ввести не числа, а левые строки, и при преобразовании строк в числа может выкинуть ошибку. Наш код получился небезопасным.
Императивный подход к разрешения подобных проблем делится на 2 группы: или использовать ветвления, или использовать исключения. Зачастую оба подхода комбинируется.
Эти подходы настолько эффективны, что в основном используются и в функциональных языках.
Скажем прямо — в Хаскеле присутствуют исключения, однако они недоразвиты, нуждаются в реформировании, не лучшим образом отлавливаются. Да и самое важное — в большинстве случаев они просто не нужны.
Но нем не менее — можно.
Поэтому попытаемся переписать наш код используя ветвления и исключения.
module Main where

import Control.Exception (IOException, catch)

printError :: IOException -> IO ()
printError = print


pure2args :: Int -> Int -> Int
pure2args = (+)

pure1arg :: Int -> Int
pure1arg = (+ 1)

unsafe2args :: Int -> Int -> Int
unsafe2args a b = if b == 0
then error "Error 'unsafe2args' : wrong 2nd argument = 0" --unsafe source
else div a b

foo :: String -> String -> Int
foo a b = unsafe2args extraUnsafeE unsafeC
where

unsafeA :: Int
unsafeA = read a --unsafe source

unsafeB :: Int
unsafeB = read b --unsafe source

unsafeC :: Int
unsafeC = pure1arg unsafeB

reallyUnsafeD :: Int
reallyUnsafeD = pure2args unsafeA unsafeC

extraUnsafeE :: Int
extraUnsafeE = unsafe2args unsafeA reallyUnsafeD

main :: IO ()
main = do
putStrLn "Input a: "
a <- getLine
putStrLn "Input b: "
b <- getLine
catch (print (foo a b)) printError --пробуем вычислить значение или напечатать исключение

Грязные данные

В Хаскеле (да и многих функциональных языках) есть достойный ответ на подобные задачи.
Основная сила заключена в Алгебраических Типах Данных.

Если мы рассматриваем вышеприведённый пример, видно, что наши функции могут падать.
Решение — пользоваться нулабельными типами данных.
В ML языках и Scala такой тип называется Option, в Хаскеле он называется Maybe a.
import Prelude hiding (Maybe) -- этот тип уже описан в стандартной библиотеке. Мы попробуем создать его с нуля

data Maybe a = Nothing | Just a
deriving Show

Мы не обращаем внимание на deriving часть, мы тут просто говорим, что просим компилятор самостоятельно уметь переводить в строку наш тип данных.
А именно,
show Nothing == "Nothing"
show (Just 3) == "Just 3"

Тип данных принимает значение Nothing если у нас нет данных, и Just a, если есть.
Как видим, тип данных — «грязный», так как содержит лишнюю информацию.
Давайте перепишем наши функции более правильно, более безопасно и без исключений.

Прежде всего заменим функции, которые вызывали падение на безопасные аналоги:
maybeResult2args :: Int -> Int -> Maybe Int
maybeResult2args a b = if b == 0
then Nothing --safe
else Just (div a b)


...
maybeA :: Maybe Int
maybeA = readMaybe a --safe

maybeB :: Maybe Int
maybeB = readMaybe b --safe

Теперь эти функции вместо падения дают результат Nothing, если всё в порядке — то Just результат.

Но весь остальной код у нас зависит от этих функций. Нам придётся изменить почти все функции, в том числе и те, которые много раз тестировались.
pure2args :: Int -> Int -> Int
pure2args = (+)

safePure2args :: Maybe Int -> Maybe Int -> Maybe Int
safePure2args a b = case a of
Nothing -> Nothing
Just a' -> case b of
Nothing -> Nothing
Just b' -> Just (pure2args a' b')

pure1arg :: Int -> Int
pure1arg = (+ 1)

safePure1arg :: Maybe Int -> Maybe Int
safePure1arg a = case a of
Nothing -> Nothing
Just a' -> Just (pure1arg a')

maybeResult2args :: Int -> Int -> Maybe Int
maybeResult2args a b = if b == 0
then Nothing
else Just (div a b)

foo :: String -> String -> Maybe Int
foo a b = case maybeE of
Nothing -> Nothing
Just e -> case maybeC of
Nothing -> Nothing
Just c -> maybeResult2args e c
where

maybeA :: Maybe Int
maybeA = readMaybe a

maybeB :: Maybe Int
maybeB = readMaybe b

maybeC :: Maybe Int
maybeC = safePure1arg maybeB

maybeD :: Maybe Int
maybeD = safePure2args maybeA maybeC

maybeE = case maybeA of
Nothing -> Nothing
Just a1 -> case maybeD of
Nothing -> Nothing
Just d -> maybeResult2args a1 d

printMaybe :: Show a => Maybe a -> IO ()
printMaybe Nothing = print "Something Wrong"
printMaybe (Just a) = print a

main :: IO ()
main = do
putStrLn "Input a: "
a <- getLine
putStrLn "Input b: "
b <- getLine
printMaybe (foo a b)

Как видим простая программа превратилась в достаточно монстро-образный код.
Много обёрточных функций, много избыточного кода, много изменено.
Но именно на этом останавливаются многие функциональные языки программирования.
Теперь можно понять почему в тех языках, несмотря на возможность создания множества АТД, АТД не так уж часто используются в коде.

Можно жить с АТД, но без подобной вакханалии? Оказывается можно.

Функторы

На помощь нам в начале приходят функторы.

Функторы — это такие типы данных, для которых существует функция fmap
class Functor f where
fmap :: (a -> b) -> f a -> f b

а так же его инфиксный синоним:
(<$>) :: Functor f => (a -> b) -> f a -> f b
(<$>) = fmap

такая что для всех значений типа данных всегда выполняются следующие условия:

Условие идентичности:
fmap id == id
Условие композиции:
fmap (f . g) == fmap f . fmap g

Где id — функция идентичности
id :: a -> a
id x = x

И (.) — функциональная композиция
(.) :: (b -> c) -> (a -> b) -> a -> c
f . g = \x -> f (g x)

Функтор — это класс типов, где мы создали специальную функцию fmap. Посмотрим на её аргументы — она берёт одну «чистую» функцию a -> b, берём «грязное» функторное значение f a и получаем на выходе функторное значение f b.

Тип данных Maybe является функтором. Создадим инстанс (экземпляр) для типа Maybe, так чтобы не нарушались законы функторов:
instance Functor Maybe where
fmap _ Nothing = Nothing
fmap f (Just a) = Just (f a)

Как нам использовать чистую функцию с функтором Maybe? Очень просто:
safePure1arg :: Maybe Int -> Maybe Int
safePure1arg = fmap pure1arg

Мы тут видим главное — мы не переписывали нашу функцию pure1arg, а значит нам не надо её ещё раз тестировать на баги и ко всему она осталась универсальной и чистой, зато мы с лёгкостью создали её безопасную версию, которая на вход принимает не числа, а нулабельные числа.

Однако, если мы захотим применить функтор, пытаясь переписать safePure2args, мы потерпим фиаско.
Функторы работают только с функциями с единственным функторно-«грязным» аргументом.
Что же делать для функций с несколькими параметрами?

Аппликативные функторы

Тут нам на помощь приходят аппликативные функторы:

Аппликативные функторы — такие функторы, для которых определены 2 функции: pure и (<*>)
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b

Такие, что для них для любых значений одного типа данных всегда выполняются следующие правила:

Условие идентичности:
pure id <*> v == v
Условие композиции:
pure (.) <*> u <*> v <*> w == u <*> (v <*> w)
Условие гомоморфизма:
pure f <*> pure x == pure (f x)
Условие обмена:
u <*> pure y == pure ($ y) <*> u

Основное отличение фунтора от аппликативного фунтора состоит в том, что фунтор протаскивает сквозь функторное значение чистую функцию, в то время как аппликативный фукнтор позволяет нам протаскивать сквозь функторное значение функторную функцию f (a -> b).

Maybe является аппликативным функтором и определяется следующим образом:
instance Applicative Maybe where
pure = Just

Nothing <*> _ = Nothing
_ <*> Nothing = Nothing
(Just f) <*> (Just a) = Just (f a)

Самое время переписать safePure2args.
В основном функцию переписывают, совмещая функторый fmap для первого аргумента, и аппликативное нанизывание остальных аргументов:
safePure2args :: Maybe Int -> Maybe Int -> Maybe Int
safePure2args a b = pure2args <$> a <*> b

Но можно переписать функцию, пользуясь исключительно аппликативными функциями (монадный стиль) — вначале «чистую» функцию делаем чисто-аппликативной, и аппликативно нанизываем аргументы:
safePure2args :: Maybe Int -> Maybe Int -> Maybe Int
safePure2args a b = (pure pure2args) <*> a <*> b

Замечательно!
Может можно заодно переписать функцию maybeE с помощью аппликативных функторов? Увы.

Монады

Давайте обратим внимание на подпись функции maybeResult2args:
maybeResult2args :: Int -> Int -> Maybe Int
Функция берёт на вход «чистые» аргументы, и выдаёт на выходе «грязный» результат.
Так вот, в большинстве своём в реальном программировании, именно такие функции встречаются чаще всего — берут на вход «чистые» аргументы, и на выходе — «грязный» результат.
И когда у нас есть несколько таких функций, вместе совместить их помогают монады.

Монады — это такие типы данных, для которых существует функции return и (>>=)
class Monad m where
return :: a -> m a
(>>=) :: m a -> (a -> m b) -> m b

такие, что выполняются правила для любых значений типа:

Левой идентичности:
return a >>= k == k a
Правой идентичности:
m >>= return == m
Ассоциативности:
m >>= (\x -> k x >>= h) == (m >>= k) >>= h

Для удобства, есть дополнительная функция с обратным порядком аргументов:
(=<<) :: Monad m => (a -> m b) -> m a -> m b
(=<<) = flip (>>=)

Где
flip :: (a -> b -> c) -> b -> a -> c
flip f a b = f b a

Мы понимаем, что тип Maybe является монадой, а значит можно определить его инстанс (экземпляр):
instance Monad Maybe where
return = Just

(Just x) >>= k = k x
Nothing >>= _ = Nothing

Кстати, если мы присмотримся внимательнее к внутреннему содержанию, и подписям, увидим, что:
pure == return
fmap f xs == xs >>= return . f

Пришло время переписать функцию maybeE
maybeE = maybeA >>= (\a1 -> maybeD >>= (maybeResult2args a1))

Да уж, вышло не намного красивее. Это связано с тем, что монады красиво пишутся для одной переменной. К счастью существуют много дополнительных функций.
Можно написать функцию bind2
bind2 :: Monad m => (a -> b -> m c) -> m a -> m b -> m c
bind2 mf mx my = do
x <- mx
y <- my
mf x y

maybeE = bind2 maybeResult2args maybeA maybeD

Или использовать функцию liftM2 и join
liftM2 :: Monad m => (a1 -> a2 -> r) -> m a1 -> m a2 -> m r
join :: Monad m => m (m a) -> m a

maybeE = join $ liftM2 maybeResult2args maybeA maybeD

На крайний случай, можно воспользоваться синтаксическим сахаром для монад, используя do нотацию:
maybeE = do
a1 <- maybeA
d <- maybeD
maybeResult2args a1 d

Различие в применении фунторов и монад

Если мы сведём основные функции к одному виду, то увидим:
(<$>) :: Functor f => (a -> b) -> f a -> f b
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
(=<<) :: Monad f => (a -> f b) -> f a -> f b

Все используются для того, чтобы передавать функциям «грязные» значения, тогда как функции ожидают «чистые» значения на входе.
Фунторы используют «чистую» функцию.
Аппликативные функторы — «чистую» функцию внутри «загрязнения».
Монады используют функции, которые на выходе имеют «грязное» значение.

Программа без рутины

Что ж, наконец, можно полностью и аккуратно переписать всю программу:
module Main where

import Control.Monad
import Control.Applicative
import Text.Read (readMaybe)

bind2 :: Monad m => (a -> b -> m c) -> m a -> m b -> m c
bind2 mf mx my = do
x <- mx
y <- my
mf x y

pure2args :: Int -> Int -> Int
pure2args = (+)

pure1arg :: Int -> Int
pure1arg = (+ 1)

maybeResult2args :: Int -> Int -> Maybe Int
maybeResult2args a b = if b == 0
then Nothing --safe
else Just (div a b)

foo :: String -> String -> Maybe Int
foo a b = bind2 maybeResult2args maybeE maybeC
where

maybeA :: Maybe Int
maybeA = readMaybe a --safe

maybeB :: Maybe Int
maybeB = readMaybe b --safe

maybeC :: Maybe Int
maybeC = fmap pure1arg maybeB

maybeD :: Maybe Int
maybeD = pure2args <$> maybeA <*> maybeC

maybeE :: Maybe Int
maybeE = bind2 maybeResult2args maybeA maybeD

printMaybe :: Show a => Maybe a -> IO ()
printMaybe Nothing = print "Something Wrong"
printMaybe (Just a) = print a

main :: IO ()
main = do
putStrLn "Input a: "
a <- getLine
putStrLn "Input b: "
b <- getLine
printMaybe (foo a b)

Код снова стал прост и понятен!
При этом мы не поступились ни пядью безопасности!
При этом мы почти не изменили код!
При этом чистые функции остались чистыми!
При этом избежали рутины!

Вывод

Можно ли жить в функциональном мире без функторов и монад? Можно.
Но, если мы хотим вовсю использовать всю силу Алгебраических Типов Данных, нам для удобной функциональной композиции различных функций придётся использовать функторы и монады.
Ибо это отличное средство от рутины и путь к краткому, понятному и часто пере-используемому коду!

P.S. Следует понимать, что для различных типов данных, аналогия с «чистыми» и «грязными» типами данных не совсем уместна.
Например, для списков
fmap = map
А монада:
a = do
c <- cs
d <- ds
return (zet c d)

на самом деле является
a = [zet c d | c <- cs, d <- ds]

Что не всегда очевидно с первого взгляда.
монады, функторы, аппликативные функторы, haskell
+36 19277
169Vitter 0,0
Похожие публикации

Очисти код свободными монадами (7)
Пример решения типичной ООП задачи на языке Haskell (7)
Слово на букву «М», или Монады уже здесь (33)
Функторы, аппликативные функторы и монады в картинках (60)
Монада ContT в картинках (Haskell) (10)
Комментарии (44) отслеживать новые: в почте в трекере

+14 NeoCode18 февраля 2014 в 12:07#
Спасибо за статью!
И как всегда объяснения на Хаскеле:) Думаю, для людей знакомых с Хаскелем, думаю это все и так понятно, а для незнакомых… сложно просто ориентироваться в коде, в синтаксисе языка, даже чтобы «распарсить» программу в уме.
Я время от времени все пытаюсь понять, что же такое эти функторы и монады, чего-то даже иногда понимаю, но единой картины все-же нет.
Начать надо с вопроса — что такое алгебраический тип данных и в чем его отличие от неалгебраических? Если пользоваться терминологией императивных языков, то например переменная типа int — АТД? Массив чисел? Структура? Класс?
В вашей предыдущей статье вы даете определение АТД как «АТД называются алгебраическими, потому что их можно представить как некую алгебраическую композицию типов его составляющих», но это все-же несколько не то, что могло бы облегчить понимание)…
+3 dotneter18 февраля 2014 в 12:54#↵↑
Попробуйте прочитать вариацию на тему с С# habrahabr.ru/post/151703/
+2 NeoCode18 февраля 2014 в 13:06 (комментарий был изменён)#↵↑
Я очень хочу разобраться в этом вопросе и помочь разобраться другим. Статью ту я с интересом читал как и многие другие статьи по теме здесь (но все равно спасибо, освежу в памяти).
Просто если есть люди, которые безусловно понимают тему на глубоком уровне (как автор топика), то почему не воспользоваться их присутствием на Хабре? Серией наводящих вопросов (возможно даже глупых) можно уточнить некоторые моменты, попытаться совместными усилиями переформулировать результаты обсуждения несколько раз до тех пор, пока не будет достигнуто полное понимание со стороны незнающих и согласие в корректности и полноте формулировок со стороны знающих.
Комментарии к статье ИМХО идеальный вариант для этой цели.
+1 Vitter18 февраля 2014 в 13:24#↵↑
Можно представить, что Int определён так:
data Int = -33554432 | -33554431 | ... |-2 | -1 | 0 | 1 | 2 | 3 | ... | 33554431 | 33554432

Да, Int — АТД, как и любые типы в Хаскеле.
Другое дело, что под капотом они реализованы по другому.

Списки — тоже АТД — рекурсивные
data List a = Nil | Cons a (List a)


Структура — это обычный тупл(кортеж)
Тупл — это АТД
data Struct = Struct Int (String->Int) String


Класс — это структура со спец-способностями (инкапсуляция, полиморфизм, наследование)
Классов и объектов в Хаскеле нет.
+1 NeoCode18 февраля 2014 в 13:48#↵↑
Какие типы не АТД? Что нужно «сломать» в АТД, чтобы он перестал быть АТД?
0 mibori18 февраля 2014 в 14:00#↵↑
например, тип Collection n a — «коллекция из n элементов типа a», кажется, не может быть АДТ. Т. е. — это тип от терма.

да поправят меня математики, если это не так.
0 NeoCode18 февраля 2014 в 14:01#↵↑
Поясните плиз. Для меня «коллекция n элементив типа a» это массив a[n] :)
0 mibori18 февраля 2014 в 14:07#↵↑
да, вы почти правильно написали на си:

a collection[n]; // так лучше

n — тут это число. Не тип. Т. е. — это терм. Т. е. — это значение некого типа, а не сам тип.
0 NeoCode18 февраля 2014 в 14:34#↵↑
То есть получается, что для списка Maybe будет работать, а для массива фиксированной длины — нет? Почему?
0 mibori18 февраля 2014 в 15:39 (комментарий был изменён)#↵↑
Потому что вам придется число(терм) выразить с помощью типа. Но в конечном итоге это уже не будет честный Algebraic Data Type (это будет называться Generalized Algebraic Data Type)

Определим стандартный (нефиксированный длины) список с указанием типа его конструкторов:

data List a where
Nil :: List a
Cons :: a -> List a -> List a

видите, оба конструктора имеют тип результата List a

теперь определим список с фиксированным количеством элементов.
сначала термы поднимем в типы. Вот аналог нуля и единицы:
data Zero
data Succ n

Вот как будут выглядеть алиасы для остальных натуральных чисел:
type One = Succ Zero
type Two = Succ One
type Three = Succ Two
type Four = Succ Three

Теперь определим сам список ListN:
data ListN n a where
Nil :: ListN Zero a
Cons :: a -> ListN n a -> ListN (Succ n) a

После этого тип списка из четырех картинок будет выглядеть так ListN Four Image

Как видите, в отличии от List a, типы результата двух конструкторов отличаются — один ListN Zero a, а второй ListN (Succ n) a.
Поэтому это не совсем ADT. Хотя в хаскелле его можно определить.
0 NeoCode18 февраля 2014 в 15:58#↵↑
Ну я совсем не это хотел получить в ответ:) Меня интересует не реализация на хаскеле, а общие принципы.
Все что я хотел узнать — что такое АДТ с точки зрения классических императивных языков, а не Хаскеля.
0 mibori18 февраля 2014 в 16:09#↵↑
АДТ, с императивной точки зрения, это продвинутый enum

Color = enum {Red, Green, Yellow, RGB(Byte, Byte, Byte) }

Эти enum еще могут иметь параметры (дженерики)

Так понятнее? :)

0 NeoCode18 февраля 2014 в 16:46 (комментарий был изменён)#↵↑
Это я знаю, когда-то на rsdn читал статьи про язык nemerle. Это все определения той или иной степени формальности: от совсем неформальных («продвинутый enum») до строгих математических.
Но что это дает в реальном кодинге?
В чем киллер-фича ADT?
0 mibori18 февраля 2014 в 17:30#↵↑
В мире со строгой типизацией кратко, лаконично и как можно точнее описать тип того, с чем ты работаешь.

Вот, например, описан тип выражающий грамматику javascript hackage.haskell.org/package/hjs-0.2.1/docs/HJS-Parser-JavaScript.html (тип JSProgram вконце странички).
+2 Si1en7ium18 февраля 2014 в 22:54#↵↑
Киллер-фича ADT, на мой взгляд — это простота в сочетании с гибкостью.

Классы смешивают воедино идентичность, состояние и поведение. Это сложно (не тяжело для восприятия, а просто объективно сложно: много сущностей, с которыми нельзя работать по отдельности)

Структуры недостаточно мощны: тип классических структур не может быть параметризован, и структуры представляют из себя произведение типов, но никогда — сумму (то есть если взять значение типа A И значение типа B, то можно получить значение структуры, содержащей A и B, но нельзя создать структуру, которая бы хранила значение типа A ИЛИ значение типа B)

ADT же просты, так как являются просто данными, без всяких смешений с какими-то идентичностями да поведениями, и при этом достаточно гибки, так как позволяют создавать сумму/произведение (алгебраическую композицию) других типов, да ещё и с параметрическим полиморфизмом (как в дженериках .net или java).
Благодаря этим двум свойствам ADT позволяют выразить очень много полезных типов данных и при этом достаточно удобно с ними работать посредством сопоставления с шаблоном, без написания горы методов для доступа к ним.

Например, в статье выше, определив в одну строку простейший тип данных Maybe, мы получили:
1) Параметризованный «контейнер» для 0 либо 1 элемента
2) Два конструктора: Just :: a -> Maybe a и Nothing :: Maybe a
3) Два «деконструктора» с аналогичными именами, которые используются при сопоставлении по шаблону и позволяют избежать условных операторов/операторов ветвлений, а также сравнений.
И всё это совершенно без дополнительных трудозатрат со стороны разработчика, то есть задаром.

К тому же можно было заметить строку deriving (Show), которая автоматически определила экземпляр класса типа Show для Maybe. Компилятор легко может создать этот и некоторые другие (например, Read, Eq, Ord) экземпляры автоматически, потому что ADT очень просты по своей сути.
Получается, что ADT — это удобный фреймворк для создания новых типов данных на основе композиции существующих.
0 Vitter18 февраля 2014 в 20:42#↵↑
Не совсем. Вообще в языках с зависимыми типами(типа Coq, Agda) тоже есть АТД.

Объекты — не АТД, поскольку они обладают свойствами.
АТД — чистый тип, он не может обладать свойствами.
0 mibori18 февраля 2014 в 13:54#↵↑
Кажется, вы немного неправы на счет Int. hackage.haskell.org/package/base-4.2.0.0/docs/Data-Int.html#t%3AInt

> A fixed-precision integer type with at least the range [-2^29… 2^29-1].
0 Vitter18 февраля 2014 в 20:44#↵↑
На самом деле числа Int на 32-битных машинах как правило (на GHC) 32-битные, на 64-х битных машинах — 64-битные
0 nickolaym19 февраля 2014 в 15:30#↵↑
Полиморфизм в хаскелле есть в ассортименте.

Инкапсуляция есть: модули и GADT.
Далеко ходить не надо: IO a (без инкапсуляции любой дурак смог бы создать поддельный real world object и поломать порядок вычислений).

Наследование — с этим сложнее.
Интерфейс-реализация — это классы типов и их инстансы.
0 mibori18 февраля 2014 в 13:48#↵↑
> то например переменная типа int — АТД? Массив чисел? Структура? Класс?

Переменная типа Int — это АДТ. Вот псевдо-определение этого типа:

data Int = -536870912 | -536870911 |… | -1 | 0 | 1 |… | 536870910 | 536870911

+27 eschava18 февраля 2014 в 12:24#
Вот как обычно, статья с таким названием, будто я ее сейчас прочту и пойму зачем нужна вся эта функциональщина с хаскелями и прочими скалами
Но нет, после второго же абзаца все как всегда скатывается в дебри странных слов и непонятного кода
Или это я такой тупой?
+3 steck18 февраля 2014 в 12:36#↵↑
Пост просто назван неправильно. Было бы коррейтней его назвать «пример использования Maybe как функтора и как монады».
А Ваш вопрос можно разделить на два:
1) Зачем вообще нужны эти языки
2) Как понимать и использовать хаскель (скалу, что угодно ещё)

И если на первый вопрос ответить можно достаточно просто, то второй потребует практики и набивания руки.
+2 Vitter18 февраля 2014 в 12:37#↵↑
чесно, а что сложного в функциях?
bar = (+)
baz = (+ 1)
foo = div

Это функции сложения, добавление единицы, целочисленное деление.
0 yomayo19 февраля 2014 в 14:25#↵↑
Если смотреть на эти функции глазами математика, то в них нет ничего сложного. Вопросы возникают, когда на них смотрят глазами программиста. Какими типами данных оперируют эти функции? А что будет, если я сложу 32-битное знаковое с 16-битовым беззнаковым и какой тип будет иметь результат? А какова будет реакция на переполнение? Описана функция целочисленного деления, а для деления чисел с плавающей запятой используется одноимённая функция или другая? Есть функция «+ 1», а что – там нет обычного инкремента? Он даже в ассемблере есть.

Как-то многовато информации остаётся за кадром – для тех, кто не знает Хаскель. Чтобы понять функциональное программирование, нужно читать учебники. Но и в них многое остаётся за кадром. Есть люди, которым материал остаётся непонятным до тех пор, пока они не поймут, как это устроено изнутри. Си и Паскаль понятны, потому что известно, какой ассемблерный код они генерируют. А что генерирует Хаскель – увы, пока сие неведомо, хотя хотелось бы разобраться.
+1 Vitter19 февраля 2014 в 16:20#↵↑
Спасибо за ответ!

В Хаскеле с переполнением чисел не всё опрятно.
А вот сложить можно однотиповые числа:
(+) :: Num a => a -> a -> a

то есть или только 32-битно-знаковые, или только 16-битовые беззнаковые.

Что касается целочисленного деления и нецелого деления, есть 2 функции:
div :: Integral a => a -> a -> a
(/) :: Fractional a => a -> a -> a


Есть функция «+ 1», а что – там нет обычного инкремента?

Тут сложнее ответ. Ответ двояк — есть и нету одновременно.
Есть функция «следующее» для любых перечислимых типов данных:
succ :: Enum a => a -> a

А теперь что касается «обычного» инкремента, он подразумевает изменение переменной.
В Хаскеле все данные неизменяемые, поэтому функции инкремента, равно как и циклов (и много чего ещё) нет.
0 HaruAtari18 февраля 2014 в 13:23#↵↑
Вы хотите понять, как и где применять ФП, но при этом не имеете желания разбираться в нем. Не удивительно, что вы не понимаете.
0 ImLiar18 февраля 2014 в 15:16 (комментарий был изменён)#↵↑
Скалка раскрывает себя полноценно в многоядерных(процессорных), а так же кластерных системах. Akka вместе с иммутабельными структурами данных и функциональный подход в их обработке сокращают количество потенциальных ошибок до минимума (дедлоки например), при этом количество написанных строк гораздо меньше тех же реализаций каких-нибудь семафоров с тредами на джавасишках, да и система становится прозрачнее и понятнее, т.к. никакой черт из коробки дебрей кода внезапно не выскочит и не изменит нашу переменную просто потому, что ему так захотелось.

офк это все справедливо для более менее средних и крупных проектов. гостевые книги на этом писать имхо излишне…
+1 burjui19 февраля 2014 в 22:29#↵↑
Наверное, дело в примерах. В этой статье из простой задачи делают сложную, пытаясь таким образом продемонстрировать мощь языка. Эффект получается противоположный, потому что делать надо наоборот: решать относительно сложную задачу так, чтобы решение получалось простым. Это по-настоящему интригует и мотивирует, особенно, если решение получается проще, чем на большинстве других популярных ЯП.
+6 Vanger1318 февраля 2014 в 13:53#
Не понятно зачем эта статья.
Те, кто не понимали\не знали азов монадного исчисления — не поймут. А те кто знали — зачем им еще одно, не самое очевидное, объяснение?
Нельзя использовать в качестве примера однобуквенные переменные, функции с именем foo, baz, <*> и >>=. Людей не знакомых с синтаксисом хаскеля и без этого много что смущать будет в примерах.
Ну а даже если и знаком с хаскелем — это все равно страшно:
safebaz a b = baz <$> a <*> b
...
(>>=) :: m a -> (a -> m b) -> m b
+1 I_Am_Hated18 февраля 2014 в 14:43#
Чтобы быстро получить представление о монадах и функторах, достаточно посмотреть вот этот очень толковый пост. Поясняется синтаксис конструкций и становится понятной общая идея. Все вполне понятно без знания синтакисиса.
0 NeoCode18 февраля 2014 в 15:05#↵↑
Не совсем.
Вот допустим, я понял следующее.
Есть функция, принимающая на вход допустим тип T1 и возвращающая тип T2. Простая функция, ведать не ведающая ни о каких монадах.
Все эти конструкции (функторы, аппликативные функторы и монады) позволяют подавать на вход функции и получать на выходе этой функции разные другие типы, порожденные на основе T1 и T2 соответственно (например, Maybe, List, Future и т.д.). Что важно — без переписывания кода функции. Функция как не знала о монадах, так и дальше не знает, но мы получаем принципиально новые возможности в программе.
То есть если у нас есть функция увеличения числа на 1, мы можем ее совершенно прозрачно применить к целому списку чисел, получив на выходе другой список.
Я прав?
0 I_Am_Hated18 февраля 2014 в 15:31#↵↑
То есть если у нас есть функция увеличения числа на 1, мы можем ее совершенно прозрачно применить к целому списку чисел, получив на выходе другой список.
Я прав?


Да, все верно. Список в haskell — это тоже функтор и для списка определена композиция функции и значений списка.
0 RomanYankovsky18 февраля 2014 в 18:42#↵↑
Воспринимайте монады как паттерн программирования. Это всего-лишь способ связывать функции в цепочки. Монада — это абстрактный интерфейс, а Maybe, List и т.д. — его реализации.
+3 grey_kristy18 февраля 2014 в 14:58#
Не покидает ощущение, что люди сначала создают себе проблемы (АДТ) а потом изобретают остроумные способы их решения (Монады и Функторы)
+1 corristo18 февраля 2014 в 16:17#↵↑
Ну да, лучше по старинке с null сравнивать.
0 qehgt18 февраля 2014 в 20:33 (комментарий был изменён)#↵↑
Нет, ADT — это очень удобно. Это как правильно сделанный union из C/C++. Вместе с (встроенным в язык) pattern matching — сильно упрощает код, и уменьшает количество мест, где можно ошибиться. К монадам и функторам отношения не имеет.
0 ilammy18 февраля 2014 в 23:01#↵↑
s/АДТ/отсутствие побочных эффектов/
+6 Si1en7ium18 февраля 2014 в 23:24#↵↑
ADT — это просто. Серьёзно. Не обязательно легко доступно для понимания, но очень просто по сути.
Есть примитивные типы, которые имеют только один экземпляр. Есть их суммы (объединение множеств значений) и произведения (декартово произведение множеств значений). Есть параметрический полиморфизм: Bool не паметризован ничем и живёт сам по себе, Maybe a параметризован типом элемента. Всё. Больше в ADT нет ничего. Это намного меньше, чем то, что есть в системах типов классических ООП-языков, и при этом позволяет добиться схожей (а зачастую даже большей) гибкости. Где здесь создание проблем? Налицо устранение некоторых проблем сложности, присущих другим системам типов.

Монады и функторы пришли в программирование из теории категорий, и, действительно, монады изначально были призваны решить проблему языка Хаскель, хотя и никак не связанную с алгебраическими типами данных: проблему работы с IO в чистом функциональном языке. Только спустя некоторое время нашлись многие другие применения этого полезного в хозяйстве, хм, паттерна. Оказалось, что монады — это остроумный способ решения многих других проблем, не связанных с IO. Просто ещё один способ композиции вычислений, как и функтор, и аппликативный функтор, и всё в таком духе. Больше способов композиции — больше возможностей писать композабельные системы — налицо loose coupling.

Разумеется, это всё, вероятно, не попадёт в мэйнстрим ещё очень долго. Отчасти из-за высокого порога вхождения, отчасти из-за того, что бизнес предъявляет несколько отличные от внутренней простоты требования. Бизнесу нужна лёгкость. Лёгкость замены и обучения разработчиков, доступность инструментов и так далее. Остроумных решений этой проблемы теория категорий нам, к сожалению, пока не предложила.
0 worldmind19 февраля 2014 в 09:56#
Благодарю, очень в тему — вчера вечером пролистал последние главы книги «Изучай haskell во имя добра» ибо начиная с функторов только ничего не понял, статья помогла немного прояснить картину
0 uvelichitel20 февраля 2014 в 00:12#
В GHC 7.8 решились наконец сделать аппликативный функтор суперклассом монады. Решение логически очевидное но архитектурно радикальное и разрушающее обратную совместимость.
0 shock_one28 октября 2014 в 23:02#↵↑
Это нововведение Haskell 2014. www.haskell.org/haskellwiki/Functor-Applicative-Monad_Proposal
+1 shock_one28 октября 2014 в 22:59 (комментарий был изменён)#
let foo = "oчень"
foo' = "названы"
baz = "беспорядочно"
bar = "функции"
quux = "сложно"
bar1 = "потому"

Было foo quux следить за кодом bar1, что bar foo' baz.
+1 Vitter30 октября 2014 в 02:49#↵↑
А вот подумал, чем чёрт не шутит, и поменял имена всем переменным для более осознанного чтения ))
Спасибо за отзыв
0 shock_one30 октября 2014 в 08:55 (комментарий был изменён)#↵↑
Спасибо большое, не ожидал. Ваши труды не пропадут зря: как минимум они улучшат вашу карму.
Изображение

dyvniy M
Автор темы, Администратор
Администратор
Аватара
dyvniy M
Автор темы, Администратор
Администратор
Возраст: 41
Репутация: 1
Лояльность: 1
Сообщения: 3579
Зарегистрирован: Ср, 10 октября 2012
С нами: 11 лет 6 месяцев
Профессия: Программист
Откуда: Россия, Москва
ICQ Сайт Skype ВКонтакте

#2 dyvniy » Пн, 30 марта 2015, 14:26:12

Изображение

dyvniy M
Автор темы, Администратор
Администратор
Аватара
dyvniy M
Автор темы, Администратор
Администратор
Возраст: 41
Репутация: 1
Лояльность: 1
Сообщения: 3579
Зарегистрирован: Ср, 10 октября 2012
С нами: 11 лет 6 месяцев
Профессия: Программист
Откуда: Россия, Москва
ICQ Сайт Skype ВКонтакте

#3 dyvniy » Вт, 31 марта 2015, 12:15:11

Как жить без монад
http://habrahabr.ru/post/118167/
Спойлер
Haskell без монад из песочницы
Haskell*
Любой программист, изучающий haskell, рано или поздно встречается с таким непостижимым понятием как монада. Для многих знакомство с языком заканчивается монадами. Существует множество руководств по монадам, и постоянно появляются новые (1). Те немногие, кто понимает монады, тщательно скрывают свои знания, объясняя монады в терминах эндофункторов и естественных преобразований (2). Ни один опытный программист не может найти монадам место в своей устоявшейся картине мира.

В результате java-программисты только посмеиваются над хаскелем, не отрываясь от своего миллионострочного энтерпрайзного проекта. Разработчики на С++ патчат свои сверх-быстрые приложения и придумывают ещё более умные указатели. Веб-разработчики листают примеры и огромные спецификации по css, xml и javascript. А те из них, кто в свободное время изучает haskell, сталкивается с труднопреодолимым препятствием, имя которому монады.

Итак, узнаем как программировать на хаскеле без монад.


Для этого нам понадобится немного свободного времени, выспавшаяся голова, кружка любимого напитка и компилятор ghc. В windows и macos его можно найти в составе пакета haskell platform (3), пользователи linux могут установить ghc из репозитория. Примеры кода, начинающиеся на Prelude> можно проверерять в ghci — интерактивном интерпретаторе.

На хабре уже была похожая статья (4), однако она не объясняет всей подноготной ввода-вывода, а просто предлагает готовые шаблоны для использования.

Переход к следующему действию — оператор

Начнём издалека. Все вычисления в хаскеле поделили на «с побочными эффектами» и «без побочных эффектов». К первому относятся, например, запись/чтение с устройств ввода/вывода, вычисления с возможностью возникновения ошибки где-то посередине и т.п. К «без побочных эффектов» относятся такие операции, как сложение чисел, склеивание строк, любые математические вычисления — любые «чистые» функции.

Чистые функции комбинируются так же, как комбинируются функции во всех других языках программирования:
Prelude> show (head (show ((1 + 1) -2)))
'0'


Для составления же программ с побочными эффектами был создан специальный оператор
>>=

назовём его «соединить» (англ. bind). Все действия ввода/вывода склеиваются именно им:
Prelude> getLine >>= putStrLn
asdf
asdf

Этот оператор принимает на вход 2 функции с побочными эффектами, причём вывод левой функции подаёт на вход правой.

Посмотрим типы функций командой интерпретатора :t:
Prelude> :t getLine
getLine :: IO String

Prelude> :t putStrLn
putStrLn :: String -> IO ()


Итак, getLine не принимает на вход ничего, и возвращает тип IO String.

То, что в имени типа 2 слова, говорит о том, что этот тип составной. И слово, которое стоит на первом месте, назовём построителем типа, а всё остальное — параметры этого построителя (знаю что звучит неблагозвучно, но так надо).

В данном случае слово IO как раз обозначает побочный эффект, и оно отбрасывается оператором »=. Как пример других «индикаторов» побочных эффектов можно привести популярный тип State, обозначающий что у функции есть какое-то состояние.

Перейдём к putStrLn. Функция принимает на вход строку, и возвращает IO (). С IO всё понятно, побочный эффект, а () — это хаскельный аналог сишного void. Т.е. функция что-то там делает с вводом/выводом и возвращает пустое значение. К слову, все программы на хаскеле должны оканчиваться этим самым IO ().

Так вот, оператор «соединить» берёт из первого аргумента его результат, отрезает индикатор побочного эффекта и передаёт то что получилось во второй свой аргумент. Это кажется сложным, однако на этом одном операторе держится половина хаскеля, весь ввод/вывод программируется с помощью него. Он настолько значим, что его даже добавили на логотип языка.

Что, если возвращаемое и принимаемое значения склеиваемых функций не совпадают? На помощь приходят лямбда-функции. Например, просто принимаем на вход параметр, но ничего с ним не делаем:
Prelude> (putStrLn "Строка 1") >>= (\a -> putStrLn "Строка 2") >>= (\b -> putStrLn "Строка 3")
Строка 1
Строка 2
Строка 3

Забегая вперёд, скажу что оператор »= имеет очень низкий приоритет и при желании в этом примере можно обойтись без скобок. Кроме того, если внутри лямбда функции аргумент не используется, как в нашем примере, можно заменить его на _.

Давайте перепишем первый пример на полностью эквивалентный, но с использованием лямбда функции:
Prelude> getLine >>= \a -> putStrLn a
asdf
asdf

При выводе строки на экран мы с помощью лямбда-функции теперь явно указали, что принимаем одну переменную, и явно написали как мы её используем.

Ты сказал «переменная»?

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

В коде выше a и b очень похожи на переменные. На них можно так же ссылаться, как и в других языках. Однако эти a и b существенно отличаются от переменных в императивных языках.

Во всех императивных языках программирования переменная — это поименованная область памяти. В хаскеле такие штуки как a и b — это поименованные выражения и значения.

Приведём пример и покажем эти отличия. Рассмотрим следующий код на си:
a = 1;
a = a + 1;
printf("%d",a)

Всё кристально понятно и результат предсказуем

Теперь сделаем то же самое на хаскеле:
Prelude> let a = 1
Prelude> let a = a + 1
Prelude> print a
^CInterrupted.

Выполнение кода не завершится никогда. В первой строке мы определяем a как 1. Во второй строке мы определяем a как a + 1. По время прочтения второй строки интерпретатор забывает о предыдущем значении а, и определяет а заново, в данном случае через самого себя. Ну а это рекурсивное определение никогда не вычислится.

Что касается поименованных областей памяти — они есть в хаскеле, но это совсем другая история.

С помощью этой конструкции можно передавать параметры через несколько вызовов оператора «соединить»:
Prelude> getLine >>= \a -> putStrLn "Вы ввели:" >>= \_ -> putStrLn a
asdf
Вы ввели:
asdf


Реальный код

Теперь используя наши тайные знания напишем что-нибудь настоящее. А конкретно программу, которая получает данные от пользователя, выполняет над ними какие-нибудь действия и выводит результат на экран. Программу напишем как и подобает в отдельном файле и скомпилируем её в машинный код.

Назовём файл test.hs:
main = putStrLn "Введите целое число:" >>= \_ ->
getLine >>= \a ->
putStrLn "Возведённое в квадрат число:" >>= \_ ->
putStrLn (show ((read a)^2))

компилируем:
ghc --make test.hs

запускаем:
$ ./test
Введите целое число:
12
Возведённое в квадрат число:
144

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

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

Примесь чистоты

Отдельно возникает вопрос о том, как вызывать чистые функции из побочно-эффектного кода.

В приведенном примере чистая функция записана просто как аргумент IO функции. Часто этого бывает достаточно, но не всегда.

Существуют другие способы вызова чистого кода.

Первый из них — это насильственное превращение чистого кода в побочно-эффектный. В самом деле, можно считать чистый код частным случаем побочно-эффектного, поэтому никаких опасностей такое преобразование не таит. А осуществляется оно с помощью функции return:
main = putStrLn "Введите целое число:" >>= \_ ->
getLine >>= \a ->
putStrLn "Возведённое в квадрат число:" >>= \_ ->
return (show ((read a)^2)) >>= \b ->
putStrLn b

Компилируем, проверяем, программа работает как и прежде.

Ещё один способ — использование хаскельной конструкции let … in … Во многих мануалах ей уделяется достаточно внимания, поэтому не станем на ней останавливаться, приведу лишь готовый пример:
main = putStrLn "Введите целое число:" >>= \_ ->
getLine >>= \a ->
putStrLn "Возведённое в квадрат число:" >>= \_ ->
let b = (show ((read a)^2)) in
putStrLn b


Нужно больше сахара

Разработчики языка обратили внимание на то, что часто встречаются конструкции
>>= \_ ->

поэтому для их обозначения ввели оператор
>>

Перепишем наш код:
main = putStrLn "Введите целое число:" >>
getLine >>= \a ->
putStrLn "Возведённое в квадрат число:" >>
let b = (show ((read a)^2)) in
putStrLn b

Так стало немного красивее.

Но есть и более крутая фишка — синтаксический сахар «do»:
main = do
putStrLn "Введите целое число:"
a <- getLine
putStrLn "Возведённое в квадрат число:"
let b = (show ((read a)^2))
putStrLn b

То что нужно! Так уже можно жить.

Внутри блока do, ограниченного выравниванием по левому краю, происходят следующие замены:
a <- abc заменяется на abc >>= \a ->
abc заменяется на abc >>
let a = b заменяется на let a = b in do

Нотация «do» делает синтаксис очень похожим на синтаксис всех современных языков программмирования. И тем не менее под капотом у неё достаточно продуманный механизм разделения чистого и побочно-эффектного кода.

Интересным отличием является использование оператора return. Его можно вставить в середину блока, и он не будет прерывать выполнения функции, что может вызвать недоумение. Но в действительности его часто используют в конце блока, чтобы вернуть из IO функции чистое значение:
get2LinesAndConcat:: IO String
get2LinesAndConcat = do
a <- getLine
b <- getLine
return (a + b)


Сфера в вакууме

А сейчас вынесем наш чистый код в отдельную функцию. А заодно расставим, наконец, отсутсвующие сигнатуры типов.
main :: IO ()
main = do
putStrLn "Введите целое число:"
a <- getLine
putStrLn "Возведённое в квадрат число:"
let b = processValue (read a)
putStrLn (show b)

processValue :: Integer -> Integer
processValue a = a ^ 2

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

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

В стилистических руководствах рекомендуется минимизировать использование побочно-эффектного кода и максимум функционала выносить в чистые функции (5). Однако если программа предназначена для выполнения действий ввода/вывода, не нужно избегать использовать его везде где нужно. Как правило в таких случаях требуются вспомогательные функции, которые могут быть чистыми. Опытные программисты на хаскеле признают отличную поддерживаемость даже IO кода в сравнении с императивными языками (высказывание приписывают Simon Peyton Johnes, но прямая ссылка не нашлась).

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

Что вы несёте?

«Этот код ужасен, он неоправданно сложен, имеет слишком мало общего с тёплой ламповой семантикой всех остальных языков, для любых целей достаточно c/c++/c#/java/python etc.».

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

Если вы знаете, как сделать такой механизм более простым и понятным, пожалуйста, расскажите об этом мировому сообществу! Хаскельное комьюнити очень открыто и доброжелательно. В проеке нового стандарта, который принимается регулярно, рассматриваются любые предложения, и если они действительно будут стоящими, их обязательно примут.

Если же вы считаете, что «и в питоне всё хорошо, что вы привязались со своими побочными эффектами!», никто вам не мешает использовать тот инструмент, который вам нравится. От себя могу добавить, что хаскель действительно упрощает разработку и делает код более понятным. Единственный способ убедиться в этом или обратном — попробовать писать на хаскеле!

Куда идти дальше

Для дальнейшего изучения или вместо этой статьи можно порекомендовать статью «мягкое введение в haskell» (6), а особенно её перевод (7).

Кроме этого, конечно, подойдут любые другие статьи (8). Руководств написано очень много, но все они объясняют одни и те же вещи с разных точек зрения. К сожалению, очень мало информации переведено на русский язык. Несмотря на обилие руководств, язык прост, его описание вместе с описанием стандартных библиотек занимает всего 270 страниц (9).

Достаточно много информации содержится также в документации по стандартным библиотекам (10).

Буду рад, если статья поможет кому-то или просто покажется интересной, комментарии и критика приветствуется.

p.s. То, что я назвал «построителем типов» в мире хаскеля называется «конструктором типов». Сделано это для того, чтобы легче было забыть значение слова «конструктор», взятое из ООП, это совершенно разные вещи. Ситуация усугубляется тем, что помимо конструкторов типов есть ещё конструкторы данных, тоже ничего общего с ООП не имеющие.

Ссылки

www.haskell.org/haskellwiki/Monad_tutorials_timeline
http://en.wikipedia.org/wiki/Monad_(category_theory)
hackage.haskell.org/platform/
habrahabr.ru/blogs/Haskell/80396/
www.haskell.org/haskellwiki/Avoiding_IO
www.haskell.org/tutorial/
www.rsdn.ru/article/haskell/haskell_part1.xml
www.haskell.org/haskellwiki/Tutorials
www.haskell.org/definition/haskell98-report.pdf
www.haskell.org/ghc/docs/7.0.3/html/libraries/


upd: (SPOILER!)

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

Так вот, словом «монада» называют набор операторов
>>=
>>
return
fail

и любой тип данных, на котором они определены. Например, IO.

Вокруг этого слова сложилась не очень хорошая аура, но в действительности в нём нет никакого тайного смысла. Это просто название паттерна программирования, который можно объяснить без монад.

upd2:
Пользователь afiskon привёл ссылку на интересную презентацию
о хаскеле.
haskell, монады
+52 2785
46tranquil 2,1
Похожие публикации
Арифметика с контролем диапазонов в Haskell с помощью Type-Level Literals 16 марта в 10:47
Мины в Haskell и Gloss: быстрое прототипирование интерактивной графики 5 февраля в 16:12
Немного о каррировании в Haskell 29 января в 18:08
Разбираемся с монадами с помощью Javascript 2 октября 2014 в 20:19
Монада ContT в картинках (Haskell) 10 августа 2012 в 01:09
Еще Одно Руководство по Монадам (часть 4: Монада Maybe и монада списка) 7 октября 2011 в 07:11
Еще Одно Руководство по Монадам (часть 3: Монадные Законы) 16 сентября 2011 в 03:13
Еще Одно Руководство по Монадам (часть 2: функции >>= и return) 9 сентября 2011 в 10:53
Еще Одно Руководство по Монадам (часть 1: основы) 2 сентября 2011 в 07:43
Объяснение ввода-вывода в Haskell без монад 10 января 2010 в 18:29
Комментарии (26)

+3 Droid92426 апреля 2011 в 19:41#
Писать на хаскелле без монад безусловно можно. Можно даже в какой-то мере успешно.
Однако, мне это напоминает вождение автомобиля с закрытым лобовым стеклом. Конечно, можно вести, высунувшись из окна, но это немного не то.
+24 Flux26 апреля 2011 в 19:56#
Haskell без монад


+4 ControlFlow26 апреля 2011 в 20:19#
прочитал название топика.
прокрутил листинги кода.
везде монада IO, с do-нотацией и без.
о каком «хаскеле без монад» вообще идёт речь?
просто пипец.
0 netslow26 апреля 2011 в 20:36#
Странный подход. «Я не понимаю как работает мотор, давайте вырвем его с корнем, а машину будем толкать». Быть может лучше объяснить что такое монады и как они применяются?
+1 saidaino26 апреля 2011 в 20:48#
1. А что, в общем-то, такого в объяснении монад через эндофункторы и естественные преобразования? :) В сумме выходит максимум 20 простых определений, начиная с категорий. Это же гораздо проще понять чем, скажем, объектно-ориентированное программирование.

2. rsdn.ru/forum/decl/4049523.1.aspx — вполне понятное описание без «страшных слов».

3. Конструкции, которые по сути являются монадами или моноидами широко используются и в других языках программирования. И пока их не называют монадами, проблем с пониманием обычно не возникает.
+1 Nashev27 апреля 2011 в 18:05#↵↑
Объяснять непонятное непонятным — само по себе порочно, ибо сепулькарий выходит. 20 новых определений в рамках одного объяснения — тоже ужасно, ибо среди них выход из сепулькария найти трудно, да и удерживается в голове 5-7 элементов одновременно, а среди них должны быть не только новые понятия, но и уже известные, и связи этих новых с ними.

Ваше «вполне понятное описание» оперирует кучей непонятных слов и ещё большей кучей непонятных символов типа рыбки >=>. Лично я, не знакомый с хаскелем, понял лишь что-то в части про рефакторинг — что в хаскеле можно преобразовывать его выражения по формальным правилам.

А вот про то, что можно было бы назвать монадами в других языках — было бы полезно услышать. Так может и монады понятны станут… Когда станут опираться на известные понятия.
+1 tranquil26 апреля 2011 в 20:55#
ответы добавлены в конец топика
+4 sylvio26 апреля 2011 в 22:57#
Эта статья замечательный способ запутать разработчика. Давайте лучше писать вообще на haskell без haskell, например:

import BASIC

main = runBASIC $ do

10 LET X =: 1
20 PRINT "Hello BASIC world!"
30 LET X =: X + 1
40 IF X <> 11 THEN 20
50 END

0 tranquil27 апреля 2011 в 06:13#↵↑
Эта статья замечательный способ запутать разработчика.
Что вы хотели бы там прояснить? Может быть, лямбда функции?

Давайте лучше писать вообще на haskell без haskel например:
Многие так и делают. Хотя назвать это «haskell без haskell» не получится, но встроенные проблемно-ориентированные языки очень распространены, примеры:

hackage.haskell.org/package/atom/ — edsl для генерации embedded кода на си
jaspervdj.be/blaze/ — edsl для построения html
legacy.cs.uu.nl/daan/parsec.html — edsl для парсинга контекстно-свободных(и некоторых КЗ — грамматик)
www.lexifi.com/downloads/frankau.pdf — edsl для трэйдинговой системы
+2 Dragonizer27 апреля 2011 в 00:20#
Prelude> (putStrLn "Строка 1") >>= (\a -> putStrLn "Строка 2") >>= (\b -> putStrLn "Строка 3")
Строка 1
Строка 2
Строка 3

Забегая вперёд, скажу что оператор »= имеет очень низкий приоритет и при желании в этом примере можно обойтись без скобок.
Насколько я понял, здесь результат не поменяется, НО поменяется порядок поулчения этого результата. Если бы приоритет >>= был ниже, чем у ->, то вот этот код, который вы привели позже:
Prelude> getLine >>= \a -> putStrLn "Вы ввели:" >>= \_ -> putStrLn a
asdf
Вы ввели:
asdf
хорошенько бы вас отругал. Что он непременно и сделает, стоит только поставить скобки:
Prelude> getLine >>= (\a -> putStrLn "Вы ввели:") >>= (\_ -> putStrLn a)

:1:62: Not in scope: `a'
0 tranquil27 апреля 2011 в 05:43#↵↑
ответ чуть ниже
0 tranquil27 апреля 2011 в 05:43#
Отлично подмечено!
Prelude> getLine >>= \a -> putStrLn "Вы ввели:" >>= \_ -> putStrLn a

В данном случае \a -> это парметр лямбда-функции, тело которой — весь остаток строки. А первый bind связывает getLine и (\a -> putStrLn «Вы ввели:» >>= \_ -> putStrLn a).

Поэтому скобки более правильно было бы расставлять так:
Prelude> getLine >>= (\a -> putStrLn "Вы ввели:" >>= (\_ -> putStrLn a))
0 bagyr27 апреля 2011 в 11:29#
Превосходная статья.
Лично у меня после этого www.muitovar.com/monad/moncow.xhtml наступило просветление.
+1 afiskon27 апреля 2011 в 19:35#
Ну и от себя 5 копеек.

1. Есть прекрасная презентация, объясняющая преимущества Хаскеля mmcs.sfedu.ru/~ulysses/IT/Haskell/papers/why-haskell-censored.pdf
2. На русском языке есть две годные книжки за авторством Душкина.
0 tranquil27 апреля 2011 в 20:00#↵↑
ответ ниже
НЛО прилетело и опубликовало эту надпись здесь
0 tranquil 1 ноября 2011 в 18:18#↵↑
ru-declarative.livejournal.com/97251.html

Вот здесь первоисточник
–1 tranquil27 апреля 2011 в 19:59#
Действительно, годная презентация!

Но. Опытному программисту на haskell это всё и не нужно объяснять. А вот опытному программисту на с++ никакими аргументами не доказать что map лучше for. Хоть об стену разбейся. Почему? Вопрос открыт.

Диалог слепого с глухим обычно примерно следующий:
— В map нет побочных эффектов, код более надёжен!
— Чо? Каких эффектов? Какой надёжен, он же непонятен! А вот for понятен каждому!
0 afiskon27 апреля 2011 в 20:17#↵↑
Почему-то мне, как опытному (более-менее) программисту на C++ аргументы показались убедительными. Сами идея хорошая. Особенно понравилась возможность параллельного выполнения программ на функциональных языков (без мьютексов, нитей и тд). Я бы на всякий случай включил ссылку в статью.
0 afiskon27 апреля 2011 в 20:22#↵↑
Кстати, в институте, когда нам объясняли ФП (кажется, это был Lisp, а возможно как раз и Haskell) на вопрос «а кому это все нужно» лектор (аспирант) ответил что-то в стиле «ну, математикам такой язык понятнее». Вопиющая безграмотность!
0 tranquil27 апреля 2011 в 20:40#↵↑
Всё таки кто автор презентации, на каких условиях распространяется?
0 afiskon27 апреля 2011 в 20:44#↵↑
Не знаю, мне ссылка через твиттер пришла.
0 ulysses28 апреля 2011 в 00:18#↵↑
Изначально презентация была размещена в ЖЖ-сообществах ру-лямбда и ру-декларатив, сходу могу дать ссылку на второе, видимо, спрашивать надо там:
ru_declarative.livejournal.com/97251.html

Я менее аккуратный в вопросах лицензий человек, так что я выбросил оттуда слайд с матом (заодно потерялись все гиперссылки, что ужасно), перевыложил в сеть университета (домен sfedu.ru) и дал ссылку студентам.
0 Longes10 февраля 2012 в 20:04#
Хакель без монад называется LISP :)
0 Vlad91116 января 2013 в 21:44#
Для тех, кто все-таки хочет разобраться с тем, что такое монады я перевел очень толковую статью о них, которая на примерах на Javascript и Haskell отвечает на вопрос, что же это такое и зачем их придумали. joydevel.blogspot.ru/2013/01/haskell-javascript.html
0 tranquil17 января 2013 в 10:15#↵↑
Больше мануалов по монадам!
Изображение

dyvniy M
Автор темы, Администратор
Администратор
Аватара
dyvniy M
Автор темы, Администратор
Администратор
Возраст: 41
Репутация: 1
Лояльность: 1
Сообщения: 3579
Зарегистрирован: Ср, 10 октября 2012
С нами: 11 лет 6 месяцев
Профессия: Программист
Откуда: Россия, Москва
ICQ Сайт Skype ВКонтакте

#4 dyvniy » Вс, 23 августа 2015, 09:23:37

OpenGL через haskell
http://habrahabr.ru/post/120686/
Спойлер
Графика через OpenGL на Haskell
Haskell*
Введение

Бытует мнение, что Хаскелл — это язык для нердов-математиков. Конечно, это все стереотипы, но туториалов по нему действительно мало, что несколько препятствует изучению. Особенно мало таких, где пишутся реальные приложения (тут выделяется замечательный Real World Haskell, который, впрочем, несколько сумбурен). Поэтому возникла идея написать этот туториал, об одной из наименее освещенных областей в Хаскелле — выводе графики. Постараюсь сделать его подробным, но предполагается, что читатель знаком с основами Хаскелла, особенно с понятием монад. Если нет — рекомендую почитать этот топик, а также книги, которые советует Skiminok в комментариях к нему.
Дисклеймер: работа будет вестись с экспериментальной библиотекой. Так что не удивляйтесь всяким извратам, для того, чтобы все работало.

Shall we?


Установка

По моему скромному мнению, весьма нетривиальна. Особенно для не-системщиков, как я.

Linux (Ubuntu 10.10)

Незнаю, в каких версиях OpenGL либы поставляются изначально, но я скачал и скомпилил GHC и Haskell Platform отсюда. В процессе пришлось скачать пару библиотек (точно помню, был нужен freeglut3-dev). Компилим тестовый пример, и — облом. Окно показывается на доли секунды и сворачивается. Запускаю из наутилуса — работает. На irc-канале на этот вопрос мне никто отвечать не захотел :) Если кто может предположить причину — прозьба высказаться в комментах.

Windows 7 (x86_64)

Традиционно больше возни. Большую помощь в установке оказал этот туториал.
1. Ставим MinGW. При инсталяции выбирайте минимальную установку. Главное, не ставте MinGW make.

2. Ставим MSys. Соглашаемся на post install, отвечаем, что MinGW установлен, и указываем путь к нему. Запускаем msys, пишем в консоли «gcc --version» и убеждаемся, что все работает.

3. Качаем Freeglut отсюда, распаковываем в Путь_к_MinGW/1.0/home/Имя_пользователя (Парсер — лох). Имя_пользователя — это имя вашей учетной записи в винде. После, запускаем msys и пишем:
cd freeglut-2.4.0/src/
gcc -O2 -c -DFREEGLUT_EXPORTS *.c -I../include
gcc -shared -o glut32.dll *.o -Wl,--enable-stdcall-fixup,--out-implib,libglut32.a -lopengl32 -lglu32 -lgdi32 -lwinm

Естественно, если директория названа по-другому, в команде ее надо изменить :)
На выходе получаем два файла — glut32.dll и libglut32.a. Копируем dll в Системный_диск/Windows/System. Если вы устанавливали стандартный Haskell-platform то это все (и libglut32.a не понадобится). Если же у вас все как-то по-другому (ghc ставился отдельно, к примеру) — отсылаю к тому-же туториалу, чтобы не раздувать топик.

Важно: А можно просто воспользоваться Cabal.

Для проверки можете использовать этот кусок кода. Если все работает, вы увидите затененную сферу.

Практикум

Haskell и OpenGL не очень гармонируют, так как практически все действия совершаются через монады. Также используются аналоги переменных, которым присваивается значение через оператор $=. Попробуем написать и скомпилировать примитивную программу, создающую окно c красным фоном.
Примечание: компиляция под виндой проходит как обычно, под линуксом же используется команда ghc -package GLUT -lglut Program.hs -o Program

import Graphics.UI.GLUT
import Graphics.Rendering.OpenGL

main = do
getArgsAndInitialize
createAWindow "Red Window"
mainLoop

createAWindow windowName = do
createWindow windowName
displayCallback $= display

display = do
clearColor $= Color4 1 0 0 1
clear [ColorBuffer]
flush


Итак, мы очищаем экран предварительно заданым цветом. Если окно не очищать, в нем будут виден скриншот рабочего окружения. Стоит отметить тип Сolor4 — задает цвет в формате RGBA, за каждый цвет отвечает GLFloat (который суть самый обыкновенный 32-битный float) от 0 до 1. Цепочка монад всегда завершается вызовом функции flush. Это гарантирует, что вся цепочка действий отправилась отрисовываться на видеокарту. Результат:

Самое время что-то отобразить в окне. Это делается через функцию renderPrimitive, которая принимает 2 аргумента: тип примитива и координаты вершин. Вершины в 3D пространстве задаются как
vertex (Vertex3 x y z)

или
vertex$Vertex3 x y z

OpenGL использует Декартову систему координат:

Попробуем отрисовать 3 синих точки на черном фоне:
import Graphics.UI.GLUT
import Graphics.Rendering.OpenGL

main = do
getArgsAndInitialize
createAWindow "Points Window"
mainLoop

createAWindow windowName = do
createWindow windowName
displayCallback $= display

display = do
clear [ColorBuffer]
currentColor $= Color4 0 0.3 1 1
renderPrimitive Points(
do
vertex (Vertex3 (0.1::GLfloat) 0.5 0)
vertex (Vertex3 (0.1::GLfloat) 0.2 0)
vertex (Vertex3 (0.2::GLfloat) 0.1 0))
flush

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

Поскольку мы оперируем вершинами, а не точками, логично все триплеты точек конвертировать в вершины:
map (\(x,y,z)->vertex$Vertex3 x y z)

И получившиеся монады преобразовать в одну с помощью sequence. Впрочем есть более вкусный синтаксический сахар — mapM_, который включает в себя обе эти функции:
mapM_ (\(x,y,z)->vertex$Vertex3 x y z)

Создадим вспомогательный модуль с горстью синтаксического сахара:
module PointsRendering where
import Graphics.UI.GLUT
import Graphics.Rendering.OpenGL
import Random

renderInWindow displayFunction = do
(progName,_) <- getArgsAndInitialize
createWindow "Primitive shapes"
displayCallback $= displayFunction
mainLoop

getRand::IO Float
getRand = getStdRandom( randomR (0,1))

displayPoints points primitiveShape = do
renderAs primitiveShape points
flush

renderAs figure ps = renderPrimitive figure(makeVertx ps)

makeVertx = mapM_ (\(x,y,z)->vertex$Vertex3 x y z)

exampleFor primitiveShape
= renderInWindow (displayExmplPoints primitiveShape)

displayExmplPoints primitiveShape = do
clear [ColorBuffer]
r <- getRand
currentColor $= Color4 0 0.3 r 1
displayPoints myPoints primitiveShape

myPoints
= [(0.2,-0.4,0::GLfloat)
,(0.46,-0.26,0)
,(0.6,0,0)
,(0.6,0.2,0)
,(0.46,0.46,0)
,(0.2,0.6,0)
,(0.0,0.6,0)
,(-0.26,0.46,0)
,(-0.4,0.2,0)
,(-0.4,0,0)
,(-0.26,-0.26,0)
,(0,-0.4,0)
]

Как вы заметили, мы определили еще и список точек, а также фунцию, которая отображает заданный примитив на этих точках. Теперь можно писать программы из одной строчки:
import PointsRendering
import Graphics.UI.GLUT
import Graphics.Rendering.OpenGL

main = exampleFor Polygon

Результат:

Вместо Polygon можно подставить любое другое значение из ADT PrimitiveMode
data PrimitiveMode =
Points
| Lines
| LineLoop
| LineStrip
| Triangles
| TriangleStrip
| TriangleFan
| Quads
| QuadStrip
| Polygon
deriving ( Eq, Ord, Show )

Вместо заключения

Эта статья еле затронула основы отображения через HOpenGL. За кадром остались другие примитивы (типа круга), трансформации, 3D и еще много всего. Если сообществу интересно, могу написать еще пару статей по теме.

Источники

«HOpenGL – 3D Graphics with Haskell» — Sven Eric Panitz
Официальный туториал по HOpenGL
haskell, opengl, hopengl, ломаем стереотипы
+23 4484
21Nordvind 0,0
Похожие публикации

Пример решения типичной ООП задачи на языке Haskell (7)
Генератор кода для Haskell (5)
REST-сервер для простого блога на Haskell (30)
Создание циферблатов для Android Wear на OpenGL ES 2.0 (6)
Ломаем стереотипы: Женская озвучка World of Tanks (75)
Комментарии (20) отслеживать новые: в почте в трекере

–2 GreyCat11 июня 2011 в 02:31#
Компилим тестовый пример, и — облом. Окно показывается на доли секунды и сворачивается

Попробуйте ./тестовый-пример </dev/null — что получится?
–7 Andrew100000011 июня 2011 в 02:47#
OpenGL традиционно считается графическим пакетом для научных задач, так что он по крайней мере ближе к Haskell, чем DirectX.
+4 urandom11 июня 2011 в 02:59#↵↑
>ближе к Haskell, чем DirectX.
Что?
–10 Andrew100000011 июня 2011 в 03:07#↵↑
Да просто сейчас все игры под DirectX пишут, OpenGL чаще используется для визуализации математических вычислений.
+7 nuclear11 июня 2011 в 04:12#↵↑
Откуда к нам такой эксперт пожаловал?
–1 chilly11 июня 2011 в 08:34#↵↑
Я вобще-то тоже так считал. Растолкуйте, пожалуйста, для тех кто «не шарит» на чём такие штуки делают en.wikipedia.org/wiki/Scientific_visualization
+1 ArtemSmirnov11 июня 2011 в 09:48#↵↑
OpenGL действительно чаще используют для визуализации математических вычислений, но просто есть миф о том что Haskell это язык для нердов-математиков вот некоторые и думают что OpenGL больше подходит для Haskell
0 Andrew100000011 июня 2011 в 14:42#↵↑
По красному фону в окне трудно определить, для каких задач планируется использовать Haskell и OpenGL.
0 Nordvind11 июня 2011 в 15:13#↵↑
Для этого есть гугл — http://www.haskell.org/haskellwiki/Opengl — первая ссылка по запросу «haskell opengl»
+1 jakobz11 июня 2011 в 15:11#↵↑
Ученые любят linux, под linux нет DirectX, только OpenGL. Поэтому всякие научные штуки делаются на OpenGL. Если ты пишешь игрушку и тебе класть на линуксы, то DirectX ловчее.

Поэтому где-то OpenGL, а где-то Direct3D.
0 jakobz11 июня 2011 в 15:18#
Загадочна душа линуксоида. Многие готовы писать инструкции как ставить компиляторы, писать много букв и цифр в консольках, чтобы собрать какой-нибудь бинарник. Когда этот бинарник можно просто взять и выложить куда-нибудь. Или даже просто нагуглить и дать ссылку:

files.transmissionzero.co.uk/software/development/GLUT/freeglut-MSVC.zip
+1 GreyCat13 июня 2011 в 01:03#↵↑
Угу, загадочна. А потом некоторые качают бинарники «откуда-нибудь» и удивляются, почему это система начала вести себя так загадочно, рассылает какие-то письма миллионами, показывает порнобаннеры и время от времени требует послать смс для входа в нее…
НЛО прилетело и опубликовало эту надпись здесь
0 jtootf11 июня 2011 в 16:03#
Спасибо за статью. Очень хотелось бы посмотреть на программирование OpenGL без fixed functions, с шейдерами (с рантайм-компиляцией, текстурами, сменой шейдерной программы в процессе отрисовки).
0 graninas14 июня 2011 в 07:59#
Благодарю!

Если можете — напишите еще статей на эту тему. Очень полезно и нужно.

Я как-то пробовал заставить OpenGL работать с Haskell, чтобы запустить Frag, — но там еще целая куча нюансов возникла…
0 baltazar_bz 8 июля 2011 в 13:51#
В моём случае (ArchLinux) всё было проще. Для установки зависимостей я использовал cabal:
$ cabal install GLUT

В винде (Win XP x86 in VirtualBox) поставил Haskell Platform 2011.2.0.1 и так же GLUT через cabal. glut32.dll, впрочем, пришлось скачать по первый ссылке в гугле — в поставке винды не оказалось.

Однако у меня есть вопрос. Когда дело доходит до анимации, как вы контролируете FPS? В линуксе у меня фигуры двигаются по кругу с нормальной скоростью, а в винде как бешеные скачут. В линуксе в настройках nvidia есть флажочек для ограничения VSync, но это костыль, а не решение проблемы. Для винды есть расширение wgl_ext_swap_control, но на практике оно не везде работало.

Пробовал на этом: bitbucket.org/balta2ar/learnopengl
0 Nordvind10 июля 2011 в 21:02#↵↑
Черт, о cabal я забыл совершенно. Добавлю в статью. Насчет glut32.dll — у меня по первой попавшейся ссылке не заработал. Может потому что винда 64-бинтная. Думаю, собрать самому не сильно сложно.
К своему стыду, на данный момент забросил HOpenGL, не дойдя до анимации :) Это мой первый опыт с OpenGL вообще, графику до этого выводил только в 2D и не библиотеками такого уровня, многие концепции непривычны, поэтому отложил изучение до того, как будет больше времени (надеюсь, в августе).
Но надеюсь, эта статья сподвигла кого-то на изучение HOpenGL.
0 mixrin12 октября 2011 в 18:16#↵↑
На 64битной windows 32ух битные библиотеки надо бросать в SysWOW64
0 kingpin12 июля 2011 в 06:38#
оффтопик: Подскажите, пожалуйста, как добились подсветки кода?
+1 Nordvind12 июля 2011 в 21:32#↵↑
тег sourcecode?
Изображение

dyvniy M
Автор темы, Администратор
Администратор
Аватара
dyvniy M
Автор темы, Администратор
Администратор
Возраст: 41
Репутация: 1
Лояльность: 1
Сообщения: 3579
Зарегистрирован: Ср, 10 октября 2012
С нами: 11 лет 6 месяцев
Профессия: Программист
Откуда: Россия, Москва
ICQ Сайт Skype ВКонтакте

#5 dyvniy » Вс, 23 августа 2015, 09:48:34

Изображение

dyvniy M
Автор темы, Администратор
Администратор
Аватара
dyvniy M
Автор темы, Администратор
Администратор
Возраст: 41
Репутация: 1
Лояльность: 1
Сообщения: 3579
Зарегистрирован: Ср, 10 октября 2012
С нами: 11 лет 6 месяцев
Профессия: Программист
Откуда: Россия, Москва
ICQ Сайт Skype ВКонтакте

#6 dyvniy » Вс, 23 августа 2015, 09:53:07

Load dll in haskell
http://stackoverflow.com/questions/1027246/haskel ... import-stdcall-on-dll-function
Спойлер
Haskell foreign import stdcall on DLL function

up vote
9
down vote
favorite
2
This is probably a really easy question to answer, but for some reason I'm really struggling with it.

I have a DLL written in C to access hardware at a protocol level, and I want to write a Haskell program that calls some of those C functions. Here's a snippet of the relevant C header (with names just slightly obfuscated due to possible copyrighting issues):

#ifdef HWDRIVER_EXPORTS
#define HWDRIVER_API __declspec(dllexport)
#else
#define HWDRIVER_API __declspec(dllimport)
#endif
HWDRIVER_API int HW_Init(void);
This has been compiled as a DLL in Visual Studio 2003, and I have successfully loaded the DLL from both C and C#, so I am confident that the DLL works fine. The DLL is named "hw-driver.dll".

Next, here's the Haskell source code just to test if I can properly load the DLL and call the simplest function in it:

{-# LANGUAGE ForeignFunctionInterface #-}
module Main
where
import Foreign
import Foreign.C

foreign import stdcall "hw-driver" "HW_Init" hwInit :: IO (CInt)

main = do
x <- hwInit
if x == 0
then putStr "Successfully initialized"
else putStr "Could not initialize"
The line that is giving me trouble is the foreign import line. As I understand it, the syntax is foreign (import/export) (ccall/stdcall) library-name C-function-name haskell-function-name :: Haskell type declaration. So mine should be foreign import stdcall (because you use stdcall when loading a DLL in Win32) "hw-driver" (because the file is named "hw-driver.dll" and it is located in the same directory as dlltest.hs) "HW_Init" (the name of the function in C) hwInit :: IO (Cint) (void arguments, returning an int).

However, when I try running ghci dlltest.hs, I get the following output:

[1 of 1] Compiling Main ( dlltest.hs, interpreted )

dlltest.hs:8:43: parse error on input `"'
Failed, modules loaded: none.
Line 8, column 43 is the first quotation mark on HW_Init. Okay, so maybe I have to put both the library name and the function name in one string, I've seen that in a few places. If I try running that, then I get:

[1 of 1] Compiling Main ( dlltest.hs, interpreted )

dlltest.hs:8:23: Malformed entity string
Failed, modules loaded: none.
8:23 is the first quotation mark of the new string "hw-driver HW_Init".

I don't believe there's anything wrong with my ghc setup (6.10.3), because I can run the following code that was copy-pasted from Real World Haskell in ghci:

{-- snippet pragma --}
{-# LANGUAGE ForeignFunctionInterface #-}
{-- /snippet pragma --}

{-- snippet imports --}
import Foreign
import Foreign.C.Types
{-- /snippet imports --}

{-- snippet binding --}
foreign import ccall "math.h sin"
c_sin :: CDouble -> CDouble
{-- /snippet binding --}

{-- snippet highlevel --}
fastsin :: Double -> Double
fastsin x = realToFrac (c_sin (realToFrac x))
{-- /snippet highlevel --}

{-- snippet use --}
main = mapM_ (print . fastsin) [0/10, 1/10 .. 10/10]
{-- /snippet use --}
So long question short, how do I properly declare a foreign import on a Win32 DLL? I haven't been able to find anything on Google.

And to kind of tag along on that question, will I be able to use a program like c2hs or hsc2hs to parse the header file hw-driver.h so I don't have to manually write the foreign import calls for all 20-25 functions contained in that DLL? I haven't been able to find any decent examples of that either.

EDIT: ephemient has pointed out that the correct syntax for the foreign import line is:

foreign import stdcall "hw-driver.h HW_Init" hwInit :: IO CInt
With this, I am able to call ghci dlltest.hs -lhw-driver and properly call the main function with a successful return code. However, the command ghc --make dlltest.hs -lhw-driver fails with a linker error. So, here's the verbose output of that command (note that I have all of hw-driver.{dll,h,lib} in the working directory):

Glasgow Haskell Compiler, Version 6.10.3, for Haskell 98, stage 2 booted by GHC version 6.10.1
Using package config file: C:\ghc\ghc-6.10.3\package.conf
hiding package base-3.0.3.1 to avoid conflict with later version base-4.1.0.0
wired-in package ghc-prim mapped to ghc-prim-0.1.0.0
wired-in package integer mapped to integer-0.1.0.1
wired-in package base mapped to base-4.1.0.0
wired-in package rts mapped to rts-1.0
wired-in package haskell98 mapped to haskell98-1.0.1.0
wired-in package syb mapped to syb-0.1.0.1
wired-in package template-haskell mapped to template-haskell-2.3.0.1
wired-in package dph-seq mapped to dph-seq-0.3
wired-in package dph-par mapped to dph-par-0.3
Hsc static flags: -static
*** Chasing dependencies:
Chasing modules from: *dlltest.hs
Stable obj: [Main]
Stable BCO: []
Ready for upsweep
[NONREC
ModSummary {
ms_hs_date = Mon Jun 22 13:20:05 Eastern Daylight Time 2009
ms_mod = main:Main,
ms_imps = [Foreign.C, Foreign]
ms_srcimps = []
}]
compile: input file dlltest.hs
Created temporary directory: C:\DOCUME~1\CHRISC~1\LOCALS~1\Temp\/ghc4428_0
*** Checking old interface for main:Main:
[1 of 1] Skipping Main ( dlltest.hs, dlltest.o )
*** Deleting temp files:
Deleting: C:\DOCUME~1\CHRISC~1\LOCALS~1\Temp\/ghc4428_0/ghc4428_0.s
Warning: deleting non-existent C:\DOCUME~1\CHRISC~1\LOCALS~1\Temp\/ghc4428_0/ghc4428_0.s
Upsweep completely successful.
*** Deleting temp files:
Deleting:
link: linkables are ...
LinkableM (Mon Jun 22 13:22:26 Eastern Daylight Time 2009) main:Main
[DotO dlltest.o]
Linking dlltest.exe ...
*** Windres:
C:\ghc\ghc-6.10.3\bin/windres --preprocessor="C:\ghc\ghc-6.10.3\gcc" "-BC:\ghc\ghc-6.10.3\gcc-lib/" "-IC:\ghc\ghc-6.10.3\include/mingw" "-E" "-xc" "-DRC_INVOKED" --use-temp-file --input=C:\DOCUME~1\CHRISC~1\LOCALS~1\Temp\/ghc4428_0/ghc4428_0.rc --output=C:\DOCUME~1\CHRISC~1\LOCALS~1\Temp\/ghc4428_0/ghc4428_0.o --output-format=coff
*** Linker:
C:\ghc\ghc-6.10.3\gcc -BC:\ghc\ghc-6.10.3\gcc-lib/ -IC:\ghc\ghc-6.10.3\include/mingw -v -o dlltest.exe -DDONT_WANT_WIN32_DLL_SUPPORT dlltest.o -lhw-driver C:\DOCUME~1\CHRISC~1\LOCALS~1\Temp\/ghc4428_0/ghc4428_0.o -LC:\ghc\ghc-6.10.3\base-4.1.0.0 -LC:\ghc\ghc-6.10.3\integer-0.1.0.1 -LC:\ghc\ghc-6.10.3\ghc-prim-0.1.0.0 -LC:\ghc\ghc-6.10.3 -LC:\ghc\ghc-6.10.3/gcc-lib -lHSbase-4.1.0.0 -lwsock32 -lmsvcrt -lkernel32 -luser32 -lshell32 -lHSinteger-0.1.0.1 -lHSghc-prim-0.1.0.0 -lHSrts -lm -lffi -lgmp -lwsock32 -u _ghczmprim_GHCziTypes_Izh_static_info -u _ghczmprim_GHCziTypes_Czh_static_info -u _ghczmprim_GHCziTypes_Fzh_static_info -u _ghczmprim_GHCziTypes_Dzh_static_info -u _base_GHCziPtr_Ptr_static_info -u _base_GHCziWord_Wzh_static_info -u _base_GHCziInt_I8zh_static_info -u _base_GHCziInt_I16zh_static_info -u _base_GHCziInt_I32zh_static_info -u _base_GHCziInt_I64zh_static_info -u _base_GHCziWord_W8zh_static_info -u _base_GHCziWord_W16zh_static_info -u _base_GHCziWord_W32zh_static_info -u _base_GHCziWord_W64zh_static_info -u _base_GHCziStable_StablePtr_static_info -u _ghczmprim_GHCziTypes_Izh_con_info -u _ghczmprim_GHCziTypes_Czh_con_info -u _ghczmprim_GHCziTypes_Fzh_con_info -u _ghczmprim_GHCziTypes_Dzh_con_info -u _base_GHCziPtr_Ptr_con_info -u _base_GHCziPtr_FunPtr_con_info -u _base_GHCziStable_StablePtr_con_info -u _ghczmprim_GHCziBool_False_closure -u _ghczmprim_GHCziBool_True_closure -u _base_GHCziPack_unpackCString_closure -u _base_GHCziIOBase_stackOverflow_closure -u _base_GHCziIOBase_heapOverflow_closure -u _base_ControlziExceptionziBase_nonTermination_closure -u _base_GHCziIOBase_blockedOnDeadMVar_closure -u _base_GHCziIOBase_blockedIndefinitely_closure -u _base_ControlziExceptionziBase_nestedAtomically_closure -u _base_GHCziWeak_runFinalizzerBatch_closure -u _base_GHCziTopHandler_runIO_closure -u _base_GHCziTopHandler_runNonIO_closure -u _base_GHCziConc_runHandlers_closure -u _base_GHCziConc_ensureIOManagerIsRunning_closure
Reading specs from C:/ghc/ghc-6.10.3/gcc-lib/specs
Configured with: ../gcc-3.4.5-20060117-3/configure --with-gcc --with-gnu-ld --with-gnu-as --host=mingw32 --target=mingw32 --prefix=/mingw --enable-threads --disable-nls --enable-languages=c,c++,f77,ada,objc,java --disable-win32-registry --disable-shared --enable-sjlj-exceptions --enable-libgcj --disable-java-awt --without-x --enable-java-gc=boehm --disable-libgcj-debug --enable-interpreter --enable-hash-synchronization --enable-libstdcxx-debug
Thread model: win32
gcc version 3.4.5 (mingw-vista special r3)
C:/ghc/ghc-6.10.3/gcc-lib/collect2.exe -Bdynamic -o dlltest.exe -u _ghczmprim_GHCziTypes_Izh_static_info -u _ghczmprim_GHCziTypes_Czh_static_info -u _ghczmprim_GHCziTypes_Fzh_static_info -u _ghczmprim_GHCziTypes_Dzh_static_info -u _base_GHCziPtr_Ptr_static_info -u _base_GHCziWord_Wzh_static_info -u _base_GHCziInt_I8zh_static_info -u _base_GHCziInt_I16zh_static_info -u _base_GHCziInt_I32zh_static_info -u _base_GHCziInt_I64zh_static_info -u _base_GHCziWord_W8zh_static_info -u _base_GHCziWord_W16zh_static_info -u _base_GHCziWord_W32zh_static_info -u _base_GHCziWord_W64zh_static_info -u _base_GHCziStable_StablePtr_static_info -u _ghczmprim_GHCziTypes_Izh_con_info -u _ghczmprim_GHCziTypes_Czh_con_info -u _ghczmprim_GHCziTypes_Fzh_con_info -u _ghczmprim_GHCziTypes_Dzh_con_info -u _base_GHCziPtr_Ptr_con_info -u _base_GHCziPtr_FunPtr_con_info -u _base_GHCziStable_StablePtr_con_info -u _ghczmprim_GHCziBool_False_closure -u _ghczmprim_GHCziBool_True_closure -u _base_GHCziPack_unpackCString_closure -u _base_GHCziIOBase_stackOverflow_closure -u _base_GHCziIOBase_heapOverflow_closure -u _base_ControlziExceptionziBase_nonTermination_closure -u _base_GHCziIOBase_blockedOnDeadMVar_closure -u _base_GHCziIOBase_blockedIndefinitely_closure -u _base_ControlziExceptionziBase_nestedAtomically_closure -u _base_GHCziWeak_runFinalizzerBatch_closure -u _base_GHCziTopHandler_runIO_closure -u _base_GHCziTopHandler_runNonIO_closure -u _base_GHCziConc_runHandlers_closure -u _base_GHCziConc_ensureIOManagerIsRunning_closure C:/ghc/ghc-6.10.3/gcc-lib/crt2.o C:/ghc/ghc-6.10.3/gcc-lib/crtbegin.o -LC:\ghc\ghc-6.10.3\base-4.1.0.0 -LC:\ghc\ghc-6.10.3\integer-0.1.0.1 -LC:\ghc\ghc-6.10.3\ghc-prim-0.1.0.0 -LC:\ghc\ghc-6.10.3 -LC:\ghc\ghc-6.10.3/gcc-lib -LC:/ghc/ghc-6.10.3/gcc-lib dlltest.o -lhw-driver C:\DOCUME~1\CHRISC~1\LOCALS~1\Temp\/ghc4428_0/ghc4428_0.o -lHSbase-4.1.0.0 -lwsock32 -lmsvcrt -lkernel32 -luser32 -lshell32 -lHSinteger-0.1.0.1 -lHSghc-prim-0.1.0.0 -lHSrts -lm -lffi -lgmp -lwsock32 -lmingw32 -lgcc -lmoldname -lmingwex -lmsvcrt -luser32 -lkernel32 -ladvapi32 -lshell32 -lmingw32 -lgcc -lmoldname -lmingwex -lmsvcrt C:/ghc/ghc-6.10.3/gcc-lib/crtend.o
C:\ghc\ghc-6.10.3\gcc-lib\ld.exe: cannot find -lhw-driver
collect2: ld returned 1 exit status
*** Deleting temp files:
Deleting: C:\DOCUME~1\CHRISC~1\LOCALS~1\Temp\/ghc4428_0/ghc4428_0.o C:\DOCUME~1\CHRISC~1\LOCALS~1\Temp\/ghc4428_0/ghc4428_0.rc
*** Deleting temp dirs:
Deleting: C:\DOCUME~1\CHRISC~1\LOCALS~1\Temp\/ghc4428_0
As it turns out, the actual linking wasn't as difficult as I was making it out to be. I was using foreign import stdcall which I believed to be correct with a DLL built in Visual Studio 2003. I had to download the pexports tool for MinGW, which lists the functions exported from a DLL. The linker had been looking for HWInit@0 the whole time, but pexports said the DLL was exporting just HWInit.
I changed my line to foreign import ccall instead, and I was successfully able to link the program using either of ghc --make dlltest.hs hw-driver.lib or ghc --make dlltest.hs -L. -lhw-driver due to having both the .lib and the .dll file available in the working directory.

dll haskell linker ffi
shareimprove this question
edited Apr 17 '11 at 21:26

Don Stewart
108k26276407
asked Jun 22 '09 at 13:35

Mark Rushakoff
116k18255316
add a comment
2 Answers
activeoldestvotes
up vote
3
down vote
accepted
FFI spec # 4.1.1 Import Declarations,

impent → " [static] [*chname*] [&] [*cid*] "
| " dynamic "
| " wrapper "
where chname is "C header name", not "library name".

FFI spec # 4.1.4 Specification of Header Files

A C header specified in an import declaration is always included by #include "chname". There is no explicit support for #include <chname> style inclusion. The ISO C99 [3] standard guarantees that any search path that would be used for a #include <chname> is also used for #include "chname" and it is guaranteed that these paths are searched after all paths that are unique to #include "chname". Furthermore, we require that chname ends on .h to make parsing of the specification of external entities unambiguous.
Try with a proper header name,

foreign import stdcall "hw-driver.h HW_Init" hwInit :: IO CInt
or with no header name at all.

foreign import stdcall "HW_Init" hwInit :: IO CInt
Your command line doesn't seem to include . as a library search path. It is most likely that this is the problem. GHCi magically includes . in the library search path.

ghc --make dlltest.hs -L. -lhwdriver
If that still fails, maybe it's the static library which is causing issues. Unlikely, but...

GHC on Windows uses dynamic linking by default. Since you have a .lib, which is a static library, try informing the linker that you want static linking.

ghc --make dlltest.hs -L. -optl-Bstatic -lhwdriver -optl-Bdynamic
As for auto-generated bindings, there's

Green Card
C->Haskell
hsc2hs
I've found c2hs to be the easiest to use, but I've never tried it on anything requiring stdcalls.

It's not that onerous to write all the foreign stuff manually, if there's only 25 calls or so. I managed to manually write bindings to libvlc a few years back, for some little project...

shareimprove this answer
edited Jun 22 '09 at 18:16

answered Jun 22 '09 at 16:10

ephemient
103k22154283

ghci dlltest.hs -lhw-driver allowed me to run the main function in ghci, but I'm having issues with compiling in gcc: C:\temp\hs>ghc --make dlltest.hs -lhw-driver Linking dlltest.exe ... C:\ghc\ghc-6.10.3\gcc-lib\ld.exe: cannot find -lhw-driver collect2: ld returned 1 exit status This is very strange to me since it works properly in ghci. I'm going to play around with it some more. – Mark Rushakoff Jun 22 '09 at 16:57

GHCi doesn't use ld and implements its own linker instead. More commonly there are situations when a library can be used in compiling but not interactively, without hackish workarounds, but this reverse case seems quite probable too. Can you run with -v and post the intermediate commands that ghc runs? – ephemient Jun 22 '09 at 17:13

The -L. option changes my output from "cannot find -lhw-driver" to "undefined reference to 'HW_Init@0'", but neither suggestion got me a successful linking. Found a similar issue at nabble.com/… but no useful answer. Might end up just getting on the GHC mailing lists tomorrow... it is looking like it's just an issue with the linker options somewhere. I am sure I'm not the first person to use ld to link with a DLL. – Mark Rushakoff Jun 22 '09 at 18:54

I'm pretty much a Linux-only user, and GHC links with .a and .so flawlessly for me, so any further help will be pure guesswork on my part. Sorry, and hope you find an answer. – ephemient Jun 22 '09 at 19:01

I wouldn't be using Windows at all if it wasn't for work :X You helped me nail down the original question, so I'm accepting your answer, and I'll open a new issue sometime later regarding the weird ld behavior. Thanks. – Mark Rushakoff Jun 22 '09 at 20:06
add a comment
up vote
1
down vote
Below is a working example that calls GetComputerName from kernel32.dll:

{-# LANGUAGE ForeignFunctionInterface #-}

module Main where

import Control.Monad
import Foreign.C
import Foreign.Marshal.Alloc
import Foreign.Marshal.Array
import System.Win32.Types

foreign import stdcall "GetComputerNameW"
win32_getComputerName :: LPTSTR -> LPDWORD -> IO Bool

getComputerName :: IO String
getComputerName = do
withTString maxBuf $
\buf -> do
alloca $ \len -> do
pokeArray len [fromIntegral maxLength]

success <- win32_getComputerName buf len
when (not success) $ fail "GetComputerName failed"

[len'] <- peekArray 1 len
peekTStringLen (buf, (fromIntegral len'))
where
maxBuf = take maxLength $ repeat 'x'
maxLength = 15 -- cheating

main :: IO ()
main = getComputerName >>= putStrLn
Build it with

ghc --make compname.hs -lkernel32
shareimprove this answer
edited Jun 22 '09 at 17:59

answered Jun 22 '09 at 17:21

Greg Bacon
62k15127185

That hard-codes the C:\windows\system32 path into the output executable, which I wouldn't consider desirable. – ephemient Jun 22 '09 at 17:23

Then please suggest a desirable alternative! – Greg Bacon Jun 22 '09 at 17:30

Make sure the library search paths is correct (though that path should already be searched), then use -lkernel32. – ephemient Jun 22 '09 at 17:35

Thanks for the pointer. With -Lc:/windows/system32, I was getting tons of undefined-reference errors. Omitting the -L argument made everyone happy. KISS, I guess. – Greg Bacon Jun 22 '09 at 18:01
Изображение


Название раздела: Программирование (под Desktop и Android)
Описание: Разработка и отладка приложений. Упор на 3D-графику.

Быстрый ответ


Введите код в точности так, как вы его видите. Регистр символов не имеет значения.
Код подтверждения
:) ;) :hihi: :P :hah: :haha: :angel: :( :st: :_( :cool: 8-| :beee: :ham: :rrr: :grr: :* :secret: :stupid: :music: Ещё смайлики…
   

Вернуться в «Программирование (под Desktop и Android)»

Кто сейчас на форуме (по активности за 15 минут)

Сейчас этот раздел просматривают: 8 гостей