2013-11-17 6 views
9

В Real World Haskell, они описывают комбинаторов, как это:Что комбинатор в Haskell

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

А потом они утверждают, что maybeIO функция комбинатор и его тип подписи выглядит следующим образом:

maybeIO :: IO a -> IO (Maybe a) 

Но все, что я могу видеть, что maybeIO это функция, которая принимает значение, завернутые в ИО monad и возвращает значение в монаде IO. Тогда как эта функция становится комбинатором?

+3

Есть некоторые технические значения слова «combinator», но по большей части люди Haskell полностью игнорируют их. В разговорной речи это обычно означает «ценность» или «штучка». – shachaf

+1

У Haskell wiki есть страница под названием Combinator с двумя определениями для вас: http://www.haskell.org/haskellwiki/Combinator –

ответ

13

Есть действительно 2 вещи, которые мы могли бы иметь в виду, когда говорим комбинатор. Слово немного перегружено.

  1. Мы обычно подразумеваем функцию, которая «объединяет» вещи. Например, ваша функция принимает значение IO и создает более сложное значение. Используя эти «комбинаторы», мы можем комбинировать и создавать новые комплексные значения из относительно нескольких примитивных функций для создания значений IO.

    Например, вместо создания функции, которая считывает 10 файлов, мы используем mapM_ readFile. Комбинаторы - это функции, которые мы используем для объединения и построения значений.

  2. Более строгий термин «научная наука» - это «функция без свободных переменных». Так

    -- The primitive combinators from a famous calculus, SKI calculus. 
    id a   = a -- Not technically primitive, genApp const const 
    const a b = a 
    genApp x y z = x z (y z) 
    

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

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

2

«Комбинатор» точно не определен в его использовании в Haskell. Правильнее использовать его для обозначения функций, которые принимают другие функции в качестве аргументов a la Combinator Calculus, но в терминологии Haskell часто перегружаются, что также означает функцию «модификации» или «комбинации», особенно Functor или Monad. В этом случае вы можете сказать, что комбинатор - это функция, которая «принимает какое-то действие или значение в контексте и возвращает новое, измененное действие или значение в контексте».

Ваш пример, maybeIO часто называют optional

optional :: Alternative f => f a -> f (Maybe a) 
optional fa = (Just <$> fa) <|> pure Nothing 

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

Причина, по которой они называются комбинаторами, также связана с тем, как они используются. Типичное место, чтобы увидеть optional (и действительно, Alternative в целом) находится в библиотеках комбинаторов парсеров.Здесь мы, как правило, строить основные парсеров с использованием простых Parser сек как

satisfy :: (Char -> Bool) -> Parser Char 
anyChar = satisfy (const True) 
whitespace = satisfy isSpace 
number  = satisfy isNumeric 

, а затем мы «изменить» свое поведение с помощью «комбинаторов»

-- the many and some combinators 
many :: Alternative f => f a -> f [a] -- zero or more successes 
some :: Alternative f => f a -> f [a] -- one or more successes 

many f = some f <|> pure [] 
some f = (:) <$> f <*> many f 

-- the void combinator forgets what's inside the functor 
void :: Functor f => f a -> f() 
void f = const() <$> f 

-- from the external point of view, this is another "basic" Parser 
-- ... but we know it's actually built from an even more basic one 
-- and the judicious application of a few "combinators" 
blankSpace = Parser() 
blankSpace = void (many whitespace) 

word :: Parser String 
word = many (satisfy $ not . isSpace) 

Oftentimes мы называем функции, которые сочетают в себе несколько функций/Functors/Monads "комбинаторы", а также, что, возможно, делает мнемонический смысл

-- the combine combinator 
combine :: Applicative f => f a -> f b -> f (a, b) 
combine fa fb = (,) <$> fa <*> fb 

-- the ignore-what's-next combinator 
(<*) :: Applicative f => f a -> f b -> f a 
fa <* fb = const <$> fa <*> fb 

-- the do-me-then-forget-me combinator 
(*>) :: Applicative f => f a -> f b -> f b 
fa *> fb = flip const <$> fa <*> fb 

line = Parser String 
line = many (satisfy $ \c -> c /= '\n') <* satisfy (=='\n') 

Но в конечном счете, Комбинаторы больше о намерении и использовании API, чем его строгом обозначении. Вы часто увидите библиотеки, созданные из «основных элементов», например, функций или satisfy, которые затем модифицируются и объединены с набором «комбинаторов». Приведенный выше пример Parser является типичным примером, но в целом это очень распространенный шаблон Haskell.

9

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

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

approxSqrt x = round (sqrt x) 

Вы также можете реализовать то, что мы действительно делаем здесь принимает две функции и построение более сложной функции, использующей их в качестве строительных блоков. Нам нужно какое-то клей положить их вместе, однако, и что клей (.):

approxSqrt = round . sqrt 

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

do 
    contents <- readFile "my_file.txt" 
    let messages = lines contents 
    ... 

Но! Что бы мы сделали, если бы у нас была функция, которая читает файл и возвращает содержимое в виде строк? Тогда мы могли бы сделать

do 
    messages <- readFileLines "my_file.txt" 
    ... 

Как выясняется, у нас есть функция, которая считывает файл и у нас есть функция, которая принимает большую строку и возвращает список строк в нем. Если бы у нас был только какой-то клей, чтобы связать эти две функции значимым образом, мы могли бы построить readFileLines! Но, конечно, это Haskell, этот клей легко доступен.

readFileLines = fmap lines . readFile 

Здесь мы используем два комбинатора! Мы используем (.) от ранее, и fmap на самом деле очень полезный комбинатор.Мы говорим, что это «лифты» чистое вычисление в монаде IO, и то, что мы на самом деле означает, что lines имеет тип подписи

lines :: String -> [String] 

но fmap lines имеет подпись

fmap lines :: IO String -> IO [String] 

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


Это только два простых примера. По мере того, как вы узнаете больше Haskell, вам будет нужно (и изобретать) все больше и больше функций комбинатора для себя. Haskell очень эффективен в том, как вы можете выполнять функции и преобразовывать их, комбинировать их, выворачивать их наизнанку, а затем склеивать их. Биты клея, которые нам иногда нужны, когда мы это делаем, те биты, которые мы называем комбинаторами.

+0

Существует строгое определение того, что такое комбинатор, и оно происходит от комбинаторной логики. Комбинаторы применительно к языкам программирования не были изобретены в Haskell. Дело в том, что сообщество Haskell заимствовало устоявшийся термин из компьютерных наук и начало использовать его здесь и там, загрязняя его значение. – fnt