Что мы заново нашли или двигаем ересь в массы
От: Mirrorer  
Дата: 26.02.08 08:48
Оценка: 82 (10)
Навеяно топиком
Что мы потеряли?
Автор: Mamut
Дата: 24.08.06
и беглым просмотром книжки F# Expert (2007)

Мне удалось раздобыть выкинутую кем-то книгу по Лиспу, написанную в 1982 году Уинстоном и Хорном (Winston and Horn). Что приводит меня в изумление — это те типы проблем, которые решались в конце 70-х. Из оглавения [1]:
18: Lisp in Lisp (Building an interpreter)
24: Symbolic pattern matching
26: Rule based expert systems and forward chaining
30: Procedure writing programs

...
Что меня волнует, так это то, что "старые" языки могли выполнять задачи, которые (как мне кажется) сложны или вообще невыполнимы в современных языках. Как так произошло, что все эти Java и C# потеряли все эти возможности, а называются "современными"?


В качестве ответа Чемберлену список глав из F# Professional

CHAPTER 9 Introducing Language-Oriented Programming
CHAPTER 12 Working with Symbolic Representations
CHAPTER 13 Reactive, Asynchronous, and Concurrent Programming
CHAPTER 16 Lexing and Parsing

Похоже не все возможности современные языки потеряли-то.

Что приятно удивило, так это то, что F# из чисто экспериментальной поделки которой он был год-полтора назад превратился в нормальный инструмент для работы.
Единственное чего пожалуй не хватает это полноценной интеграции в студию, но MS обещает включить F# в стандартную поставку следующей студии. Ну и есть подозрение что Preview версии будут выпускаться по ходу дела.

Краткий обзор того, чего умеет F# сейчас.
pattern matching, первоклассные функции, partial application, ООП, лямбды, это все понятно и в дополнительных объяснениях не нуждается

Imutable-ность по умолчанию. Для того, чтобы специфицировать изменяемую переменную нужно явно писать слово mutalbe

Но весь .net Framework естественно насквозь мутабельный.

Порадовала возможность писать
html |> getWords |> List.filter (fun s -> s = "href")

вместо
List.filter(s=> s == "href", getWords(html))

реализация этого оператора
let (|>) x f = f x

Мелочь, а приятно

Еще F# умеет быть ленивым, там где надо
// тут сумма не вычислится
let sum = lazy{ 2 + 2}

// а тут посчитается
let val = sum.Force()

Вопрос о том, удобно ли будет пользоваться явным форсированием вычислений остается открытым. Как мне подсказали умные люди в Scala ленивые вычисления сами форсирутся при первом обращении к ним для получения результата.

computation expressions или workflows.
примеры workflows
seq { for i in 0 .. 3 -> (i,i*i) }

здесь все просто, обычные list comprehension
а вот тут удобное пользование асинхронными функциями
async { let req  = WebRequest.Create("http://moma.org/")
        let! resp  = req.GetResponseAsync()
        let stream = resp.GetResponseStream()
        let reader = new StreamReader(stream)
        let! html = reader.ReadToEndAsync()
        html }

As you saw in Chapter 9, the key to understanding the F# workflow syntax is always to
understand the meaning of let!. In the case of async workflows, let! executes one asynchro-
nous computation and schedules the next computation for execution once the first
asynchronous computation completes.


А вот тот же кусок кода, но без сахара

async.Delay(fun () ->
   async.Let(WebRequest.Async("http://moma.org/"), (fun req ->
      async.Bind(req.GetResponseAsync(), (fun resp ->
         async.Let(resp.GetResponseStream(), (fun stream ->
            async.Let(new StreamReader(stream), (fun reader ->
               async.Bind(reader.ReadToEndAsync(), (fun html ->
                  async.Return(html))))))))))

Таким вот образом ересь проникает в мейнстрим, ага

Ну и последнее. Цитирование и квазицитирование.
// это простое выражение
let sum = <@  2 + 2 @>

// а это выражение, в котором можно потом специфицировать часть
let genFun = <@ 2 + _ @>

// и его можно специфицировать вот так
// после выполнения значение anotherFun будет <@ 2 + 1 @>
let anotherFun = genFun <@ 1 @>

// а можно и так
let yetAnotherFun = genFun <@ cos(0) @>
//после выполенения значение yetAnotherFun будет <@ 2 + cos(0) @>


Что я вообще хотел скзать этим постом. Правильные идеи рано или поздно попадут в менстрим. В пережеванном обсахаренном, возможно упрощенном виде, но они там будут. И если F# пока мейнстримом назвать сложно, но раз MS уже взялась за его продвижение, то она его пропихнет в массы. Почему-то мне так кажется.
Что не может не радовать.

P.S. порядок глав считаю симптоматичным

CHAPTER 3 Introducing Functional Programming
CHAPTER 4 Introducing Imperative Programming

... << RSDN@Home 1.2.0 alpha rev. 676>>
Re: Что мы заново нашли или двигаем ересь в массы
От: Andy Panda США  
Дата: 26.02.08 08:57
Оценка:
Здравствуйте, Mirrorer, Вы писали:

Похоже, надо её начинать читать
... << RSDN@Home 1.2.0 alpha rev. 789>>
Re: Что мы заново нашли или двигаем ересь в массы
От: cadet354 Россия
Дата: 26.02.08 09:06
Оценка:
Здравствуйте, Mirrorer, Вы писали:


M>

M>CHAPTER 13 Reactive, Asynchronous, and Concurrent Programming


а тут про что? все те же локи, семафоры и т.д., или как в erlang обмен сообщениями?
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re: Что мы заново нашли или двигаем ересь в массы
От: Curufinwe Украина  
Дата: 26.02.08 09:34
Оценка:
Здравствуйте, Mirrorer, Вы писали:





M>computation expressions или workflows.


M>здесь все просто, обычные list comprehension

M>а вот тут удобное пользование асинхронными функциями
M>
M>async { let req  = WebRequest.Create("http://moma.org/")
M>        let! resp  = req.GetResponseAsync()
M>        let stream = resp.GetResponseStream()
M>        let reader = new StreamReader(stream)
M>        let! html = reader.ReadToEndAsync()
M>        html }
M>


M>А вот тот же кусок кода, но без сахара


M>
M>async.Delay(fun () ->
M>   async.Let(WebRequest.Async("http://moma.org/"), (fun req ->
M>      async.Bind(req.GetResponseAsync(), (fun resp ->
M>         async.Let(resp.GetResponseStream(), (fun stream ->
M>            async.Let(new StreamReader(stream), (fun reader ->
M>               async.Bind(reader.ReadToEndAsync(), (fun html ->
M>                  async.Return(html))))))))))
                                    
M>

M>Таким вот образом ересь проникает в мейнстрим, ага

Заменили слово "монада" на "computation expressions" чтобы народ не пугался заранее

M>Ну и последнее. Цитирование и квазицитирование.

M>
M>// это простое выражение
M>let sum = <@  2 + 2 @>

M>// а это выражение, в котором можно потом специфицировать часть
M>let genFun = <@ 2 + _ @>

M>// и его можно специфицировать вот так
M>// после выполнения значение anotherFun будет <@ 2 + 1 @>
M>let anotherFun = genFun <@ 1 @>

M>// а можно и так
M>let yetAnotherFun = genFun <@ cos(0) @>
M>//после выполенения значение yetAnotherFun будет <@ 2 + cos(0) @>
M>


В более сложных случаях квазицитирование F# сильно проигрывает оному из Nemerle по понятности (ИМХО).


P.S. Книжку в бумажном виде читали?
... << RSDN@Home 1.2.0 alpha rev. 693>>
Re[2]: Что мы заново нашли или двигаем ересь в массы
От: Mirrorer  
Дата: 26.02.08 09:51
Оценка:
Здравствуйте, Curufinwe, Вы писали:

C>В более сложных случаях квазицитирование F# сильно проигрывает оному из Nemerle по понятности (ИМХО).

Меня удивил сам факт наличия такой вещи.

C>P.S. Книжку в бумажном виде читали?

Из бумажной копипастить куски кода неудобно
... << RSDN@Home 1.2.0 alpha rev. 676>>
Re[2]: Что мы заново нашли или двигаем ересь в массы
От: Mirrorer  
Дата: 26.02.08 09:51
Оценка: 8 (1)
Здравствуйте, cadet354, Вы писали:
M>>

M>>CHAPTER 13 Reactive, Asynchronous, and Concurrent Programming


C>а тут про что? все те же локи, семафоры и т.д., или как в erlang обмен сообщениями?

Passing and Processing Messages
A distinction is often made between shared-memory concurrency and message passing
concurrency. The former is often more efficient on local machines and is covered in the section
“Using Shared-Memory Concurrency” later in this chapter. The latter scales to systems where
there is no shared memory, for example, distributed systems, and can also be used to avoid
performance problems associated with shared memory. Asynchronous message passing and
processing is a common foundation for concurrent programming, and in this section we look
at some simple examples of message-passing programs.

... << RSDN@Home 1.2.0 alpha rev. 676>>
Re[3]: Что мы заново нашли или двигаем ересь в массы
От: Curufinwe Украина  
Дата: 26.02.08 10:06
Оценка:
Здравствуйте, Mirrorer, Вы писали:

C>>P.S. Книжку в бумажном виде читали?

M>Из бумажной копипастить куски кода неудобно

может поделитесь ссылкой?
... << RSDN@Home 1.2.0 alpha rev. 693>>
Re[4]: Что мы заново нашли или двигаем ересь в массы
От: Schade Россия  
Дата: 26.02.08 10:09
Оценка: 8 (1)
Здравствуйте, Curufinwe, Вы писали:

C>может поделитесь ссылкой?


Не знаю, как насчет финальной версии, а черновики лежат в блоге Дона здесь
Re: Что мы заново нашли или двигаем ересь в массы
От: Schade Россия  
Дата: 26.02.08 10:31
Оценка:
Здравствуйте, Mirrorer, Вы писали:

M>Что приятно удивило, так это то, что F# из чисто экспериментальной поделки которой он был год-полтора назад превратился в нормальный инструмент для работы.

M>Единственное чего пожалуй не хватает это полноценной интеграции в студию, но MS обещает включить F# в стандартную поставку следующей студии. Ну и есть подозрение что Preview версии будут выпускаться по ходу дела.
Интересно, express edition будет?

M>Еще F# умеет быть ленивым, там где надо

M>
M>// тут сумма не вычислится
M>let sum = lazy{ 2 + 2}

M>// а тут посчитается
M>let val = sum.Force()
M>

M>Вопрос о том, удобно ли будет пользоваться явным форсированием вычислений остается открытым. Как мне подсказали умные люди в Scala ленивые вычисления сами форсирутся при первом обращении к ним для получения результата.
В Scala — может быть. Тут надо явно форсить. Чтобы не было мучительно больно, можно поступить как Окасаки в его PFDS:
let (!$) = Lazy.force
let val = !$ sum

У Окасаки, правда, его вымышленная синтаксическая конструкция еще и участвует в Pattern-matching. NB: ленивыми имеет смысл делать только "тяжелые" вычисления, бо текущая реализация ленивости в F# довольно тормозная (гораздо хуже, чем в Haskell )

M>computation expressions или workflows.

M>Таким вот образом ересь проникает в мейнстрим, ага
Ага, computation expressions. Warm fuzzy things
Все не доходят руки попробовать их "в бою", но терзают смутные... Похоже, вся эта вавилонская башня из let'ов и bind'ов конструируется в рантайме, что настораживает.

M>Что я вообще хотел скзать этим постом. Правильные идеи рано или поздно попадут в менстрим. В пережеванном обсахаренном, возможно упрощенном виде, но они там будут. И если F# пока мейнстримом назвать сложно, но раз MS уже взялась за его продвижение, то она его пропихнет в массы. Почему-то мне так кажется.

M>Что не может не радовать.
Таки да. Лишь бы совсем в манную кашку не пережевали.

M>P.S. порядок глав считаю симптоматичным

M>

M>CHAPTER 3 Introducing Functional Programming
M>CHAPTER 4 Introducing Imperative Programming

Ну, все-таки ML в большей степени ФЯ, чем ИЯ
Re[2]: Что мы заново нашли или двигаем ересь в массы
От: Mirrorer  
Дата: 26.02.08 11:29
Оценка:
Здравствуйте, Schade, Вы писали:
S>Интересно, express edition будет?


S> NB: ленивыми имеет смысл делать только "тяжелые" вычисления, бо текущая реализация ленивости в F# довольно тормозная (гораздо хуже, чем в Haskell )

А ссылки нету на тесты ? Или откуда там эта информация бралась. Любопытно было бы посмотреть.

S>Ага, computation expressions. Warm fuzzy things

Тсссс! Распугаете народ понимаешь..

S>Все не доходят руки попробовать их "в бою", но терзают смутные... Похоже, вся эта вавилонская башня из let'ов и bind'ов конструируется в рантайме, что настораживает.

не вижу причин почему оно не может собраться в компайл тайме

S>Таки да. Лишь бы совсем в манную кашку не пережевали.

Думаю полная кашка это будет VB, потом C#, а в F# все будет в удобоваримом виде еще.
... << RSDN@Home 1.2.0 alpha rev. 676>>
Re[4]: Что мы заново нашли или двигаем ересь в массы
От: Mirrorer  
Дата: 26.02.08 11:29
Оценка:
Здравствуйте, Curufinwe, Вы писали:

C>может поделитесь ссылкой?

Согласно правилам сайта не положено. Но гугл, он помогает
... << RSDN@Home 1.2.0 alpha rev. 676>>
Re[3]: Что мы заново нашли или двигаем ересь в массы
От: Schade Россия  
Дата: 26.02.08 21:55
Оценка: 16 (1)
Здравствуйте, Mirrorer, Вы писали:

S>> NB: ленивыми имеет смысл делать только "тяжелые" вычисления, бо текущая реализация ленивости в F# довольно тормозная (гораздо хуже, чем в Haskell )

M>А ссылки нету на тесты ? Или откуда там эта информация бралась. Любопытно было бы посмотреть.
Дык, ручками, однако. Вот простой синтетический пример:
#light

open System
open Lazy
type sc = System.Console

let inline (!$) = Lazy.force

type 'a CCell = Nil | Cons of 'a * 'a CCell Lazy

let rec fromArray arr pos =
    if pos < Array.length arr then Cons (arr.(pos), lazy fromArray arr (pos+1)) 
    else Nil
    
let runLazy arr () =
    let rec sum acc = function Nil -> acc | Cons (a, rest) -> sum (acc+a) (!$ rest)
    sum 0 (fromArray arr 0)
    
type 'a ECell = ENil | ECons of 'a * (unit -> 'a ECell)

let rec fromArrayE arr pos () =
    if pos < Array.length arr then ECons (arr.(pos), fromArrayE arr (pos+1)) else ENil
    
let runFun arr () =
    let rec sum acc = function ENil -> acc | ECons (a, rest) -> sum (acc+1) (rest())
    sum 0 (fromArrayE arr 0 () )
    
let measured act =
    let sd = System.Diagnostics.Stopwatch()
    sd.Start()
    act () |> ignore
    sd.Stop()
    double (sd.ElapsedMilliseconds) / 1000.0

do
    let data = [|1 .. 100000000 |]
    let timeL = measured (runLazy data)
    sc.WriteLine("runLazy time: {0} s", timeL);
    let timeF = measured (runFun data)
    sc.WriteLine("runFun time: {0} s", timeF);
    sc.ReadKey() |> ignore

Ленивый список, реализованный в первом случае (runLazy) — через lazy value, во втором (runFun) — через функцию unit -> Cell.
Понятно, что их поведение не совсем идентично — второй вариант может допустить многократное выполнение функции, но для иллюстрации сойдет.
Результат:
runLazy time: 18.503 s
runFun time: 3.669 s
Кстати, force по дефолту не потокобезопасный, то есть тормоза не от этого. С локом, естественно, еще дороже.
А, вот еще, с появлением в языке Active patterns появился и упомянутый Окасаки паттерн-матчинг по lazy value. С ним runLazy можно переписать так:
let runLazy arr () =
    let rec sum acc (Lazy l) = match l with Nil -> acc | Cons (a, rest) -> sum (acc+a) rest
    sum 0 (lazy fromArray arr 0)
Re[3]: Что мы заново нашли или двигаем ересь в массы
От: Schade Россия  
Дата: 02.03.08 12:40
Оценка: 8 (1)
Здравствуйте, Mirrorer, Вы писали:

S>>Все не доходят руки попробовать их "в бою", но терзают смутные... Похоже, вся эта вавилонская башня из let'ов и bind'ов конструируется в рантайме, что настораживает.

M>не вижу причин почему оно не может собраться в компайл тайме

Ну вот тут
Автор: Schade
Дата: 02.03.08
они в условиях, приближенных к боевым. Тоскливая картинка — в 7 раз медленнее чем Haskell. Не знаю, правда, "монады" ли в этом виноваты, или просто неприспособленность CLR к особенностям ФП-языков.
Re[3]: Что мы заново нашли или двигаем ересь в массы
От: VladD2 Российская Империя www.nemerle.org
Дата: 16.03.08 16:49
Оценка: :)
Здравствуйте, Mirrorer, Вы писали:

C>>В более сложных случаях квазицитирование F# сильно проигрывает оному из Nemerle по понятности (ИМХО).

M>Меня удивил сам факт наличия такой вещи.

Насколько я помню они квази-цитирование сделали как раз после того как на Немерле поглядели.

Что забавно когда-то на их сайтах вопросы по Немерле (точнее "почему у вас нет Х как в Немерле") можно было легко найти. А сейчас я что-то ни одного не нашел.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re: Что мы заново нашли или двигаем ересь в массы
От: Кэр  
Дата: 23.03.08 21:24
Оценка:
Здравствуйте, Mirrorer, Вы писали:

M>
M>async { let req  = WebRequest.Create("http://moma.org/")
M>        let! resp  = req.GetResponseAsync()
M>        let stream = resp.GetResponseStream()
M>        let reader = new StreamReader(stream)
M>        let! html = reader.ReadToEndAsync()
M>        html }
M>


А где здесь такие признаки работающего кода как using или явный Dispose()?
Re[2]: Что мы заново нашли или двигаем ересь в массы
От: EvilChild Ниоткуда  
Дата: 24.03.08 08:04
Оценка: -1
Здравствуйте, Кэр, Вы писали:

M>>
M>>async { let req  = WebRequest.Create("http://moma.org/")
M>>        let! resp  = req.GetResponseAsync()
M>>        let stream = resp.GetResponseStream()
M>>        let reader = new StreamReader(stream)
M>>        let! html = reader.ReadToEndAsync()
M>>        html }
M>>


Кэр>А где здесь такие признаки работающего кода как using или явный Dispose()?


Спрятаны под толстым слоем сахара.
Re[3]: Что мы заново нашли или двигаем ересь в массы
От: Кэр  
Дата: 24.03.08 15:17
Оценка:
Здравствуйте, EvilChild, Вы писали:

EC>Спрятаны под толстым слоем сахара.

Забавно. А как система понимает, какие объекты нужно dispos'ить сначала, а какие потом?
Re[4]: Что мы заново нашли или двигаем ересь в массы
От: Andy Panda США  
Дата: 24.03.08 16:11
Оценка: 1 (1)
Здравствуйте, Кэр, Вы писали:

Кэр>Здравствуйте, EvilChild, Вы писали:


EC>>Спрятаны под толстым слоем сахара.

Кэр>Забавно. А как система понимает, какие объекты нужно dispos'ить сначала, а какие потом?

На самом деле там код приведен для примера.
На практике используются старые добрые using'и

Cleaning Up with IDisposable, use, and using
Many constructs in the System.IO namespace need to be closed after use, partly because they
hold on to operating system resources such as file handles. This issue can be ignored when
prototyping code in F# Interactive. However, as we touched on earlier in this chapter, in more
94 CHAPTER 4 ¦ INTRODUCING IMPERATI VE PROGRAMMING
polished code you should use language constructs such as use var = expr to ensure the resource
is closed at the end of the lexical scope where a stream object is active. For example:


Exceptions may also be processed using the try ... finally ... construct. This guarantees to
run the finally clause both when an exception is thrown and when the expression evaluates
normally. This allows the programmer to ensure that resources are disposed after the completion
of an operation. For example, we can ensure that the web response from the previous example is
closed as follows:
let httpViaTryFinally(url: string) =
let req = System.Net.WebRequest.Create(url)
let resp = req.GetResponse()
try
let stream = resp.GetResponseStream()
let reader = new StreamReader(stream)
let html = reader.ReadToEnd()
html
finally
resp.Close()

In practice, you can use a shorter form to close and dispose of resources, simply by using
a use binding instead of a let binding. This closes the response at the end of the scope of the
resp variable, a technique is discussed in full in Chapter 8. Here is how the previous function
looks using this form:
let httpViaUseBinding(url: string) =
let req = System.Net.WebRequest.Create(url)
use resp = req.GetResponse()
let stream = resp.GetResponseStream()
let reader = new StreamReader(stream)
let html
... << RSDN@Home 1.2.0 alpha rev. 789>>
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.