Скрытые возможности Haskell

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

  • Попробуйте ограничить ответы на ядро ​​Haskell
  • Одна функция на ответ
  • Дайте пример и краткое описание функции, а не просто ссылку на документацию.
  • Обозначьте объект, используя жирный заголовок в первой строке.

См. Сообщения meta.stackexchange.com/questions/56669/…, meta.stackexchange.com/questions/57226/… и связанные с ними мета-сообщения.

Roger Pate 16.07.2010 06:53

Считаются ли «простые вещи» скрытыми функциями Haskell? Является ли этот вопрос списком обсуждения каждой функции Haskell?

Roger Pate 16.07.2010 06:54

Хорошо, почему это закрывается, когда есть то, что 103 некоторые другие вопросы с названием «Скрытая функция *» не закрыты, давай!

Pharaun 19.07.2010 23:59
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
32
3
18 334
25
Перейти к ответу Данный вопрос помечен как решенный

Ответы 25

Дополнительный макет

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

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

Оператор Fixity

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

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> print (a foo b) 6

bortzmeyer 17.10.2008 21:22

Да, это должны быть обратные кавычки. Неужели есть люди, которые считают это скрытой функцией?

Jonathan Tran 18.10.2008 00:12

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

Santiago Palladino 18.10.2008 00:39

«Infix означает отсутствие ассоциативности» - как это возможно? Если я использую infix 6 +++, то как будет оцениваться 1 +++ 2 +++ 3? Мне кажется, это должен быть либо (1 +++ 2) +++ 3, либо 1 +++ (2 +++ 3). Даже те, которые могут оценивать одно и то же, Haskell должен выбрать то или иное, чтобы на самом деле делать, верно?

MatrixFrog 01.03.2010 21:33

Ошибка анализа приоритета не может смешивать +++' [infix 6] and +++ '[инфикс 6] в одном и том же инфиксном выражении

Samuel Danielson 26.11.2010 19:53

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..] не внизу (что можно найти сразу), напечатает «готово» и выйдет.

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

Что значит «не дно»?

Claudiu 17.10.2008 18:02

Добавил объяснение. Это ответ на ваш вопрос?

Jonathan Tran 17.10.2008 18:43

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

Jonathan Tran 17.10.2008 18:49

Ах, спасибо, это проясняет! Как он может проверить, что что-то повторяется вечно? Разве это не проблема остановки, которую невозможно решить?

Claudiu 17.10.2008 20:18

Кроме того, разве [1 ..] не зацикливается вечно? (По крайней мере, он генерирует бесконечное количество целых чисел

Claudiu 17.10.2008 20:19

Я считаю, что если что-то зацикливается вечно, вызов seq также будет бесконечным. [1 ..] генерирует бесконечное количество целых чисел. Из-за лени они фактически не будут вычислены, пока вы не попытаетесь взять один, а seq этого не делает.

Jonathan Tran 17.10.2008 20:33

[1 ..] просто оценивается как 1: [2 ..], то есть список, который начинается с 1, за которым следует другой список. Он будет повторяться вечно, только если вы попытаетесь добраться до конца списка.

ephemient 17.10.2008 22:24

Кроме того, GHC обнаруживает (некоторые) бесконечные циклы - вы можете просто определить «loop = loop», но если вы попытаетесь распечатать значение «loop», это приведет к ошибке «<<loop>>».

ephemient 17.10.2008 22:27

больше информации о нижнем значении: haskell.org/haskellwiki/Bottom

Jared Updike 31.10.2008 20:28

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

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

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.

Расширения синтаксиса в GHC позволяют записать это сейчас: fib = 1: 1: [a + b | a <- fib | b <- хвостик]; однако я бы написал это 1: 1: zipWith (+) fib (tail fib)

ephemient 17.10.2008 21:46

Ваш первый пример на самом деле демонстрирует еще более скрытую особенность Haskell: вы можете использовать полные шаблоны при определении значений / функций!

Martijn 05.06.2009 12:48

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

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

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

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]

Должен ли я разделить это на несколько ответов?

ephemient 17.10.2008 22:19

Вы бы предпочли получить 1 голос "за" (или, возможно, 1 "против" за несоблюдение правил вопроса) или 1 голос "за" за каждую функцию?

Jonathan Tran 17.10.2008 22:55

Ну тогда. А вот и наводнение ...

ephemient 17.10.2008 22:57

Я не совсем понимаю, как это работает - не могли бы вы объяснить это немного подробнее?

Claudiu 17.10.2008 23:51

Список - это экземпляр Monad. >> = для конкатенации списков.

Jonathan Tran 17.10.2008 23:54

Это должно быть (>> =) = flip concatMap.

Martijn 05.06.2009 12:43

@Martijn: Верно. Думаю, никто другой не заметил, что (>>=) = concatMap не проверяет типы;)

ephemient 05.06.2009 17:43

После более подробного ознакомления с ней я думаю, что монада списка немного проблематична ... Жалоба в том, что это не единственный возможный способ заставить список следовать монадическим законам. Обратите внимание, что ответ гласит: «Если мы будем использовать реализацию» - но почему мы должны быть вынуждены использовать эту реализацию? Другая допустимая реализация была бы похожа на монаду Maybe, где пустой список эквивалентен Nothing, а непустой список эквивалентен Just first element ... Может быть, не лучший пример, но я думаю, что суть остается.

Aidan Cully 14.12.2009 20:46

List Monad определенно не единственная возможная или разумная реализация, удовлетворяющая правилам Monad, но она задокументирована в отчете Haskell и является наиболее полезной. Если бы он работал как Maybe, [x | x <- xs] и do x <- xs; return x больше не были бы эквивалентными.

ephemient 14.12.2009 21:30

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

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

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

g . f
f >>> g

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

Отметим также, что (<<<) = (.) :)

porges 15.04.2009 14:19

Избегайте скобок

Функции (.) и ($) в 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 -}

Я предпочитаю скобки. Они более ясны.

bortzmeyer 19.10.2008 20:16

Я предпочитаю исключать круглые скобки везде, где это возможно, даже когда я работаю на менее удобных языках, таких как C (при условии, что все читают мой код должен знать, все приоритеты операторов). Дело вкуса.

ephemient 19.10.2008 22:35

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

pitr 06.07.2009 11:57

Я пришел с Java, поэтому не использовать круглые скобки в Haskell - одно удовольствие.

miloshadzic 23.12.2009 01:13

Скобки - как раз правильный символ для вложенности. Зачем искать что-то другое?

Iulius Curt 06.03.2012 00:26

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

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

Как насчет вложенных однострочных комментариев? ;-)

Jonas Kölker 21.02.2009 17:33

Довольно охранники

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

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

Если я правильно помню, GHC на самом деле рассматривает otherwise несколько иначе, чем True: используя True, вы получаете предупреждение о том, что вы не охватили все возможные случаи; с otherwise вы этого не сделаете.

MatrixFrog 02.11.2011 10:38

@MatrixFrog Я не получаю предупреждения с True, но если я использую not False, True || False, True || True, True || undefined и т. д., Он предупреждает. (GHC 6.12.3, с параметром компиляции -Wall).

L̲̳o̲̳̳n̲̳̳g̲̳̳p̲̳o̲̳̳k̲̳̳e̲̳̳ 16.11.2011 08:46

@Longpoke они, вероятно, пошли дальше и сделали то же самое для True, что они ранее сделали для otherwise. Я бы сказал, что, вероятно, было бы хорошо продолжать использовать otherwise просто потому, что он читается немного лучше.

MatrixFrog 18.11.2011 03:22

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

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

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

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

Импорт и экспорт - это хорошо.

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'
Ответ принят как подходящий

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

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

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

Я думаю, что следующее должно по-прежнему выдавать эту ошибку: ignorefoo f = 1 where (Foo a) = f

Jason Dagit 19.10.2008 07:11

Ах, ты прав, который заставляет мозг GHC взорваться. Я не считать они еще исправили, но я не мог сломать мой простой пример ...

ephemient 19.10.2008 19:07

@ephemient Они не могут это исправить, потому что "исправление" нарушит систему типов Haskell. Подумайте об этом так: все в Haskell имеет тип. В строке where Foo a = f какой типа a?

Lambda Fairy 25.11.2011 10:33

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

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

Определенный таким образом, мне нужно поставить a или b в скобки, когда с, если я этого не сделаю. Здесь может помочь инфикс?

gorlum0 11.02.2011 12:17

@ gorlum0: Конечно, просто дайте ему более высокую степень фиксации, чем $, например infixr 1 ? или что-то в этом роде.

ephemient 11.02.2011 17:22

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

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

+1, Полностью зависимый типизированный язык программирования, такой как Agda, довольно сложно понять / работать с ним (по крайней мере, для меня :-). Но GADT уже приносят в Haskell некоторые из больших преимуществ зависимых типов, не добавляя слишком много умственной нагрузки для моего мозга.

Tom Lokhorst 15.04.2009 14:18

В экземпляре Num вы имеете в виду "fromInteger = Num. FromInteger"?

Samuel Danielson 27.11.2010 00:18

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

five :: Int
Just five = Just 5

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

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

Google

Google - ваш друг. Признаюсь, это не часть "ядра", поэтому cabal install 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, обязательно посмотрите hlint!

Hoogle также доступен в Интернете по адресу haskell.org/hoogle

ShreevatsaR 06.07.2009 21:52

Это лучший инструмент поиска API, с которым я когда-либо сталкивался. Потому что в большинстве случаев вы примерно знаете, что ищете; как в том, что он должен делать. Это позволяет вам искать все, что похоже на это.

Robert Massaioli 14.06.2010 09:20

Рассуждение по уравнениям

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

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

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

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

Это также хорошо проявляется в форме законов или прагм RULES, ожидаемых для действительных членов экземпляра, например, законы Монад:

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

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

Бесплатные теоремы

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

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

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

  1. forall f g. fmap f. fmap g = fmap (например, g)
  2. fmap id = id

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

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

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

+1 Совершенно не знал об этом, но это действительно круто; почему это не выше. Это избавляет от множества проверок, если вы можете получить результат прямо из сигнатуры типа. Читаю об этом сейчас.

Robert Massaioli 14.06.2010 09:15

Улучшенное сопоставление с образцом

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

    let ~(Just x) = someExpression
    

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

Это дает ошибку компилятора: Происходит проверка: невозможно построить бесконечный тип: t = Возможно t

Martijn 09.10.2009 01:03

Монады

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

Это действительно так, и монады, и другие классы типов, которые их окружают, настолько мощны, что было бы глупо использовать их на всех возможных языках. Я считаю, что в тринадцатом выпуске «The Monad.Reader» есть действительно хорошее объяснение того, как все это происходит вместе.

Robert Massaioli 14.06.2010 09:18

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

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

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

Лень

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

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

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

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

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

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

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

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

Ссылка ограничения значения теперь мертва.

Squidly 10.02.2011 05:58

let 5 = 6 in ... - это действительный Haskell.

Позволяет ли 5 ​​= 6 ... иметь какой-либо эффект?

I. J. Kennedy 13.11.2009 19:56

Его постоянный образец 5 сопоставлен со значением 6, но поскольку let-привязки полностью ленивы, сопоставление никогда не будет выполнено.

Christian Klauser 14.12.2009 17:16

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

В арифметической последовательности можно использовать любой тип, являющийся экземпляром 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]"

Когда вы впервые видите это, удивительно, что [A ..] на самом деле является конечным перечислением.

u0b34a0f6ae 12.11.2010 13:12

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

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

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

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