LINUX.ORG.RU

Муки выбора языка программирования

 , , , ,


2

4

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

Хочется, чтобы у языка были:

  • библиотека для загрузки/выгрузки изображений с поддержкой широкого круга форматов
  • биндинги для sdl2
  • работа с битовыми массивами размером больше чем 64 элемента (с поиском единиц)
  • перегрузка оператора индекса в том числе при присвоении
  • ассоциативные массивы с лаконичным доступом к элементам
  • документацией с поддержкой мобильного просмотра в 2023 году-то
  • поддержкой компиляции для мобильных архитектур
  • нормальный полиморфизм, а не как в Rust
  • востребованность на рынке труда

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

C++ и Rust имеют очень странные конструкторы для битовых массивов. Может это проблема документации, но я с ходу не нашёл как мне создать битовый массив из готового байтового массива, чтобы каждый байт превратился в 8 бит.

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

Что выбрать?

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

import qualified Data.ByteString.Lazy as BL

Не, ну если так извращаться, то да. Я уж думал, стандартная ленивость где-то ломается.

Если очень-очень постараться, то через FFI можно и UB сотворить.

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

Не, ну если так извращаться, то да. Я уж думал, стандартная ленивость где-то ломается.

Если ты в его коде прочитаешь String вместо Lazy ByteString, он точно так же упадёт. Lazy ByteString ленив в плане работы с самим массивом, потому что там внутри пачка чанков вместо одного большого массива. К ленивости чтения из файла это всё отношения не имеет. В его примере bs – это просто thunk, который не вычислится, пока он никому не нужен.

hateyoufeel ★★★★★
()
Последнее исправление: hateyoufeel (всего исправлений: 2)
Ответ на: комментарий от monk

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

Вот пример со стандартными строками

module Main where

import System.IO (openFile, IOMode(ReadMode), hClose, hGetContents)

main :: IO ()
main = do
  h <- openFile "./test.txt" ReadMode
  s <- hGetContents h
  hClose h
  putStrLn $ show $ length s

точно так-же будет падать.

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

Вообще неожиданно.

Once a semi-closed handle becomes closed, the contents of the associated list becomes fixed. The contents of this final list is only partially specified: it will contain at least all the items of the stream that were evaluated prior to the handle becoming closed.

Я бы в худшем случае ожидал неполное чтение. Ладно, на эти грабли я ещё не наступал.

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

К ленивости чтения из файла это всё отношения не имеет. В его примере bs – это просто thunk, который не вычислится, пока он никому не нужен.

По идее, если заменить

bs <- hGetContents h

на

bs <- hGetContents' h

должно перестать падать.

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

Скорее перерасход памяти, пока данные или не вычислишь или не выкинешь.

это все изза ленивости небось. читать на хаселе не умею, потому просто беру за рабочую версию.

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

Добро при правильном использовании.

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

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

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

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

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

Добро при правильном использовании.

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

Главная фишка ленивости в оптимизациях, которые она даёт и которые могут быть далеко не совсем очевидны при ручном её использовании. Так что тут шило на мыло.

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

Главная фишка ленивости в оптимизациях,

тогда наверное ленивый хаскел всегда обгонит неленивые плюсы?.. а вот и нет. никогда не обгонит. значит тут что-то не так.

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

Главная фишка ленивости в оптимизациях,

тогда наверное ленивый хаскел всегда обгонит неленивые плюсы?.. а вот и нет. никогда не обгонит. значит тут что-то не так.

Ага. В том, что кроме ленивости у хачкелла ещё есть рантайм, сборщик мусора и т.д.

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

hateyoufeel ★★★★★
()
Последнее исправление: hateyoufeel (всего исправлений: 1)
Ответ на: комментарий от alysnix

Да. Ленивость заставляет хранить вычисления, а не вычисленные значения. Если вычисленное значение занимает значительно меньше места, то получается «утечка».

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

Именно для семантики Хаскела лучше ленивый по умолчанию.

Ленивость + чистота позволяют обеспечить ссылочную прозрачность и бесконечные списки, а также более простое описание многих алгоритмов.

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

тогда наверное ленивый хаскел всегда обгонит неленивые плюсы?.. а вот и нет. никогда не обгонит. значит тут что-то не так.

Для равного алгоритма обгонит.

process context templateFile outFile =
    readFile templateFile
        >>= foldM (processLine processTemplate) context . lines
        >>= writeFile outFile . unlines . reverse . result

работает быстрее, чем

void process(Context & context, File & templateFile, File & outFile)
{
  auto s = readFile(templateFile)
  auto r1 = foldM(processLine(processTemplate), context, lines(s));
  writeFile(outFile, unlines(reverse(result(r1))));  
}

processTemplate – это функция

Context & processTemplate(Context & context, std::string & s);
monk ★★★★★
()
Ответ на: комментарий от hateyoufeel
size_t lev_dist(const std::string& s1, const std::string& s2)
{
  const auto m = s1.size();
  const auto n = s2.size();

  std::vector<int64_t> v0;
  v0.resize(n + 1);
  std::iota(v0.begin(), v0.end(), 0);

  auto v1 = v0;

  for (size_t i = 0; i < m; ++i)
  {
    v1[0] = i + 1;

    for (size_t j = 0; j < n; ++j)
    {
      auto delCost = v0[j + 1] + 1;
      auto insCost = v1[j] + 1;
      auto substCost = s1[i] == s2[j] ? v0[j] : (v0[j] + 1);

      v1[j + 1] = std::min({ delCost, insCost, substCost });
    }

    std::swap(v0, v1);
  }

  return v0[n];
}

переписывается в

{-# LANGUAGE Strict #-}
{-# OPTIONS_GHC -fllvm #-}

import qualified Data.Array.Base as A(unsafeRead, unsafeWrite)
import qualified Data.Array.ST as A
import qualified Data.ByteString as BS
import qualified Data.ByteString.Unsafe as BS
import Control.Monad.ST

levenshteinDistance :: BS.ByteString -> BS.ByteString -> Int
levenshteinDistance s1 s2 = runST $ do
  v0Init <- A.newListArray (0, n) [0..]
  v1Init <- A.newArray_ (0, n)
  loop 0 v0Init v1Init
  A.unsafeRead (if even m then v0Init else v1Init) n

  where
    m = BS.length s1
    n = BS.length s2

    loop :: Int -> A.STUArray s Int Int -> A.STUArray s Int Int -> ST s ()
    loop i v0 v1 | i == m = pure ()
                 | otherwise = do
      A.unsafeWrite v1 0 (i + 1)
      let s1char = s1 `BS.unsafeIndex` i
      let go j prev | j == n = pure ()
                    | otherwise = do
            delCost <- v0 `A.unsafeRead` (j + 1)
            substCostBase <- v0 `A.unsafeRead` j
            let substCost = if s1char == s2 `BS.unsafeIndex` j then 0 else 1
            let res = min (substCost + substCostBase) $ 1 + min delCost prev
            A.unsafeWrite v1 (j + 1) res
            go (j + 1) res
      go 0 (i + 1)
      loop (i + 1) v1 v0

И работает на 40% быстрее.

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

В разборе C++ версии замечательная фраза «Как мы уже увидели, даже маленькие исправления могут быть абсолютно непредсказуемы в зависимости от компилятора».

То есть без намеренной профилировки Haskell на равных алгоритмах будет работать примерно с той же скоростью, что и C++ (плюс-минус лапоть).

monk ★★★★★
()
Последнее исправление: monk (всего исправлений: 1)
Ответ на: комментарий от monk

То есть без намеренной профилировки Haskell на равных алгоритмах будет работать примерно с той же скоростью, что и C++ (плюс-минус лапоть).

Или не будет.

fluorite ★★★★★
()