LINUX.ORG.RU

[haskell] Как реализовать LET форму?

 


0

1

Скажем, IF можно сделать просто ленивой функцией:

if' True  a b = a
if' False a b = b

test_1 = if' True "ok" "wrong!"
test_2 = if' False "wrong!" "ok"

test_3 = if' True
             "ok"
             "wrong!"

test_4 = if'
    False
  "wrong!"
  "ok"

Но как сделать LET? В Common Lisp LET можно ввести с помощью макроса, который транслирует код в определённый вызов лямбда-функции:

(defmacro my/let (bindings &body body)
  (let (arguments values types)
      (dolist (binding bindings)
        (etypecase binding
          (symbol (push binding arguments)
                  (push nil     values))
          (cons   (push (first  binding) arguments)
                  (push (second binding) values)
                  (when (third binding)
                    (push `(type ,(third binding) ,(first binding)) types)))))
      `(funcall
        (lambda (,@(nreverse arguments))
          ,@(when types
              `((declare ,@(nreverse types))))
          ,@body)
        ,@(nreverse values))))

(macroexpand-1 '(my-let ((a 2 fixnum) (b 2 fixnum)) (* a b)))

(FUNCALL
 (LAMBDA (A B)
   (DECLARE (TYPE FIXNUM A)
            (TYPE FIXNUM B))
   (* A B))
 2
 2)

В какую строну смотреть, чтобы выражаться подобным образом на Haskell?

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

Ну я поставил тэг [haskell] - значит хаскелевский. На CL я привёл пример потому что там есть макросы с call by macro expansion соглашением о вызове но нет ленивых функций. В haskell есть ленивые функции c call-by-need стратегией - ими можно делать простенькие специальные формы вроде if (так же как в CL можно использовать макросы для предотвращения энергичного вычисления аргументов). Но в случае более сложных форм, вроде let ленивых функций уже не хватает:

let' var val form = (\ var -> form var) val

не работает - val is unbound. Т.е. нужны средства со стратегией вычисления call by macro-expansion, что-то вроде:

macro let' var val form = `((\ ,var -> ,form ,var) ,val)

Конечно дело не в самом let (это простейший пример), а в наличии неких средств выразительности.

quasimoto ★★★★
() автор топика
Ответ на: комментарий от quasimoto
- let' var val form = (\ var -> form var) val
- macro let' var val form = `((\ ,var -> ,form ,var) ,val)

+ let' var val form = (\ var -> form) val
+ macro let' var val form = `((\ ,var -> ,form) ,val)
quasimoto ★★★★
() автор топика
Ответ на: комментарий от cathode

Смотрел, но большую часть не понял. Как написать что-нибудь аналогичное простому макросу?

quasimoto ★★★★
() автор топика

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

let' x f = f x

main = 
  let' "foo" $ \x -> do
    putStrLn x
    let' "bar" $ \y -> do
      putStrLn $ x ++ y
tarc
()
Ответ на: Где достать циклодол? от balodja

Э-э-эм, форма?

Да, вопрос такой - как определить форму со стратегией вычисления call by macro expansion.

IF можно определить как ленивую функцию. А LET уже не получается, нужно что-то вроде TH, но я не понял как его использовать.

Зачем? В чем сакральный смысл?

Работать с AST?

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

> Да, вопрос такой - как определить форму со стратегией вычисления call by macro expansion.

IF можно определить как ленивую функцию. А LET уже не получается, нужно что-то вроде TH, но я не понял как его использовать.

Считай, что let в haskell — примитив. Тот же core haskell, который в ghc используется, почти полностью состоит из лямбды, let и case of. Вопрос — зачем его выражать через что-то?

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

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

Если хочешь работать с AST программы, то тебе только Template Haskell и поможет, но радости от него в текущей его реализации никакой, ибо соотношение пользы для среднестатистичиской программы и траха мозга минимально. Так что проще забить, тем более что в хаскеле полно других средств для метапрограммирования.

tarc
()

я всё-таки не совсем понял, чем тебя стандартный let не устроил, или ты так, побаловаться решил? =)

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

Чисто ради развлечения.

module LetNoRec where

import Language.Haskell.TH
import Language.Haskell.TH.Syntax

letNoRec :: Q Exp -> Q Exp
letNoRec = fmap letPure

letPure :: Exp -> Exp
letPure (LetE ds e) = foldr letdecToLam e ds
letPure x = x

letdecToLam :: Dec -> Exp -> Exp
letdecToLam (ValD p (NormalB b) []) e = AppE (LamE [p] e) b
{-# LANGUAGE TemplateHaskell #-}
module Main where

import LetNoRec (letNoRec)
import Language.Haskell.TH

main :: IO ()
main = runQ (letNoRec [| let {x = 2; y = 2}  in x*y |]) >>= putStrLn.pprint

Вывод:

$ ./Main 
(\x_0 -> (\y_1 -> x_0 GHC.Num.* y_1) 2) 2

Понятное дело, что при:

main = (putStrLn . show) $(letNoRec [| let {x = 2; y = 2} in x*y |])

вывод 4.

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

P.S. И боже упаси кого-нибудь подумать, что я это серьезно :)

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

Ах, да, забыл ругалку добавить в letdecToLam.

letdecToLam :: Dec -> Exp -> Exp 
letdecToLam (ValD p (NormalB b) []) e = AppE (LamE [p] e) b 
letdecToLam d e = error "Unexpandable declaration in let-expression."

Что-нибудь в этом стиле.

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

как определить форму со стратегией вычисления call by macro expansion

зачем это делать в Haskell?

Работать с AST?

пиши на CL

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

Считай, что let в haskell — примитив. Тот же core haskell, который в ghc используется, почти полностью состоит из лямбды, let и case of. Вопрос — зачем его выражать через что-то?

Я подумал - если я увижу как можно ввести простую специальную форму вроде let, то мне станет понятно как определять более сложные пользовательские специальные формы.

На самом деле я пытаюсь выдумать пример - когда бы это было нужно именно в Haskell и не могу :). Если брать вещи из CL, то для with-* макросов хватает ленивых функций, анафорические макросы теряют смысл, т.к. в Haskell нет такой ситуации как в CL: всё что не nil (False) - t (True). Вещи среднего уровня... как я понимаю, предполагается обходится базовыми конструкциями (никто не станет делать data* вместо data чтобы оно генерило какие-нибудь «сопряжённые» данные к данным? разве что генерация в Generics, но они как раз используют TH). Что касается сложных DSL то для них есть система типов. Короче не в коня формы все эти пользовательские специальные формы в Haskell ;)

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

Там let предназначен только для связывания значений в локальной области видимости, для функций отдельные макросы (flet - не рекурсивные, labels - (взаимо)рекурсивные). Как для let тот пример вполне хорош, даже более хорош чем что-либо ещё :)

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

зачем это делать в Haskell?

наверное, привычка.

пиши на CL

Ну на CL мне ничего не мешает писать :) Я спрашиваю не в контексте выбора языка, а в контексте изучения разных парадигм.

quasimoto ★★★★
() автор топика

Вот у Харрисона в самом начале есть:

let x = s in t ≡ (λ x . t) s

(let z = 2 + 3 in z + z) = (λ z . z + z) (2 + 3) = (2 + 3) + (2 + 3) = 5 + 5 = 10

Это `≡' понимается как «средство введения синтаксической глазури поверх чистого лямбда исчисления». Тот макрос на CL выглядит слишком длино, но на самом деле это ровно то же что и запись выше, т.е., условно:

let (var val) form ≡ funcall (lambda var form) val

(квазицитирование спрятано)

При попытке записать:

ghci> let let' x s t = (\ x -> t) s
ghci> let' z 2 (z * z)
Not in scope: `z'

По моему кто-то темнит :) В Inroduction to FP либо `≡' работает по стратегии call by macro expansion, т.е спрятано квазицитирование, либо упущено представление символов (как переменных) в виде «синтаксической глазури поверх чистого лямбда исчисления».

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