LINUX.ORG.RU

Анализ пользователей Common Lisp и Racket

 , ,


11

7

Common Lisp разрабатывался и используется в предположении, что пользователь программы — программист. Поэтому из языка намеренно исключены сложные для понимания конструкции (пользователь не обязательно квалифицированный программист), поэтому в языке мощнейший отладчик, позволяющий без остановки программы переопределять функции и вообще делать что угодно. Но из-за этого документация по большей части библиотек Common Lisp существует только в виде docstring и комментариев в коде (некоторые вообще считают, что код сам себе документация). Из-за этого обработка ошибок почти всегда оставляется на отладчик (главное сделать рестарт «перезапустить с последней итерации», а там пользователь сам разберётся). Из-за этого в программе проверяется только happy path (пользователь ведь «тоже программист»).

Racket разрабатывался и используется в предположении, что пользователь программы не программист, а задача разработчика написать программу так, чтобы она корректно работала при любых входных данных (если данные некорректны, то сообщала об этом в том месте, где данные были введены). Поэтому в языке эффективная библиотека для написания тестов, система контрактов на уровне модулей, макимально широкий спектр инструментов программирования (разработчик должен быть профессионалом!). Также реализована идея инкапсуляции: считается, что пользователь модуля не должен знать особенности реализации и, более того, не может в своём коде изменить функцию чужого модуля если это явно не разрешено разработчиком того модуля. Исходный код разумеется доступен, но его не требуется смотреть, чтобы использовать модуль. Достаточно документации. Поэтому реализована мощнейшая система документировния Scribble, а при реализации макроса есть возможность обеспечить указание на ошибки в коде, предоставленном макросу пользователем, не показывая потроха макроса.

И поэтому в Racket нет CLOS (есть как минимум две реализации, но не используются) - провоцирует заплаточное программирование (monkey patching), поэтому отладчик намеренно ограничен (если ты отлаживаешь программу, значит ты не знаешь как она должна работать!), поэтому нет разработки в образе (image based) - она провоцирует разработку через отладку (а значит непонимание программы и проверку только happy path).

Таким образом, Racket и Common Lisp несмотря на внешнее сходство являются очень разными языками. И я рекомендую писать на Racket, если только конечными пользователями программы не являются исключительно программисты на Common Lisp.

Взято с http://racket-lang.blog.ru/#post214726099

Хотелось бы знать, что по этому поводу думают пользователи ЛОРа. А также, мне кажется, что для Java и C++ будет где-то такая же разница.

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

А в примере выполняется произвольный код в компайлтайме, ага.

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

Дык, сделано.

Что сделано? Контракты? фвп-контракты есть? Какие есть стандартные комбинаторы для контрактов?

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

И ошибка percents a = 60+60; тоже будет выявлена только при запуске.

struct percents
{
    constexpr percents( const int v )
    {
        if( v > 100 )
            throw "Surprise, motherfucker.";
    }
};

int main()
{
    constexpr percents p( 60 + 60 );
}
anonymous
()
Ответ на: комментарий от DarkEld3r

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

Я на сколько помню, в хаскелевском (ghci?) репле нельзя делать всё то же самое, что компилятор при обработке всего файла делает. В общелиспе можно.

Такое (у меня, по крайней мере) происходит ещё реже, чем опечатки.

Очепятки в общелиспе будут warning'ами. Делов-то, поправить warning'и.

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

Я на сколько помню, в хаскелевском (ghci?) репле нельзя делать всё то же самое, что компилятор при обработке всего файла делает.

можно, но при этом ghci сильно слабее лиспового репла.

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

constexpr percents

Я так понимаю, что этим мы требуем инициализации percents только константными выражениями. И толку тогда от него такого?

В примере на Racket будет работать и проверка от 60+60 и проверка от x+60 (в последнем случае предупреждение).

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

Я так понимаю, что этим мы требуем инициализации percents только константными выражениями.

struct percents
{
    constexpr percents( const int v )
    {
        if( v > 100 )
            throw "Surprise, motherfucker.";
    }
};

int main()
{
    constexpr percents p( 60 + 30 );
    percents p2 = 60 + 50;
}
anonymous
()
Ответ на: комментарий от anonymous

все ок, только не компилится

g++ -std=c++14 constexpr.cpp 
constexpr.cpp: In constructor ‘constexpr percents::percents(int)’:
constexpr.cpp:7:5: error: constexpr constructor does not have empty body
     }
     ^
constexpr.cpp: In function ‘int main()’:
constexpr.cpp:12:35: error: call to non-constexpr function ‘percents::percents(int)’
     constexpr percents p( 60 + 30 );
x4DA ★★★★★
()
Ответ на: комментарий от tailgunner

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

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

Половина твоих хотелок

Вот за это и не люблю статически-типизированные языки. Реализуется всегда только половина хотелок, а страдать за это надо постоянно (описывать типы, там где они не важны; для каждой переменной только один тип). Причём это постоянно преподносится как достоинство: «половина же задачи решена проще, а вторая половина на самом деле не нужна». Тьфу!

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

мне лень искать корень дискуссии:

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE FlexibleContexts #-}
module Percent where

import GHC.TypeLits
import Data.Proxy
import Data.Function

data Percent (a :: Nat) where
  Percent :: (KnownNat a, a <= 100) => Percent a

deriving instance Show (Percent a)

percent :: (KnownNat a, a <= 100) => Proxy a -> Percent a
percent = const $ Percent

unwrap :: KnownNat a => Percent a -> Integer
unwrap = natVal

sum1 :: (KnownNat (a+b), a + b <= 100) => Percent a -> Percent b -> Percent (a + b)
sum1 _ _ = Percent

toSome :: KnownNat a => Percent a -> SomePercent
toSome p = SomePercent $ SomeNat (toProxy p)
  where toProxy :: Percent a -> Proxy a
        toProxy g = Proxy

data SomePercent = SomePercent SomeNat

unwrapSome :: SomePercent -> Integer
unwrapSome (SomePercent (SomeNat x)) = natVal x

-- unwrapSome $ SomePercent (Percent :: Percent 70)

mkPercent :: Integer -> Maybe SomePercent
mkPercent i
  | i >= 0 && i < 100 = fmap SomePercent (someNatVal i)
  | otherwise = Nothing

sum2 :: SomePercent -> SomePercent -> Maybe SomePercent
sum2 = (mkPercent .). (+) `on` unwrapSome

тут есть тип «проценты» (на самом деле это просто тип чисел от 0 до 100). Соотв для известных на этапе компиляции все будет выполняться и работать, проверяя при этом арифметику без выполнения программы. Для данных получаемых из-вне и неизвестных, нужно явно использовать SomePercent (и никаких дурацких ворнингов). Реализация наверняка не идеальная, но покактит. Так же можно поизвращаться и выдавать SomePercent constraint, где constraint это всякие ограничения типа (<=50). Естественно проверка в компайлтайме без запуска программы.

qnikst ★★★★★
()
Последнее исправление: qnikst (всего исправлений: 1)
Ответ на: все ок, только не компилится от x4DA

все ок, только не компилится

Проблемы пользователей gcc шерифа не волнуют. В С++14 нет такого ограничения и в clang все работает. Кстати для clang есть еще вариант на аттрибутах:

struct percents
{
    percents( int v )
    {
        if( v < 0 || v > 100 )
            throw "Surprise";
    }
	
    percents( int v ) __attribute__((enable_if( v < 0 || v > 100, "Surprise"))) __attribute__((unavailable("RTFM")))
    {
    }
};

int main( int v, char** )
{
    percents p1( 60 + 30 );
    percents p2( v );
    percents p3( 60 + 50 );
}
anonymous
()
Ответ на: комментарий от monk

Вот за это и не люблю статически-типизированные языки. Реализуется всегда только половина хотелок,

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

а страдать за это надо постоянно

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

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

Для данных получаемых из-вне и неизвестных, нужно явно использовать SomePercent (и никаких дурацких ворнингов)

А можно все-таки с ворнингами? Потому что все, что ты сделал - это аналог:

(define (mk-percent x)
  (if (> percent 100)
      (error)
      (percent x)))

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

весь этот ад на самом деле просто для того, чтобы поднять «проценты» в typelevel, если этого не нужно, то достаточно сделать умный конструктор:

module Percent 
  ( Percent -- конструктор не экспортируется
  , percent
  , safePercent
  ) where

newtype Percent a = Percent { unPercent :: a }

percent :: Num a => a -> Percent a
percent = maybe (error "!!!") id . safePercent

safePercent :: Num a => a -> Maybe (Percent a)
safePercent i 
  | i < 0 && i > 100 = Percent i
  |        = error "illegal"


instance Num Percent where
  (+) = (percent .).  (+) `on` unPercent
  ...
qnikst ★★★★★
()
Ответ на: комментарий от qnikst

Алсо:

mkPercent :: Integer -> Maybe SomePercent

Если я прочитаю два некоторых числа (<100 каждое, допустим 60 и 80), сделаю из них при помощи mlPercent SomePercent, то как мне потом на них сделать sum1?

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

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

Почему же? Вон реализация всего чего он хотел есть выше.

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

нет, читай внимательнее. то, что ты написал это аналог моего поста чуть ниже.

какими ворнингами? У monk ворнинг должен выбрасываться в функции где мы складываем «проценты», в моём варианте оно проверится на этапе компиляции и или будет отклонено или будет принято. Тип неизвестный в компайл тайме имеет отдельный тип и явно отделёт, т.е. когда ты пишешь подобную функцию возвращающую неизвестно что - то у тебя будет просто напросто другой тип. Если ты пояснишь юзкейс, то я возможно я смогу что-нибудь на сей счет придумать.

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

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

Racket наиболее близок к идеалу. Racket + Typed Racket идеальны за исключением производительности при взаимодействии.

monk ★★★★★
() автор топика
Ответ на: комментарий от anonymous
*Percent> sum1 (Percent :: Percent 60) (Percent :: Percent 80)

<interactive>:4:1:
    Couldn't match type ‘'False’ with ‘'True’
    Expected type: 'True
      Actual type: (60 + 80) <=? 100

если ты делаешь mkPercent - и получаешь SomePercent - т.е. число значение которого не может быть выведено в компайл тайме, то ты не сможешь использовать его там, где Percent и используешь sum2 с рантайм проверками. Если не хочется испоьзовать разные функции, то можно ввести класс, который для значений известных в compiletime будет тайпчекать, а для неизвестных делать рантайм проверку. Можно поупарываться и потаскать доказательства, но мне честно лень этим заниматься, и для этого лучше взять более подходящий язык.

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

Так на этой строчке что происходит? Ворнинг, эррор?

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

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

описывать типы, там где они не важны;

можно пример?

для каждой переменной только один тип

можно пример, где это не так?

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

haskell:

module Percent 
  ( Percent -- конструктор не экспортируется
  , percent
  , safePercent
  ) where

newtype Percent a = Percent { unPercent :: a }

percent :: Num a => a -> Percent a
percent = maybe (error "!!!") id . safePercent

safePercent :: Num a => a -> Maybe (Percent a)
safePercent i 
  | i < 0 && i > 100 = Percent i
  |        = error "illegal"


instance Num Percent where
  (+) = (percent .).  (+) `on` unPercent
  ...
lisp:
(define-struct/contract percent ([val (</c 100)]))

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

какими ворнингами? У monk ворнинг должен выбрасываться в функции где мы складываем «проценты», в моём варианте оно проверится на этапе компиляции и или будет отклонено или будет принято.

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

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

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

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

Это и есть _вся_ функциональность, разве что кроме реализации ф-и +, но это не слишком интересно.

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

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

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

Не работает

main = do
    x <- getLine
    print (sum1 (read x) 60)

должно компилироваться (возможно с предупреждением). А пишет, что x+60 больше 100.

z = sum2 (toSome 50) (toSome 30)

вообще много ругается про «No instance for...». Хотя может я что-то напутал.

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

как будто вместо "!!!" я не могу написать сообщение об ошибке. Вообще это из-за инстанса Num, который на самом деле реализовывать не правильно, т.к. нужно только Additive.C, а то будут тут «проценты» умножать и получать «квадратные проценты» и прочую чушь.

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

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

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

(+) и прочее как раз интересно, чтобы проценты с собаками не сложили :) и экспорт модуля так, чтобы не смогли систему обмануть.

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

можно пример, где это не так?

http://erlang.org/doc/man/gen_tcp.html#connect-3

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

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

первый пример очевидным образом работает для любых типов, для которых определено «>», т.е. в терминах хацкеля ":: Ord a => a -> a -> a", что в общем-то автоматически выведется.

Второй пример, очевидным образом тоже автоматически выводится и работает для любого типа, что и выведется.

Впрочем это все неважно если взять и почитать про внутреннее представление объектов в лиспе и радостно реализовать universe of values в haskell и все будет настолько же «динамическим».

и будут функции вида ':: LispValue -> LispValue -> ...' как и у вас.

qnikst ★★★★★
()
Ответ на: комментарий от qnikst
(provide (struct-out percent)
         (contract-out [plus (-> percent? percent? percent?)]))
  
(define-struct/contract percent ([val (</c 100)]))

(define (plus x y)
  (percent (+ (percent-val x) (percent-val y))))
anonymous
()
Ответ на: комментарий от monk

import Control.Applicative, ну и deriving Show для SomePercent добавить.

P.S. я таким бредом с притаскиванием typelevel констрейнтов для ограничения размеров числа в первый раз страдаю, может есть способы и лучше.

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

Впрочем это все неважно если взять и почитать про внутреннее представление объектов в лиспе и радостно реализовать universe of values в haskell и все будет настолько же «динамическим».

И это будет эквивалентно реализации интерпретатора лиспа. Десятое правило Гринспена же!

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

достаточно простым образом превращается в sum2 60 =<< (mkPersent $ read x), но зачем?

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

Если про хацкель, то

print $ sum2 $ (mkPercent $ read x) * pure (toSome $ (Percent :: Percent 60))

как наглядный пример, какие крокодилы получаются вместо print $ sum2 $ (read x) * 60 как только требуется хоть что-то нетривиальное.

Про три монады в одной функции я уже спрашивал. А так, да: если не считать синтаксиса, хаскель очень хороший язык.

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

это от лени:

instance Num SomePercent where
 fromInteger = maybe (error "!") id . mkPercent

instance Read SomePercent where
  readsPrec j s = let [(i,s)] = readsPrec j s
                  in [(fromInteger i, s)]

тогда можно:

    print $ sum2 60 (read x)

даже лучше, чем я предполагал. Правда т.к. функции больше не тотальные, то это может к error-ам приводить, что полный отстой. Анонимус прав в отношении того, что error-reporting страдает, можно сделать +RTS -xc, и получить что-то похожее на stacktrace, и был какой-то пакет с TemplateHaskell, но навскидку я его не нашёл, так же можно через implicit parameters как-то таскать location, но я тоже не нашёл, и не использовал.

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

Про три монады в одной функции я уже спрашивал.

а подробнее можно, может я пропустил и что толковое сказать случайно смогу?

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

у меня нету слов..

Я там имел в виду C++/Java/...

В хаскеле тоже не всё гладко:

А если ещё есть функция принимающая параметр Ok a | Error b | Nothing , то уже не работает. Приходится или Maybe Responce делать или

data ResponceOrNothing = Ok2 a | Error2 b | Nothing2

+ функцию конвертации из Responce в ResponceOrNothing. А потом будет Ok3 | Error3 b (http://erlang.org/doc/man/gen_tcp.html#send-2), а http://erlang.org/doc/man/pg2.html#get_closest_pid-1 — вообще нельзя сделать.

Или я опять недостаточно знаю хаскель?

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

Анализ пользователей Common Lisp и Racket (комментарий)

На обычных языках получается что-то вроде

string f()
{
   string s = readln();
   if(s = "ok") { Refresh(); return "ok"} else { return Nothing; }
}

На хаскеле я не понимаю, какой будет тип и чей будет do (X или IO).

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