LINUX.ORG.RU

[scheme][haskell][oop][fp] Мысли вслух

 , ,


5

7

Была на ЛОРе такая тема — [Haskell] простой вопрос. Хотелось бы немножко её развить и высказаться на тему предпочтения того или иного языка при изучении ФП (графомания mode on :)).

У Scheme есть довольно давняя история использования в качестве подопытного языка в курсах изучения ФП. Я не знаю чем это вызвано, но факт остаётся фактом — есть известный курс у MIT (или был?) и разные полезные книжки — SICP, HTDP, PLAI, OOPLAI, которые обычно и советуют читать если нужно ознакомиться с ФП.

Касательно SICP — одним из сторонников использования ML для целей изучения ФП была написана статья (http://www.cs.kent.ac.uk/people/staff/dat/miranda/wadler87.pdf) в которой достаточно хорошо освещены некоторые недостатки Scheme. Если говорить про Haskell, то тут всё так же. Далее, по пунктам (опуская кое-что из того что уже было в той статье).

Более явный синтаксис

Вместо

(define (foo x y z)
  (if (> (+ x (* y z) 1) 7) (print (+ x y)) (print (- x y))))

будет

foo x y z = if x + y * z + 1 > 7 then print $ x + y else print $ x - y

при этом по-прежнему можно написать выражение в префиксной форме:

(if' ((>) ((+) x ((*) y z) 1) 7) (print ((+) x y)) (print ((-) x y)))

почти как в Scheme. То есть, кроме префикса также есть (расширяемый пользователем) инфикс (в том числе функции вроде ($) и (.) позволяющие в некоторых случаях опускать лишние аргументы у функций и некоторые скобки в выражениях) и разные специальные формы (вроде if, let, лямбды и т.п.). Во всём что не касается макросов это более удобно. S-выражения обретают свой особый смысл только когда доходит до их цитирования:

'(if (> (+ x (* y z) 1) 7) (print (+ x y)) (print (- x y)))

и разбора с целью написания макросов. Тем не менее, для изучения именно ФП эта возможность незначительна (ФП не про макросы, в SICP и HTDP не ни слова про макросы, в PLAI есть только немного, в OOPLAI — побольше). Про то как правильно (ну, _правильно_, то есть без использования s-выражений) организовывать символьные вычисления (вроде дифференцирования из SICP) также расказывается в упомянутой статье.

Каррированные функции

Такое определение, например:

(define add
  (lambda (n)
    (lambda (m)
      (+ m n))))

заменяется простым

add = (+)

так как все функции уже каррированные (позволяют частичное применение). Фактически, в хаскеле функция с n аргументами сразу задаёт n разных функций (выбор конкретной функции осуществляется во время компиляции и не имеет эффекта во время выполнения). Некаррированные функции это функции от кортежей (те и другие переводятся друг в друга с помощью ФВП carry/uncarry).

Частичное применение, секции, pointfree запись

add2 = (+ 2)

add2 5
7

вместо

(define add2 (add 2))

(add2 5)
7

Мутабельные замыкания

Это единственная вещь которая есть в Scheme и которую можно не увидеть сразу в хаскеле (и про неё нет в той статье). Тот тред был как раз про них. Чтобы прояснить этот момент, ниже приводятся некоторые примеры из OOPLAI и их аналоги на хаскеле.

Простейший вариант:

(define counter
  (let ((count 0))
    (lambda ()
      (begin
        (set! count (add1 count))
        count))))

(counter)
1
(counter)
2

аналог:

counter = (=~ (+ 1)) <$> new 0

тут (=~ (+ 1)) играет роль мутирующего «метода», а (new 0) — мутируемого «объекта», (<$>) — применение «диспетчера» (тут — просто единичный анонимный «метод»). Вся конструкция функториальная (не монадическая). Использование:

ctr <- counter      -- Инстанцирование класса counter объектом ctr.
ctr                 -- Применение единственного метода ((=~ (+ 1)) который).
1                   -- Результат.
ctr                 -- Снова.
2                   -- Другой результат.

Чуть более сложный пример:

(define counter-
  (let ((count 0))
    (lambda (cmd)
      (case cmd
        ((dec) (begin
                 (set! count (sub1 count))
                 count))
        ((inc) (begin
                 (set! count (add1 count))
                 count))))))

(counter- 'inc)
1
(counter- 'dec)
0

Для начала определим имена методов dec и inc:

data CounterMethod = Dec | Inc

это не символы и не строки (так что код не будет ill-typed как в примере на Scheme, иначе говоря, применение несуществующего метода не пройдёт компиляции). И теперь функцию:

counter' = dispatch <$> new 0
  where dispatch obj Dec = obj =~ flip (-) 1
        dispatch obj Inc = obj =~ (+ 1)

тут dispatch играет роль диспетчеризирующей функции которая получает объект (obj) и имя метода, а затем изменяет объект (как того требует метод). (new 0) — начальный объект.

Пример:

ctr <- counter'     -- Инстанцирование класса counter' объектом ctr.
ctr Inc             -- Применение метода Inc на объекте ctr.
1
ctr Inc
2
ctr Inc
3
ctr Dec             -- Тут уже метод Dec.
2
ctr Dec
1
ctr Dec
0

Тут применение (ctr Inc) весьма похоже на каноничное, через точку, obj.method и является, по сути, посылкой сообщения объекту.

Третий пример:

(define stack
  (let ((vals '()))
    (define (pop)
      (if (empty? vals)
          (error "cannot pop from an empty stack")
        (let ((val (car vals)))
          (set! vals (cdr vals))
          val)))
    (define (push val)
      (set! vals (cons val vals)))
    (define (peek)
      (if (empty? vals)
          (error "cannot peek from an empty stack")
        (car vals)))
    (lambda (cmd . args)
       (case cmd
         ((pop) (pop))
         ((push) (push (car args)))
         ((peek) (peek))
         (else (error "invalid command")))))) 

(stack 'push 1)
(stack 'push 2)
(stack 'pop)
2
(stack 'peek)
1
(stack 'pop)
1
(stack 'pop)
; cannot pop from an empty stack

аналогично:

data StackMethod = Pop | Push | Peek

stack = dispatch <$> new []
  where
    dispatch x Pop _  = get x >>= (x =~ tail >>) . return . head
    dispatch x Push y = x =~ (y :) >> return y
    dispatch x Peek _ = head <$> get x

и пример:

stk <- stack :: IO (StackMethod -> Int -> IO Int)
                    -- stack это параметрически-полиморфный класс, в данном
                    -- случае берётся его спецификация когда элементы стека
                    -- имеют тип Int (можно взять что-то более общее).
                    -- Этот специфичный класс инстанцируется объектом stk.
mapM_ (stk Push) [1, 2, 3]
                    -- (stk Push) это применение метода Push на объекте stk,
                    -- с помощью ФВП mapM_ оно производится для всех элементов
                    -- списка.
repeat 4 $ stk Pop __ >>= print
                    -- 4 раза вызывается метод Pop, элементы печатаются.
                    -- Последний раз вызывается исключение (так как стек пуст).
3
2
1
*** Exception: Prelude.head: empty list

тут точно так же — в StackMethod перечислены нужные методы для стека, функция stack определяет класс, то есть объединение данных и функций с нужным поведением, она имеет тип IO (StackMethod -> a -> IO a), то есть принимает метод, элемент стека и возвращает элемент стека (в IO, мутабельно), сама функция в IO (вся структура данных ведёт себя мутабельно).

Дальше в OOPLAI начинают использовать макросы для придания более удобоваримого вида этим конструкциям. В настоящем (ну, _настоящем_ :)) ФП этого не нужно — примитивные ООП конструкции объединяющие данные и функции выглядят естественно и так, и являются частным случаем использования ФВП, IO и ADT с паттерн-матчингом (последние два — для удобства). Использование макро-системы может иметь смысл разве что если действительно нужно реализовать сложную ООП систему (например, со множественным наследованием и изменяемой иерархией классов, впрочем, обойтись одними функциями тут тоже можно, просто придётся делать больше механических действий).

Ещё пример:

-- | Данные — конструктор и аккессоры.
data Point = Point
  { x :: Double
  , y :: Double
  } deriving ( Show, Eq ) -- ad-hoc перегруженные функции.

-- | Методы привязываемые к данным (это уже _не_ ad-hoc перегруженные функции).
data PointMethod = Pos | Mov

-- | Класс (= функция), объединяющий данные и методы.
pointClass :: Double -> Double -> IO (PointMethod -> Double -> Double -> IO Point)
pointClass initX initY = dispatch <$> new (Point initX initY)
  where
    -- | (Динамический) диспетчер по методам. Он принимает объект (Var Point),
    -- имя метода (PointMethod, т.е. статическое, в данном случае, сообщение)
    -- и два опционных аргумента для методов (Double -> Double). Эту функцию
    -- можно помещать непосредственно в Point.
    dispatch :: Var Point -> PointMethod -> Double -> Double -> IO Point
    dispatch obj Pos _ _ = get obj
    dispatch obj Mov x y = obj =: Point x y
pnt <- pointClass 2 4         -- Инстанцирование класса pointClass объектом pnt
                              -- с начальными значениями полей 2 и 4.
:t pnt
pnt :: PointMethod -> Double -> Double -> IO Point
pnt Pos __ __                 -- Вызов метода Pos на объекте pnt.
Point {x = 2.0, y = 4.0}
pnt Mov 3 5                   -- Вызов метода Mov.
Point {x = 3.0, y = 5.0}
pnt Pos __ __                 -- Положение изменилось:
{x = 3.0, y = 5.0}

Нужно заметить, что это всё довольно примитивные конструкции (простые функции и IO). В случае использования ADT для имён методов получится динамическая диспетчеризация с фиксированным набором методов (well-typed), если же переписать функцию dispatch с завязкой на хэш-табличку (которая должна быть переменной в данных класса), то будет динамическая диспетчеризация с пополняемым набором методов и перегруженными методами (одни и те же сообщения можно посылать разным инстанцированным объектам, их dispatch будет их искать в хэш-таблице и обрабатывать, это уже ill-typed, то есть с исключениями вида «нет такого метода»). Разные прочие вещи вроде наследования и self точно также можно изобразить (аггрегация данных, представление иерархии классов в данных (в переменной или нет, в зависимости от возможности менять иерархию) и более сложная функция dispatch), но как-то не интересно.

P.S.

Код на хаскеле использует такие упрощения:

import Prelude hiding ( repeat )
import Data.IORef
import Control.Applicative
import Control.Monad

type Var a = IORef a

new :: a -> IO (IORef a)
new = newIORef

get :: IORef a -> IO a
get = readIORef

(=~) :: IORef a -> (a -> a) -> IO a
x =~ f = modifyIORef x f >> get x

(=:) :: IORef a -> a -> IO a
x =: x' = x =~ const x'

repeat :: Monad m => Int -> m a -> m ()
repeat = replicateM_

__ :: a
__ = undefined

P.P.S.

OOP / ООП в контексте данного поста — объектно-ориентированное программирование в духе объединения данных и процедур, то есть в духе C++, Java, Python и т.п. _Не_ ООП в духе классы = структуры, методы = перегруженные функции, наследование = схемы агрегаций и распространения методов (как это в CLOS и классах типов Haskell).

★★★★

Ответ на: комментарий от chinarulezzz

Одна из самых хороших книжек, имхо — Elements of ML Programming Ульмана (в гугле легко находится электронный вариант). На сайтах MLton и SML/NJ есть различные туториалы (и список других интересных книг). Здесь лежит перевод одной из самых простых книжек — Introduction to Standard ML Харпера. Из реализаций можно рекомендовать MLton, PolyML и SML/NJ, остальные не так хороши. MLton — агрессивно оптимизирующий компилятор, самый быстрый для SML. SML/NJ — наиболее широко распространенная реализация, часто используется в книгах. PolyML — одна из самых первых реализаций SML, мне нравится в качестве интерактивной среды для обучения.

buddhist ★★★★★
()
Ответ на: комментарий от anonymous

Просто для справки - хаскелем может называется любое надмножество Haskell 98.

Не в тему: если в топике схема рассматривается как стандарт, то и хаскель так надо.

любое надмножество Haskell 98. Это позиция авторов стандартов.

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

anonymous
()
Ответ на: комментарий от anonymous

Фактической неправильностью. GHC реализует не «много чего», а хаскель.

Очень хреновое определение: чисто гипотетически появится hugs / jhc + несовместимые с ghc костыли (те же pragma-ы, а смысл другой). И вообще хватит фапать на хаскель.

anonymous
()
Ответ на: комментарий от anonymous

Прув для справки

A History of Haskell: Being Lazy With Class, глава 3.7

а так любой разумный человек отличает язык от реализации.

Реализация, разумеется, отличается от языка. Но она реализует язык. В данном случае, GHC реализует хаскель. Конкретная версия указана.

anonymous
()
Ответ на: комментарий от anonymous

Реализация, разумеется, отличается от языка. Но она реализует язык. В данном случае, GHC реализует хаскель. Конкретная версия указана.

Боюсь показаться мелочным, но хаскель и «реализуют хаскель» - не одно и то же, небольшая разница есть, и как насчет этого:

[scheme][haskell][oop][fp] Мысли вслух (комментарий)

anonymous
()
Ответ на: комментарий от anonymous

«Еще нет в языке и неизвестно будет ли» - не имеет смысла в данном случае. Если реализовано в GHC - значит есть в языке, который реализует GHC - т.е. в хаскеле. Это про синонимы паттернов или инстансы суперклассов по умолчанию «неизвестно будут ли», а тут все уже известно и есть.

anonymous
()
Ответ на: комментарий от Miguel

Есть другие случаи.

Есть. И что? В ленивом ЯП работает точно в тех же случаях + в случаях, где можно использовать неэквивалентность (x, _|_) и _|_.

Не понял. То есть, (lambda () (/ 1 0)) = (_|_)? (Ну, условно говоря - я не помню, бросает ли исключение конструкция (/ 1 0)).

Ну если исключение бросается, то да: (lambda () (/ 1 0)) = (_|_). То есть «функция ничего не вернула».

Тогда определи понятия «строгий» и «ленивый». Потому что стандартное понимание к ассемблеру, например, неприложимо.

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

Но ты её не переопределял. Так что она продолжает работать как раньше.

В том и дело, что она не продолжает работать как раньше. Вначале она вызывает некую ф-ю f, а после определения она не взызывает ф-ю f, она вызывает хз что. В любом случае - это все вопрос определений. Он не важен. Важно, что для возможности экспериментирования с программой нам надо уметь переопределять так, как в нормальных реплах (как в лисповом, например). Иначе эта возможность закрыта.

Так если, как ты говоришь, эта функция неважна - нафиг её перекомпилировать?

Почему не важна? g как раз важна, не важны какие-то другие ф-и, которых мы в данном случае не видим.

Уже приведён правильный синтаксис.

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

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

пруфы, братюнь.

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

Кассирше так же пофиг и на то, какой там ранг полиморфизма в System F.

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

Это в какой-то в сферической в вакууме математике. ИРЛ даже в работах по основаниям немало просто подразумевается, в более «прикладных» разделах типа алгебры или топологии за кадром остается больше, чем, собственно, пишется в явном виде. Иначе никак. Есть определенный контекст и попытка везде описать весь этот контекст привела бы к тому, что вместо какой-нибудь математической статьи на десяток листов перед нами было бы целое собрание сочинений на шкаф.

Я ни в какой момент не упоминал Fw

Ты в самом начале начал говрить про System Fw, когда упомянул «лямбды над типами», которых нету в System F, но есть в System Fw.

anonymous
()
Ответ на: комментарий от anonymous

«лямбды над типами», которых нету в System F

Мало тебе было «неявных предположений» о ранге полиморфизма, теперь у тебя уже System F в System F нету. Крепкий учебник у тебя получился, будет весомей, чем гастроном на улице Герцена.

anonymous
()
Ответ на: комментарий от anonymous

Нет, Митяй. Это не type operators. Это то, что у Пирса называется type abstractions/type application. Одну главу TAPL ты, видимо, уже читал - прочитай теперь еще одну (23-ю) хотя-бы.

anonymous
()
Ответ на: комментарий от anonymous

Ну если исключение бросается, то да: (lambda () (/ 1 0)) = (_|_). То есть «функция ничего не вернула».

И ты наврал. Потому что: (car (cons x (lambda () (/ 1 0)))) = x. Соответственно, либо эта лямбда — не (_|_), либо (car (cons x (_|_))) != (_|_), либо, наконец, всё в схеме (_|_).

Приложимо. Для энергичной модели все ясно

Ничего не ясно. Определи, что значит «ленивость» и «энергичность» для того, что не основано на лямбда-исчислении и вообще не обязательно является языком.

В том и дело, что она не продолжает работать как раньше.

Ты сам привёл пример, доказывающий, что таки продолжает.

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

Враньё. Ибо противоречит практике.

Почему не важна?

Ну, не знаю, ты так сказал несколько страниц назад.

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

Да, мой — правильный, твой — нет.

пруфы, братюнь.

В этом треде до фига примеров, когда ты не просто врал, а врал нагло.

Кассирше так же пофиг и на то, какой там ранг полиморфизма в System F.

И хорошо. Это не отменяет того факта, что она пользуется математикой.

ИРЛ даже в работах по основаниям немало просто подразумевается

Враньё. Если ты так считаешь, значит, ты не читал работ по основаниям математики. Я читал. Не произносится явно только то, что есть во всех нормальных учебниках по теме, либо в других статьях (на которые идёт явная ссылка). Причём есть в виде явных ОПРЕДЕЛЕНИЙ.

Ты в самом начале начал говрить про System Fw, когда упомянул «лямбды над типами», которых нету в System F, но есть в System Fw.

Они есть в System F. Посмотри в википедию, я уже давал ссылку. $\Lambda\alpha$ и так далее, причём аргумент $\alpha$ означает тип.

Miguel ★★★★★
()
Ответ на: комментарий от anonymous

Операторы над типами = лямбды над типами.

anonymous
()
Ответ на: комментарий от Miguel

И ты наврал. Потому что: (car (cons x (lambda () (/ 1 0)))) = x.

Так ты скобки опять забыл: (car (cons x ((lambda () (/ 1 0))))).

Ничего не ясно. Определи, что значит «ленивость» и «энергичность» для того, что не основано на лямбда-исчислении и вообще не обязательно является языком.

Так любой код на ассемблере можно заменить эквивалентным кодом в ЯП, который основан на лямбда-исчислении.

Ты сам привёл пример, доказывающий, что таки продолжает.

Нет, не продолжает. Вместо вызова f она делает хз что.

Враньё. Ибо противоречит практике.

Пруф? Где противоречие?

Ну, не знаю, ты так сказал несколько страниц назад.

Не говорил.

В этом треде до фига примеров, когда ты не просто врал, а врал нагло.

Хоть один пруф? Обычно все примеры «вранья» упирались в твою тупость и то, что ты забывал, о чем вообще шла речь.

И хорошо. Это не отменяет того факта, что она пользуется математикой.

Ну да. То есть допускает факт непротиворечивости математики.

Если ты так считаешь, значит, ты не читал работ по основаниям математики. Я читал.

Не читал. если бы читал, то знал бы, что я прав.

Не произносится явно только то, что есть во всех нормальных учебниках по теме, либо в других статьях (на которые идёт явная ссылка).

Вот именно. Всегда есть контекст, который явно не произносится - о чем я и говорю.

Они есть в System F.

В System F есть только полиморфные ф-и, лямбды над типами появляются в System Fw.

anonymous
()
Ответ на: комментарий от anonymous

Ну они и должны быть чистыми, да.

Чистыми, в смысле ссылочно-прозрачными, т.е. функциями в математическом смысле. Если ты используешь некую f в g и потом меняешь f, ожидая, что g изменит своё поведение тоже, то это значит, что ты не придаёшь значения ссылочной прозрачности. Ок, если это то что нужно, но это не ситуация с хаскелем (тут _специально_ сделано со ссылочной прозрачностью и невозможностью такого рода подмены, так что какие-то претензии предъявлять нет смысла - можно, конечно, считать, что это плохо вообще, как класс).

Хотя, тут можно поспорить - если f начинает возвращать новые (ссылочно-прозрачные, после переопределения) значения после переопределения, то почему бы g не начать это делать.

А если потом окажется, что там должно было быть String -> String?

Ты же не можешь иметь терм который одновременно и число и строка. Если на параметрически полиморфном аргументе сделать (+), то полиморфный тип сотрётся в Integer (или что там в default Num), если (++), то в String, использовать и (+) и (++) на одном аргументе значит сделать ошибку, которую заметит тайпчекер.

quasimoto ★★★★
() автор топика
Ответ на: комментарий от anonymous

Так ты скобки опять забыл:

Не я. Ты.

Так любой код на ассемблере можно заменить эквивалентным кодом в ЯП, который основан на лямбда-исчислении.

Угу. Причём как в ленивом, так и в энергичном. Так что не катит.

Вместо вызова f она делает хз что.

Как это хз? Что раньше делала, то и сейчас.

Где противоречие?

На практике.

Не говорил.

[scheme][haskell][oop][fp] Мысли вслух (комментарий)

Обычно все примеры «вранья» упирались в твою тупость и то, что ты забывал, о чем вообще шла речь.

Нет, я просто не расшифровывал за тебя, что ты имел в виду. Что тоже было глупостью или враньём, но не тем, что ты сказал.

То есть допускает факт непротиворечивости математики.

Она об этом факте даже не задумывается. И не факт, что знает такие слова.

то знал бы, что я прав.

Пруфлинк?

Всегда есть контекст, который явно не произносится - о чем я и говорю.

Нет. Ты говоришь о том, что якобы «подразумевается без определений». Этого — нет.

лямбды над типами появляются в System Fw.

Посмотри статью в википедии. Увидь наконец $\Lambda\alpha...$

Miguel ★★★★★
()
Ответ на: комментарий от quasimoto

Ок, если это то что нужно

Это то, что нужно, но это не нарушает ссылочной прозрачности. Ссылочная прозрачность нарушается, если изменение f вшито в логику программы (ну как в твоем варианте на хаскеле). В моем же случае у нас есть просто две _разные_ программы (первая до переопределения и вторая - после, само переопределение не является частью программы, как не является частью программы процесс переписывания исходников), каждая из которых ссылочно прозрачна.

тут _специально_ сделано со ссылочной прозрачностью и невозможностью такого рода подмены

Нет, не специально, просто по-другому сделать из-за статической типизации нельзя.

Ты же не можешь иметь терм который одновременно и число и строка.

Нет. Но до и после переопределения - это разные термы, т.к. это термы, которые находятся в контексте разных программ. Очевидно же, что в одной программе f x может значить одно, а в другой - совсем другое.

сделать ошибку, которую заметит тайпчекер.

В этом и проблема. Ошибки-то никакой нет. В первом случае мы имеем тип String -> String, во втором Integer -> Integer. Это две разных программы и две разные ф-и с двумя разными типами.

anonymous
()
Ответ на: комментарий от anonymous

И получаем бойлерплейт

Это не бойлерплейт, это статически-типизированный код, то есть нормальное ФП над алгебраическими типами. «Нормальное» тут значит также, что этот код обладает свойством well-typedness, в отличии аналога на Scheme. В примерах на Scheme именами методов являются символы, а не типизированные конструкторы - в рантайме будут возможны вызовы несуществующих методов, при том, что способа добавить классу новый метод в рантайме нет (пока мы не станем делать диспетчеризацию не с помощью case, а с помощью хэш-таблицы (символ ~ функция) и не сделаем метод, добавляющий новый метод в эту таблицу). В хаскеле тайпчекер не пропустит вызов несуществующего метода, как сделать в Scheme такую гарантию я себе не представляю.

Какая вообще цель преследуется в OOPLAI? Показать, что в ФП элементы классического ООП делаются довольно просто. При этом сначала мы видим невнятные конструкции из вложенных define и lambda вместе с set!, а потом макросы, которые стараются скрыть эту невнятность за красивым eDSL. Почему невнятные - потому что если бы они были внятными, то макросы бы не понадобились (как их нет в SICP или HTDP).

Я вижу причину этого в том, что Scheme не в достаточной мере ФП язык в какой нужно. Если человек берётся изучать ФП, то естественно ожидать, что он уже ознакомился с основаниями, то есть с лямбда исчислением, например, начало книжки Харрисона или Пирса прочёл (также как перед SQL даются реляционные алгебры, а алгоритмы с графами требуют введения графов как математических вещей). При этом в лямбда исчислении функции именно каррированные и допускают частичное применение, у многих авторов встречается инфикс и даже секции.

λx.x                (lambda (x) x)
f a b c             (f a b c)
f a b               (lambda (c) (f a b c))
f a                 (lambda (c) (lambda (b) (f a b c)))
f                   (lambda (c) (lambda (b) (lambda (a) (f a b c)))
a + b               (+ a b)
(a +)               (lambda (b) (+ a b))
(+ b)               (lambda (a) (+ a b))
f ∘ g ∘ h           (lambda (x) (f (g (h x))))
                    | (compose f (compose g h))
                    | (compose f g h)

слева - лямбда исчисление (и хаскель, он тут почти ничего не меняет), справа - лисп. Вот мне не понятно, зачем было убирать классическую некаррированную аппликацию f(a, b, c) и заменять её аппликацией из лямбда исчисления (где она бинарна и лево-ассоциативна, а применяемые функции каррированные) (f a b c), но при этом убирать карринг. Карринг ведь на рантайм никак не влияет, его можно нормально компилировать. Это вредит выразительности так же как введение какого-нибудь funcall.

Просто само ФП нужно не для того чтобы можно было написать lambda (или λ), а чтобы активно использовать ФВП, чтобы это было легко. Отсутствие карринга и частичных применений только ставит палки в колёса, отсутствие инфиксных ФВП не позволяет писать функциональных конвейеров - получаются портянки, вместо декларативного кода (вроде printResult . clusterData param . pareseData <$> fetchData).

К ADT с PM можно точно так же придираться (писать define-struct на каждый тип-произведение и явно расписывать аккессоры, при том, что с PM без них можно обойтись?). Они возможны, но слишком вербозны, чтобы их можно было легко применять, не создавая бойлерплейта, да и не принадлежат какой-нибудь фиксированной сумме типов (универсуму Top только) и чекать их некому.

сделать то же самое, что и с макросами

Ага, сделать то же самое без макросов. Хорошо же - макросы следует привлекать только тогда, когда без них не обойтись, если без них можно обойтись, то они и не нужны.

quasimoto ★★★★
() автор топика
Ответ на: комментарий от quasimoto

printResult . clusterData param . pareseData <$> fetchData

Вот ещё:

mapReduce :: [a] -> (TChan b -> a -> IO ()) -> ([b] -> r) -> IO r
mapReduce xs mf rf = do
  chan <- newTChanIO
  forM_ xs $ forkIO . mf chan
  atomically $ rf <$> forM xs (const $ readTChan chan)

не совсем функциональный конвейер, но забавно.

quasimoto ★★★★
() автор топика
Ответ на: комментарий от Miguel

Причём как в ленивом

В ленивом - нельзя. Попробуй замени «mov ax, bx; mov bx, ax». В ленивом ЯП никак нельзя гарантировать, что вторая инструкция будет исполнена после первой.

Как это хз? Что раньше делала, то и сейчас.

Раньше она вызывала f. Сейчас она _не_ вызывает f. Это факт. То есть сейчас она делает не то, что раньше. Что именно - открытый вопрос.

На практике.

Конкретно?

[scheme][haskell][oop][fp] Мысли вслух (комментарий)

И? Там нигде не написано «g не важна».

Нет, я просто не расшифровывал за тебя

Я и говорю - все упирается в твою тупость и игнорирование контекста.

Она об этом факте даже не задумывается.

Но пользуется им при выполнении каждой математической операции. Ведь она принимает, что 2*2=4. Не 5, не 6, не 457645746573678, а 4.

Пруфлинк?

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

Нет. Ты говоришь о том, что якобы «подразумевается без определений». Этого — нет.

Я говорю о том, что эти определения подразумеваются, а не перечисляются явно в каждом месте.

Посмотри статью в википедии.

посомтерл. Там чотко написано - операторы над типами появляются в System F.

Увидь наконец $\Lambda\alpha...$

И где тут лямбда над типами? Это обычная полиморфная функция (тип -> терм), а нам надо (тип -> тип).

anonymous
()
Ответ на: комментарий от quasimoto

Это не бойлерплейт, это статически-типизированный код,

Как говно не назови...

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

На макросах, конечно.

Какая вообще цель преследуется в OOPLAI? Показать, что в ФП элементы классического ООП делаются довольно просто.

Именно. «Довольно просто» = при помощи элементарных конструкций. А не при помощи зигохистоморфических препроморфизмов.

Я вижу причину этого в том, что Scheme не в достаточной мере ФП язык в какой нужно.

Каррирование легко реализуется на макросах (20 строк в Racket), так что оно в scheme есть.

Если человек берётся изучать ФП, то естественно ожидать, что он уже ознакомился с основаниями

Неверно. Это значит, что он как раз за изучение лямбда-исчисления и принимается.

Отсутствие карринга и частичных применений только ставит палки в колёса, отсутствие инфиксных ФВП не позволяет писать функциональных конвейеров - получаются портянки, вместо декларативного кода (вроде printResult . clusterData param . pareseData <$> fetchData).

Это не декларативный код, за такой код надо руки отрывать. Скажи пожалуйста, глядя только на этот код, сколько аргументов принимает каждая из упомянутых в нем функций? Какова сущность этих аргументов? Из-за каррирования и поинт-фри записи теряется куча информации, которая при нормальной записи всегда присутствует в коде, что резко увеличивает понятность. (f x) - хз что это такое, (\informativeName -> (f x informativeName)) - да, длиннее, зато сразу понятно, что f - ф-я от двух аргументов, а лямбда - ф-я от одного, причем из имени аргумента ясен его смысл. Да, каррирование и поинт-фри иногда удобно - когда мы применяем его к каким-нибудь +/map... - то есть к одним из часто применяемых ф-й стандартной библиотеки, сигнатуры которых нам заведомо известны. Конвееры такие, кстати, тоже легко делаются на макросах, но это bad way. Принято вместо них делать нормальную декомпозицию.

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

Ну это верно для ЯП, в которых макросы сделаны плохо, негодно. Если же макросы сделаны хорошо, годно - то их нет смысла бояться и надо применять везде, где удобно.

anonymous
()
Ответ на: комментарий от quasimoto

forM_ xs $ forkIO . mf chan

Не могу распарсить. Это forM_ xs $ (forkIO . (mf chan))? или forM_ xs $ ((forkIO . mf) chan)? или (forM_ xs $ forkIO . mf) chan?

anonymous
()
Ответ на: комментарий от anonymous

Попробуй замени «mov ax, bx; mov bx, ax».

Сначала я хочу увидеть «полностью эквивалентный» код в энергичном языке.

Раньше она вызывала f. Сейчас она _не_ вызывает f.

Она точно так же считает результат по исходным данным. Она осталась той же.

Там нигде не написано «g не важна».

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

Но пользуется им

Это как, собственно?

Ведь она принимает, что 2*2=4.

Да. А вовсе не то, что «арифметика непротиворечива».

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

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

Там чотко написано - операторы над типами появляются в System F.

Во-первых, в System Fw. Во-вторых, про «операторы над типами» зачем-то заговорил ты.

И где тут лямбда над типами? Это обычная полиморфная функция (тип -> терм), а нам надо (тип -> тип).

Прочитай. Аргумент $\alpha$ означает именно ТИП. А не ТЕРМ.

Miguel ★★★★★
()
Ответ на: комментарий от anonymous

На макросах, конечно.

Только никто так и не сделал пригодной к использованию реализации. И не сделает никогда, ибо проблемы в системе типов основного языка МАКРОСАМИ НЕ РЕШАЮТСЯ. Никогда. В крайнем случае, несколько облегчаются (и тогда получается уродец типа C++).

Неверно. Это значит, что он как раз за изучение лямбда-исчисления и принимается.

В koi-то веки согласен с тобой. Требовать изучать лямбду до знакомства с ФП - это явный оверкилл.

Скажи пожалуйста, глядя только на этот код, сколько аргументов принимает каждая из упомянутых в нем функций?

Один, как любая хаскельная функция.

Из-за каррирования и поинт-фри записи теряется куча информации, которая при нормальной записи всегда присутствует в коде

То есть, (printResult . clusterData param . pareseData <$> fetchData) несёт меньше информации, чем (\x -> printResult (clusterData param (parseData x))) <$> fetchData?

Слушай, ты юниксовую командную строку, вообще, в глаза видел?

макросы сделаны хорошо, годно

Взаимоисключающие параграфы.

Miguel ★★★★★
()
Ответ на: комментарий от Miguel

Только никто так и не сделал пригодной к использованию реализации.

Просто никому это не нужно.

Один, как любая хаскельная функция.

Ладно, зададим вопрос по-другому - к какому количеству аргументов надо ее применить, чтобы получить константу?

То есть, (printResult . clusterData param . pareseData <$> fetchData) несёт меньше информации, чем (\x -> printResult (clusterData param (parseData x))) <$> fetchData?

Естественно. Например, если у нас первый вариант код - что значит (pareseData x)? Неизвестно. Быть может, это распарсенные данные, а может - функция, которая должна принять еще один аргумент (специфицирующий настройки парсинга, допустим), чтобы вернуть данные. Во втором случае все сразу ясно. Аналогично для других ф-й.

anonymous
()
Ответ на: комментарий от anonymous

а может - функция, которая должна принять еще один аргумент

У parseData один аргумент, это вытекает из типа точки

unC0Rr ★★★★★
()
Ответ на: комментарий от Miguel

Сначала я хочу увидеть «полностью эквивалентный» код в энергичном языке.

(begin (mov ax bx) (mov bx ax))

Она точно так же считает результат по исходным данным. Она осталась той же.

Она не вызывает ф-ю f. Она изменилась.

Там написано «Так нам не важно, что она стала работать иначе.»

Ну да, то, что какая-то функция (хз какая) стала работать иначе (или не стала) - это не важно. А вот то, что g должна изменить поведение - важно.

Это как, собственно?

Считает.

Да. А вовсе не то, что «арифметика непротиворечива».

Так одно без другого невозможно.

Нет. Я согласился с тем, что не все определения приводятся в каждой статье.

О чем и речь. Предполагается, что читателю и так понятны определения, учитывая контекст статьи. об этом я и говорил. С чем же ты тогда споришь?

Во-первых, в System Fw

Да, опечатка.

Во-вторых, про «операторы над типами» зачем-то заговорил ты.

Не я, а ты (операторы над типами = лямбды над типами).

Прочитай. Аргумент $\alpha$ означает именно ТИП. А не ТЕРМ.

Ну да, то есть полиморфная ф-я это тип -> терм. А нам надо лямбду над типами, то есть тип -> тип. А оно есть только в System Fw.

anonymous
()
Ответ на: комментарий от unC0Rr

У parseData один аргумент, это вытекает из типа точки

Не вытекает.

[code] Prelude> let x = (.) . (.) Prelude> [/code] ты же не хочешь сказать, что у (.) один аргумент?

anonymous
()
Ответ на: комментарий от anonymous

Будут выполняться, если правильно fmap (он же (<$>)) напишешь.

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

Я же говорю, если сделаешь правильную реализацию интерфейса Monad, т.е. правильные (>>=) и return, то всё будет хорошо. Например:

> putStrLn "1" >> putStrLn "2"
1
2

putStrLn :: String -> IO () - из строки строит действие имеющее эффектом печать этой строки, (>>) :: IO () -> IO () -> IO () - из двух действий строит новое действие имеющее эффектом последовательность (во времени) эффектов этих двух действий (аргументов). Почему так - просто это так, это аксиома. Пользователя не должно интересовать как написаны эти комбинаторы, его же не интересует как написана (+) (а она умеет длинную арифметику, так что как-то точно написана).

> putStrLn "1" >> return (putStrLn "2")
1

тоже понятно - выполнение действия return (putStrLn «2») возвращает не нормальный эффект, а другое действие. Вычисление вложенного IO не производится.

> (+ 1) . read <$> getLine  -- == return . (+ 1) . read =<< getLine
2
3

getLine :: IO String - действие имеющее эффектом введённую строку, (<$>) :: Num t => (String -> t) -> IO String -> IO t - создаёт действие эффектом которого является применение чистой функции (аргумента) на эффекте другого действия (аргумента). (>>=), в свою очередь, создаёт действие эффектом которого является применение нечистой (т.е. с кодоменом в IO) функции (аргумента) на эффекте другого действия (аргумента).

Тут (IO a) это «тип действия», терм этого типа - «действие» (мы не видим эти термы, но они хранят всю структуру IO операций которые будут выполняться), значение этого терма - «эффект» («нормальный эффект» - не «действие»).

quasimoto ★★★★
() автор топика
Ответ на: комментарий от quasimoto

Я же говорю, если сделаешь правильную реализацию интерфейса Monad, т.е. правильные (>>=) и return, то всё будет хорошо. Например:

Да ничего у тебя хорошо не будет. еще раз: map (f . g) = map f . map g. допустим f = putStrLn «1», g = putStrLn «2». допустим имеем список [1, 2]. Тогда map (f . g) = 2 1 2 1, map f . map g = 2 2 1 1.

putStrLn :: String -> IO () - из строки строит действие имеющее эффектом печать этой строки

Неправильно! Не строит действие (это в хаскеле так), а именно печатает эту строку. Мы ведь говорим о том случае, когда IO - это просто такой id-враппер, не меняющий семантику вычислений. то есть у нас была ф-я putStrLn :: String -> Void и другие такие же ф-и (все без IO) и мы просто поменяли им тип, не меняя сами ф-и.

из двух действий строит новое действие имеющее эффектом последовательность

Неправильно! Не строит новое действие, а выполняет второе вслед за первым.

выполнение действия return (putStrLn «2») возвращает не нормальный эффект, а другое действие. Вычисление вложенного IO не производится.

Неправильно! Производится.

anonymous
()
Ответ на: комментарий от anonymous

Не могу распарсить.

forM_ xs $ forkIO . mf chan
forM_ xs (forkIO . mf chan)
forM_ xs (forkIO . (\init -> mf chan init))
forM_ xs (\init -> forkIO (mf chan init)) -- и тут мы понимаем, что это же
forM_ xs $ forkIO . mf chan               -- обратно :)

mf это типа worker который форкается, xs - список начальных значений для тредов, init начальное значание для данного треда, chan - канал, куда тред будет писать (на стадии map, нужно заметить, что стадия reduce начинается вместе со стадией map, она просто ждёт по мере поступления данных в STM канале), forM_ это перевёрнутый mapM_ - map обычного списка, только с функциями в IO.

quasimoto ★★★★
() автор топика
Ответ на: комментарий от quasimoto

Тут (IO a) это «тип действия», терм этого типа - «действие» (мы не видим эти термы, но они хранят всю структуру IO операций которые будут выполняться), значение этого терма - «эффект» («нормальный эффект» - не «действие»).

Это в хаскеле так. У нас же случай другой.

Напоминаю о чем шел разговор:

Смотри - надо указать, что ф-и типа print не исполняют сайд-эффект, а возвращают некую программную сущность, его инкапсулирующую, которая уже потом как-то атсрально исполняется.

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

anonymous
()
Ответ на: комментарий от anonymous

допустим

Ты написал плохо-типизированный код - допускать нечего (f . g, где f :: a, g :: a - нонсенс).

Не строит действие

Строит. Я говорю о представленном случае - IO в хаскеле. Ну и строит не более магическим путём, чем это делает любой другой компилятор / интерпретатор (я вот на си всё киваю).

Не строит новое действие, а выполняет второе вслед за первым.

Строит действие, в термах что-то вроде:

data IO :: * -> * where
  ...
  Next :: IO a -> IO b -> IO b
  ...

x >> y = x `Next` y -- (>>) = Next, да

Производится.

> :t putStrLn "1" >> return (putStrLn "2")
putStrLn "1" >> return (putStrLn "2") :: IO (IO ())
> putStrLn "1" >> return (putStrLn "2")
1

где?

quasimoto ★★★★
() автор топика
Ответ на: комментарий от anonymous

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

А, ну тогда ладно. По крайней мере про IO в который-то раз поговорили :)

quasimoto ★★★★
() автор топика
Ответ на: комментарий от quasimoto

Ты написал плохо-типизированный код - допускать нечего (f . g, где f :: a, g :: a - нонсенс).

ок, исправим: f x = putStrLn «1», g x = putStrLn «2». Теперь код корректно типизирован, на результат это не повлияло.

Строит. Я говорю о представленном случае - IO в хаскеле.

Да нет же, ты как раз начал не о хаскеле. И забыл о чем речь.

где?

В ЯП о котором ты говоришь (энергичном без IO, в котором только добавили тип IO, не переписывая сами ф-и).

anonymous
()
Ответ на: комментарий от quasimoto

Ну не знаю, forM_ xs (\init -> forkIO (mf chan init)) = forM_ xs (forkIO (mf chan)). Значит ты где-то сам ошибся, когда писал forM_ xs (\init -> forkIO (mf chan init)) :)

anonymous
()
Ответ на: комментарий от anonymous

ты же не хочешь сказать, что у (.) один аргумент?

Кстати, все функции в хаскеле имеют либо ноль (CAF), либо один аргумент и всегда одно значение. Точно так же, как все стрелки в категории имеют один домен и один кодомен (CAFы там это стрелки из терминального объекта), при этом кодоменом может быть экспоненциал. Короче, a -> b -> c это функция с одним аргументом типа a и одним значением типа (b -> c).

Про вот это:

answer param = printResult . clusterData param . parseData <$> fetchData

Должна возникнуть догадка - у fetchData от нуля и более аргументов, у pareseData от одного и более, у clusterData от двух и более, у printResult от одного и более. Можно взять минимальные за правдоподобные (кроме fetchData). Но дальше обычно идёт:

  where
    printResult :: здесь нужная сигнатура

или

printResult :: здесь нужная сигнатура

ну и haddock никто не отменял. В Scheme такая вещь будет выглядеть так:

(define answer (param ...)
  (print-result ...
    (cluster-data param ...
      (parse-data ...
        (fetch-data ...)))))

где вместо ... - пропись всех 100500 аргументов. Либо, если пытаться решить через compose:

(define answer (param ...)
  (compose
    print-result
    (lambda (param ...)
      (cluster-data param ...))
    parse-data
    fetch-data))

где вместо ... снова пропись всех 100500 аргументов, если в print-result, parese-data и fetch-data будут частичные применения - понятно, что нужно писать лямбды и пропись всех 100500 аргументов.

quasimoto ★★★★
() автор топика
Ответ на: комментарий от anonymous

forM_ xs (\init -> forkIO (mf chan init)) = forM_ xs (forkIO (mf chan))

> let xs = undefined; mf = undefined; chan = undefined
> :t forM_ xs (\init -> forkIO (mf chan init))
forM_ xs (\init -> forkIO (mf chan init)) :: IO ()
> :t forM_ xs (forkIO (mf chan))

<interactive>:1:11:
    Couldn't match expected type `a0 -> m0 b0'
                with actual type `IO ThreadId'
    In the return type of a call of `forkIO'
    In the second argument of `forM_', namely `(forkIO (mf chan))'
    In the expression: forM_ xs (forkIO (mf chan))
quasimoto ★★★★
() автор топика
Ответ на: комментарий от anonymous

Просто никому это не нужно.

А, ну-ну. Зелен виноград.

Если бы сие было так просто, как лисперы любят заявлять - сделали бы уже давно хотя бы «на слабо».

к какому количеству аргументов надо ее применить, чтобы получить константу?

Э... к нулю. Функции, как и прочие значения в хаскеле, иммутабельны.

Или ты что-то другое имеешь в виду под словом «константа»?

Например, если у нас первый вариант код - что значит (pareseData x)? Неизвестно. Быть может, это распарсенные данные, а может - функция, которая должна принять еще один аргумент (специфицирующий настройки парсинга, допустим), чтобы вернуть данные. Во втором случае все сразу ясно.

Э... что ясно? Оборвал на самом интересном месте. Что такое parseData x во втором случае?

Miguel ★★★★★
()
Ответ на: комментарий от anonymous

(begin (mov ax bx) (mov bx ax))

Странно, не работает: reference to undefined identifier: mov

Она не вызывает ф-ю f.

Которую?

(хз какая)

Не притворяйся, речь шла про g.

А вот то, что g должна изменить поведение - важно.

С чего это? Из-за того, что мы поменяли что-то, внешне с ней никак не связанное?

Считает.

Не пользуясь никакими предположениями.

Так одно без другого невозможно.

Вообще-то, наоборот. Второе упрощает первое.

Предполагается, что читателю и так понятны определения, учитывая контекст статьи. об этом я и говорил. С чем же ты тогда споришь?

Нет никаких определений, говорящих, что System F - это с рангом не выше 1. Ты не согласен?

Не я, а ты (операторы над типами = лямбды над типами).

Нет, конечно.

А нам надо лямбду над типами, то есть тип -> тип.

Это кто сказал?

Miguel ★★★★★
()
Ответ на: комментарий от Miguel

Я и так знаю, что схемовский репл - полное говно. Именно по этой причине.

shell, python, ruby и common lisp точно такие же :)

quasimoto ★★★★
() автор топика
Ответ на: комментарий от quasimoto

printResult :: здесь нужная сигнатура

Проблема в том, что эта сигнатура расположена не в месте кода и ее еще надо распарсить.

где вместо ... - пропись всех 100500 аргументов

Почему 100500? В данном случае всего лишь один. Вообще непонятна эта лень у хаскелистов. Какая разница, что код на 10% длиннее, если он станет вдвое понятнее? Такое чувство, что во время написания программы стучать по клавишам - это самая сложная часть процесса :)

Можно написать вот такой вот простенький макрос:

(define-syntax >>
  (syntax-rules (@)
    [(>> arg (@ fun x ...)) (fun x ... arg)]
    [(>> arg fun) (fun arg)]
    [(>> arg (@ fun1 x ...) fun ...) (>> (fun1 x ... arg) fun ...)]
    [(>> arg fun1 fun ...) (>> (fun1 arg) fun ...)]))

(>> '(1 2 3 4 5 6 7 8 9 10)
    (@ filter even?)
    (@ map add1)
    (@ foldl + 0)
    display)

(define (answer param) 
  (>> fetch-data
      parse-data
      (@ cluster-data param)
      print-result))

anonymous
()
Ответ на: комментарий от anonymous

Скажи пожалуйста, глядя только на этот код, сколько аргументов принимает каждая из упомянутых в нем функций?

C-c C-t RET

quasimoto ★★★★
() автор топика
Ответ на: комментарий от Miguel

Если бы сие было так просто, как лисперы любят заявлять - сделали бы уже давно хотя бы «на слабо».

Так уже неоднократно делали.

Э... к нулю. Функции, как и прочие значения в хаскеле, иммутабельны.

Ладно, хватит придуриваться. Ты прекрасно понял, что я имел ввиду.

Э... что ясно? Оборвал на самом интересном месте. Что такое parseData x во втором случае?

Разобранные данные. Нам во втором случае достоверно известно, что у parseData второго аргумента нет. В первом случае - неизвестно. Может, он есть. А может - нет. А может, там еще два аргумента.

anonymous
()
Ответ на: комментарий от quasimoto

C-c C-t RET

То есть это мне для каждой ф-и надо куда-то лезть, вместо того, чтобы получить эту информации из кода просто один раз на него взглянув? Причем дальше все гораздо хуже - напишем мы какое-нибудь (f x) . (g x y) - и думай, что у тебя получилсоь в результате. Нет, конечно, можно посмотреть сигнатуру f, сигнатуру g, и потом прикинуть какие аргументы будут у результата - но зачем это делать, если можно сразу написать код из которого все будет ясно?

Еще момент, кстати - без карринга, если ты напишешь лишний/не допишешь нужный аргумент, тебе выдаст конкретную ошибку «вызов ф-и с неверным количеством аргументов». Если же карринг есть, то компилятор не знает, что такое (f x) - полная апликация или частичная. Как следствие, при ошибке чекер плюнет невнятным выблевом о несоответствии типов.

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

anonymous
()
Ответ на: комментарий от anonymous

Можно написать вот такой вот простенький макрос

DrRacket такая весёлая программа - после нажатия «проверить синтаксис» с этим кодом она у меня перестала отвечать (в консоль ничего не пишет, drracket - poll_shedule_timeout / 188MB). Может, дистропроблемы.

quasimoto ★★★★
() автор топика
Ответ на: комментарий от quasimoto

Может ты скопипастил что-то не так? Если макрос повиснет, то оно по идее на проверке синтаксиса виснуть будет.

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.