LINUX.ORG.RU

[Haskell] простой вопрос

 


3

4

Есть функция на Scheme (из sicp):

(define new-withdraw
  (let ((balance 100))
    (lambda (amount)
      (if (>= balance amount)
	  (begin (set! balance (- balance amount))
		 balance)
	  "Недостаточно денег на счете"))))
Как реализовать подобное на Haskell?

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

> *Main> f' <- newWithdraw

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

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

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

f' <- newWithdraw

f = f' 20

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

И это, очевидно, не функция ОПа.

Про карринг слышал? В Хаскеле это делается автоматически. ТС сделал карринг руками на Лиспе. Вот и вся разница.

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

Чего? Это не карринг. Карринг был бы вот:

[code] (define (new-withdraw balance) (let ((balance 100)) (lambda (amount) (if (>= balance amount) (begin (set! balance (- balance amount)) balance) «Недостаточно денег на счете»)))) [/code]

А у ОПа обычное мутабельное замыкание.

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

Чего? Это не карринг. Карринг был бы вот:

(define (new-withdraw balance)
  (let ((balance 100))
    (lambda (amount)
      (if (>= balance amount)
	  (begin (set! balance (- balance amount))
		 balance)
	  "Недостаточно денег на счете"))))

А у ОПа обычное мутабельное замыкание.

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

Согласен, был неправ. Карринг используется только в моём последнем варианте.

Тем не менее, смысл претензий я не понимаю. Ни в одном варианте не возвращается функция с двумя аргументами, ни в каком виде. Изначально баланс установлен в 100, во всех вариантах.

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

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

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

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

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

и оно во всех вариантах на хаскеле передается (либо явно, либо в монаде)

Так оно тогда в Лиспе тоже передаётся. В переменной.

В варианте ОПа состояние никуда не передается, оно существует только внутри самой ф-и.

Ну дык. Во всех приведённых вариантах на Хаскеле состояние наличествует только внутри замыкания.

В чём разница-то?

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

Вместо обычной аппликации

Можно перегрузить обычную аппликацию (в динамических языках она именно такая - перегруженная):

class Apply f g | f -> g where
  ($) :: f -> g

instance Apply (a -> b) (a -> b) where
  ($) = (Prelude.$)

instance Functor f => Apply (f (a -> b)) (a -> f b) where
  x $ n = fmap (Prelude.$ n) x

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

> Так оно тогда в Лиспе тоже передаётся. В переменной.

В примере ОПа ничего не передается, в том и дело. На схеме можно сделать вариант с передачей, можно без передачи. На хаскеле - только с передачей.

Ну дык. Во всех приведённых вариантах на Хаскеле состояние наличествует только внутри замыкания.

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

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

В примере ОПа ничего не передается, в том и дело.

Как же не передаётся? Вот она, переменная balance. Не видишь суслика, что ли?

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

Самый первый вариант: [Haskell] простой вопрос (комментарий)

Что тут торчит наружу?

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

нетривиальные костылики.

там только liftM ($ 20) newWithdraw, если что. Т.е. «суём применение к 20 в монаду».

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

> (в динамических языках она именно такая - перегруженная):

В динамических языках она как раз обычная.

Можно перегрузить

А еще можно написать компилятор любого другого ЯП.

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

В динамических языках она как раз обычная.

Как минимум - от переменного количества аргументов, следовательно, уже перегружена.

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

> Как же не передаётся? Вот она, переменная balance. Не видишь суслика, что ли?

При вызове функции - она не передается.

Что тут торчит наружу?

IO. И при вызове функции мы теперь должны взаимодействовать с этим IO. В варианте на схеме IO спрятано внутри, нам с ним взаимодействовать не надо.

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

> Как минимум - от переменного количества аргументов, следовательно, уже перегружена.

Это откуда сделан такой вывод? Всмысле тот, что для переменного количества аргументов - перегружена?

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

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

А ты не слушай тех, кто вертит нетривиальные костылики.

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

Да точно так же.

main = do
  f' <- newWithdraw
  let f = f' 40
  f
  f
  f
  return ()
anonymous
()
Ответ на: комментарий от anonymous

Всмысле тот, что для переменного количества аргументов - перегружена?

Тогда это просто функция от типа Object (супертип всех типов), или <Object>_n (кортеж, который тоже : Object). Ну или apply : {n : Nat} -> (<Object>_n -> Object) -> <Object>_n -> Object.

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

> Да точно так же.

Ну ты засунул внутрь do, предполагается что тебе этот f' надо использовать в нескольких других функциях.

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

> Тогда это просто функция от типа Object (супертип всех типов), или <Object>_n (кортеж, который тоже : Object). Ну или apply : {n : Nat} -> (<Object>_n -> Object) -> <Object>_n -> Object.

Ну и в чем проблема?

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

> Всё ок, я соглашаюсь. При условии, что у нас есть Object.

Речь шла о ЯП с динамической типизацией. Там определенно есть Object, не так ли?

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

предполагается что тебе этот f' надо использовать в нескольких других функциях.

Да liftM это, обычный liftM. Он же fmap, в данном случае.

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

При вызове функции - она не передается.

И на Хаскеле не передаётся.

IO.

Ну дык это несколько другое дело. Состояние наружу не торчит, мы не можем снаружи до него достучаться.

И при вызове функции мы теперь должны взаимодействовать с этим IO. В варианте на схеме IO спрятано внутри, нам с ним взаимодействовать не надо.

IO - как раз такая вещь, которая не может быть спрятана внутри. Если бы оно было спрятано внутри, то последовательные вызовы давали бы один и тот же результат. Тот факт, что они таки дают разные результаты - это и есть «торчащее наружу IO».

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

> Ну ты засунул внутрь do, предполагается что тебе этот f' надо использовать в нескольких других функциях.

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

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

тебя реально не смущает ситуация, когда надо делать lifrM ($ 20) f вместо f 20?

== «тебя реально не смущает статическая типизация?»

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

предполагается что тебе этот f' надо использовать в нескольких других функциях.

В чём проблема-то?

a f' =
  do let f = f' 40
     f
     f
     return ()

b f' =
  do f' 30
     return ()

main =
  do f' <- newWithdraw
     a f'
     b f'

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

> Ну дык это несколько другое дело. Состояние наружу не торчит, мы не можем снаружи до него достучаться.

Мы видим снаружи, по крайней мере, что некоторое состояние есть. И что мы должны корректно с ним взаимодействовать и учитывать его наличие. Грубо говоря - мы видим наличие некоего черного ящика и вынуждены писать код с учетом его наличия. Этот черный ящик и торчит наружу. А на схеме он - внутри.

Тот факт, что они таки дают разные результаты - это и есть «торчащее наружу IO».

«Торчащее наружу ИО» - это когда нам приходится писать код, учитывающий особенности взаимодействия с этим ИО. По крайней мере - такого мое понимание.

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

Ну вот, приходится f' явно передавать :) Собственно, уже же написано вариант с liftM - это и есть то, что требовалось.

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

делать lifrM ($ 20)

Кстати, в таких ситуациях можно не играть в point-free, а расписать do-нотацию (псевдо-императивно), чтобы было всё понятно.

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

> == «тебя реально не смущает статическая типизация?»

Ну здесь статическая типизация не при чем, если в динамике повторить семантику ИО, то получится такой же код.

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

Ну здесь статическая типизация не при чем, если в динамике повторить семантику ИО, то получится такой же код.

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

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

Так дело тут именно в разделении кода, а не в типизации. В динамике с подобным разделением тайпчек пройдет (потому что тайпчека и нет), но даст ошибку в рантайме.

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

> Ну вот, приходится f' явно передавать :)

Если не передавать f' явно, то это будет называться «глобальной переменной».

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

«Торчащее наружу ИО» - это когда нам приходится писать код, учитывающий особенности взаимодействия с этим ИО. По крайней мере - такого мое понимание.

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

Ну вот, приходится f' явно передавать

Гм. А как ты собираешься использовать нечто (результат new-witdraw) в нескольких разных функциях, не передавая это явно?

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

> Так нам приходится. В любом языке.

В варианте схемке не приходится, например.

Гм. А как ты собираешься использовать нечто (результат new-witdraw) в нескольких разных функциях, не передавая это явно?

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

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

В варианте схемке не приходится, например.

Дочитай до конца то, на что отвечаешь. Приходится.

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

Да. Перечитай ещё раз вопрос.

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

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

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

> Дочитай до конца то, на что отвечаешь. Приходится.

Дочитал. При чем тут возможность закешировать результат? Не приходится.

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

А с этим есть какие-то проблемы?

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