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

Также, (есть такое впечатление, что) само по себе классическое ООП выглядит как полу-мера — с одной стороны не требует введения неограниченных ФВП (и, как следствие, GC) и, с другой стороны, позволяет создать для функций (методов класса) _контекст_ (в виде данных класса). В случае ФВП у функций есть контекст вычисления из коробки. Соответственно, классические паттерны тоже требуют пересмотрения как только в языке появляются ФВП (например, http://norvig.com/design-patterns/, http://blog.ezyang.com/2010/05/design-patterns-in-haskel/).

Правда, начёт наследования мне не совсем ясно — какого его место?

quasimoto ★★★★ ()

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

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

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

Код же на хаскеле порою выглядит как шифрограмма, тогда как код на лиспе чист и понятен, даже если лямбды вложенные вместо каррирования ;)

P.S. до конца прочитать не осилил - много букв

dave ★★★★★ ()

Я тоже не сильно догнал мысль.

Код как-то не особо понравился. PointClass и Stack - откровенно нерасширяемы (например, добавление туда метода с несколькими аргументами другого типа сломает весь код и сделает его отвратительным).

Мне кажется, чем писать в таком стиле сразу - лучше сразу С++ взять. Всё-таки на Haskell нужно стремиться максимальную часть кода оставить чистой, и только там где необходимо, залезть в IO. А тут и вовсе ST можно обойтись, особенно если это вычисления (Point...)

p.s. Глянь, если вдруг не видел. Там внизу ссылка на pdf. Сам я правда не читал.

http://homepages.cwi.nl/~ralf/OOHaskell/

ratatosk ()

Почему бы не говорить об ООП в духе Smalltalk? Где объект - сущность, принимающая сообщения, а вызов метода - отправка сообщения?

В GHC это все «из коробки» имеется.

Кроме того, вроде бы как в рамках System FC можно проэмулировать некоторые вещи из ООП наследования, через механизм эквивалентности типов.

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

классическое ООП выглядит как полу-мера

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

Тут тебе и подтипы, и система модулей, и интерфейсы, и все 4 вида полиморфизма, и процедуры, и простая типизация (в смысле отдельно от механизма подтипов). Результат получился так себе.

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

Я тоже не сильно догнал мысль.

Я только сделал _прямое_ переложение схемовых мутабельных замыканий (это там где let over lambda + set!). Насколько это хорошо или плохо - я не знаю. Просто что в SICP (третья глава), что в OOPLAI много материала на этом основано, и вот на хаскеле всё выглядит примерно также (и даже получается проще и лаконичнее, но такая лаконичность на любителя). Чтобы было, короче.

Насчёт OOHaskell - я не верю в него :) Ну, то есть, это ведь более «крутой» уровень чем HList того же автора, но HList не произвёл на меня впечатление чего-то рабочего / завершённого / полезного (только как type-level exercises). Смысла смотреть дальше на OOHaskell не вижу.

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

Где объект - сущность, принимающая сообщения, а вызов метода - отправка сообщения?

threads + stm channels? Это уже скорее акторы.

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

Не первый раз уже эту картинку постишь. Книжку то прочитал или до этой страницы только?

qaqa ()

если говорить именно об _изучении_, то:

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

Это еще более удобно для _изучения_, т.к. синтаксис секспров намного проще и можно практически сразу приступить, собственно, к написанию программ. Кроме того, когда по курсу надо будет написать простенький интерпретатор, то при наличии инфикса синтаксический разбор будет сложнее, собственно, самого интерпретатора - то есть синтаксис опять не позволяет сконцентрироваться на сути.

так как все функции уже каррированные (позволяют частичное применение).

И снова - для _изучения_ удобнее вариант схемы, т.к. не надо объяснять, что такое каррирование. Еще одно значительно преимущество, которая почти всегда упускают - писать поинт-фри код на каррированных ф-ях схема тоже не позволяет, сразу прививая, таким образом, правильный стиль.

вместо

У тебя там эквивалентыне записи, ты что-то напутал, видимо.

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

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

anonymous ()

 — | (Динамический) диспетчер по методам. Он принимает объект (Var Point),
 — имя метода (PointMethod, т.е. статическое, в данном случае, сообщение)
 — и два опционных аргумента для методов (Double -> Double). Эту функцию
 — можно помещать непосредственно в Point.

А теперь сделай на своем «настоящем ФВП» да с такими-то АТД и паттерн-матчингом так, чтобы dispatch мог принимать какое угодно количество аргументов (ведь метод не обязательно принимает два) и мог возвращать любые другие типы (ведь метод не обязательно должен возвращать тип класса, для которого был вызван).

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

без тредов. Это подход из smalltalk/ruby.

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

как ты собираешься объяснять это людям, которые только начали изучение?

Очевидно, никак. Это пишется для нас, а не для возможных учеников.

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

И потом это все используется через do-нотацию.

do короче progn.

Miguel ★★★★★ ()

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

Reset ★★★★★ ()

Скажу за себя. Я с SICP знакомился уже после чтения «Programming in Haskell» и детального разбора Prelude.hs, и меня не покидало ощущение, что на Haskell большая часть примеров выглядело бы доступнее что-ли(хотя бы на паттерн-матчинг посмотрите)... С другой стороны, нельзя не отдать должное SICP - так как после него уже не остается всяких «пробелов», которые несомненно будут после «gentle introduction» или же совсем детского «learn your haskell for great good»...

p.s. у меня где-то в закладках был отчет Пентагона(открытый, а не то что вы подумали ;]), но никак не могу сейчас найти - в ней описывалось как разработчикам предлагали реализовать алгоритм на разных языках программирования, и лучшим оказался алгоритм написанный на Haskell. Затем взяли программиста, который не знал Haskell, заставили его за короткое время познакомиться с Haskell и реализовать алгоритм - так вот, у него получилось еще нагляднее и быстрее чем оригинальный вариант. Ни у кого, случайно нету линка на этот отчет?

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

Очевидно, никак.

Вроде, в начале шла речь о том, что хаскель для обучения лучше схемы?

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

do короче progn.

Только для использования do есть лишь два подхода:

1. объяснить, как do раскрывается в лямбды с >>=/>>, изучить SystemF, монады, IO, основные особенности рантайма хаскеля. После этого можем написать hello world.

2. сказать «считайте, что оно работает как progn, хотя на самом деле это и не так. Как на самом деле - узнаете когда-нибудь потом. Может быть.»

Не знаю, какой вариант лучше.

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

Я с SICP знакомился уже после чтения «Programming in Haskell» и детального разбора Prelude.hs, и меня не покидало ощущение, что на Haskell большая часть примеров выглядело бы доступнее что-ли(хотя бы на паттерн-матчинг посмотрите)...

У меня попытка прочесть SICP вылилась в постоянное «ой, как оно сейчас наебнётся». Там, где даже примитивная система типов могла бы очень здорово поддержать.

но никак не могу сейчас найти

http://haskell.cs.yale.edu/wp-content/uploads/2011/03/HaskellVsAda-NSWC.pdf - оно?

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

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

Там таких мест нет.

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

Вроде, в начале шла речь о том, что хаскель для обучения лучше схемы?

Да. Это не означает, что учеников надо грузить теорией.

объяснить, как do раскрывается в лямбды с >>=/>>,

Абсолютно тривиальным образом. Сравни:

main = do
  putStrLn $ "What is your name?"
  name <- getLine
  putStrLn $ "Hello, " ++ name

и

main = do
  (putStrLn $ "What is your name?") >>
  (getLine) >>= \name ->
  (putStrLn $ "Hello, " ++ name)

изучить SystemF

Бред. Не нужно.

основные особенности рантайма хаскеля.

Бред. Не нужно.

После этого можем написать hello world.

main = putStrLn "Hello world!"

Тут даже do не нужно.

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

Вроде, в начале шла речь о том, что хаскель для обучения лучше схемы?

Я хоть так и считаю (ссылку на Wadler поэтому добавил), но примеры из OP ни на что не претендуют, это просто «в схеме было так, в хаскеле будет так (если делать прямо точно так же, может лучше будет сделать по-другому)».

Хотя, я сам начинал знакомство с вещами с SICP, и тогда видеть (+ (* 2 4) 1) с деревом рядом было откровением, никого хацкеля я тогда не слышал :)

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

Только для использования do есть лишь два подхода

Я там, кстати, do специально не использовал, всё через комбинаторы. Можно было бы сделать более нудно и понятно (вообще без комбинаторов).

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

без тредов. Это подход из smalltalk/ruby.

У Macil было «В GHC это все „из коробки“ имеется», мне кроме тредов и сообщений в TChan ничего на ум больше не приходит (из того что есть особого именно в GHC для объектов/сообщений).

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

оно?

да, спасибо!

Только я спутал Пентагон с ARPA и US Navy.

Вот интересная цитата оттуда (плачь про сложность Haskell`я):

====
Intermetrics, independently and without the knowledge of NSWC or Yale University, conducted an experiment of its own: the Haskell Report was given to a newly hired college graduate, who was then given 8 days to learn Haskell. This new hire received no formal training, but was allowed to ask an experienced Haskell programmer questions as issues came up during the self study. After this training period, the new hire was handed the geo-server specification and asked to write a prototype in Haskell. The resulting metrics shown in row 10 of the table are perhaps the most stunning of the lot, suggesting the ease with which Haskell may be learned and effectively utilized.
====

Ну и таблица на странице 9 доставляет ;)

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

Да. Это не означает, что учеников надо грузить теорией.

Так это как раз принципиальный недостаток, а не достоинство.

Абсолютно тривиальным образом. Сравни:

Понятно, что это не сложно - речь шла о том, что объяснять это придется.

Бред. Не нужно.

Нужно, иначе нельзя объяснить, что такое IO.

Бред. Не нужно.

нужно, иначе нельзя объяснить, как исполняется IO.

Тут даже do не нужно.

Но нужно IO - то есть SystemF, АТД, тайпклассы, монады, рантайм.

Если человек не знает хоть один пункт из вышеперечисленного - ему невозможно корректно объяснить, что значит строка main = putStrLn «Hello world!». Это не значит, что он не сможет написать эту строку - но как и почему она работает, он понимать не будет.

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

Я там, кстати, do специально не использовал, всё через комбинаторы. Можно было бы сделать более нудно и понятно (вообще без комбинаторов).

Через do-нотацию (неявно) делаются вызовы в репле. Этот факт тоже, кстати, надо будет объяснить :)

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

Нужно, иначе нельзя объяснить, что такое IO.

Как связаны IO и System F?

Но нужно IO - то есть SystemF, АТД, тайпклассы, монады, рантайм.

Ты бредишь.

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

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

Я думаю оптимальнее такой вариант - курс SICP, параллельно теория. Два курса заканчиваются одновременно, после чего можно быстро переложить изученное на haskell-way, а дальше уже как угодно.

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

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

(>>), ($), (:) и (<$>) в хаскеле это базовые инфиксные функции которые входят в Prelude (кроме (<$>)), т.е. знание языка предполагает знание того что это такое. (=~) это просто определённая мной инфиксная функция (как оператор в плюсах).

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

Как связаны IO и System F?

IO - это монада, каким образом ты сумеешь объяснить, что такое монада, не затрагивая типизацию?

Ты бредишь.

Ну попробуй объясни, что такое ИО и как оно работает, не используя эти понятия.

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

Вот вот. В схеме сущностей гораздо меньше, поэтому код читается проще.

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

IO - это монада, каким образом ты сумеешь объяснить, что такое монада, не затрагивая типизацию?

Почему не затрагивая? Затрагивая. Но System F тут при чём? Haskell 98 почти ортогонален ей.

Ну попробуй объясни, что такое ИО и как оно работает, не используя эти понятия.

Гм. IO есть везде, вообще-то.

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

Почему не затрагивая? Затрагивая. Но System F тут при чём? Haskell 98 почти ортогонален ей.

SystemF - это чтобы упростить объяснения. Система типов хаскеля, естественно, сложнее, но для объяснения это усложнение будет излишним.

Гм. IO есть везде, вообще-то.

Я говорю об IO, которое монада в хаскеле и которое возвращает ф-я putStrLn, например.

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

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

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

В схеме сущностей гораздо меньше, поэтому код читается проще.

В схеме сущностей БОЛЬШЕ, а уж сколько их в CL...

Почему-то лисперы считают, что у них, якобы, синтаксис проще. Хотя там те же if/do/define, только завёрнутые в кучу скобок и прогны.

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

SystemF - это чтобы упростить объяснения.

Смешно.

Система типов хаскеля, естественно, сложнее

Система типов Haskell98, естественно, проще. В SystemF есть лямбды по типам, в Хаскеле их нет.

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

Примеры в этой теме говорят об обратном.

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

Смешно

Чем же?

Система типов Haskell98, естественно, проще. В SystemF есть лямбды по типам, в Хаскеле их нет.

SystemF, а не SystemF_w.

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

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

Ну, вот я раскрываю SICP на первом попавшемся месте. Вижу код:

(define (sum term a next b)
  (if (> a b)
      0
      (+ (term a)
         (sum term (next a) next b))))
И сразу понимаю, что если туда пойдут такие a и b, что отношение порядка между ними не определено, то система навернётся с невнятным сообщением об ошибке. Что функция next абстрагирована, а функция (>), почему-то - нет, и в интерфейсе функции нет НИЧЕГО, что указывало бы на её присутствие, нужно копаться в реализации. Я ещё понимаю, равенство прятать - но отношение порядка?

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

Примеры в этой теме говорят об обратном.

О, да, особенно этот:

(define add
  (lambda (n)
    (lambda (m)
      (+ m n))))
против
add = (+)

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

Чем же?

Что для «упрощения» ты предлагаешь копаться в деталях реализации.

В SystemF есть лямбды по типам, в Хаскеле их нет.

SystemF, а не SystemF_w.

То есть, ты System F в глаза не видел?

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

Вот это жесть. Код короткий, но не прозрачный.

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
Reset ★★★★★ ()
Ответ на: комментарий от Miguel

Почему-то лисперы считают, что у них, якобы, синтаксис проще. Хотя там те же if/do/define, только завёрнутые в кучу скобок и прогны.

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

В схеме сущностей БОЛЬШЕ, а уж сколько их в CL...

В схеме сущностей меньше из-за энергичной модели вычисления (которая более выразительна) и отсутствия типизации (которая добавляет множество сущностей). Ленивый статически типизированный ЯП всегда будет в несколько раз более bloated, чем энергичный динамически типизированный.

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

Если уж говорить о ML языках, то есть гораздо более простые для понимания и чтения кода OCaml и F#.

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

Что для «упрощения» ты предлагаешь копаться в деталях реализации.

Нет, не предлагаю.

То есть, ты System F в глаза не видел?

Видел, конечно. «лямбды по типам» - это f_w. А SystemF - это типизированная лямбда с полиморфизмом. Система типов Haskell 98 - это SystemF с дополнениями.

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

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

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