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

★★★★

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

В каком месте?

Для Схемы ты говоришь о «начать программировать». Для Хаскеля - «разобрать данное выражение». Ты правда не видишь разницы?

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

или IO смущает?

именно так. чтобы человек понимал код, ему надо объяснить, что такое IO. а учитывая тот факт, что IO - это вообще самое сложное, что есть в хаскеле, то нутыпонелда.

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

Для Схемы ты говоришь о «начать программировать». Для Хаскеля - «разобрать данное выражение». Ты правда не видишь разницы?

Просто для схемы эти вещи совпадают, а для хаскеля - нет. О чем и идет разговор в этом треде.

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

Скобки в хаскеле тоже есть - их надо знать.

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

Лямбды в хаскеле тоже есть - их надо знать.

В данном случае - нет.

То есть чтобы писать не хелло ворлды нам все равно надо знать аналоги define лямбды и скобок.

Естественно.

В хаскеле _в дополнение к этому_ будет нужно каррирование, частичное применение, синтаксис инфиксных операторов и т.д..

Зато не будет set! и его странного взаимодействия с call/cc.

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

Там должно быть «под полиморфной типизированной лямбдой». Опечатка. Мог бы сам догадаться.

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

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

Нет. В Хаскеле интерфейс функции будет явно включать ограничение (Ord a).

И нам придется лезть в реализацию, чтобы узнать, а почему не позволяет.

Нет. Достаточно посмотреть на тип этой функции. Реализация никому не нужна.

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

Тогда почему не (- a 1)?

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

Нет, по двум параметрам сразу.

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

Во-вторых, в случае статики оно будет видно ДО запуска.

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

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

Конечно.

Это я его жду.

То ест ты утверждаешь, что грамматика хаскеля - это «очень просто», но линк на нее привести не можешь? Так и запишем.

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

Это в нетипизированной лямбде только бета, а в типизированной еще будут правила типизации.

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

Я под семантикой подразумеваю операционную семантику, то есть правила редукции, отвечающие за типизацию в том числе.

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

Не совсем понимаю. Y-комбинатор в энергичном ЯП пишется, значит и любые лупы - тоже. Может пояснить, в чем проблема?

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

Потому что ИРЛ программы нужны исключительно для того, что исполнять какие-то сайд-эффекты, и если порядок сайд-эффектов не специфицирован, то программа не будет делать то, что надо. Например, нам андо вывести на экран сперва «1», потом «2». Как это сделать, не специфицируя порядок исполнения?

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

Но код без него неполон.

Ещё раз: оно написано единожды, после чего может использоваться хоть тысячу раз. А потому не влияет.

Алсо, см. выше укороченную реализацию на схеме.

Видел. По понятности чуть хуже хаскельной.

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

Вообще разница, конечно, есть. А в данном контексте --- не особо. Сравни _все_ примеры quasimoto. Для понимания хаскельного варианта нужен существенно больший бэкграунд.

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

а учитывая тот факт, что IO - это вообще самое сложное, что есть в хаскеле

Опять смешно.

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

Просто для схемы эти вещи совпадают

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

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

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

Значит лиспо-скобки - часть понятия «инфиксный синтаксис». То есть в любом случае нам надо знать скобки + еще что-то.

В данном случае - нет.

Это в данном случае, а вообще - надо.

Зато не будет set! и его странного взаимодействия с call/cc.

call/cc вводится когда совсем потом (если вообще вводится). А аналог set! вводить в любом случае придется. только в схеме он сделан на элементарном уровне, в хаскеле же для set! нам нужны монады.

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

Там должно быть «под полиморфной типизированной лямбдой».

OK. Но System F - штука строго определённая, тут «понимают под» неуместны.

Опечатка. Мог бы сам догадаться.

Мог. Не считаю нужным делать за тебя твою работу.

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

Нет. В Хаскеле интерфейс функции будет явно включать ограничение (Ord a).

Мы говорим о сообщении об ошибке. И в случае схемы и в случае хаскеля будет сказано, что надо уметь применять <, а мы не умеем.

Нет. Достаточно посмотреть на тип этой функции. Реализация никому не нужна.

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

Тогда почему не (- a 1)?

Можно и (- a 1).

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

Это обычные статикосказки. Теоретически - может, практически же почти всегда случаются. Чтобы не случились - надо _очень_ по особу написать код (типа "(if (= x 4324243242) «ya sosu huy» 'ok)"). Ну так и на хаскеле если _очень_ по особому написать код (с каким-нибудь Dynamic или unsafePerformIO) можно нарваться на грабли.

Во-вторых, в случае статики оно будет видно ДО запуска.

И тайпчек и выполнение тестов - это все процесс отладки. Главное найти ошибку во время процесса отладки, а будет это ДО запуска ВО время запуска или еще там когда - это никого не ебет.

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

Конечно.

Не вижу в этом списке слово if. А без него схемы нет.

То ест ты утверждаешь, что грамматика хаскеля - это «очень просто», но линк на нее привести не можешь?

Ты предложил «посчитать». Я до сих пор жду, что ты посчитаешь.

Это в нетипизированной лямбде только бета, а в типизированной еще будут правила типизации.

Правила типизации не есть правила редукции. Учи матчасть, ничтожество.

Я под семантикой подразумеваю операционную семантику

Ну, если ты заранее берёшь более сложную вещь, кто ж тебе виноват.

правила редукции, отвечающие за типизацию в том числе.

Нет, они за неё не отвечают.

Не совсем понимаю.

Я вижу.

Y-комбинатор в энергичном ЯП пишется, значит и любые лупы - тоже

Так запиши. Хотя бы на той же схеме.

Можешь пояснить, в чем проблема?

(fixed). Могу, конечно. В том, что порядочного Y-комбинатора в энергичных ЯП нет.

ИРЛ программы нужны исключительно для того, что исполнять какие-то сайд-эффекты

Которые входят в результат. А так как на нём порядок редукций не отражается...

Например, нам андо вывести на экран сперва «1», потом «2». Как это сделать

do putStrLn "1"
   putStrLn "2"

Я тебе отвечаю, они будут именно в таком порядке.

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

чтобы человек понимал код, ему надо объяснить, что такое IO

Тут Data.IORef подключается. Возможно, не так просто, с точки зрения первого взгляда. Но я эти new, get, (=~), (=:) добавил, чтобы создать впечатление, что уже есть мутабельность и переменные (а они есть) как в случае с set! в Scheme. Их же можно реализовать спрятав на заднем плане вовсе какие-нибудь транзакционно-безопасные переменные из STM. С точки зрения интерфейса нужно только знать сигнатуры и производимые эффекты. Касательно сложности концепции IO вообще - ну не знаю, можно же относится к этому в духе RWH, LYAHFGG иметь нормальное интуитивное представление и писать программы с IO.

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

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

Сравни _все_ примеры quasimoto.

Дык сравнил. В каждом случае Хаскель справляется не хуже схемы (а в некоторых случаях лучше). И знать нужно примерно одинаковый объём информации.

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

http://www.alphabetglobal.com/img/greek_alphabet.gif

Вообще, я не знаю, что такое «лямбды над типами» - нигде такого понятия не встречал. Знаю «операторы над типами» и «параметрический полиморфизм». Я так предположил, что «лямбды над типами» = «операторы над типами». Следующий раз выражайся корректнее и используй общепринятую терминологию.

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

В каждом случае Хаскель справляется не хуже схемы

Ещё бы он с учебным примером не справился.

И знать нужно примерно одинаковый объём информации.

Зачем ты меня обманываешь?

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

Ещё раз: оно написано единожды, после чего может использоваться хоть тысячу раз. А потому не влияет.

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

Видел. По понятности чуть хуже хаскельной.

Для тех, кто _уже_ знает хаскель, а не только начал его изучать.

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

Значит лиспо-скобки - часть понятия «инфиксный синтаксис».

Бредишь. В данном случае нет «лиспо-скобок». Есть скобки, превращающие инфиксный оператор в функцию.

Это в данном случае, а вообще - надо.

«А вообще» знать в обоих случаях нужно много. И в Схеме и в Хаскеле.

call/cc вводится когда совсем потом (если вообще вводится).

Угу. Только его, к тому же, реализуют обычно хреново (видел я как-то тестик на правильное взаимодействие call/cc и set! - ни одна существующая реализация схемы его не проходила).

А аналог set! вводить в любом случае придется.

Какой ещё аналог set! в языке с немутабельными переменными?

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

Для понимания хаскельного варианта нужен существенно больший бэкграунд.

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

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

Мы говорим о сообщении об ошибке. И в случае схемы и в случае хаскеля будет сказано, что надо уметь применять <, а мы не умеем.

Угу, только хаскель скажет об этом ДО запуска, и с гарантией.

Мы же хотим узнать, для чего оно нужно

Зачем? Мы свою ошибку уже поняли.

Можно и (- a 1).

И цикл не закончится никогда.

Теоретически - может, практически же почти всегда случаются.

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

(if (= x 4324243242) «ya sosu huy» 'ok)

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

Ну так и на хаскеле если _очень_ по особому написать код (с каким-нибудь Dynamic или unsafePerformIO) можно нарваться на грабли.

Угу, только это будет видно ещё на уровне заголовка МОДУЛЯ.

И тайпчек и выполнение тестов - это все процесс отладки.

Нет. Отладка - то, что происходит во время работы программы. И хорошо бы, чтобы её было поменьше.

а будет это ДО запуска ВО время запуска или еще там когда - это никого не ебет.

...или у конечного пользователя. Ну-ну.

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

У меня возникла идея. Назови какую-нибудь хаскельную книгу, являющуюся примерным аналогом сикпа.

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

Вообще, я не знаю, что такое «лямбды над типами»

Букву «лямбда» знаешь? Заглавную?

Я так предположил, что «лямбды над типами» = «операторы над типами».

У тебя дислексия, что ли? «Лямбда» - это «лямбда», а не «оператор».

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

Просвети, какова в данном случае «общепринятая терминология». И нет, «параметрический полиморфизм» не катит.

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

Зачем ты меня обманываешь?

Я хочу развести тебя на бабки.

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

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

Покажи, как это будет выглядеть.

Для тех, кто _уже_ знает хаскель, а не только начал его изучать.

Для тех, кто уже знает и хаскель, и схему. А тут есть другие?

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

Не вижу в этом списке слово if. А без него схемы нет.

Извини, но ты слепой пзидоглазый уебок, п. 7.1.3:

(conditional) = (if (test) (consequent) (alternate))

Так запиши. Хотя бы на той же схеме.

пожалуйста:

(define Y
  (lambda (X)
    ((lambda (p)
       (X (lambda (arg) ((p p) arg))))
     (lambda (p)
       (X (lambda (arg) ((p p) arg)))))))

В том, что порядочного Y-комбинатора в энергичных ЯП нет.

Чем вышеприведенный - не порядочный?

Которые входят в результат.

Не входят.

Я тебе отвечаю, они будут именно в таком порядке.

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

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

Касательно сложности концепции IO вообще - ну не знаю, можно же относится к этому в духе RWH, LYAHFGG иметь нормальное интуитивное представление и писать программы с IO.

То есть пишем код, не понимая, что он значит? Прекрасный подход.

то нафига ему скобочки + динамика для изучения ФП?

А зачем его инфикс+статика для изучения ФП?

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

То ест ты утверждаешь, что грамматика хаскеля - это «очень просто», но линк на нее привести не можешь?

_Грамматика_ есть в The Haskell 98 Report. Про формальную семантику тоже есть публикации, но уже немного не того формата.

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

Бредишь. В данном случае нет «лиспо-скобок». Есть скобки, превращающие инфиксный оператор в функцию.

«скобки» - это и есть «лиспо-скобки».

«А вообще» знать в обоих случаях нужно много. И в Схеме и в Хаскеле.

Но в схеме на порядок меньше, чем в хаскеле.

Угу. Только его, к тому же, реализуют обычно хреново (видел я как-то тестик на правильное взаимодействие call/cc и set! - ни одна существующая реализация схемы его не проходила).

Это уже вообще к делу не относится, но из интереса - что за тестик?

Какой ещё аналог set! в языке с немутабельными переменными?

Механизм, который будет эквивалентен по выразительности мутабельным переменным, очевидно. Без него никакой полезной программы написать не удастся.

anonymous
()

tl;dr, толсто, хаскель не нужен, если уж хотите ML, то выбирайте Standard ML.

// Я вам также могу сказать, что

decodeLZW=:[:;]{[:;[:(],<@(>@{.,{.@>@{:)@:{)&.>/<@(;/@[),~|.@(2<\])
это просто и удобно, потому что я привык.

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

Нужно только знание языка. Неужели ML сложнее лиспа?

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

обычные типизированные каррированные функции - этого достаточно

И это уже больше, чем нужно в scheme.

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

То есть пишем код, не понимая, что он значит?

А почему IO это именно сложно? Операционные семантики и теор. кат. хотя и полезны для изучения и оснований, но вовсе не обязательны для понимания и использования IO / STM / ST / etc.

А зачем его инфикс+статика для изучения ФП?

Потому что это хорошо, удобно, полезно :)

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

Угу, только хаскель скажет об этом ДО запуска, и с гарантией.

Он скажет об этом во время отладки - так же, как и динамический ЯП. Гарантий тоже нет - никто ведь не гарантирует, что чекер хаскеля работает полностью корректно.

Зачем? Мы свою ошибку уже поняли.

Откуда я знаю - зачем? Это же ты сказал, что надо лезть.

И цикл не закончится никогда.

Ну да. И?

Функция вместо числа и правда случается не слишком часто, но вот nil - сплошь и рядом.

Это в CL, лисп в принципе тут не при чем. CL в этом плане (с nil) напоминает ЯП со слабой типизацией, я же говорил о сильной.

Хочешь сказать, это редко бывает?

Я хочу сказать, что этого практически никогда не бывает.

Угу, только это будет видно ещё на уровне заголовка МОДУЛЯ.

Видно что именно?

Нет. Отладка - то, что происходит во время работы программы.

Нет. Отладка - это процесс поиска и исправления ошибок в программе.

...или у конечного пользователя. Ну-ну

если во время отладки - то это _не_ у пользователя.

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

Извини, но ты слепой пзидоглазый уебок, п. 7.1.3: (conditional) =

Не вижу на странице http://schemers.org/Documents/Standards/R5RS/HTML/ строки "(conditional) ="

пожалуйста:

Я просил loop, а не Y-комбинатор. Который, к тому же, непорядочный.

Чем вышеприведенный - не порядочный?

Ну, попробуй (Y (lambda (arg) (list 1 (car arg)))).

Не входят.

Входят.

Но это все бабушка надвое сказала

Нет, это всё совершенно строго.

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

То есть пишем код, не понимая, что он значит? Прекрасный подход.

А вы, можно подумать, понимаете, в какой ассемблер ваш код превращается?

Какие-то абстракции всегда есть.

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

Это уже вообще к делу не относится, но из интереса - что за тестик?

Извини, не могу найти.

Механизм, который будет эквивалентен по выразительности мутабельным переменным, очевидно. Без него никакой полезной программы написать не удастся.

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

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

Букву «лямбда» знаешь? Заглавную?

Буквы тут при чем?

У тебя дислексия, что ли? «Лямбда» - это «лямбда», а не «оператор».

Заебись, а «параметрический полиморфизм», видимо, ближе?

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

Параметрический полиморфизм.

И нет, «параметрический полиморфизм» не катит.

Он не «не катит», это он и есть.

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

Покажи, как это будет выглядеть.

В OOPLAI смотри.

Для тех, кто уже знает и хаскель, и схему. А тут есть другие?

А при чем тут «тут», если речь об изучении?

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

А почему IO это именно сложно? Операционные семантики и теор. кат. хотя и полезны для изучения и оснований, но вовсе не обязательны для понимания и использования IO / STM / ST / etc.

Какие семантики и теоркат, братюнь? Я говорил об общем понимании как и почему :)

Потому что это хорошо, удобно, полезно :)

Это твое мнение.

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

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

ПОСЛЕ запуска. С некоторой вероятностью.

никто ведь не гарантирует, что чекер хаскеля работает полностью корректно.

А это тут при чём? Компилятор схемы тоже может быть забагованным.

Откуда я знаю - зачем? Это же ты сказал, что надо лезть.

Хорошо, реквестирую способ заранее узнать о том, что эти a и b нужно уметь сравнивать. В Схеме. Не залезая в реализацию.

Ну да. И?

И вот. То есть, не все йогурты одинаково полезны. Так что примеры других йогуртов не в тему.

CL в этом плане (с nil) напоминает ЯП со слабой типизацией, я же говорил о сильной.

Не понял, в схеме нет nil-а?

Я хочу сказать, что этого практически никогда не бывает.

Врёшь.

Видно что именно?

Что здесь могут использоваться Dynamic или unsafePerformIO.

Отладка - это процесс поиска и исправления ошибок в программе.

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

если во время отладки - то это _не_ у пользователя.

И? Пользователю от этого не легче.

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

Буквы тут при чем?

При слове «лямбда».

Заебись, а «параметрический полиморфизм», видимо, ближе?

К чему ближе?

Он не «не катит», это он и есть.

Нет. Ещё раз: в System F конструкции вида $\Lambda\alpha...$ имеются, в Хаскеле их нет. Ergo, система типов Хаскеля не содержит System F.

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

В OOPLAI смотри.

Этот конкретный пример? Сомневаюсь, что он там может быть.

А при чем тут «тут», если речь об изучении?

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

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

Не вижу на странице http://schemers.org/Documents/Standards/R5RS/HTML/ строки "(conditional) ="

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

Я просил loop, а не Y-комбинатор. Который, к тому же, непорядочный.

Какой луп? Всмысле - что он должен делать?

Ну, попробуй (Y (lambda (arg) (list 1 (car arg)))).

И с какого хуя это должно работать, если arg должен быть ф-ей, а ты к нему car применяешь?

Нет, это всё совершенно строго.

Правда? Ну ок, приведи мне операционную семантику хаскеля, из которой ясно - да, совершенно строго, сперва выведет «1», потом «2».

Входят.

Докажи.

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

А вы, можно подумать, понимаете, в какой ассемблер ваш код превращается?

Не надо доводить до абсурда. Я как раз речь веду о понимании в рамках терминов самого ЯП. то есть: тайпклассов, монад, IO.

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

Нет. Лиспо-скобки входят в понятие «лиспо-список».

Не входят.

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