In [24]:
type 't tree = Nil | Node of 't * 't tree * 't tree
let Leaf x = Node(x, Nil, Nil)
let t = Node(1, Node(2, Nil, Leaf(3)), Leaf(4))
let rec size = function
| Nil -> 0
| Node(_, L, R) -> 1 + (size L) + (size R)
size t
Out[24]:
In [26]:
1 |> (+)1 |> (*)2 |> printfn "%d"
In [27]:
let plus1 x f = f(x + 1)
let t2 x f = f(x * 2)
plus1 1 (fun x -> t2 x (printfn "%d"))
In [28]:
plus1 1 <| t2 <| printfn "%d"
Продолжение - такая функция, которая вызывается после завершения нашей функции.
In [29]:
let rec len cont = function
| [] -> cont(0)
| h::t -> len (fun x -> cont(x + 1)) t
len (fun x -> x) [1..100]
Out[29]:
In [34]:
let rec size t cont =
match t with
| Nil -> cont 0
| Node(_, L, R) -> size L (fun sL -> size R (fun sR -> cont(1 + sL + sR)))
size t (fun x -> x)
Out[34]:
Как видно, оно позволяет такие нелинейные рекурсии делать хвостовыми. Можно еще совместить с аккумулятором.
In [36]:
let rec size t acc cont =
match t with
| Nil -> cont acc
| Node(_, L, R) -> size L (1 + acc) (fun sL -> size R sL cont)
size t 0 (fun x -> x)
Out[36]:
Продолжение используются в реальной жизни, например, для асинхронных вычислений.
Это когда есть функция, которая внутри себя содержит переменную из внешнего контеста; ее значение захватывается и внутри функции не меняется. Пример: функция для вычисления производной. Запоминается шаг, который потом считается...
In [43]:
let derivative (dx:float) f = fun x -> (f(x + dx) - f(x)) / dx
let deriv = derivative 0.01
let косинус = deriv sin
косинус 0.
Out[43]:
In [39]:
let x = 1
let f z = x + z
f 1
Out[39]:
Генератором называется функция, которая по числу получается элемент бесконечной последовательности.
In [49]:
let mutable x = 1
let d z = z + z
x <- 2
f 1
Out[49]:
In [63]:
let new_counter n =
let mutable x = n in
fun () ->
x <- x + 1
x
let c = new_counter 0
c()
Out[63]:
In [51]:
c()
Out[51]:
In [52]:
c()
Out[52]:
Видно, что в замыкании применяется уже новое значение функции.
Хотим сделать генератор общего вида. Для, например, чисел Фибоначчи, потребуется уже два счетчика.
В более общем подходе у нас есть состояния и функция, которая совершает переход от старого состояния к новому.
In [57]:
let generator f n =
let mutable x = n in
fun () ->
x <- f x
x
let numbers = generator ((+)1) 0
numbers ()
numbers ()
Out[57]:
In [58]:
let fib = generator (fun (u, v) -> (v, u+v)) (1, 1)
fib ()
fib ()
Out[58]:
Немного не то, хотим еще выбрать один элемент из пары. Можно написать обертку, однако лучше использовать map
.
In [64]:
let map f g = // g = generator
fun () -> f(g()) // Применяем функцию f к новому значению генератора.
let fibs = fib |> map fst
fibs () // Обратим внимание, что мы не перезапускаем генератор из предыдущей ячейки, так что числа сразу со сдвигом.
Out[64]:
Как написать фильтр?
In [67]:
let rec filter p g =
fun () ->
let x = g()
if p x
then x
else (filter p g)()
let f3 = fibs |> filter (fun x -> x % 3 = 0)
f3 () // Получаем последовательные числа Фибоначчи, делящиеся на 3.
Out[67]:
Это все очень часто применяется в реальной жизни. В DotNet и Java есть понятие Enumerator - функция, которая возвращает следующий элемент.
На самом деле в F# уже есть готовые (ленивые) последовательности; написанное нами было нужно для понимания, как оно работает под капотом.
In [70]:
[1..100] // Выделяется память под 100 чисел.
{1..100} // Ничего не выделяется, лишь функция, умеющая выдывать элемент.
Out[70]:
In [73]:
[1..100] |> Seq.map ((*) 2) // Выделяется память под еще 100 чисел!
{1..100} |> Seq.map ((*) 2) // Память все равно не выделяется; добавляется новая функция.
Out[73]:
In [80]:
let fib = Seq.unfold (fun (u, v) -> Some(u, (u + v, u))) (1, 1)
fib
Out[80]:
In [81]:
fib |> Seq.filter (fun x -> x % 3 = 0)
Out[81]:
In [83]:
let numbers = Seq.unfold (fun x -> Some(x, x + 1)) 1
numbers
Out[83]:
In [84]:
let numbers = seq { // Никогда так нельзя писать.
let mutable x = 1
while true do
yield x // Это возвращает в Seq еще один элемент.
x <- x + 1
}
numbers
Out[84]:
In [95]:
let onec = seq { // Ужасно, некрасивно, нефункционально!
while true do
yield 1
}
ones
Out[95]:
In [101]:
let rec ones = seq { // Уже красиво.
yield 1 // Сначала возвращаем единицу.
yield! ones // А потом эту же последовательность рекурсивно.
}
ones
In [103]:
let rec numbers = seq {
yield 1
yield! Seq.map((+) 1) numbers
}
numbers
In [105]:
let fact n = [1..n] |> List.reduce (*) // Тратится много памяти.
In [106]:
let fact n = seq {1..n} |> Seq.reduce (*)
Заметим, что ..
- оператор с двумя аргументами, так что частично применим его.
In [112]:
let fact = (..) 1 >> Seq.reduce (*)
fact 7
Out[112]:
In [110]:
numbers |> Seq.scan (*) 1 // Самое красиво описание факториала.
Out[110]:
Все это используется, например, при работе с памятью. Всегда нужно предполагать, что файл не помещается в оперативную память. Для этого используется чтение построчно при помещении в бесконечный список.
Смотрим пример с "Анной Карениной".
In [ ]:
let Readlines fn =
seq {
use inp = File.OpenText fn in
while not(inp.EndOfStream) do
yield (inp.ReadLine())
}
Readlines @"c:\books\anna_kar.txt"
|> Seq.collect (fun x -> s.Split([' '; ',', '.', ';'])) // Стоит добавить еще раздлелителей.
|> Seq.filter (fun s -> s.Length > 0) // Здесь можно указать большую длину, чтобы избавиться от предлогов.
|> Seq.map (fun s -> s.ToLower()) // Приводим все в нижний регистр.
// |> Seq.maxBy (fun s -> s.Legnth) // Самое длинное слово.
|> Seq.groupBy (fun x -> x) // Получаем последовательность пар: (значение, [список;всех;таких;же;слов])
|> Seq.map (fun (u, s) -> fun (u, Seq.length, s)) // Теперь получили частотный словарь!
|> Seq.sortByDescending snd // Получаем самое популярное слово.
Демонстрация работы в F# с графиками на примере частотного словаря "Анны Карениной".
Можно делать анализ текста на позитивность / негативность на примере английской версии "Войны и мира". Есть список хороших и плохих слов.
In [117]:
let flip f x y = f y x
let readset f =
Readlines f
|> Seq.map (fub s -> s.Trim())
|> Seq.fold (flip Set.add) Set.empty // Получили множество слов, где можно проверять принадлежность.
let pos = readset @"c:\books\positive.txt"
let neg = readset @"c:\books\negative.txt"
let wt (s:string) =
s.Split([' '; ',', '.', ';'])
|> Seq.fold (fun acc s ->
if Set.contains s pos
then acc + 1
else if Set.cintains s neg
then acc - 1
else acc) 0
ReadLines @"c:\books\wap_1.txt"
|> Seqfilter (fun s -> s <> "")
|> Seq.scan (fun acc s ->
if (s.Contains("CHAPTER"))
then (s, s)
else (acc, s))) "" ""
|> Seq.filter (func (c, _) -> c <> "")
|> Seq.groupBy fst
|> Seq.map (fun (c, s) -> (c, s |. Seq.map snd |> Seq.,ap wt |> Seq.sum)) // Пары (глава, позитивность)
|> Chart.Bar
Это применяется для обработки данных, появляющихся в реальном времени. Например, анализ тональности Twitter'а.