Скрытые черты Haskell
Каковы менее известные, но полезные функции языка программирования Haskell. (Я понимаю, что сам язык менее известен, но работайте со мной. Даже объяснения простых вещей в Haskell, такие как определение последовательности Фибоначчи с одной строкой кода, будут поддерживаться мной.)
- Попробуйте ограничить ответы на ядро Haskell
- Одна функция для каждого ответа
- Приведите пример и краткое описание функции, а не ссылку на документацию
- Обозначьте эту функцию, используя полужирный заголовок в качестве первой строки
Ответы
Ответ 1
Мой мозг просто взорвался
Если вы попытаетесь скомпилировать этот код:
{-# LANGUAGE ExistentialQuantification #-}
data Foo = forall a. Foo a
ignorefoo f = 1 where Foo a = f
Вы получите это сообщение об ошибке:
$ ghc Foo.hs
Foo.hs:3:22:
My brain just exploded.
I can't handle pattern bindings for existentially-quantified constructors.
Instead, use a case-expression, or do-notation, to unpack the constructor.
In the binding group for
Foo a
In a pattern binding: Foo a = f
In the definition of `ignorefoo':
ignorefoo f = 1
where
Foo a = f
Ответ 2
Пользовательские структуры управления
У Haskell нет стенографического тройного оператора. Встроенный if
- then
- else
всегда тернарный и является выражением (императивные языки имеют тенденцию иметь ?:
= выражение, if
= statement). Если вы хотите, тем не менее,
True ? x = const x
False ? _ = id
будет определять (?)
как тернарный оператор:
(a ? b $ c) == (if a then b else c)
Вам нужно будет прибегать к макросам на большинстве других языков, чтобы определить свои собственные закорачивающие логические операторы, но Haskell - полностью ленивый язык, поэтому он просто работает.
-- prints "I'm alive! :)"
main = True ? putStrLn "I'm alive! :)" $ error "I'm dead :("
Ответ 3
Hoogle
Hoogle - ваш друг. Я признаю, что это не часть "ядра", поэтому cabal install hoogle
Теперь вы знаете, как "если вы ищете функцию более высокого порядка, она уже существует" (эфемерный комментарий). Но как вы находите эту функцию? С hoogle!
$ hoogle "Num a => [a] -> a"
Prelude product :: Num a => [a] -> a
Prelude sum :: Num a => [a] -> a
$ hoogle "[Maybe a] -> [a]"
Data.Maybe catMaybes :: [Maybe a] -> [a]
$ hoogle "Monad m => [m a] -> m [a]"
Prelude sequence :: Monad m => [m a] -> m [a]
$ hoogle "[a] -> [b] -> (a -> b -> c) -> [c]"
Prelude zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
Программист hoogle-google не может самостоятельно писать свои программы на бумаге так же, как с помощью компьютера. Но он и машина вместе вынуждены не считаться с *.
Кстати, если вам понравилось hoogle, не забудьте проверить hlint!
Ответ 4
Свободные теоремы
Фил Вадлер познакомил нас с понятием бесплатной теоремы , и с тех пор мы злоупотребляли ими в Haskell.
Эти замечательные артефакты систем типа типа Хиндли-Милнера помогают в эквациональном рассуждении, используя параметричность, чтобы рассказать вам о том, что функция не будет делать.
Например, существуют два закона, которые должны удовлетворять каждый экземпляр Functor:
forall f g. fmap f. fmap g = fmap (f. g)
fmap id = id
Но, свободная теорема говорит нам, что нам не нужно доказывать первое, но учитывая второе, оно приходит для "свободного" только от сигнатуры типа!
fmap :: Functor f => (a -> b) -> f a -> f b
Вам нужно быть немного осторожным с лени, но это частично описано в оригинальной статье, а в Janis Voigtlaender на свободных теоремах в присутствии seq
.
Ответ 5
Сокращение для общей операции списка
Следующие эквиваленты:
concat $ map f list
concatMap f list
list >>= f
Изменить
Поскольку запрошена более подробная информация...
concat :: [[a]] -> [a]
concat
принимает список списков и объединяет их в один список.
map :: (a -> b) -> [a] -> [b]
map
отображает функцию над списком.
concatMap :: (a -> [b]) -> [a] -> [b]
concatMap
эквивалентен (.) concat . map
: отображает функцию над списком и объединяет результаты.
class Monad m where
(>>=) :: m a -> (a -> m b) -> m b
return :: a -> m a
A Monad
имеет операцию связывания, которая называется >>=
в Haskell (или ее sugared do
-эквивалент). Список, aka []
, является Monad
. Если подставить []
для m
в приведенном выше примере:
instance Monad [] where
(>>=) :: [a] -> (a -> [b]) -> [b]
return :: a -> [a]
Какая естественная вещь для операций Monad
в списке? Мы должны удовлетворять законам монады,
return a >>= f == f a
ma >>= (\a -> return a) == ma
(ma >>= f) >>= g == ma >>= (\a -> f a >>= g)
Вы можете проверить, соблюдаются ли эти законы, если мы используем реализацию
instance Monad [] where
(>>=) = concatMap
return = (:[])
return a >>= f == [a] >>= f == concatMap f [a] == f a
ma >>= (\a -> return a) == concatMap (\a -> [a]) ma == ma
(ma >>= f) >>= g == concatMap g (concatMap f ma) == concatMap (concatMap g . f) ma == ma >>= (\a -> f a >>= g)
Это, по сути, поведение Monad []
. В качестве демонстрации
double x = [x,x]
main = do
print $ map double [1,2,3]
-- [[1,1],[2,2],[3,3]]
print . concat $ map double [1,2,3]
-- [1,1,2,2,3,3]
print $ concatMap double [1,2,3]
-- [1,1,2,2,3,3]
print $ [1,2,3] >>= double
-- [1,1,2,2,3,3]
Ответ 6
Вложенные многострочные комментарии.
{- inside a comment,
{- inside another comment, -}
still commented! -}
Ответ 7
Обобщенные типы алгебраических данных. Вот пример интерпретатора, в котором система типов позволяет вам охватывать все случаи:
{-# LANGUAGE GADTs #-}
module Exp
where
data Exp a where
Num :: (Num a) => a -> Exp a
Bool :: Bool -> Exp Bool
Plus :: (Num a) => Exp a -> Exp a -> Exp a
If :: Exp Bool -> Exp a -> Exp a -> Exp a
Lt :: (Num a, Ord a) => Exp a -> Exp a -> Exp Bool
Lam :: (a -> Exp b) -> Exp (a -> b) -- higher order abstract syntax
App :: Exp (a -> b) -> Exp a -> Exp b
-- deriving (Show) -- failse
eval :: Exp a -> a
eval (Num n) = n
eval (Bool b) = b
eval (Plus e1 e2) = eval e1 + eval e2
eval (If p t f) = eval $ if eval p then t else f
eval (Lt e1 e2) = eval e1 < eval e2
eval (Lam body) = \x -> eval $ body x
eval (App f a) = eval f $ eval a
instance Eq a => Eq (Exp a) where
e1 == e2 = eval e1 == eval e2
instance Show (Exp a) where
show e = "<exp>" -- very weak show instance
instance (Num a) => Num (Exp a) where
fromInteger = Num
(+) = Plus
Ответ 8
Шаблоны в привязках верхнего уровня
five :: Int
Just five = Just 5
a, b, c :: Char
[a,b,c] = "abc"
Как здорово! Сохраняет ваш вызов fromJust
и head
время от времени.
Ответ 9
Дополнительный макет
Вы можете использовать явные фигурные скобки и точки с запятой вместо пробелов (ака макет), чтобы разделить блоки.
let {
x = 40;
y = 2
} in
x + y
... или эквивалентно...
let { x = 40; y = 2 } in x + y
... вместо...
let x = 40
y = 2
in x + y
Поскольку макет не требуется, программы Haskell могут быть легко созданы другими программами.
Ответ 10
Фиксация оператора
Вы можете использовать ключевые слова infix, infixl или infixr для определения ассоциативности операторов и приоритета. Пример, взятый из reference:
main = print (1 +++ 2 *** 3)
infixr 6 +++
infixr 7 ***,///
(+++) :: Int -> Int -> Int
a +++ b = a + 2*b
(***) :: Int -> Int -> Int
a *** b = a - 4*b
(///) :: Int -> Int -> Int
a /// b = 2*a - 3*b
Output: -19
Число (от 0 до 9) после инфикса позволяет определить приоритет оператора, будучи самым сильным. Infix означает отсутствие ассоциативности, тогда как infixl связывает права слева и infixr.
Это позволяет вам определять сложные операторы для выполнения операций высокого уровня, написанных как простые выражения.
Обратите внимание, что вы также можете использовать двоичные функции как операторы, если вы поместите их между обратными окнами:
main = print (a `foo` b)
foo :: Int -> Int -> Int
foo a b = a + b
И как таковой, вы также можете определить приоритет для них:
infixr 4 `foo`
Ответ 11
seq
и ($!)
достаточно оценить достаточно, чтобы проверить, что что-то не внизу.
Следующая программа будет печатать только "там".
main = print "hi " `seq` print "there"
Для тех, кто не знаком с Haskell, Haskell не является строгим в целом, что означает, что аргумент функции оценивается только в том случае, если это необходимо.
Например, следующие отпечатки "игнорируются" и завершаются с успехом.
main = foo (error "explode!")
where foo _ = print "ignored"
Известно, что seq
меняет это поведение, оценивая его до нижнего уровня, если его первый аргумент является нижним.
Например:
main = error "first" `seq` print "impossible to print"
... или эквивалентно, без инфикса...
main = seq (error "first") (print "impossible to print")
... взорвется с ошибкой "первым". Он никогда не будет печатать "невозможно распечатать".
Итак, может быть немного удивительно, что даже если seq
является строгим, он не будет оценивать то, что оценивает нетерпеливые языки. В частности, он не будет пытаться заставить все положительные целые числа в следующей программе. Вместо этого он проверит, что [1..]
не является нижней (что можно найти сразу), напечатайте "done" и выйдите.
main = [1..] `seq` print "done"
Ответ 12
Избегание круглых скобок
Функции (.)
и ($)
в Prelude
имеют очень удобные исправления, позволяя вам избегать круглых скобок во многих местах. Следующие эквиваленты:
f (g (h x))
f $ g $ h x
f . g $ h x
f . g . h $ x
flip
также помогает следующее:
map (\a -> {- some long expression -}) list
flip map list $ \a ->
{- some long expression -}
Ответ 13
Довольно охранники
Prelude
определяет otherwise = True
, в результате чего условия полной защиты читаются очень естественно.
fac n
| n < 1 = 1
| otherwise = n * fac (n-1)
Ответ 14
Перечисления C-Style
Объединение совпадений шаблонов верхнего уровня и арифметических последовательностей дает нам удобный способ определения последовательных значений:
foo : bar : baz : _ = [100 ..] -- foo = 100, bar = 101, baz = 102
Ответ 15
Считываемая функциональная композиция
Prelude
определяет (.)
как математическую функцию; то есть g . f
сначала применяется f
, затем применяет g
к результату.
Если вы import Control.Arrow
, то следующие эквиваленты:
g . f
f >>> g
Control.Arrow
предоставляет instance Arrow (->)
, и это хорошо для людей, которые не любят читать приложение-приложение назад.
Ответ 16
let 5 = 6 in ...
действителен Haskell.
Ответ 17
Бесконечные списки
Поскольку вы упомянули о фибоначчи, существует очень элегантный способ генерировать числа фибоначчи из бесконечного списка, подобного этому:
[email protected](1:tfib) = 1 : 1 : [ a+b | (a,b) <- zip fib tfib ]
Оператор @позволяет использовать сопоставление шаблонов в структуре 1: tfib, все еще ссылаясь на весь шаблон как fib.
Обратите внимание, что список понимания вводит бесконечную рекурсию, генерируя бесконечный список. Однако вы можете запрашивать у него элементы или управлять ими, если вы запрашиваете конечную сумму:
take 10 fib
Вы также можете применить операцию ко всем элементам, прежде чем запрашивать их:
take 10 (map (\x -> x+1) fib)
Это благодаря Haskell ленивой оценке параметров и списков.
Ответ 18
Гибкая спецификация импорта и экспорта модулей
Импорт и экспорт приятный.
module Foo (module Bar, blah) -- this is module Foo, export everything that Bar expored, plus blah
import qualified Some.Long.Name as Short
import Some.Long.Name (name) -- can import multiple times, with different options
import Baz hiding (blah) -- import everything from Baz, except something named 'blah'
Ответ 19
Если вы ищете список или функцию более высокого порядка, она уже существует
В стандартной библиотеке есть много удобных функций и функций более высокого порядка.
-- factorial can be written, using the strict HOF foldl':
fac n = Data.List.foldl' (*) 1 [1..n]
-- there a shortcut for that:
fac n = product [1..n]
-- and it can even be written pointfree:
fac = product . enumFromTo 1
Ответ 20
Рациональное рассуждение
Haskell, будучи чисто функциональным, позволяет вам читать знак равенства как знак реального равенства (при отсутствии неперекрывающихся шаблонов).
Это позволяет подставлять определения непосредственно в код, а с точки зрения оптимизации дает много возможностей компилятору о том, когда происходит материал.
Хороший пример этой формы рассуждения можно найти здесь:
http://www.haskell.org/pipermail/haskell-cafe/2009-March/058603.html
Это также хорошо проявляется в виде законов или правил, которые ожидаются для действительных членов экземпляра, например законов Монады:
- returrn a → = f == f a
- m → = return == m
- (m → = f) → = g == m → = (\ x → f x → = g)
часто можно использовать для упрощения монадического кода.
Ответ 21
Усовершенствованное сопоставление шаблонов
См. соответствие шаблонов
Ответ 22
Учет параллельного списка
(Специальная функция GHC)
fibs = 0 : 1 : [ a + b | a <- fibs | b <- tail fibs ]
Ответ 23
Лень
Вездесущая лень означает, что вы можете делать такие вещи, как define
fibs = 1 : 1 : zipWith (+) fibs (tail fibs)
Но это также дает нам много более тонких преимуществ с точки зрения синтаксиса и рассуждений.
Например, из-за строгости ML приходится иметь дело с ограничение значения и очень осторожно отслеживать циклические привязки let, но в Haskell, мы можем позволить каждому let быть рекурсивным и не нужно различать val
и fun
. Это удаляет основную синтаксическую бородавку с языка.
Это косвенно приводит к нашему прекрасному предложению where
, потому что мы можем безопасно перемещать вычисления, которые могут использоваться или не использоваться из основного потока управления, и пусть лень обсуждает результаты.
Мы можем заменить (почти) все те функции стиля ML, которые должны принимать() и возвращать значение, с ленивым вычислением значения. Есть причины не делать этого время от времени, чтобы избежать утечки пространства с помощью CAF, но такие случаи встречаются редко.
Наконец, он допускает неограниченное eta-сокращение (\x -> f x
можно заменить на f). Это делает комбинаторно-ориентированное программирование для таких вещей, как комбинаторы парсеров, намного приятнее, чем работа с подобными конструкциями на строгом языке.
Это помогает вам, когда рассуждает о программах в стиле без ссылок, или о переписывании их в стиле без точек и уменьшает шум аргументов.
Ответ 24
Перечисления
Любой тип, который является экземпляром Enum, может использоваться в арифметической последовательности, а не только в цифрах:
alphabet :: String
alphabet = ['A' .. 'Z']
Включая собственные типы данных, просто выведите из Enum, чтобы получить реализацию по умолчанию:
data MyEnum = A | B | C deriving(Eq, Show, Enum)
main = do
print $ [A ..] -- prints "[A,B,C]"
print $ map fromEnum [A ..] -- prints "[0,1,2]"
Ответ 25
Монады
Они не скрыты, но они просто повсюду, даже если вы не думаете о них (списки, возможно-типы)...