Хабр Курсы для всех
РЕКЛАМА
Практикум, Хекслет, SkyPro, авторские курсы — собрали всех и попросили скидки. Осталось выбрать!
Компилятор, кстати, так и делает — когда он встречаем имя функции, то подставляет вместо него выражение, определённое в её теле.Нет, так он не делает: если он начнет так делать, то любая рекурсия приведет к исполнимому файлу бесконечного размера.
Распараллеливание мы получаем «из коробки», просто потому, что пишем на Haskell.И снова странное утверждение. Не слышал я про распаралеливающие трансляторы Хаскеля. Если бы такое делалось автоматически — зачем был бы нужен оператор `par`?
Haskell запоминает вычисленный однажды результат, и при повторном вызове функции с теми же аргументами не вычисляет его снова, а подставляет ранее вычисленный.
factorial 0 = 1
factorial x = x * (factorial (x - 1))
square x = x * x
result = square (factorial 1000000)
factorial 0 = 1
factorial x = x * (factorial (x - 1))
result1 x = (factorial x) * (factorial (x - 1)) * x
result = result1 1000000
x
у нас является выражением типа (2 + 3) * 5
: result1 x = (factorial x) * (factorial (x - 1)) * x
x
будут ссылаться на thunk
, содержащий это невычисленное выражение. Вычислено оно будет тогда, когда понадобится один из этих x
. Как только один из иксов понадобится, выражение (2 + 3) * 5
будет заменено в памяти на 25
. А поскольку на этот участок памяти ссылаются все иксы, то они автоматически будут ссылаться уже на 25
. fac n = product [1..n]
:)Компилятор, кстати, так и делает — когда он встречаем имя функции, то подставляет вместо него выражение, определённое в её теле.
Нет, так он не делает: если он начнет так делать, то любая рекурсия приведет к исполнимому файлу бесконечного размера.
Вы можете проверить это сами: сколько бы раз вы ни передавали этой функции в качестве аргументов значения 1, 2 и 4, вы всегда в качестве результата получите 7
addThreeNumbers
с аргументами 1, 2 и 4, которая миллиард раз вернула 7 не означает, что на следующий раз она вернет именно 7.(>>=)
. Мы просто пишем код, как будто вычисления успешны (или у нас более нуля результатов вычислений), а неуспешные случаи вычислений обрабатываются автоматически. Грубо говоря, эти монады позволяют нам не писать кучу вложенных if then else
и ручных манипуляций с разветвляющимися вычислениями. get
и put
(а также выводимая из этих функций функция modify
). В разных монадах они могут называться по-разному, но суть их одна и та же: вы можете получить текущее значение внешнего окружения (состояния), произвести с ним какие-то манипуляции и заменить текущее значение новым его. В монаде IO
, правда, функции get
и put
размножились, потому что мы можем хотеть получить строку или символ, хэндл, название директории и т.д., но суть их точно такая же. А работа с этими монадами заключается в реализации нужного вам алгоритма в do
-блоке, в котором вы композируете эти функции и функции, работающие со значениями ваших основных вычислений, так, как вам нужно. do
-блока к значениям состояния и «основных» вычислений, которые вы «вытащили» из обёртки. Они производят вычисления, а затем возвращают результат обратно в вашу обёртку (в do
-блок, откуда пришли их аргументы). public Either<string, Error> UpdateCustomerWithErrorHandling() {
return receiveRequest
.Bind(validateRequest)
.Bind(canonicalizeEmail)
.Bind(updateDbFromRequest)
.Bind(sendEmail)
.Bind(returnMessage);
}
Тот же пример из слайдов мог бы выглядеть как
Автор, присоединяюсь. Очень хочется увидеть продолжение.
Добавил статью в закладки в 2015. Понял только в 2019. Однако :)
«Страшные» абстракции Haskell без математики и без кода (почти). Часть I