Вопрос

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

  • Постарайтесь ограничить ответы ядром Haskell
  • По одной характеристике на каждый ответ
  • Приведите пример и краткое описание функции, а не просто ссылку на документацию
  • Выделите объект жирным шрифтом в качестве первой строки
Это было полезно?

Решение

Мой мозг просто взорвался

Если вы попытаетесь скомпилировать этот код:

{-# 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

Другие советы

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

В Haskell нет сокращенного троичного оператора.Встроенный if-then-else всегда является троичным и является выражением (императивные языки, как правило, имеют ?:=выражение, if=утверждение).Хотя, если ты хочешь,

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 :("

Хугл

Хугл - твой друг.Я признаю, что это не является частью "ядра", так что 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!

Свободные Теоремы

Фил Уодлер познакомил нас с понятием свободная теорема и с тех пор мы злоупотребляем ими в Haskell.

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

Например, существует два закона, которым должен удовлетворять каждый экземпляр функтора:

  1. для всех f g.fmap f .fmap g = fmap (f .g)
  2. fmap id = идентификатор

Но теорема о свободе говорит нам, что нам не нужно утруждать себя доказательством первой, но, учитывая вторую, она получается "бесплатно" только из подписи типа!

fmap :: Functor f => (a -> b) -> f a -> f b

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

Сокращение для обычной операции со списком

Следующие значения эквивалентны:

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 (или его засахаренном виде do-эквивалент).Список, он же [], является 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]

Вложенные многострочные комментарии.

{- inside a comment,
     {- inside another comment, -}
still commented! -}

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

{-# 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

Шаблоны в привязках верхнего уровня

five :: Int
Just five = Just 5

a, b, c :: Char
[a,b,c] = "abc"

Как это круто!Избавит вас от этого звонка fromJust и head время от времени.

Необязательный макет

Вы можете использовать явные фигурные скобки и точку с запятой вместо пробела (он же layout) для разделения блоков.

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 могут быть непосредственно созданы другими программами.

seq и ($!) только оценивать достаточно проверить, что чего-то нет внизу.

Следующая программа будет печатать только "там".

main = print "hi " `seq` print "there"

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

Например, следующий выводит "игнорируется" и завершается успешно.

main = foo (error "explode!")
  where foo _ = print "ignored"

seq известно, что он изменяет это поведение путем вычисления значения bottom, если его первый аргумент равен bottom.

Например:

main = error "first" `seq` print "impossible to print"

...или, что эквивалентно, без инфикса ...

main = seq (error "first") (print "impossible to print")

...взорвется с ошибкой на "первом".Он никогда не напечатает "невозможно распечатать".

Так что может показаться немного удивительным, что, хотя seq является строгим, он не будет оценивать что-либо так, как оценивают нетерпеливые языки.В частности, он не будет пытаться принудительно ввести все положительные целые числа в следующей программе.Вместо этого он проверит, что [1..] не находится внизу (что можно найти сразу), выведите "готово" и выйдите.

main = [1..] `seq` print "done"

Фиксированность оператора

Вы можете использовать инфикс, infixl или infixr ключевые слова для определения ассоциативности и приоритета операторов.Пример взят из ссылка:

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) после инфикса позволяет определить приоритет оператора, поскольку 9 является самым сильным.Infix означает отсутствие ассоциативности, тогда как infixl связывает влево, а infixr - вправо.

Это позволяет вам определять сложные операторы для выполнения высокоуровневых операций, записанных в виде простых выражений.

Обратите внимание, что вы также можете использовать двоичные функции в качестве операторов, если разместите их между обратными метками:

main = print (a `foo` b)

foo :: Int -> Int -> Int
foo a b = a + b

И как таковой, вы также можете определить для них приоритет:

infixr 4 `foo`

Избегание круглых скобок

Тот Самый (.) и ($) функции в 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 -}

Хорошенькие охранники

Prelude определяет otherwise = True, благодаря чему полные условия охраны читаются очень естественно.

fac n
  | n < 1     = 1
  | otherwise = n * fac (n-1)

Перечисления в стиле C

Объединение сопоставления с образцом верхнего уровня и арифметических последовательностей дает нам удобный способ определения последовательных значений:

foo : bar : baz : _ = [100 ..]    -- foo = 100, bar = 101, baz = 102

Читаемый функциональный состав

Prelude определяет (.) быть составом математической функции;это, g . f сначала применяется f, затем применяется g к результату.

Если вы import Control.Arrow, следующие значения эквивалентны:

g . f
f >>> g

Control.Arrow обеспечивает instance Arrow (->), и это приятно для людей, которые не любят читать функциональное приложение задом наперед.

let 5 = 6 in ... действителен Haskell.

Бесконечные списки

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

fib@(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.

Гибкая спецификация импорта и экспорта модулей

Импортировать и экспортировать - это приятно.

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'

Если вы ищете список или функцию более высокого порядка, она уже есть

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

-- factorial can be written, using the strict HOF foldl':
fac n = Data.List.foldl' (*) 1 [1..n]
-- there's a shortcut for that:
fac n = product [1..n]
-- and it can even be written pointfree:
fac = product . enumFromTo 1

Уравнительные рассуждения

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

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

Хороший пример такой формы рассуждений можно найти здесь:

http://www.haskell.org/pipermail/haskell-cafe/2009-March/058603.html

Это также прекрасно проявляется в форме законов или ПРАВИЛ, прагм, ожидаемых для действительных членов экземпляра, например, законов монад:

  1. возврат a >>= f == f a
  2. m >>= возврат == m
  3. (m >>= f) >>= g == m >>= (\x -> f x >>= g)

часто может использоваться для упрощения монадического кода.

Лень

Вездесущая лень означает, что вы можете делать такие вещи, как определение

fibs = 1 : 1 : zipWith (+) fibs (tail fibs)

Но это также дает нам множество более тонких преимуществ с точки зрения синтаксиса и рассуждений.

Например, из-за строгости ML приходится иметь дело с ограничение стоимости, и очень тщательно отслеживает циклические привязки let , но в Haskell мы можем позволить каждому let быть рекурсивным и не нуждаться в различении между val и fun.Это устраняет основную синтаксическую бородавку в языке.

Это косвенно порождает нашу прекрасную where предложение, потому что мы можем безопасно перенести вычисления, которые могут использоваться, а могут и не использоваться, из основного потока управления и позволить лени заниматься обменом результатами.

Мы можем заменить (почти) все те функции в стиле ML, которые должны принимать () и возвращать значение, простым ленивым вычислением значения.Есть причины не делать этого время от времени, чтобы избежать утечки Кафе, но такие случаи редки.

Наконец, это позволяет неограниченно снижать eta (\x -> f x может быть заменен на f).Это делает комбинаторно-ориентированное программирование для таких вещей, как комбинаторы синтаксического анализа, намного более приятным, чем работа с аналогичными конструкциями на строгом языке.

Это поможет вам при рассуждении о программах в стиле без точек или при переписывании их в стиле без точек и уменьшит шум аргументации.

Понимание параллельного списка

(Специальная GHC-функция)

  fibs = 0 : 1 : [ a + b | a <- fibs | b <- tail fibs ]

Улучшенное соответствие шаблону

  • Ленивые паттерны
  • Неопровержимые закономерности

    let ~(Just x) = someExpression
    

Видишь соответствие шаблону

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

Любой тип, который является экземпляром Перечисление может использоваться в арифметической последовательности, а не только в числах:

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]"

Монады

Они не то чтобы скрыты, но они просто повсюду, даже там, где вы о них не думаете (Списки, возможно, Типы) ...

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top