Skip to content

Commit

Permalink
Move data-memocombinators into its own repo.
Browse files Browse the repository at this point in the history
  • Loading branch information
luqui committed Jan 3, 2010
0 parents commit 2715c01
Show file tree
Hide file tree
Showing 3 changed files with 202 additions and 0 deletions.
184 changes: 184 additions & 0 deletions Data/MemoCombinators.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
------------------------------------------------
-- |
-- Module : Data.MemoCombinators
-- Copyright : (c) Luke Palmer 2008
-- License : BSD3
--
-- Maintainer : Luke Palmer <lrpalmer@gmail.com>
-- Stability : experimental
--
-- This module provides combinators for building memo tables
-- over various data types, so that the type of table can
-- be customized depending on the application.
--
-- This module is designed to be imported /qualified/, eg.
--
-- > import qualified Data.MemoCombinators as Memo
--
-- Usage is straightforward: apply an object of type @Memo a@
-- to a function of type @a -> b@, and get a memoized function
-- of type @a -> b@. For example:
--
-- > fib = Memo.integral fib'
-- > where
-- > fib' 0 = 0
-- > fib' 1 = 1
-- > fib' x = fib (x-1) + fib (x-2)
------------------------------------------------

module Data.MemoCombinators
( Memo
, wrap
, memo2, memo3, memoSecond, memoThird
, bool, char, list, boundedList, either, maybe, unit, pair
, switch, integral, bits, unsignedBits
, RangeMemo
, arrayRange, unsafeArrayRange, chunks
)
where

import Prelude hiding (either, maybe)
import Data.Bits
import qualified Data.Array as Array
import Data.Char (ord,chr)

-- | The type of a memo table for functions of a.
type Memo a = forall r. (a -> r) -> (a -> r)

-- | Given a memoizer for a and an isomorphism between a and b, build
-- a memoizer for b.
wrap :: (a -> b) -> (b -> a) -> Memo a -> Memo b
wrap i j m f = m (f . i) . j

-- | Memoize a two argument function (just apply the table directly for
-- single argument functions).
memo2 :: Memo a -> Memo b -> (a -> b -> r) -> (a -> b -> r)
memo2 a b = a . (b .)

-- | Memoize a three argument function.
memo3 :: Memo a -> Memo b -> Memo c -> (a -> b -> c -> r) -> (a -> b -> c -> r)
memo3 a b c = a . (memo2 b c .)

-- | Memoize the second argument of a function.
memoSecond :: Memo b -> (a -> b -> r) -> (a -> b -> r)
memoSecond b = (b .)

-- | Memoize the third argument of a function.
memoThird :: Memo c -> (a -> b -> c -> r) -> (a -> b -> c -> r)
memoThird c = (memoSecond c .)

bool :: Memo Bool
bool f = cond (f True) (f False)
where
cond t f True = t
cond t f False = f

list :: Memo a -> Memo [a]
list m f = table (f []) (m (\x -> list m (f . (x:))))
where
table nil cons [] = nil
table nil cons (x:xs) = cons x xs

char :: Memo Char
char = wrap chr ord integral

-- | Build a table which memoizes all lists of less than the given length.
boundedList :: Int -> Memo a -> Memo [a]
boundedList 0 m f = f
boundedList n m f = table (f []) (m (\x -> boundedList (n-1) m (f . (x:))))
where
table nil cons [] = nil
table nil cons (x:xs) = cons x xs

either :: Memo a -> Memo b -> Memo (Either a b)
either m m' f = table (m (f . Left)) (m' (f . Right))
where
table l r (Left x) = l x
table l r (Right x) = r x

maybe :: Memo a -> Memo (Maybe a)
maybe m f = table (f Nothing) (m (f . Just))
where
table n j Nothing = n
table n j (Just x) = j x

unit :: Memo ()
unit f = let m = f () in \() -> m

pair :: Memo a -> Memo b -> Memo (a,b)
pair m m' f = uncurry (m (\x -> m' (\y -> f (x,y))))

-- | @switch p a b@ uses the memo table a whenever p gives
-- true and the memo table b whenever p gives false.
switch :: (a -> Bool) -> Memo a -> Memo a -> Memo a
switch p m m' f = table (m f) (m' f)
where
table t f x | p x = t x
| otherwise = f x

-- | Memoize an integral type.
integral :: (Integral a) => Memo a
integral = switch (>= 0) unsignedIntegral (\f -> unsignedIntegral (f . negate) . negate)

integralBits :: (Integral a) => a -> [Bool]
integralBits 0 = []
integralBits x = let (q,r) = quotRem x 2 in toBool r : integralBits q
where
toBool 0 = False
toBool 1 = True

integralFromBits :: (Integral a) => [Bool] -> a
integralFromBits [] = 0
integralFromBits (x:xs) = unbit x + 2*integralFromBits xs
where unbit True = 1 ; unbit False = 0

unsignedIntegral :: (Integral a) => Memo a
unsignedIntegral f = list bool (f . integralFromBits) . integralBits


-- | Memoize an ordered type with a bits instance. Good for most integral
-- types.
bits :: forall a. (Ord a, Bits a) => Memo a
bits | isSigned (undefined :: a)
= switch (>= 0) unsignedBits (\f -> unsignedBits (f . negate) . negate)
| otherwise = unsignedBits

-- | Memoize an unsigned type with a bits instance. Good for nonnegative
-- integral types. Warning: if a negative @Integer@ is given to an
-- @unsignedBits@-ized function, it will loop forever.
unsignedBits :: (Bits a) => Memo a
unsignedBits f = list bool (f . unsignedFromBits) . unsignedToBits

unsignedToBits :: (Bits a) => a -> [Bool]
unsignedToBits 0 = []
unsignedToBits x = testBit x 0 : unsignedToBits (shiftR x 1)

unsignedFromBits :: (Bits a) => [Bool] -> a
unsignedFromBits [] = 0
unsignedFromBits (x:xs) = unbit x .|. shiftL (unsignedFromBits xs) 1
where unbit True = 1 ; unbit False = 0


-- | The type of builders for ranged tables; takes a lower bound and an upper
-- bound, and returns a memo table for that range.
type RangeMemo a = (a,a) -> Memo a

-- | Build a memo table for a range using a flat array. If items are
-- given outside the range, don't memoize.
arrayRange :: (Array.Ix a) => RangeMemo a
arrayRange rng = switch (Array.inRange rng) (unsafeArrayRange rng) id

-- | Build a memo table for a range using a flat array. If items are
-- given outside the range, behavior is undefined.
unsafeArrayRange :: (Array.Ix a) => RangeMemo a
unsafeArrayRange rng f = (Array.listArray rng (map f (Array.range rng)) Array.!)


-- | Given a list of ranges, (lazily) build a memo table for each one
-- and combine them using linear search.
chunks :: (Array.Ix a) => RangeMemo a -> [(a,a)] -> Memo a
chunks rmemo cs f = lookup (cs `zip` map (\rng -> rmemo rng f) cs)
where
lookup [] _ = error "Element non in table"
lookup ((r,c):cs) x | Array.inRange r x = c x
| otherwise = lookup cs x
4 changes: 4 additions & 0 deletions Setup.lhs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#! /usr/bin/env runhaskell

> import Distribution.Simple
> main = defaultMain
14 changes: 14 additions & 0 deletions data-memocombinators.cabal
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Name: data-memocombinators
Description:
Combinators for building memo tables.
Version: 0.3
Stability: experimental
Synopsis: Combinators for building memo tables.
License: BSD3
Category: Data
Author: Luke Palmer
Maintainer: lrpalmer@gmail.com
Build-Type: Simple
Build-Depends: base, array
Exposed-Modules: Data.MemoCombinators
Extensions: Rank2Types, ScopedTypeVariables

0 comments on commit 2715c01

Please sign in to comment.