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).

★★★★

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

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

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

чтобы dispatch мог принимать какое угодно количество аргументов ... и мог возвращать любые другие типы

data Point = Point
  { x :: Double
  , y :: Double
  } deriving ( Show, Eq )

data PointMethodCall = Pos | Mov Double Double
type PointMethodResult = Maybe Point

pointClass :: Double -> Double -> IO (PointMethodCall -> IO PointMethodResult)
pointClass initX initY = dispatch <$> new (Point initX initY)
  where
    dispatch :: Var Point -> PointMethodCall -> IO PointMethodResult
    dispatch obj Pos = Just <$> get obj
    dispatch obj (Mov x y) = const Nothing <$> obj =: Point x y
pnt <- pointClass 2 4
pnt Pos
Just (Point {x = 2.0, y = 4.0})
pnt (Mov 0 0)
Nothing
pnt Pos
Just (Point {x = 0.0, y = 0.0})

Это статически-типизированный код.

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

И сразу понимаю, что если туда пойдут такие a и b, что отношение порядка между ними не определено, то система навернётся с невнятным сообщением об ошибке.

Почему же невнятным? Вполне внятным. И это ничем не отличается от статической системы типов - там тоже система навернется с некоторым сообщением.

Что функция next абстрагирована, а функция (>), почему-то - нет

Это уже претензии к ТЗ. Дано ТЗ - сделать для обычного числового равенства, оно так и сделано. На хаскеле было бы точно так же. при чем тут система типов - не ясно.

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

бренфак, я так понимаю, почти образцовый по простоте язык?

По простоте синтаксиса? Вполне возможно.

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

Я и не говорил, что это SystemF, я говорил о SystemF с дополнениями. Или ты про n-rank полиморфизм?

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

Это уже скорее акторы.

А в чем принципиальные отличия? Тем, что традиционный ООП в конкурентных средах превращается в попоболь? Так это можно организовать и в хаскеле.

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

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

data PointMethodCall = Pos | Mov Double Double
type PointMethodResult = Maybe Point

Для каждого класса. Так и надо честно говорить «с настоящими фвп, такими-то АТД, паттерн-матчингом и кучей бойлерплейта можно сделать то же самое, что и с макросами».

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

Это конечно хорошо, но всякие непонятные значки типа >>, =~, $, <$>, :, ... только усложняют чтение кода.

Не усложняют. Для примера можешь посмотреть использование библиотек, написанных в доаппликативную эпоху. Например, http://www.haskell.org/haskellwiki/HXT/Conversion_of_Haskell_data_from/to_XML

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

dispatch :: Var Point -> PointMethodCall -> IO PointMethodResult

Вот так правильно:

dispatch :: Var Point -> PointMethodCall -> PointMethodResult

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

Это, конечно, лучше:

(define stack
  (let ((vals '()))
    (define (pop)
      (let ((val (car vals)))
        (set! vals (cdr vals))
        val))
    (define (push val)
      (set! vals (cons val vals)))
    (define (peek)
      (car vals))
    (lambda (cmd . args)
       (case cmd
         ((pop) (pop))
         ((push) (push (car args)))
         ((peek) (peek)))))) 
Много понятнее.

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

Неясно, что должен был продемонстрировать этот линк?

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

Это, конечно, лучше:

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

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

Для того, чтобы сравнить два синтаксиса по простоте - достаточно написать грамматику для каждого и сравнить размер грамматики.

Ну, попробуй. Только не забудь пресловутые if и do, не ограничиваясь скобочками.

В схеме сущностей меньше

Нет.

из-за

Не связано.

энергичной модели вычисления (которая более выразительна)

Это вообще бред. Ленивая модель на порядок выразительнее. Хотя бы уже возможностью завязать узел (tie the knot), это уже дорогого стоит.

отсутствия типизации (которая добавляет множество сущностей)

Вот только в коде это практически не отображается.

всегда будет в несколько раз более bloated

Практика говорит обратное.

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

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

Ты жалуешься на необходимость знать язык?

OK, тогда так: в Хаскеле необходимо знать 1) каррирование, 2) инфиксный синтаксис. В Схеме необходимо знать 1) define, 2) lambda, 3) скобочный синтаксис.

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

Почему же невнятным? Вполне внятным.

В нём будет сказано нечто про отсутствие оператора сравнения для данных объектов. И дальше нужно лезть в реализацию, чтобы понять - а зачем он тут?

И это ничем не отличается от статической системы типов - там тоже система навернется с некоторым сообщением.

Угу, только в ней будет сказано другое: что интерфейс функции не позволяет вызвать её с такими аргументами. И он таки не позволяет. Реализация функции никакого значения не имеет.

Дано ТЗ - сделать для обычного числового равенства, оно так и сделано.

Не-а. Просто для чисел было бы не (next a), а (+ a 1).

А система типов при том, что важная деталь, без которой реализация не работает, в интерфейсе не видна.

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

Да, тут всё прозрачно и нет кучи непонятных сущностей за которыми спрятана сложность.

Это пока прочитаешь - забудешь, с чего началось.

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

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

На схеме ты привел полную реализацию, а на хаскеле - только кусок. сравнивай весь код, мб?

Чего не хватает?

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

Ну, попробуй. Только не забудь пресловутые if и do, не ограничиваясь скобочками.

А чего пробовать? Возьми да посмотри грамматику r5rs.

Нет.

Посчитаем?

Не связано.

Связано, конечно. Ведь чтобы писать на ЯП со статической типизацией, тебе надо знать его систему типов. А наличие системы типов резко усложняет семантику (сравни семантику типизированной и нетипизированной лямбды, например).

Это вообще бред. Ленивая модель на порядок выразительнее.

Нет. Если модель энергичная, то любая конструкция, которая реализуется в ленивой модели, может быть реализована в виде синтаксического сахара над энергичной (тот же if). С другой стороны, в рамках ленивой модели нет способа специфицировать порядок вычисления (то есть написать конструкцию, аналогичную begin ...). Причем эта проблема принципиальна - ее нельзя решить добавлением синтаксического сахара. Надо изменять семантику.

Вот только в коде это практически не отображается.

Это отображается в написании и понимании кода.

Практика говорит обратное.

Если смотреть по этому треду - нет. Приведешь другие «практические» подтверждения?

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

http://en.wikipedia.org/wiki/System_F и кончай нести бред.

Ну ты уж не ленись, прочитай статью. System F - полиморфная типизированная лямбда (то есть хаскель 98 - это System F с добавками), а лямбды по типам добавляются с омегой.

anonymous
()

Большим плюсом схемы является её убогость^W простота. Чтобы начать программировать слушателям курса СИКП достаточно дать понятие λ-функции (максимум --- страница текста), объяснить что такое α-конверсия (одно предложение), рассказать о ленивой и энергичной β-редукции (абзац), сказать об энергичности схемы и объяснить почему if не может быть функцией (ещё два абзаца). После чего следует перечислить основные ключевые слова, что займёт не больше одной страницы со всеми пояснениями. Итого, три страницы несложного чтения и можно идти в бой.

А вот чтобы разобрать

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

нужно знать существенно, существенно больше.

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

Чего не хватает?

реализации всяких =~, очевидно. К слову, код на схеме можно переписать вот так ():

(define stack
  (let ((vals '()))
    (lambda (cmd . args)
      (case cmd
        ((pop) (begin0 (car vals) (set! vals (cdr vals))))
        ((push) (set! vals (cons (car args) vals)))
        ((peek) (car vals))))))

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

Нет. Если модель энергичная, то любая конструкция, которая реализуется в ленивой модели, может быть реализована в виде синтаксического сахара над энергичной (тот же if). С другой стороны, в рамках ленивой модели нет способа специфицировать порядок вычисления (то есть написать конструкцию, аналогичную begin ...). Причем эта проблема принципиальна - ее нельзя решить добавлением синтаксического сахара. Надо изменять семантику.

case (action1) in
  a1 -> case (action2 a1) in
         a2 -> case (action3 (a1-a2) ) in

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

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

Это пока прочитаешь - забудешь, с чего началось.

Именно для этого там методы выведены в (define ...) - чтобы можно было всегда себе напомнить. Есть вариант и более короткий (см. выше). Как я понимаю, у хаскидебилов понятность кода определяется исключительно по его длине и обфускации всякими <$> ~= и прочим говном. Следуя такой логике, нет ничего понятнее однострочников перла.

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

Ты можешь смеяться, но в твоем примере лисповый код намного понятнее, особенно для новичков, на которых и ориентируется SICP :)

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

А чего пробовать? Возьми да посмотри грамматику r5rs.

Ну, и?

Посчитаем?

Давай.

(сравни семантику типизированной и нетипизированной лямбды, например).

Сравнивал. Семантика типизированной много проще. Для нетипизированной нужно придумывать такой домен C, что C->C вкладывается в C - что возможно, но противоречит интуиции (говорящей, что X->X всегда больше X, кроме тривиальных случаев).

Если модель энергичная, то любая конструкция, которая реализуется в ленивой модели, может быть реализована в виде синтаксического сахара над энергичной (тот же if).

Угу, только надо учитывать, что на вход поступают исключительно энергичные функции.

С другой стороны, в рамках ленивой модели нет способа специфицировать порядок вычисления (то есть написать конструкцию, аналогичную begin ...).

«Порядок вычисления» - это деталь реализации, если он не отражается на результате. А если отражается, то это делается do-блоком (который таки сахар).

Это отображается в написании и понимании кода.

Угу. Есть тебе на что опереться, или нет.

Если смотреть по этому треду - да.

Fixed.

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

Ты конкретный пример приведи, а не для каких-то абстрактных action1/action2. В качестве ЯП возьмем ленивое лямбда-исчисление + print, всякие true/false - на лямбдах, естественно.

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

Ты можешь смеяться, но в твоем примере лисповый код намного понятнее, особенно для новичков, на которых и ориентируется SICP :)

Статистика есть?

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

Код короткий, но не прозрачный.

А так?

data StackMethod = Pop | Push | Peek

stack = dispatch <$> new []

dispatch x Pop _  = do
  val <- get x
  x =~ tail
  return $ head val

dispatch x Push y = do
  x =~ (y :) 
  return y

dispatch x Peek _ = head <$> get x
-- do
--  val <- get x
--  return $ head val   val <- get x
-- ^ hlint это исправляет.

Обычно do-нотация рефакторится в какой-либо pointfree (и не обязательно с комбинаторами из Control.Monad), просто потому что комбинаторы первичны - do-нотация это сахар, ей можно расписать что угодно, но не всегда это естественно.

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

Я и не говорил, что это SystemF, я говорил о SystemF с дополнениями.

Для Haskell98 достаточно HM. HM < SystemF, HM это такое подмножество SystemF для которого возможен TI без аннотаций, для SystemF он не возможен, в общем случае.

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

Ну, и?

Вот r5rs: http://schemers.org/Documents/Standards/R5RS/HTML/

давай линк на хаскель

Давай.

ок. жду линка.

Сравнивал. Семантика типизированной много проще.

Ну что за чушь ты несешь? Семантика типизированной лямбды - это семантика нетипизированной + дополнительный набор правил редукции. Загляни в какой-нибудь тапл уже, хз.

Угу, только надо учитывать, что на вход поступают исключительно энергичные функции.

Ну да, и?

«Порядок вычисления» - это деталь реализации, если он не отражается на результате. А если отражается, то это делается do-блоком (который таки сахар).

Естественно, отражается. Но никаким do-блоком этого сделать нельзя - в результате рассахаривания do-блока мы получим лямбды, которые раскрываются в IO. Но при вычислении этого IO не происходит исполнения сайд-эффектов, то есть в ЯП никакого способа указать «сперва вывести 1, затем 2» - нету. Опровергнуть мое утверждение легко - надо лишь привести этот способ.

Угу. Есть тебе на что опереться, или нет.

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

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

Для Haskell98 достаточно HM. HM < SystemF, HM это такое подмножество SystemF для которого возможен TI без аннотаций, для SystemF он не возможен, в общем случае.

Ты говоришь о rank-n, n>2. Под типизированной лямбдой (то есть SystemF) практически всегда (если иного не сказано) понимается rank-n, n<=2.

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

OK, тогда так: в Хаскеле необходимо знать 1) каррирование, 2) инфиксный синтаксис. В Схеме необходимо знать 1) define, 2) lambda, 3) скобочный синтаксис.

В хаскеле аналогом define будет = - его тоже надо знать. Скобки в хаскеле тоже есть - их надо знать. Лямбды в хаскеле тоже есть - их надо знать. То есть чтобы писать не хелло ворлды нам все равно надо знать аналоги define лямбды и скобок. В хаскеле _в дополнение к этому_ будет нужно каррирование, частичное применение, синтаксис инфиксных операторов и т.д..

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

В нём будет сказано нечто про отсутствие оператора сравнения для данных объектов. И дальше нужно лезть в реализацию, чтобы понять - а зачем он тут?

Аналогично в хаскеле.

Угу, только в ней будет сказано другое: что интерфейс функции не позволяет вызвать её с такими аргументами. И он таки не позволяет.

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

Не-а. Просто для чисел было бы не (next a), а (+ a 1).

Может, нам захочется (+ a 2)? (* a 2)?

А система типов при том, что важная деталь, без которой реализация не работает, в интерфейсе не видна.

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

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

Прочитал.

плохо прочитал, читай еще раз. Особенно раздел про f_w, где как раз вводятся лямбды над типами. Или по-твоему SystemF = SystemF_w?

Хотя бы вот это: http://upload.wikimedia.org/wikipedia/en/math/2/e/6/2e6f32013b2b634f0d3b1e2f047a...

Это не лямбды над типами, это параметрический полиморфизм. Который в haskell-98 ВНЕЗАПНО есть.

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

Тут вся проблема в =~, который определяется очень нетривиально и требует для своего понимания бекграунда большего, чем весь SICP.

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

говно вид в профиль

О, ну тогда весь хаскель - говно, так надо было сразу и говорить :)

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

Тут вся проблема в =~, который определяется очень нетривиально и требует для своего понимания бекграунда большего, чем весь SICP.

x =~ f = do { modifyIORef x f ; readIORef x }

т.е. вещь уровня

(define (foo x f)
  (set! x (f x))
  x)

или IO смущает? Это да, надо прививать :) (haskell is useless, btw).

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

Вот r5rs: http://schemers.org/Documents/Standards/R5RS/HTML/

Хочешь сказать, что грамматика Scheme сводится к перечислению авторов стандарта?

жду линка.

Это я его жду.

Семантика типизированной лямбды - это семантика нетипизированной + дополнительный набор правил редукции. Загляни в какой-нибудь тапл уже, хз.

Что за бред ты несёшь? Какие ещё дополнительные правила редукции? Та же самая бета.

Правда, я под семантикой подразумеваю не «правила редукции» (отвечающие за разницу между строгостью и ленивостью, а не за типизацию), а семантику Скотта.

Ну да, и?

И вот. Напиши в энергичной семантике что-нибудь вроде функции loop, хотя бы для чистых функций. Т.е., функцию типа ((a, z) -> (b, z)) -> (a -> b).

В ленивой это тривиально:

loop f = \a -> let ~(b, z) = f (a, z) in b

Но при вычислении этого IO не происходит исполнения сайд-эффектов

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

в случае хаскеля кроме _того же самого ядра_

Не того же.

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

Под типизированной лямбдой (то есть SystemF)

Ну кончай уже бредить. Под «типизированной лямбдой» практически всегда подразумевается НЕ System F.

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