Seiten

Posts mit dem Label memoization werden angezeigt. Alle Posts anzeigen
Posts mit dem Label memoization werden angezeigt. Alle Posts anzeigen

Freitag, 13. April 2012

F# Type-directed memoization. IntTrie, knapsack problem and levenshtein distance.

Nach wochenlangem Bewerbungsstress und Frustration darüber, dass es wohl kaum F#-Jobstellen auf dem Markt gibt und die wenigen, die da sind, richten sich ausschlislich an Hochschulabsolventen, habe ich mich weiter mit der "Type-directed Memoization" beschäftigt. Dabei endeckte ich eine interessante OCaml Implementation.
Ich suchte nach konkreten praktischen Beispielen und merkte sehr schnell, dass in diesem Zusammenhang oft zwei Algorithmen genannt werden. Das Knapsack Problem
und die Levenshtein-Distanz. Hier ein paar Links zu diesen Themen.
Solving the 0-1 knapsack problem using continuation-passing style with memoization in F#.
    Haskell Version.
    Haskell Version mit der IntTrie-Datenstruktur.
    Die Levenshtein-Distanz auf Rosetta Code Seite.
    "Haskell function computes the edit distance in O(length a * (1 + dist a b)) time complexity".
    Lazy Levenshtein Distanz.
Von IntTrie war ich sofort begeistert und versuchte die abgespeckte Version - also nur positive Integers - nach F# zu übertragen.
// TypeMemo.fs
namespace TypeMemoization
// from http://hackage.haskell.org/packages/archive/data-inttrie/0.0.7/doc/html/src/Data-IntTrie.html
module BitTrie =
    
    type BitTrie<'a> = BitTrie of Lazy<'a> * Lazy<BitTrie<'a>> * Lazy<BitTrie<'a>>
    // A trie from positiv integers to values of type a. 
    type IntTrie<'a> = IntTrie of Lazy<'a> * BitTrie<'a>
    
    let inline testBit x = (x &&& 1) <> 0

    let rec fmap f (BitTrie (x, l, r)) = 
        BitTrie( lazy(f x.Value), 
                 lazy(fmap f l.Value), 
                 lazy(fmap f r.Value) )

    let identityPositive = 
        let rec go x = 
            BitTrie (x, 
                     lazy(fmap (fun n-> n <<< 1) (go x)), 
                     lazy(fmap (fun n -> (n <<< 1) ||| 1) (go x)))
        go (lazy(1))

    let inline fmapi f (IntTrie(z, pos)) = 
        IntTrie(lazy(f z.Value), fmap f pos)

    //The identity trie.
    let identity = IntTrie (lazy(0), identityPositive)

    let inline toTrie f  =  fmapi f identity

    let rec applyPositive (BitTrie (one, even, odd)) x =
        match x with
        | i when i = 1 ->       one
        | i when testBit i  ->  applyPositive odd.Value (x >>> 1) 
        | otherwise   ->        applyPositive even.Value (x >>> 1)

    // Apply the trie to an argument.
    let inline apply (IntTrie(z, pos)) x =
        match x with
        | 0 -> z.Value
        |_ -> (applyPositive pos x).Value 
    
    let inline memo f = apply (toTrie f)

    // Memoize a two argument function (just apply the table directly for
    // single argument functions).
    let inline memo2 f = memo (memo << f)
//knapsack.fs
namespace TypeMemoization

module knapsack =
    open System

    let inline genItems n = 
        match n with
        | 0 -> Array.empty 
        | _ -> Array.init n 
                        ( fun i ->
                            let weight = i % 5
                            let value = (float)(weight * i)
                            weight, value )
    let inline max (x:float) (y:float) = max x y 

    let inline knapsackOriginal desiredWeight (items:_[]) =               
        let inline weightOf i = fst items.[i-1]
        let inline valueOf i = snd items.[i-1] 

        let rec knapsack' i w  = 
            match i, w  with
            | 0, _ | _, 0 -> 0.
            | i, w    -> 
                match i with
                | i' when (weightOf i') > w -> 
                    knapsack' (i' - 1) w           
                | _ -> 
                    max (knapsack' (i - 1) w)  ((knapsack' (i - 1) (w - weightOf i)) + valueOf i)
                    
        knapsack' items.Length desiredWeight

    let inline knapsack weight (value:int->float) =                
        
        let rec knapsack' i w  = 
            match i, w  with
            | 0, _ | _, 0 -> 0.
            | i, w    -> 
                match i with
                | i' when (weight i') > w ->
                    knapsackMemo (i' - 1) w           
                | _ -> 
                    max (knapsackMemo (i - 1) w)  ((knapsackMemo (i - 1) (w - weight i)) + value i)
                    
        and knapsackMemo  = BitTrie.memo2 knapsack'
        knapsackMemo  

    let inline knapsackMemoized desiredWeight (items:_[]) =
        let inline weightOf i = fst items.[i-1]
        let inline valueOf i = snd items.[i-1] 

        knapsack weightOf valueOf (items.Length) (desiredWeight)
Zwar ist die "memoized" Version schneller als der originale Knapsack-Algorithmus, aber leider viel langsamer als Zach Bray's Version und folglich um X-faches langsamer als die imperative Variante. Es kann aber auch sein, dass meine F# Implementierung von IntTrie nicht die beste ist.

Mit der Levenshtein Distanz sieht es noch schlimmer aus. Wieder ist die "memoized" Variante besser als die naive Version, aber mit der Array-Lösung kann sie überhaupt nicht mithalten. Interessant ist der bereits erwähnte Lazy Levenshtein Algorithmus. Dieser in F# zu übertragen ist mir leider nicht gelungen.
Also ist mein Frust nur noch tiefer geworden.
// from http://research.microsoft.com/en-us/um/people/simonpj/papers/assoc-types/fun-with-type-funs/typefun.pdf.
// chapter "3.1 Type-directed memoization".
namespace TypeMemoization
module BitTrie =
    ...
module TypeMemo =
    open BitTrie

    type IFromTable<'a,'w > =
        abstract inline fromTable : 'a->'w
    
    let inline fromTable t = (t :> IFromTable<_,_>).fromTable
    // "we can memoise any function from Bool by storing its two
    // return values as a lazy pair. This lazy pair is the memo table."
    type BoolTable<'w> = 
        | BTable of Lazy<'w> * Lazy<'w> 
        interface IFromTable<bool,'w> with
            member inline x.fromTable b = 
                match x with
                | BTable(x,y) -> if b then x.Force() else y.Force()
    
    let inline boolToTable f = 
        BTable (lazy(f true), lazy(f false))
    
    
    //"memoise functions from any sum type, such as the type Either."
    type Either<'a,'b>= 
        |Left of 'a
        |Right of 'b
    // "We can memoise a function from Either a b by storing a lazy pair of a
    // memo table from a and a memo table from b. That is, we take advantage
    // of the isomorphism between the function type Either a b -> w and the
    // product type (a -> w, b -> w)."
    type DiscriminatedUnionTable<'a,'b,'w> = 
        | STable of IFromTable<'a,'w> * IFromTable<'b,'w> 
        interface IFromTable<Either<'a,'b>,'w> with
            member inline x.fromTable e = 
                match x, e with
                | STable (t,_), Left  v   -> t.fromTable v
                | STable (_,t), Right v   -> t.fromTable v
    
    let inline discriminatedUnionToTable f fa fb =
        STable (fa (f<<Left),fb (f<<Right))
    
    
    // "Dually, we can memoise functions from the product type (a,b) by storing a memo table
    // from a whose entries are memo tables from b."
    type ProductTable<'a,'b,'w> = 
        | PTable of IFromTable<'a, IFromTable<'b,'w>>
        interface IFromTable<('a*'b),'w> with
            member inline x.fromTable p = 
                match x, p with
                | PTable t,(a,b)-> (t.fromTable a).fromTable b

    let inline productToTable f fa fb = 
        PTable ((fa (fun a -> fb (fun b -> f (a, b)) :> IFromTable<_,_> )) :> IFromTable<_,_>)   
   

    // "A list is a combination of a sum, a product, and recursion.
    // Since a list is either empty or not, ListTable<'a,'w> is represented by a pair, whose first component is the result of applying
    // the memoised function f to the empty list, and whose second component
    // memoises applying f to non-empty lists."
    type ListTable<'a,'w> =
        | LTable of Lazy<'w> * IFromTable<'a, IFromTable<'a list, 'w>>
        interface IFromTable<'a list,'w> with
            member inline x.fromTable l =
                match x, l with
                | LTable(t, _), [] -> t.Force() 
                | LTable(_, t), x :: xs -> fromTable (fromTable t x ) xs
                    
    let rec listToTable f fa =
        LTable (lazy(f []), fa (fun x -> listToTable (fun xs ->  f (x::xs)) fa :> IFromTable<_,_>) :> IFromTable<_,_>)
    let inline flip1 f a b c = f c a b

    type CharTable<'w> =
        | CharTable of IntTrie<'w>  
        interface IFromTable<char, 'w> with
            member inline x.fromTable c =
                match x with
                | CharTable t ->  apply t (int c)
    let inline charToTable f = 
        CharTable (toTrie (f << char))
    
    let inline memoCharList2 f = 
        let memo g =
            listToTable g charToTable  |> fromTable
        memo (memo << f)
Levenshtein Distanz.
//levenshtein .fs
namespace TypeMemoisation
module levenshtein =
    open System
    
    let inline private naiveLevenshteinDistance del sub ins  =  
        let rec inner s1 s2 =
            match s1, s2 with
            | s1,     []     -> ins * List.length s1 
            | [],     s2     -> ins * List.length s2 
            | x :: xs, y :: ys ->
                match x = y with
                | true -> inner xs ys
                | _ -> List.min [ del + inner xs s2; 
                                 sub + inner s1 ys; 
                                 ins + inner xs ys] 
        inner

    let inline runNaiveLevenshteinDistance (s1:string) (s2:string) =
        naiveLevenshteinDistance 1 1 1 (List.ofSeq s1) (List.ofSeq s2)
    //memoized version.
    let inline private levenshteinDistance del sub ins  =  
        let rec inner s1 s2 =
            match s1, s2 with
            | s1,     []     -> ins * List.length s1 
            | [],     s2     -> ins * List.length s2 
            | x :: xs, y :: ys ->
                match x = y with
                | true -> memo xs ys
                | _ -> min (del + memo xs s2)   
                                 (min (sub + memo s1 ys) (ins + memo xs ys)) 
        and memo = 
                TypeMemo.memoCharList2 inner
        memo

    let inline levenshteinDistanceMemoized (s1:string) (s2:string) =
        levenshteinDistance 1 1 1 (List.ofSeq s1) (List.ofSeq s2)
    
    //array version.
    let inline levenshteinDistanceArray (s1:string) (s2:string) =
         let sa, sb:char [] * char [] = s1.ToCharArray(), s2.ToCharArray()
         let len = Array.length sa
         let m = len - 1

         let inline compute z xc = min (z+1) xc

         let inline transform (narr : int []) chb =
              Array.zip3 sa.[..m] narr.[..m] narr.[1..m+1] 
              |> Array.map (fun (cha, x, y) -> min (y + 1) (x + abs(compare cha chb)) )
              |> Array.scan compute (narr.[0] + 1)
         let result = Array.fold transform [|0..len|] sb
         result.[result.Length - 1]

Montag, 16. Januar 2012

F# Type-directed memoization. recursive types.

Endlich habe ich es geschafft, was ich seit dem Posting fast schon aufgegeben habe. An der Stelle noch einmal der Verweis zu einem sehr interessanten und einleuchtenden Artikel "Fun with type functions".
In erste Linie ging es mir um die Memoisation einer Funktion von einem rekursiven Typ-Parameter (z.B. List). Ausserdem wollte ich die polymorphe memoization-Funktion in F# umsetzen.
Das Ergebnis sieht dann folgend aus.
//function with some long computations.
// val boolFunc : bool-> int list
let boolFunc b = 
    match b with
    | true -> 
        printfn "call boolFunc: true part." 
        List.init 1000 (fun i -> i * i)
    |false -> 
        printfn "call boolFunc: false part."
        List.init 1000 (fun i-> -1 * i * i)

// function of list type artgument.
// val testListFunc : bool list -> int list
let testListFunc l = List.map boolFunc l |> List.concat

// val memoizedFunc : (bool list -> int list)
let memoizedFunc = memoization testListFunc 

let test = (memoizedFunc [true;false;false;]) @  (memoizedFunc [true;false;false;])
printfn "run %A" test

call boolFunc: true part.
call boolFunc: false part.
call boolFunc: false part.

run [0; 1; 4; 9; 16; 25; 36; 49; 64; 81; 100; 121; 144; 169; 196; 225; 256; 289;
 324;
 361; 400; 441; 484; 529; 576; 625; 676; 729; 784; 841; 900; 961; 1024; 1089;
 1156; 1225; 1296; 1369; 1444; 1521; 1600; 1681; 1764; 1849; 1936; 2025; 2116;
 2209; 2304; 2401; 2500; 2601; 2704; 2809; 2916; 3025; 3136; 3249; 3364; 3481;
 3600; 3721; 3844; 3969; 4096; 4225; 4356; 4489; 4624; 4761; 4900; 5041; 5184;
 5329; 5476; 5625; 5776; 5929; 6084; 6241; 6400; 6561; 6724; 6889; 7056; 7225;
 7396; 7569; 7744; 7921; 8100; 8281; 8464; 8649; 8836; 9025; 9216; 9409; 9604;
 9801; ...]

Hier ist meine Implementierung. Als Kommentare habe ich einfach die Zitate aus dem Artikel verwendet.
// TypeMemo.fs
// from http://research.microsoft.com/en-us/um/people/simonpj/papers/assoc-types/fun-with-type-funs/typefun.pdf.
// chapter "3.1 Type-directed memoization".
namespace TypeMemoization
module TypeMemo =

    type IFromTable<'a,'w > =
        abstract inline fromTable : 'a->'w
    
    let inline fromTable t = (t :> IFromTable<_,_>).fromTable

    // "we can memoise any function from Bool by storing its two
    // return values as a lazy pair. This lazy pair is the memo table."
    type BoolTable<'w> = 
        | BTable of Lazy<'w> * Lazy<'w> 
        interface IFromTable<bool,'w> with
            member inline x.fromTable b = 
                match x with
                | BTable(x,y) -> if b then x.Force() else y.Force()
    
    let inline boolToTable f = 
        BTable (lazy(f true), lazy(f false))
    
    
    //"memoise functions from any sum type, such as the type Either."
    type Either<'a,'b>= 
        |Left of 'a
        |Right of 'b
    // "We can memoise a function from Either a b by storing a lazy pair of a
    // memo table from a and a memo table from b. That is, we take advantage
    // of the isomorphism between the function type Either a b -> w and the
    // product type (a -> w, b -> w)."
    type DiscriminatedUnionTable<'a,'b,'w> = 
        | STable of IFromTable<'a,'w> * IFromTable<'b,'w> 
        interface IFromTable<Either<'a,'b>,'w> with
            member inline x.fromTable e = 
                match x, e with
                | STable (t,_), Left  v   -> t.fromTable v
                | STable (_,t), Right v   -> t.fromTable v
    
    let inline discriminatedUnionToTable f fa fb =
        STable (fa (f<<Left),fb (f<<Right))
    
    
    // "Dually, we can memoise functions from the product type (a,b) by storing a memo table
    // from a whose entries are memo tables from b."
    type ProductTable<'a,'b,'w> = 
        | PTable of IFromTable<'a, IFromTable<'b,'w>>
        interface IFromTable<('a*'b),'w> with
            member inline x.fromTable p = 
                match x, p with
                | PTable t,(a,b)-> (t.fromTable a).fromTable b

    let inline productToTable f fa fb = 
        PTable ((fa (fun a -> fb (fun b -> f (a, b)) :> IFromTable<_,_> )) :> IFromTable<_,_>)   
   

    // "A list is a combination of a sum, a product, and recursion.
    // Since a list is either empty or not, ListTable<'a,'w> is represented by a pair, whose first component is the result of applying
    // the memoised function f to the empty list, and whose second component
    // memoises applying f to non-empty lists."
    type ListTable<'a,'w> =
        | LTable of Lazy<'w> * IFromTable<'a, IFromTable<'a list, 'w>>
        interface IFromTable<'a list,'w> with
            member inline x.fromTable l =
                match x, l with
                | LTable(t, _), [] -> t.Force() 
                | LTable(_, t), x :: xs -> fromTable (fromTable t x ) xs
                    
    let rec listToTable f fa =
        LTable (lazy(f []), fa (fun x -> listToTable (fun xs ->  f (x::xs)) fa :> IFromTable<_,_>) :> IFromTable<_,_>)
    let inline flip1 f a b c = f c a b
Für jeden Typ, den wir als Funktions-Typ-Parameter später benutzen wollen, sollte eine statische Operatorüberladung im MemoTable-Typ definiert werden. Hier bediene ich den Operatorüberladung-Trick als Ersatz für Type Class in F#.
type MemoTable = MemoTable with
            static member  (|>|) (MemoTable, f) =
                productToTable f (flip1 discriminatedUnionToTable boolToTable boolToTable) boolToTable
            static member  (|>|) (MemoTable, f) =
                productToTable f boolToTable (flip1 discriminatedUnionToTable boolToTable boolToTable)
            static member  (|>|) (MemoTable, f) =
                productToTable f boolToTable boolToTable
            static member  (|>|) (MemoTable, f) =
                discriminatedUnionToTable f (flip1 productToTable boolToTable boolToTable) boolToTable 
            static member  (|>|) (MemoTable, f) =
                discriminatedUnionToTable f boolToTable boolToTable) 
            static member  (|>|) (MemoTable, f) =
                discriminatedUnionToTable f boolToTable (flip1 discriminatedUnionToTable boolToTable boolToTable) 
            static member  (|>|) (MemoTable, f) =
                boolToTable f

            // functions from recursive types, like lists
            static member (|>|) (MemoTable, (f:(bool list->'a))) = 
                listToTable f ((|>|) MemoTable ) |> fromTable  
            static member (|>|) (MemoTable, (f:((bool*bool) list->'a))) = 
                listToTable f ((|>|) MemoTable ) |> fromTable
            static member (|>|) (MemoTable, (f:(Either<bool,bool> list->'a))) = 
                listToTable f ((|>|) MemoTable ) |> fromTable
            static member (|>|) (MemoTable, (f:((Either<bool,bool> * bool) list->'a))) = 
                listToTable f ((|>|) MemoTable ) |> fromTable
                             
    let inline memoization f = MemoTable |>| f


Wie man sieht, sind die vier letzten Methoden redundant. Versuchen wir mal mit "inline" und lassen die Typangabe weg.

Geht leider nicht. Dann ändern wir die Funktion ein bisschen ...

und es kompiliert. Jetzt gibt es einen kleinen Nachteil auf der Aufrufer-Seite, dass bei der Memoisation von Funktionen mit einem List-Parameter stets MemoTable als "dummy" Parameter angegeben werden soll.

Ein paar Tests.
// Script.fsx
#load "TypeMemo.fs"
open TypeMemoisation.TypeMemo
open System

let boolFunc1 b = 
    match b with
    | true -> 
        printfn "call boolFunc1: true part. Value = 10" 
        10
    |false -> 
        printfn "call boolFunc1: false part. Value = -10"
        -10
//function with long computations.
// val boolFunc : boo l-> int list
let boolFunc b = 
    match b with
    | true -> 
        printfn "call boolFunc: true part" 
        List.init 1000 (fun i->i*i)
    |false -> 
        printfn "call boolFunc: false part"
        List.init 1000 (fun i-> -1*i*i)

let eitherFunc e =
        match e with
        | Left a  -> 
            printfn "call eitherFunc: Left %A" a
            ((boolFunc a)|>List.sum) *(-2)
        | Right b ->  
            printfn "call eitherFunc: Right %A" b
            ((boolFunc b)|>List.sum) * 2
let eitherFunc1 e =
        match e with
        | Left a  -> 
            printfn "call eitherFunc: Left %A" a
            ((boolFunc a)|>List.sum) *(-2)
        | Right b ->  
            printfn "call eitherFunc: Right %A" b
            (eitherFunc b) * 2
let productFunc p =
        let x=
            printfn "call productFunc: first"
            (boolFunc1 (fst p))-3
        let y =
            printfn "call productFunc: second "
            (boolFunc1 (snd p))*2
        x + y

let productEitherFunc (e, b) =
        let x =
            printfn "call productEitherFunc: first %A" e
            (eitherFunc e)-3
        let y =
            printfn "call productEitherFunc second %A" b
            (boolFunc1 b) * 2
        x+y

//function of list type artgument.
let rec listFuncTest l  =
        match l with
        |[] -> 
            printfn "listFuncTest Empty"
            0
        |x :: xs -> 
            printfn "listFuncTest %A" x
            ((boolFunc x)|>List.sum) + listFuncTest xs

let inline memoizationSimpleType f =
    memoization f |> fromTable

// val memoizedFunc : (bool -> int)
let memoizedFunc  = memoizationSimpleType boolFunc1 
// val memoizedFunc1 : (Either<bool,bool> -> int)
let memoizedFunc1 = memoizationSimpleType eitherFunc
// val memoizedFunc2 : (bool * bool -> int)
let memoizedFunc2 = memoizationSimpleType productFunc 
// val memoizedFunc3 : (Either<bool,bool> * bool -> int)
let memoizedFunc3 = memoizationSimpleType productEitherFunc 
// val memoizedFunc4 : (Either<bool, Either<bool, bool>> -> int)
let memoizedFunc4 = memoizationSimpleType eitherFunc1

printfn "test (Either<bool,bool> -> int): "
let runSimple1= memoizedFunc1 (Left true) +  memoizedFunc1 (Left true)

printfn "runSimple1 %A" runSimple1
printfn "-------------------------------------------------------"

printfn "test (bool * bool -> int): "
let runSimple2= memoizedFunc2 (false, true) +  memoizedFunc2 (false, true)
printfn "runSimple2 %A" runSimple2
printfn "-------------------------------------------------------"

printfn "test (Either<bool,bool> * bool -> int): "
let runSimple3= memoizedFunc3 (Left true, false) +  memoizedFunc3 (Left true, false)
printfn "runSimple3 %A" runSimple3
printfn "-------------------------------------------------------"

let rec listFunc g l  =
        match l with
        |[] -> 
            printfn "call listFunc: Empty"
            0
        |x :: xs -> 
            printfn "call listFunc: %A" x
            (g x) + listFunc g xs 
printfn "test (bool * bool list -> int): "
let memoTest1  = memoization (listFunc productFunc) MemoTable
let run1 = (memoTest1 [(true,false);(false,true)]) +  (memoTest1 [(true,false);(false,true)])
printfn "run1 %A" run1
printfn "-------------------------------------------------------"

printfn "test (Either<bool,bool> * list -> int): "
let memoTest2  = memoization (listFunc eitherFunc) MemoTable
let run2= (memoTest2 [Left true; Right false]) +  (memoTest2 [Left true; Right false])
  
printfn "run2 %A" run2
type MemoTable =
    | MemoTable
    with
      static member
        ( |>| ) : MemoTable:MemoTable * f:(Either<bool,bool> * bool -> 'a) ->
                    ProductTable<Either<bool,bool>,bool,'a>
      static member
        ( |>| ) : MemoTable:MemoTable * f:(bool * Either<bool,bool> -> 'a) ->
                    ProductTable<bool,Either<bool,bool>,'a>
      static member
        ( |>| ) : MemoTable:MemoTable * f:(bool * bool -> 'a) ->
                    ProductTable<bool,bool,'a>
      static member
        ( |>| ) : MemoTable:MemoTable * f:(Either<(bool * bool),bool> -> 'a) ->
                    DiscriminatedUnionTable<(bool * bool),bool,'a>
      static member
        ( |>| ) : MemoTable:MemoTable * f:(Either<bool,bool> -> 'a) ->
                    DiscriminatedUnionTable<bool,bool,'a>
      static member
        ( |>| ) : MemoTable:MemoTable *
                  f:(Either<bool,Either<bool,bool>> -> 'a) ->
                    DiscriminatedUnionTable<bool,Either<bool,bool>,'a>
      static member
        ( |>| ) : MemoTable:MemoTable * f:(bool -> 'a) -> BoolTable<'a>
      static member
        ( |>| ) : MemoTable:MemoTable * f:('a list -> 'b) ->
                    ( ^_arg1 -> 'c list -> 'b)
                    when ( ^_arg1 or ('a -> IFromTable<'c list,'b>)) : (static
                                                                        member
                                                                        ( |>| ) :  ^_arg1 *
                                                                                  ('a ->
                                                                                     IFromTable<'c list,
                                                                                                'b>)
                                                                                    ->
                                                                                     ^d) and
                          ^d :> IFromTable<'c,IFromTable<'c list,'b>>
    end
  val inline memoization :
     ^a ->  ^_arg6
      when (MemoTable or  ^a) : (static member ( |>| ) : MemoTable *  ^a ->
                                                            ^_arg6)

test (Either<bool,bool> -> int): 
call eitherFunc: Left true
call boolFunc: true part
runSimple1 -1331334000
-------------------------------------------------------
test (bool * bool -> int): 
call productFunc: first
call boolFunc1: false part. Value = -10
call productFunc: second 
call boolFunc1: true part. Value = 10
runSimple2 14
-------------------------------------------------------
test (Either<bool,bool> * bool -> int): 
call productEitherFunc: first Left true
call eitherFunc: Left true
call boolFunc: true part
call productEitherFunc second false
call boolFunc1: false part. Value = -10
runSimple3 -1331334046
-------------------------------------------------------
test (bool * bool list -> int): 
call listFunc: (true, false)
call productFunc: first
call boolFunc1: true part. Value = 10
call productFunc: second 
call boolFunc1: false part. Value = -10
call listFunc: (false, true)
call productFunc: first
call boolFunc1: false part. Value = -10
call productFunc: second 
call boolFunc1: true part. Value = 10
call listFunc: Empty
run1 -12
-------------------------------------------------------
test (Either<bool,bool> * list -> int): 
call listFunc: Left true
call eitherFunc: Left true
call boolFunc: true part
call listFunc: Right false
call eitherFunc: Right false
call boolFunc: false part
call listFunc: Empty
run2 1632299296
val boolFunc1 : bool -> int
val boolFunc : bool -> int list
val eitherFunc : TypeMemoisation.TypeMemo.Either<bool,bool> -> int
val eitherFunc1 :
  TypeMemoisation.TypeMemo.Either<bool,
                                   TypeMemoisation.TypeMemo.Either<bool,bool>> ->
    int
val productFunc : bool * bool -> int
val productEitherFunc :
  TypeMemoisation.TypeMemo.Either<bool,bool> * bool -> int
val listFuncTest : bool list -> int
val inline memoizationSimpleType :
   ^a -> ('c -> 'd)
    when (TypeMemoisation.TypeMemo.MemoTable or  ^a) : (static member ( |>| ) : TypeMemoisation.TypeMemo.MemoTable *
                                                                                  ^a
                                                                                   ->
                                                                                    ^b) and
          ^b :> TypeMemoisation.TypeMemo.IFromTable<'c,'d>
val memoizedFunc : (bool -> int)
val memoizedFunc1 : (TypeMemoisation.TypeMemo.Either<bool,bool> -> int)
val memoizedFunc2 : (bool * bool -> int)
val memoizedFunc3 :
  (TypeMemoisation.TypeMemo.Either<bool,bool> * bool -> int)
val memoizedFunc4 :
  (TypeMemoisation.TypeMemo.Either<bool,
                                    TypeMemoisation.TypeMemo.Either<bool,bool>> ->
     int)
val runSimple1 : int = -1331334000
val runSimple2 : int = 14
val runSimple3 : int = -1331334046
val listFunc : ('a -> int) -> 'a list -> int
val memoTest1 : ((bool * bool) list -> int)
val run1 : int = -12
val memoTest2 : (TypeMemoisation.TypeMemo.Either<bool,bool> list -> int)
val run2 : int = 1632299296

Seltsamerweise funktioniert es in F#-Interactive und im Release-Modus, aber im Debug bekomme ich folgende Fehlermeldung.

Freitag, 27. Mai 2011

F# Type-directed memoization.

Ich bin gerade am lesen des interesanten Artikels Fun with type functions. Unter anderem ist da "Type-directed memoization" beschrieben. Die versuche ich in F# umzusetzen.
Ich muss aber zugeben - eine praktische Anwendung wird es wohl kaum geben. Ich betrachte es als meiner eigene Haskell Cargo-Kult
Hier so zu sagen Standart-F# Memoization Pattern und Monadic Memoization.

Da es in F# keine Typklasse gibt, könnte man mit einem abstrakten Interface kleine Abhilfe schaffen.
type ITable<'a,'w> =
    abstract inline Table : ITable<'a,'w>

type BoolTable<'w> = 
    | BTable of Lazy<'w> * Lazy<'w>
    interface ITable<bool,'w> with
        member inline x.Table = x :> ITable<_,_>

//(bool -> 'a) -> BoolTable<'a>
let boolToTable f = BTable (lazy(f true), lazy(f false))

//BoolTable<'a> -> bool -> 'a
let boolFromTable (BTable (x,y)) b = 
    if b then x.Force() else y.Force()

Weiter zitiere ich einfach aus dem Artikel (http://research.microsoft.com/en-us/um/people/simonpj/papers/assoc-types/fun-with-type-funs/typefun.pdf).
" To memoise a function f :: bool -> Int, we simply replace it by g:
g :: Bool -> Int
g = fromTable (toTable f)
The first time g is applied to True, the Haskell implementation computes
the first component of the lazy pair (by applying f in turn to True) and
remembers it for future reuse. Thus, if f is defined by
f True = factorial 100
f False = fibonacci 100
then evaluating (g True + g True) will take barely half as much time as
evaluating (f True + f True). "
let boolFunc b = 
    match b with
    | true -> 
        printfn "true. Value = 10" 
        10
    |false -> 
        printfn "false. Value = 5"
        5
val boolFunc : bool -> int

> let memoized= boolFromTable (boolToTable boolFunc)

val memoized : (bool -> int)

> let res = memoized(true) + memoized(true) + memoized(false) + memoized(false)

true. Value = 10
false. Value = 5

val res : int = 30
" Generalising the Memo instance for Bool above, we can memoise functions
from any sum type, such as the standard Haskell type Either:
data Either a b = Left a | Right b
We can memoise a function from Either a b by storing a lazy pair of a
memo table from a and a memo table from b. That is, we take advantage
of the isomorphism between the function type Either a b -> w and the
product type (a -> w, b -> w). "
type Either<'a,'b>= 
        |Left of 'a
        |Right of 'b

type SumTable<'t1,'t2,'a,'b,'w when 't1:> ITable<'a,'w> and 't2:> ITable<'b,'w>> = 
    | STable of 't1 * 't2
    interface ITable<Either<'a,'b>,'w> with
        member inline x.Table = x :> ITable<Either<'a,'b>,'w>
Leider unterstützt F# auch keine "type function". Also die entsprechende Funktionen müssen explizit übergeben werden.
// sumToTable : (('a -> 'b) -> 'c) -> (('f -> 'b) -> 'g) -> (Either<'a,'f> -> 'b) ->
//     SumTable<'c,'g,'d,'h,'e>
//    when 'c :> ITable<'d,'e> and 'g :> ITable<'h,'e> 
let sumToTable fa fb f=
    STable (fa (f<<Left), fb (f<<Right))

// sumFromTable : ('a -> 'd -> 'e) -> ('f -> 'h -> 'e) -> SumTable<'a,'f,'b,'g,'c> ->
//     Either<'d,'h> -> 'e 
// when 'a :> ITable<'b,'c> and 'f :> ITable<'g,'c>
let sumFromTable fa fb tbl e =
            match tbl, e with
            | STable (t, _), Left  v   -> fa t v
            | STable (_, t), Right v   -> fb t v

let eitherFunc e =
    match e with
    | Left a  -> 
        printfn "eitherFunc Left %A" a
        (boolFunc a) - 3
    | Right b ->  
        printfn "eitherFunc Right %A" b
        (boolFunc b) * 2
val eitherFunc : Either<bool,bool> -> int

> let memoized= sumFromTable boolFromTable boolFromTable (sumToTable boolToTable boolToTable eitherFunc);;

val memoized : (Either<bool,bool> -> int)

> let res = memoized(Left true) + memoized(Left true) + memoized(Right false) + memoized(Right false);;

eitherFunc Left true
true. Value = 10
eitherFunc Right false
false. Value = 5

val res : int = 34

" Dually, we can
memoise functions from the product type (a,b) by storing a memo table
from a whose entries are memo tables from b. That is, we take advantage
of the currying isomorphism between the function types (a,b) -> w and
a -> b -> w. "
type ProductTable<'t1,'t2,'a,'b,'w when 't1 :> ITable<'b,'w> and 't2 :> ITable<'a,'t1> > =
    | PTable of 't2
    interface ITable<'a * 'b,'w> with
        member inline x.Table = x :> ITable<('a * 'b),'w>

// productToTable : (('a -> 'b) -> 'c) -> (('d -> 'c) -> 'e) -> ('d * 'a -> 'b) ->
//     ProductTable<'g,'e,'f,'h,'i>
//    when 'e :> ITable<'f,'g> and 'g :> ITable<'h,'i>
let productToTable fa fb f= 
              let p = fb (fun a -> fa (fun b -> f (a, b)))
              PTable p

// productFromTable: ('a -> 'b -> 'c) -> ('d -> 'i -> 'a) -> ProductTable<'f,'d,'e,'g,'h> ->
//     'i * 'b -> 'c
// when 'd :> ITable<'e,'f> and 'f :> ITable<'g,'h> 
let productFromTable fa fb tbl p =
            match tbl,p with
            | PTable t,(a,b)-> fa (fb t a) b

let productFunc pair =
    let x=
        printfn "productFunc first"
        (boolFunc (fst pair))-3
    let y =
        printfn "productFunc second "
        (boolFunc (snd pair))*2
    x + y

let productEitherFunc (e, b) =
    let x =
        printfn "productEitherFunc first %A" e
        (eitherFunc e) - 3
    let y =
        printfn "productEitherFunc second %A" b
        (boolFunc b) * 2
    x + y
val productFunc : bool * bool -> int

val productEitherFunc : Either<bool,bool> * bool -> int

> let memoized =  productFromTable boolFromTable boolFromTable (productToTable boolToTable boolToTable productFunc);;

val memoized : (bool * bool -> int)

> let res = memoized (true, true) + memoized (true, true);;

productFunc first
true. Value = 10
productFunc second 
true. Value = 10

val res : int = 54

> let res = memoized (true, true) + memoized (false, false);;

productFunc first
false. Value = 5
productFunc second 
false. Value = 5

val res : int = 39

> let memoized = 
    productFromTable boolFromTable (sumFromTable boolFromTable boolFromTable) 
        (productToTable boolToTable (sumToTable boolToTable boolToTable) productEitherFunc);;

val memoized : (Either<bool,bool> * bool -> int)

> let res = memoized (Left true, true) + memoized (Left true, true);;

productEitherFunc first Left true
eitherFunc Left true
true. Value = 10
productEitherFunc second true
true. Value = 10

val res : int = 48

> let res = memoized (Left true, true) + memoized (Right false, false) + memoized (Left true, true);;

productEitherFunc first Right false
eitherFunc Right false
false. Value = 5
productEitherFunc second false
false. Value = 5

val res : int = 65

Leider ist mir nicht gelungen Memoization für rekursive Typen zu schreiben und ich vermute stark, dass dies in F# gar nicht möglich ist.