LINUX.ORG.RU

Сообщения quasimoto

 

В Chromium highlight.pack.js и combined.css неправильно отображают Haskell

Для теста — пять строчек

module A (a, b, c) where
import A (a, b, c)
data A = A { _a :: Int }
newtype A = A (B ())
foreign import ccall "a" a :: A (B ())

помещаются в [ code = haskell ]:

module A (a, b, c) where
import A (a, b, c)
data A = A { _a :: Int }
newtype A = A (B ())
foreign import ccall "a" a :: A (B ())

при включённых highlight.pack.js и combined.css у меня они разбиваются на куски (http://i.imgur.com/RNg2WCf.png), хромиум 28, происходит во всех темах, если посмотреть по форуму — уже существующий код тоже плохо отображается (пример — Haskell импорт сишных структур), при копировании обратно в редактор всё отображается нормально, то есть проблема в css который добавляет highlight.pack.js:

<code class="haskell"><span class="module"><span class="keyword">module</span> A <span class="container">(<span class="title">a</span>, <span class="title">b</span>, <span class="title">c</span>)</span> <span class="keyword">where</span></span>
<span class="import"><span class="keyword">import</span> A <span class="container">(<span class="title">a</span>, <span class="title">b</span>, <span class="title">c</span>)</span></span>
<span class="typedef"><span class="keyword">data</span> <span class="type">A</span> = <span class="type">A</span> <span class="container">{ <span class="title">_a</span> :: <span class="type">Int</span> }</span></span>
<span class="typedef"><span class="keyword">newtype</span> <span class="type">A</span> = <span class="type">A</span> <span class="container">(<span class="type">B</span> ()</span>)</span>
<span class="title">foreign</span> <span class="import"><span class="keyword">import</span> ccall "a" a :: A <span class="container">(<span class="type">B</span> ()</span>)</span>
</code>

исправляется устранением

.container:before, .container:after {
    content: "";
    display: table;
}

К слову:

k: "let in if then else case of where do module import hiding qualified type data newtype deriving class instance not as foreign ccall safe unsafe"

not тут лишний (это функция) — http://www.haskell.org/haskellwiki/Keywords.

----

import qualified A.B.C as A.B hiding (a, {- ... -} B(..), c) --
data family A = A { {- ... -} _a :: {-# UNPACK #-} !Int -- ...
#if __Q__
                  , _a :: C -- ...
#endif
                  } deriving (Foo) -- ...
newtype A = A (B ()) {- ... -}
  deriving (Foo) {-
type A a b = B (C a b) -}
class family (B a) => {- ... -} A a where -- ...
class instance Bar {- ... -} where -- ...
a :: forall a. a -- ...
{-# INLINE a #-}
a = not >> mdo print 1 >> rec 1 '2' '\'' '\n' ':':' ':msg >> proc 1 2 3 >> " -- ..."
    + importA 1
    + moduleA 1
    + classA 1
    + instanceA 1
    + dataA 1
    + newtypeA 1
    + typeA 1
    + defaultA 1
    + infixA 1
    + foreignA 1
    + whereA 1
    + doA 1
default Num {- ... -} ( Int ) -- ...
infix {- ... -} + 1 -- ...
foreign import ccall {- ... -} "a" a :: A (B ()) -- ...
#!/usr/bin/env runhaskell
#if __GLASGOW_HASKELL__
{-# LANGUAGE ... #-}
#endif
-- | ...
module A.B.C (a, {- ... -} B(..), c, -- ...
              d, -- ...
              e) {- ... -} where -- ...
{- ... -}
import qualified A.B.C as A.B hiding (a, {- ... -} B(..), c) --
data family A = A { {- ... -} _a :: {-# UNPACK #-} !Int -- ...
#if __Q__
                  , _a :: C -- ...
#endif
                  } deriving (Foo) -- ...
newtype A = A (B ()) {- ... -}
  deriving (Foo) {-
type A a b = B (C a b) -}
class family (B a) => {- ... -} A a where -- ...
class instance Bar {- ... -} where -- ...
a :: forall a. a -- ...
{-# INLINE a #-}
a = not >> mdo print 1 >> rec 1 '2' '\'' '\n' ':':' ':msg >> proc 1 2 3 >> " -- ..."
    + importA 1
    + moduleA 1
    + classA 1
    + instanceA 1
    + dataA 1
    + newtypeA 1
    + typeA 1
    + defaultA 1
    + infixA 1
    + foreignA 1
    + whereA 1
    + doA 1
default Num {- ... -} ( Int ) -- ...
infix {- ... -} + 1 -- ...
foreign import ccall {- ... -} "a" a :: A (B ()) -- ...

 

quasimoto
()

Parallel and Concurrent Programming in Haskell

Если кто-то ещё не видел, вышла книжка Simon Marlow на тему конкурентности и параллелизма в Haskell — http://shop.oreilly.com/product/0636920026365.do.

Почитать online можно тут — http://chimera.labs.oreilly.com/books/1230000000929/index.html (возможно, временно).

 

quasimoto
()

[scheme][haskell][oop][fp] Мысли вслух

Была на ЛОРе такая тема — [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).

 , ,

quasimoto
()

[haskell][news] GHC 7.4

Уже была новость про RC. -XSafe, -XTrustworthy, -XUnsafe, -XPolyKinds, compiler plugins.

Ещё из новых возможностей - в GHCi теперь кроме функций можно давать любые определения (data, type, newtype, class, instance, deriving, foreign). Функции по-прежнему требуют подставлять let (как в do-блоке, может, в будущем уберут такое поведение).

-XConstraintKinds (заметка в блоге автора). Если -XPolyKinds не очень много меняет - просто позволяет писать чуть более строго типизированный type-level код, то -XConstraintKinds - несколько более мощное расширение. Оно добавляет новый kind - Constraint. Если все типы объединяются в kind *, то все классы типов объединяются в kind Constraint, более того, теперь можно проводить forall квантификацию не только по типам (:: *), но и по классам типов (:: Constraint). Например, теперь можно делать динамическую диспетчеризацию (позднее связывание) и Dynamic-like типы более удобно.

Пример.

module Shape where

-- * Объекты (типы данных).

data Circle = Circle Double
  deriving ( Show, Eq )

data Square = Square Double
  deriving ( Show, Eq )

-- * Интерфейс (набор методов).

class ShapeInterface a where
  perimeter :: a -> Double
  area :: a -> Double

-- * Реализация интерфейса (определения методов).

instance ShapeInterface Circle where
  perimeter (Circle r) = 2 * pi * r
  area (Circle r) = pi * r * r

instance ShapeInterface Square where
  perimeter (Square s) = 4 * s
  area (Square s) = s * s

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

{-# LANGUAGE ExistentialQuantification #-}

module ShapeDynamic where

import Shape

-- * Динамический тип -- обвёртка с помощью existential quantification.

data Shape = forall a. ShapeInterface a => Shape a

-- * Реализация интерфейса для динамического типа.

instance ShapeInterface Shape where
  perimeter (Shape x) = perimeter x
  area (Shape x) = area x

-- * Умные конструкторы для динамических объектов.

circle :: Double -> Shape
circle = Shape . Circle

square :: Double -> Shape
square = Shape . Square

-- * Использование.

shapes :: [Shape]
shapes = [circle 2, square 3]

example :: [Double]
example = map area shapes

-- shapes
-- > No instance for (Show Shape)

-- example
-- > [12.566370614359172,9.0]

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

Если после вдруг окажется, что кроме ShapeInterface нужны другие интерфейсы, то нужно возвращаться и править обвёртку. Либо писать обвёртку на каждый интерфейс (а это адъ). Например, чтобы добавить интерфейс Show:

data Shape = forall a. (Show a, ShapeInterface a) => Shape a

instance Show Shape where
  show (Shape x) = show x

-- shapes
-- > [Circle 2.0,Square 3.0]

Теперь, с помощью -XConstraintKinds, можно написать так:

{-# LANGUAGE Rank2Types, KindSignatures, GADTs, ConstraintKinds,
             FlexibleInstances, UndecidableInstances #-}

module ShapeDynamicNew where

import GHC.Prim ( Constraint )

import Shape

-- * Общее место.

-- | Dynamic строит тип (*) из одно-параметрического класса типов
-- (* -> Constraint). Класс типов передаётся как аргумент конструктору типов.
-- Конструктор данных принимает любые объекты разделяющие интерфейс данного
-- класса типов и возвращает динамический объект построенного типа Dynamic cls.
data Dynamic :: (* -> Constraint) -> * where
  Dynamic :: forall cls a. cls a => a -> Dynamic cls

-- * Складываем необходимые интерфейсы.

class (Show t, ShapeInterface t) => ShowShapeInterface t
instance (Show t, ShapeInterface t) => ShowShapeInterface t

-- * Динамический тип -- все те объекты которые разделяют данный интерфейс(ы).

type Shape = Dynamic ShowShapeInterface

-- * Реализация интерфейса для динамического типа.

instance ShapeInterface Shape where
  perimeter (Dynamic x) = perimeter x
  area (Dynamic x) = area x

instance Show Shape where
  show (Dynamic x) = show x

-- Умные конструкторы для динамических объектов.

circle :: Double -> Shape
circle = Dynamic . Circle

square :: Double -> Shape
square = Dynamic . Square

-- * Использование.

shapes :: [Shape]
shapes = [circle 2.0, square 3.0]

example :: [Double]
example = map area shapes

-- shapes
-- > [Circle 2.0,Square 3.0]

-- example
-- > [12.566370614359172,9.0]

 ,

quasimoto
()

[bug] [[t]]

Если в посте набрать то что в заголовке - «[» + «[» + «t» + «]» + «]», то две скобки пропадают.

Пример:

[[t]]

но набирал две пары скобок.

Внутри тега code - аналогично.

 

quasimoto
()

Активная область некоего приложения вместо рабочего стола

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

Объясню на пальцах. Например, при запуске nautilus мы видим рамку окна, заголовок окна, меню окна, панели (основная, боковая, адреса, состояния) и активную область где и показаны сами файлы. Вот хочется, чтобы эта активная область с файлами занимала место рабочего стола, т.е. чтобы при входе в систему запускался такой nautilus (насколько я знаю, он и так ответсвенен за значки на обычном рабочем столе), не занимал места в списке окон на панели и отображал свои файлы на месте рабочего стола, при этом была бы возможность перемещаться по директориям, открывать файлы, видеть / не видеть боковую панель (для внешних носителей, сети и ftp томов), панель с вкладками и т.п.

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

Нет ли в нынешнем гноме (другом DE или WM?) таких возможностей?

P.S. Ещё лучше - если на каждый рабочий стол можно назначить своё приложение.

quasimoto
()

[webdev] Покритикуйте архитектуру

В последнее время мне всё больше нравится такая схема разработки веб-приложений:

  • Берётся веб-сервер который умеет хорошо раздавать статику и поддерживает FastCGI. Например, nginx.
  • Все html, css и js раздаются им исключительно статически.
  • Динамической шаблонизации нет, если какой-то html нужно собирать из кусков (шаблонов, комбинаторов, etc.), то это делается статически, а не во время отдачи клиенту.
  • Динамических путей (routes) тоже нет - только поддерживаемые веб-сервером.
  • Если страница всё же должна содержать динамический контент, то в статически отдаваемом html делается пустой div и пишется js-приложение которое асинхронно загружает в него нужное с fastCGI ресурса аяксом.
  • Протоколом между js-приложением и fastCGI ресурсом может служить bison (бинарный JSON).
  • Сами fastCGI-приложения более менее независимы друг от друга (но могут быть DB зависимы) и могут писаться на любом удобном языке (language agnostic).
  • Если сильно нужно, то для динамических путей можно сделать redirect на fastCGI ресурс средствами веб-сервера. Или сразу использовать URI таких ресурсов.

Как я понимаю, тут преимущества в том, что, во-первых, часть работы переносится на клиента (js-приложения, загружающие динамический контент) и, во-вторых, с помощью bison можно несколько ужать трафик. Но, с другой стороны, поисковики не могут индексировать такой динамический контент (?).

Какие ещё могут быть проблемы с такой схемой? Кто-нибудь вообще так делает?

 

quasimoto
()

[haskell] Inferred type is less polymorphic than expected

Если определить тип данных содержащий поле existential-типа, например:

data T = forall a. Show a => T a

То при попытке вытащить его:

g (T n) = n

получается такая ошибка. С GADTs - тоже самое.

При этом использовать это поле в вычислениях можно:

f (T n) = putStrLn (show n) >> return ()

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

 

quasimoto
()

[template haskell] как всё-таки его готовить?

Как, к примеру, решить такую задачу - есть бинарное дерево и набор функций:

tree'path'...

где вместо ... - последовательность букв 'l' и 'r' которые означают соответсвенно левую ветвь и правую:

tree'path'lrlr

это доступ к элементу по пути left->right->left->right.

tree'path'lrlr = tree'path'l . tree'path'r . tree'path'l . tree'path'r

Как можно нагенерировать таких функций (допустим до пути к 20-кратному уровню вложенности, всего 38 функций)? А то неохота прописывать их вручную.

quasimoto
()

[common lisp][ищу морфизм] ещё одна годная задачка про списки

В продолжение этой темы (a.k.a. «детский сад») и функциональный код vs. императивный. (Другая задачка была тут.)

Вот пусть есть такая простейшая задача - нужно отфильтровать список по предикату на две части и свернуть первую с помощью какой-то функции.

;;;; motivation (dully functional perspective)
;;;;
;;;;   (+ a 1 b 2 c 3) => (+ 6 a b c)
;;;;
;;;; wanted
;;;;
;;;;   (filter/reduce #'+ 0 #'numberp '(a 1 b 2 c 3))
;;;;   => (6 a b c)
;;;;
;;;; with
;;;;
;;;;   1) (n + m) iterations over the LIST (where n is the number of LIST elements
;;;;   that satisfy to PREDICATE and m is the number of elements which do not
;;;;   satisfy, (n + m) is the length of the LIST of course).
;;;;
;;;;   2) memory allocation only for the new list (with (m + 1) elements),
;;;;   without temporary lists.

т.е. нужна более менее общая функция filter/reduce которая фильтрует на две части и сворачивает на одной из них. Но тут ещё поставлено условие: это должно осуществляться в _один_ проход (n + m итераций) и не должно использовать лишнюю память - только аллокация списка-результата.

Отличительная особенность тут в том, что одна списочная функция (reduce) применяется к результату работы другой списочной функции (filter). Понятно, что это приводит к излишним итерациям и аллокации излишней памяти, поэтому есть варианты.

Соответственно, возможные решения:

;;;; assume that compiler don't have the list fusion optimizations

;;;; for all variants:
(declaim (ftype (function (function t function list) (values t list)) filter/reduce))

;;;; I. functional variants

;;;; higher-order functions (expressed in terms of cata/ana/para/hylo)

;;; 1) FILTER/REDUCE as two FILTER and FOLD-LEFT combination.
;;;
;;;   this version do (3*n + 2*m) iterations and allocate (2*n + m + 1) elements.
;;;
(defun filter/reduce (function initial-value predicate list)
  (values (fold-left function initial-value (filter predicate list))
          (filter (compose 'not predicate) list)))

;;; 2) FILTER/REDUCE as PARTITION and FOLD-LEFT combination.
;;;
;;;   (2*n + m) iterations in this version and (2*n + m + 1) new elements.
;;;
(defun filter/reduce (function initial-value predicate list)
  (multiple-value-bind (allows not-allows)
      (partition predicate list)
    (values (fold-left function initial-value allows) not-allows)))

;;; 3) FILTER/REDUCE straigh from the folder FOLD-LEFT.
;;;
;;;   (n + m) iterations and allocate more than (2*n + m + 1) new elements
;;;   because fold-left used accumulators which add quadratic item to the sum.
;;;
(defun filter/reduce (function initial-value predicate list)
  (fold-left #'(lambda (rest e)
                 (if (funcall predicate e)
                     (cons (funcall function (car rest) e) (cdr rest))
                     (cons (car rest) (cons e (cdr rest)))))
             (list* initial-value nil)
             list))

;;;; recursive variant (when you sedulous SICP reader)

;;; 4) FILTER/REDUCE as recursive function (in TCO form).
;;;
;;;   (n + m) iterations and allocate (m + 1) new elements + n garbage elements.
;;;
;;;   KLUDGE: REST in reverse order.
;;;
(defun filter/reduce (function initial-value predicate list)
  (labels ((rec (list reduced rest)
             (cond ((endp list)
                    (values reduced rest))
                   ((funcall predicate (first list))
                    (rec (rest list)
                         (funcall function reduced (first list))
                         rest))
                   (t
                    (rec (rest list)
                         reduced
                         (cons (first list) rest))))))
    (rec list initial-value nil)))

;;;; II. imperative variants

;;;; all variants do (n + m) iterations and allocate (m + 1) new elements
;;;; + n garbage elements.

;;; 5) COLLECT + DOLIST + SETF
(defun filter/reduce (function initial-value predicate list)
  (collect ((not-constants))
    (dolist (e list)
      (if (funcall predicate e)
          (setf initial-value (funcall function initial-value e))
          (not-constants e)))
    (values initial-value (not-constants))))

;;;; 6) LOOP
(defun filter/reduce (function initial-value predicate list)
  (loop :for e :in list
        :if (funcall predicate e)
          :do (setf initial-value (funcall function initial-value e))
        :else
          :collect e :into not-constants
        :end
        :finally (return (values initial-value not-constants))))

;;;; 7) ITERATE
(defun filter/reduce (function initial-value predicate list)
  (iter (for e in list)
        (if (funcall predicate e)
            (setf initial-value (funcall function initial-value e))
            (collect e into not-constants))
        (finally (return (values initial-value not-constants)))))

(CL notes: fold-left это reduce, filter - mapcan, collect это SB-IMPL::COLLECT)

Т.е. можно:

(1) и (2) Писать декларативно, не глядя на производительность.

Учитывая, что filter/reduce может быть представлена как:

  1. Свёртка

  2. Рекурсивная функция

  3. Инструкции некой регистровой машины с последовательным исполнением команд и GOTO.

(т.е. это три таких независимых формализма) можно:

(3) Выражать filter/reduce напрямую через свёртку, что несколько уменьшает понятность и декларативность, но эффективно. Правда, тут есть оверхед на память под аккумуляторы, чего нет в (4) варианте.

(4) Писать filter/reduce как рекурсивную процедуру (если реализация поддерживает TCO - писать в TC форме). В некоторых простых случаях это самый естесвенный способ (в SICP много таких примеров). Но в данном случае это ещё больше уменьшает понятность, но также как и (3) имеет хорошую производительность (если TCO).

(5) Писать императивно, в смысле (c).

(6) и (7) Писать на DSL для обработки последовательностей, который сводится к императивному коду. Тут это LOOP и ITERATE. Это эффективно, не декларативно (отстаёт в понятности от (1)), но довольно легко по коду проследить за логикой и понять что этот код делает.

Ну и последний вариант:

  • Использовать реализацию языка с list fusion оптимизациями :)

В целом получается такая картинка:

                                                       читаемость
  -(4)----(3)----(6?)--(5)--(7)-------------------------(2)-(1)->

 4,5,6,7
    V
  <-*--(3)--------------------------------------------------(1)--
  производительность

Т.е. (1) совсем плохо работате, а iterate оказывается компромисом в вопросе производительность/читаемость.

З.Ы.

Сама задачка такая - как заставить (1) работать эквивалентно последним вариантам? :) Почему три последних варианта работают быстро понятно - они легко сводятся к естественному для железа представлению (регистровая машина с jump-ами), про (4) тоже понятно - TCO делает своё дело (сводит к естественному для железа ...). Но что касается (1) - единственная основа которую сюда можно подвести это какие-нибудь алгебраические соображения, на тему того что cata = out . rec . gen и есть соответствующая диаграма отображений; ana, para и hylo имеют подобные соотношения (и некоторый категорный смысл), а все сабжевые функции выражаются через них (и аналогично для других АТД). Например, filter/reduce очень просто выражается через две функции одна из которых свёртка, а другая выражается через свёртку, а также filter/reduce выражается через свёртку непосредственно - должно существовать преобразование из первого во второе. Может уже есть где-нибудь такие оптимизации (list fusion?), или какие-нибудь статьи на тему?

 

quasimoto
()

[bug] тэг code - лишние пробелы

Если присмотреться, то в любом из видов этого тега:

В конец каждой строчки >
Добавляется лишний пробел >

^ и в пустую тоже.

не очень хорошо при копировании кода с форума для каких-либо целей.

 

quasimoto
()

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

Скажем, 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?

 

quasimoto
()

[haskell, pure fp] Doubly linked circular lists они же Rings

Как правильно сделать subj? Читал Tying the Knot и то что гуглится в haskell-cafe. Тут, вроде как, два варианта - ввести этот объект как базовый АТД, либо моделировать его с помощью монад. Интересует именно первое (а с помощью монад - понятно что можно что угодно имитировать, хоть мутабельную flat memory, хоть DFA, но это будет просто модель). Т.е.

data Ring a = Ring (Ring a) a (Ring a)

Тут всё нормально получается, но смущает следующее:

* Допустим у нас есть кольцо из 5 элементов, я так понимаю, что если мы по нему пройдёмся 50 раз то в памяти помимо этих 5 элементов создаться ещё 45? В языках вроде Си где есть доступ к такой вещи как память (просто возможность ссылаться на её куски) такой проблемы не возникает.

* И ещё - если мы возьмём кольцо и «изменим» (не в смысле присваивания, а в смысле создания нового кольца) в нём элемент перед первым (который суть последний) то другой последний не изменится. Вообще - «изменение» одного элемента не меняет взаимно периодичных с ним. Как вообще проделывать эту операцию?

Это и понятно - АТД Ring ничем не отличается от того же АТД Tree - в типе никак не заложена его конечность (и замкнутость). Если тут какой-нибудь трюк, чтобы АТД получался компактным по памяти?

Если нет, то - нет ли у GHC дружественного к пользователю способа введения примитивов? ;)

Ну и последний вопрос - можно ли ввести какой-нибудь такой синтаксис для своих типов:

<1, 2, 3>

?

quasimoto
()

[RFC?] Почему функциональное программирование называется функциональным?

Собственно, почему? Скажем, лет 20-30 назад это было подходящим названием - основными идеями были функции как объекты первого класса, безымянные функции (лямбда функции), рекурсия (оптимизация хвостовой рекурсии), ну и прочее лямбда-исчисление (как одна из моделей ФП - реально не один ЯП не является чистым интерпретатором лямбда исчисления).

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

И у меня появилось следующее мнение. Когда-то (после того как была установлена Тьюринг-полнота лямбда исчисления и оно нашло свою реализацию в Lisp-1 и ML) считалось что лямбда исчисление «это оно». Тем не менее лямбда исчисление это просто Тьюринг полный вычислитель (один из), а вот основными значащими объектами являются категорные понятия (типов и отображений) - чисто декларативные понятия, которые (для реализации собственно «программирования») нужно транслировать в представления того или иного вычислителя - это может быть как лямбда исчисление так и какая-нибудь машина с состояниями (допусти, регистро-стековая машина).

Что знающие люди думают по этому поводу?)

quasimoto
()

[ФП][Морфизмы] Годная задачка про списки :)

Нашёл такую штуку:

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

Какие могут быть решения? Циклы vs. Рекурсия vs. Морфизмы?

Пришлось убить минут двадцать :) Но морфизм есть.

quasimoto
()

[?] Что можно делать с BSD кодом

Последнее время интересует такой вопрос - возможно ли на законных основаниях перелицензировать BSD код в ту или иную сторону. В интернетах встречал разные мнения по этому поводу (на этом форуме кажется был спор).

Например, некоторые авторы, выпуская некий код Х, пишут что вот мол это BSD, поэтому можно делать с ним всё что угодно. В каких рамках «что угодно»? Конкретно интересует:

1) Могу я убрать этот раздражающий дискламер в каждом файле ? :)

2) Можно ли ослаблять лицензию BSD кода вплоть до выпуска этого кода под Public Domain?

3) И наоборот - можно ли перелицензировать BSD в GPL (LGPL)?

(Да, при возможности - как это всё делать?)

quasimoto
()

RSS подписка на новые темы