F# - Mono crashes/ returns inconsistent - f#

I am trying to learn F# and I have some background in Standard ML. I am working through the text "Functional Approach to Programming" converting its code into F#. The code sample presented below is from the book mentioned above.
in SML
datatype 'a genTree = GenNode of 'a * ('a genTree list);;
val tree: string genTree =
GenNode("a", [GenNode("b", []),
GenNode("c", [GenNode("d", []),
GenNode("e", [])]),
GenNode("f", [])]);;
fun map f [] = []
| map f (l::ls) = (f l) :: (map f ls);;
fun foldl f e [] = e
| foldl f e (l::ls) = foldl f (f(e, l)) ls;;
fun sigma xs = foldl (op +) 0 xs;;
fun genSize (GenNode(_, ls)) =
1 + sigma (map genSize ls);;
SML> genSize tree (* returns 6: int *)
Attempt One - in F# Interactive Shell F# 3.1 (this one crashes the shell)
type genTree<'a> = GenNode of 'a * genTree<'a> list;;
let tree: string genTree =
GenNode("a", [GenNode("b", []);
GenNode("c", [GenNode("d", []);
GenNode("e", [])]);
GenNode("f", [])]);;
let rec map f ls =
match ls with
| [] -> []
| (l::ls) -> (f l) :: (map f ls);;
let rec foldl f e ls =
match ls with
| [] -> e
| (l::ls) -> foldl f (f e l) ls;;
let sigma ls = foldl ( + ) 0 ls;;
let rec genSize (GenNode(lbl, ls)) =
1 + sigma(map genSize ls);;
Attempt Two - in F# Interactive Shell F# 3.1 (give a base case for genSize, hoping that'll do the trick!)
Here is the new definition for genSize, the rest of the code from attempt #1 carries unchanged.
let rec genSize (t: genTree<'a>) =
match t with
| GenNode(_, []) -> 1
| GenNode(_, ls) -> 1 + sigma (map genSize ls);;
FSI> genSize tree = 5
Attempt Three - in MonoDevelop 5.9.4
Copy the code from Attempt 2 into MonoDevelop 5.9.4 and run it there and it correctly returns 6.
Question:
Could someone please explain to me what is causing the discrepancy between attempt 1 - 3?
Many thanks and kind regards.
PS: As I am currently learning F# and Functional Programming I tend to implement most of library functionality as a part of the learning process.
Stack track from Attempt 1
Assertion at sgen-alloc.c:460, condition `*p == NULL' not met
Stacktrace:
at <0xffffffff>
at (wrapper managed-to-native) object.__icall_wrapper_mono_object_new_specific (intptr) <0xffffffff>
at FSI_0002.DEL.sigma (Microsoft.FSharp.Collections.FSharpList1<int>) <0x00013>
at FSI_0002.DEL.genSize<a> (FSI_0002.DEL/genTree1) <0x0005f>
at FSI_0002.DEL/genSize#51.Invoke (FSI_0002.DEL/genTree1<a>) <0x00023>
at FSI_0002.DEL.map<FSI_0002.DEL/genTree1, int> (Microsoft.FSharp.Core.FSharpFunc2<FSI_0002.DEL/genTree1, int>,Microsoft.FSharp.Collections.FSharpList1<FSI_0002.DEL/genTree1>) <0x00032>
at FSI_0002.DEL.genSize (FSI_0002.DEL/genTree1<a>) <0x00056>
at <StartupCode$FSI_0004>.$FSI_0004.main# () <0x0001b>
at (wrapper runtime-invoke) object.runtime_invoke_void (object,intptr,intptr,intptr) <0xffffffff>
at <unknown> <0xffffffff>
at (wrapper managed-to-native) System.Reflection.MonoMethod.InternalInvoke (System.Reflection.MonoMethod,object,object[],System.Exception&) <0xffffffff>
at System.Reflection.MonoMethod.Invoke (object,System.Reflection.BindingFlags,System.Reflection.Binder,object[],System.Globalization.CultureInfo) <0x000c0>
at System.MonoType.InvokeMember (string,System.Reflection.BindingFlags,System.Reflection.Binder,object,object[],System.Reflection.ParameterModifier[],System.Globalization.CultureInfo,string[]) <0x0039a>
at System.Reflection.Emit.TypeBuilder.InvokeMember (string,System.Reflection.BindingFlags,System.Reflection.Binder,object,object[],System.Reflection.ParameterModifier[],System.Globalization.CultureInfo,string[]) <0x00066>
at System.Type.InvokeMember (string,System.Reflection.BindingFlags,System.Reflection.Binder,object,object[],System.Globalization.CultureInfo) <0x0005a>
at Microsoft.FSharp.Compiler.AbstractIL.ILRuntimeWriter/execEntryPtFun#1986-1.Invoke (Microsoft.FSharp.Core.Unit) <0x00083>
at Microsoft.FSharp.Compiler.Interactive.Shell/clo#883-37.Invoke (Microsoft.FSharp.Core.FSharpFunc2,Microsoft.FSharp.Collections.FSharpList`1<T>) <0x00038>
at Microsoft.FSharp.Collections.ListModule.Iterate<T> (Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Collections.FSharpList`1<T>) <0x00027>
at Microsoft.FSharp.Compiler.Interactive.Shell.arg10#882 (Microsoft.FSharp.Compiler.Interactive.Shell/FsiDynamicCompiler,Microsoft.FSharp.Collections.FSharpList`1>>,Microsoft.FSharp.Core.Unit) <0x0005b>
at Microsoft.FSharp.Compiler.Interactive.Shell/FsiDynamicCompiler.ProcessInputs (Microsoft.FSharp.Compiler.Interactive.Shell/FsiDynamicCompilerState,Microsoft.FSharp.Collections.FSharpList1<Microsoft.FSharp.Compiler.Ast/ParsedInput>,bool,bool,bool,Microsoft.FSharp.Collections.FSharpList1) <0x006e3>
at Microsoft.FSharp.Compiler.Interactive.Shell/FsiDynamicCompiler.EvalParsedDefinitions (Microsoft.FSharp.Compiler.Interactive.Shell/FsiDynamicCompilerState,bool,bool,Microsoft.FSharp.Collections.FSharpList1<Microsoft.FSharp.Compiler.Ast/SynModuleDecl>) <0x001db>
at Microsoft.FSharp.Compiler.Interactive.Shell/FsiDynamicCompiler.EvalParsedExpression (Microsoft.FSharp.Compiler.Interactive.Shell/FsiDynamicCompilerState,Microsoft.FSharp.Compiler.Ast/SynExpr) <0x0005f>
at Microsoft.FSharp.Compiler.Interactive.Shell/clo#1590-45.Invoke (Microsoft.FSharp.Compiler.Interactive.Shell/FsiDynamicCompilerState) <0x012ef>
at Microsoft.FSharp.Compiler.Interactive.Shell/FsiInteractionProcessor.InteractiveCatch<a> (Microsoft.FSharp.Core.FSharpFunc22<a, Microsoft.FSharp.Compiler.Interactive.Shell/FsiInteractionStepStatus>>,a) <0x00039>
at Microsoft.FSharp.Compiler.Interactive.Shell/FsiInteractionProcessor.ExecInteraction (bool,Microsoft.FSharp.Compiler.Build/TcConfig,Microsoft.FSharp.Compiler.Interactive.Shell/FsiDynamicCompilerState,Microsoft.FSharp.Compiler.Ast/ParsedFsiInteraction) <0x00067>
at Microsoft.FSharp.Compiler.Interactive.Shell/FsiInteractionProcessor.ExecInteractions (bool,Microsoft.FSharp.Compiler.Build/TcConfig,Microsoft.FSharp.Compiler.Interactive.Shell/FsiDynamicCompilerState,Microsoft.FSharp.Core.FSharpOption1) <0x005fb>
at Microsoft.FSharp.Compiler.Interactive.Shell/FsiInteractionProcessor.MainThreadProcessParsedInteraction (bool,Microsoft.FSharp.Core.FSharpOption1<Microsoft.FSharp.Compiler.Ast/ParsedFsiInteraction>,Microsoft.FSharp.Compiler.Interactive.Shell/FsiDynamicCompilerState) <0x0013b>
at Microsoft.FSharp.Compiler.Interactive.Shell/res#1782.Invoke (Microsoft.FSharp.Compiler.Interactive.Shell/FsiDynamicCompilerState) <0x00033>
at Microsoft.FSharp.Compiler.Interactive.Shell/runCodeOnMainThread#2021-2.Invoke (Microsoft.FSharp.Core.Unit) <0x0003b>
at <StartupCode$FSharp-Compiler-Interactive-Settings>.$Fsiaux/Microsoft-FSharp-Compiler-Interactive-IEventLoop-Invoke#47.Invoke (Microsoft.FSharp.Core.Unit) <0x0001a>
at <StartupCode$FSharp-Compiler-Interactive-Settings>.$Fsiaux/run#38-4.Invoke (Microsoft.FSharp.Core.FSharpFunc2) <0x00039>
at Microsoft.FSharp.Primitives.Basics.List.iter (Microsoft.FSharp.Core.FSharpFunc2<T, Microsoft.FSharp.Core.Unit>,Microsoft.FSharp.Collections.FSharpList1) <0x00038>
at Microsoft.FSharp.Collections.ListModule.Iterate (Microsoft.FSharp.Core.FSharpFunc2<T, Microsoft.FSharp.Core.Unit>,Microsoft.FSharp.Collections.FSharpList1) <0x00027>
at .$Fsiaux.run#35 (Microsoft.FSharp.Compiler.Interactive.SimpleEventLoop,Microsoft.FSharp.Core.Unit) <0x000d3>
at Microsoft.FSharp.Compiler.Interactive.SimpleEventLoop.Microsoft-FSharp-Compiler-Interactive-IEventLoop-Run () <0x0001f>
at Microsoft.FSharp.Compiler.Interactive.Shell.runLoop#2066 (Microsoft.FSharp.Compiler.Interactive.Shell/FsiConsoleOutput,Microsoft.FSharp.Core.Unit) <0x00205>
at Microsoft.FSharp.Compiler.Interactive.Shell.DriveFsiEventLoop (Microsoft.FSharp.Compiler.Interactive.Shell/FsiConsoleOutput) <0x0001b>
at Microsoft.FSharp.Compiler.Interactive.Shell/FsiEvaluationSession.Run () <0x00bfb>
at Microsoft.FSharp.Compiler.Interactive.Shell.evaluateSession#2382 (string[],Microsoft.FSharp.Core.Unit) <0x0009b>
at Microsoft.FSharp.Compiler.Interactive.Shell.MainMain (string[]) <0x00123>
at Microsoft.FSharp.Compiler.Interactive.Main.FsiMain (string[]) <0x00013>
at (wrapper runtime-invoke) .runtime_invoke_int_object (object,intptr,intptr,intptr) <0xffffffff>
Native stacktrace:
mono() [0x8102a23]
[0xb775240c]
[0xb7752424]
/lib/i386-linux-gnu/libc.so.6(gsignal+0x47) [0xb7545607]
/lib/i386-linux-gnu/libc.so.6(abort+0x143) [0xb7548a33]
mono() [0x827b381]
mono() [0x827b508]
mono() [0x827b5a3]
mono() [0x8233338]
mono() [0x8233a4c]
mono() [0x81f20a8]
mono(mono_object_new_alloc_specific+0x40) [0x81f6570]
mono(mono_object_new_specific+0x88) [0x81f6628]
[0xb725cc71]
[0xb25a974c]
[0xb25a9640]
[0xb25a9724]
[0xb25a96c3]
[0xb25a9637]
[0xb25a94ac]
[0xb725b5f7]
mono() [0x80723d1]
Debug info from gdb:
Could not attach to process. If your uid matches the uid of the target
process, check the setting of /proc/sys/kernel/yama/ptrace_scope, or try
again as the root user. For more details, see /etc/sysctl.d/10-ptrace.conf
ptrace: Operation not permitted.
No threads.
=================================================================
Got a SIGABRT while executing native code. This usually indicates
a fatal error in the mono runtime or one of the native libraries
used by your application.
Aborted

Looks like a Mono bug. I tested in both VS2013 and VS2015 and your code works just fine. If you can grab the stack trace I would file a bug with the Mono guys.
I get the same stack trace as you. For the record by Mono version is
Mono JIT compiler version 4.0.2 ((detached/c99aa0c Thu Jun 11 18:53:01 EDT 2015)
If you Google "Assertion at sgen-alloc.c:460, condition `*p == NULL' not met" you will see a few references to this error with Mono.
EDIT: Tested on Mono 4.0.3 and it has the same issue. Whats interesting is if you make genTree non generic like
type genTree = GenNode of string * genTree list
it works perfectly.

Related

How to avoid stack overflow during CPS conversion?

I'm writing a transformation from Scheme subset to CPS language. It is implemented in F#. On big input programs conversion fails by stack overflow.
I'm using some sort of algorithm described in the paper Compiling with Continuations.
I've tried to increase maximum stack size of the working thread up to 50 MB, then it works.
Maybe there some way to modify the algorithm, so that I won't need to tune stack size?
For example, the algorithm transforms
(foo (bar 1) (bar 2))
to
(let ((c1 (cont (r1)
(let ((c2 (cont (r2)
(foo halt r1 r2))))
(bar c2 2)))))
(bar c1 1))
where halt is a final continuation which finishes the program.
Maybe your actual problems has simple solutions to avoid heavy stack consumption, so please don't mind adding details. However, without more knowledge about your particular code, here is a general approach to reduce the stack consumption in a recursive programs, based on trampolines and continuations.
Walker
Here is a typical recursive function that is not trivially tail-recursive, written in Common Lisp because I don't know F#:
(defun walk (form transform join)
(typecase form
(cons (funcall join
(walk (car form) transform join)
(walk (cdr form) transform join)))
(t (funcall transform form))))
The code is however quite simple, hopefully, and walks a tree made of cons cells:
if the form is a cons-cell, recursively walk on the car (resp. cdr) and join the results
Otherwise, apply a transform on the value
For example:
(walk '(a (b c d) 3 2 (a 2 1) 0)
(lambda (u) (and (numberp u) u))
(lambda (a b) (if a (cons a b) (or a b))))
=> (3 2 (2 1) 0)
The code walks the form, and retain only numbers, but preserves (non-empty) nesting.
Calling trace on walk with the above example shows a maximal depth of 8 nested calls.
Continuations and trampoline
Here is an adapted version, called
walk/then, that walks a form as previously, and when a result is
available, calls then on it. Here then is a continuation.
The function also returns a thunk, i.e. a parameterless closure.
What happens is that when we return the closure, the stack is unwound,
and when we apply the thunk it will
start from a fresh stack, but having advanced in the computation
(I usually picture someone walking up an escalator that goes down).
The fact that we return a thunk to reduce the number of stack frames is part of the trampoline.
The then function takes a value, namely
the result that the current walk eventually will return.
The result is thus passed down the stack, and what is
returned at each step is a thunk function.
Nesting continuations allows to capture the complex behaviour of transform/join, by pushing the remaining parts of the computation in nested continuations.
(defun walk/then (form transform join then)
(typecase form
(cons (lambda ()
(walk/then (car form) transform join
(lambda (v)
(walk/then (cdr form) transform join
(lambda (w)
(funcall then (funcall join v w))))))))
(t (funcall then (funcall transform form)))))
For example, (walk/then (car form) transform join (lambda (v) ...)) reads as follows: walk the car of form with
arguments transform and join, and eventually call (lambda (v) ...) on the result; namely, walk down the cdr, and then join both results; eventually, call the input then on the joined result.
What is missing is a way to continually call the returned thunk until exhaustion; here is it
with a loop, but this could easily be a tail-recursive function:
(loop for res =
(walk/then '(a (b c d) 3 2 (a 2 1) 0)
(lambda (u) (and (numberp u) u))
(lambda (a b) (if a (cons a b) (or a b)))
#'identity)
then (typecase res (function (funcall res)) (t res))
while (functionp res)
finally (return res))
The above returns (3 2 (2 1) 0), and the depth of the trace never goes over 2 when tracing walk/then.
See Eli Bendersky's article for another take at this, in Python.
I've converted algorithm to trampoline form. It looks like FSM.
There is a loop, which looks at the current state, makes some manipulations, and goes to another state. Also it uses two stacks for different kind of continuations.
Here is input language (it is a subset of the language I used originally) :
// Input language consists of only variables and function applications
type Expr =
| Var of string
| App of Expr * Expr list
Here is target language:
// CPS form - each function gets a continuation,
// added continuation definitions and continuation applications
type Norm =
| LetCont of name : string * args : string list * body : Norm * inner : Norm
| FuncCall of func : string * cont : string * args : string list
| ContCall of cont : string * args : string list
Here is original algorithm:
// Usual way to make CPS conversion.
let rec transform expr cont =
match expr with
| App(func, args) ->
transformMany (func :: args) (fun vars ->
let func' = List.head vars
let args' = List.tail vars
let c = fresh()
let r = fresh()
LetCont(c, [r], cont r, FuncCall(func', c, args')))
| Var(v) -> cont v
and transformMany exprs cont =
match exprs with
| e :: rest ->
transform e (fun e' ->
transformMany rest (fun rest' ->
cont (e' :: rest')))
| _ -> cont []
let transformTop expr =
transform expr (fun var -> ContCall("halt", [var]))
Here is modified version:
type Action =
| ContinuationVar of Expr * (string -> Action)
| ContinuationExpr of string * (Norm -> Action)
| TransformMany of string list * Expr list * (string list -> Action)
| Result of Norm
| Variable of string
// Make one action at time and return to top loop
let rec transform2 expr =
match expr with
| App(func, args) ->
TransformMany([], func :: args, (fun vars ->
let func' = List.head vars
let args' = List.tail vars
let c = fresh()
let r = fresh()
ContinuationExpr(r, fun expr ->
Result(LetCont(c, [r], expr, FuncCall(func', c, args'))))))
| Var(v) -> Variable(v)
// We have two stacks here:
// contsVar for continuations accepting variables
// contsExpr for continuations accepting expressions
let transformTop2 expr =
let rec loop contsVar contsExpr action =
match action with
| ContinuationVar(expr, cont) ->
loop (cont :: contsVar) contsExpr (transform2 expr)
| ContinuationExpr(var, contExpr) ->
let contVar = List.head contsVar
let contsVar' = List.tail contsVar
loop contsVar' (contExpr :: contsExpr) (contVar var)
| TransformMany(vars, e :: exprs, cont) ->
loop contsVar contsExpr (ContinuationVar(e, fun var ->
TransformMany(var :: vars, exprs, cont)))
| TransformMany(vars, [], cont) ->
loop contsVar contsExpr (cont (List.rev vars))
| Result(r) ->
match contsExpr with
| cont :: rest -> loop contsVar rest (cont r)
| _ -> r
| Variable(v) ->
match contsVar with
| cont :: rest -> loop rest contsExpr (cont v)
| _ -> failwith "must not be empty"
let initial = ContinuationVar(expr, fun var -> Result(ContCall("halt", [var])))
loop [] [] initial

What is wrong with 100000 factorial using ContinuationMonad?

It is powerful technique using recursion because its strong describable feature. Tail recursion provides more powerful computation than normal recursion because it changes recursion into iteration. Continuation-Passing Style (CPS) can change lots of loop codes into tail recursion. Continuation Monad provides recursion syntax but in essence it is tail recursion, which is iteration. It is supposed to reasonable use Continuation Monad for 100000 factorial. Here is the code.
type ContinuationBuilder() =
member b.Bind(x, f) = fun k -> x (fun x -> f x k)
member b.Return x = fun k -> k x
member b.ReturnFrom x = x
(*
type ContinuationBuilder =
class
new : unit -> ContinuationBuilder
member Bind : x:(('d -> 'e) -> 'f) * f:('d -> 'g -> 'e) -> ('g -> 'f)
member Return : x:'b -> (('b -> 'c) -> 'c)
member ReturnFrom : x:'a -> 'a
end
*)
let cont = ContinuationBuilder()
//val cont : ContinuationBuilder
let fac n =
let rec loop n =
cont {
match n with
| n when n = 0I -> return 1I
| _ -> let! x = fun f -> f n
let! y = loop (n - 1I)
return x * y
}
loop n (fun x -> x)
let x2 = fac 100000I
There is wrong message: "Process is terminated due to StackOverflowException."
What is wrong with 100000 factorial using ContinuationMonad?
You need to compile the project in Release mode or check the "Generate tail calls" option in project properties (or use --tailcalls+ if you're running the compiler via command line).
By default, tail call optimization is not enabled in Debug mode. The reason is that, if tail-calls are enabled, you will not see as useful information about stack traces. So, disabling them by default gives you more pleasant debugging experience (even in Debug mode, the compiler optimizes tail-recursive functions that call themselves, which handles most situations).
You probably need to add this memeber to your monad builder:
member this.Delay(mk) = fun c -> mk () c

Why does the following code cause the Erlang HiPE compiler crash?

The code is following:
-module(hipe_crash).
-export([f/6]).
f(A, B, C, D, E, L) ->
lists:foldl(fun (X, P) ->
AVar = case A of
0 -> 1 / D;
N -> N / (C - B)
end,
BVar = case E of
atom1 -> 1.0;
atom2 -> 0.8;
_ -> E
end,
CVar = case X of
atom1 -> 0.1 * AVar;
_ -> 1.0
end,
P * BVar * CVar
end, 1, L).
Compiling this code with erlc no error occur,compiling is ok.
But when I compile it with erlc +native ,the compiler crashed,with information:
<HiPE (v 3.9.2)> EXITED with reason {function_clause,[{hipe_icode_fp,assert_assigned,[[{30,{icode_variable,40,fvar,[]}}]],[{file,[104,105,112,101,95,105,99,111,100,101,95,102,112,46,101,114,108]},{line,772}]},{hipe_icode_fp,bindings_are_assigned,1,[{file,[104,105,112,101,95,105,99,111,100,101,95,102,112,46,101,114,108]},{line,766}]},{hipe_icode_fp,filter_map,3,[{file,[104,105,112,101,95,105,99,111,100,101,95,102,112,46,101,114,108]},{line,753}]},{hipe_icode_fp,transform_block,2,[{file,[104,105,112,101,95,105,99,111,100,101,95,102,112,46,101,114,108]},{line,162}]},{hipe_icode_fp,cfg,1,[{file,[104,105,112,101,95,105,99,111,100,101,95,102,112,46,101,114,108]},{line,48}]},{hipe_main,icode_ssa_type,4,[{file,[104,105,112,101,95,109,97,105,110,46,101,114,108]},{line,273}]},{hipe_main,icode_ssa,4,[{file,[104,105,112,101,95,109,97,105,110,46,101,114,108]},{line,255}]},{hipe_main,compile_icode,5,[{file,[104,105,112,101,95,109,97,105,110,46,101,114,108]},{line,109}]}]} #hipe:829
hipe_crash.erl:none: internal error in native_compile;
crash reason: {{hipe,829,
{function_clause,
[{hipe_icode_fp,assert_assigned,
[[{30,{icode_variable,40,fvar,[]}}]],
[{file,"hipe_icode_fp.erl"},{line,772}]},
{hipe_icode_fp,bindings_are_assigned,1,
[{file,"hipe_icode_fp.erl"},{line,766}]},
{hipe_icode_fp,filter_map,3,
[{file,"hipe_icode_fp.erl"},{line,753}]},
{hipe_icode_fp,transform_block,2,
[{file,"hipe_icode_fp.erl"},{line,162}]},
{hipe_icode_fp,cfg,1,
[{file,"hipe_icode_fp.erl"},{line,48}]},
{hipe_main,icode_ssa_type,4,
[{file,"hipe_main.erl"},{line,273}]},
{hipe_main,icode_ssa,4,
[{file,"hipe_main.erl"},{line,255}]},
{hipe_main,compile_icode,5,
[{file,"hipe_main.erl"},{line,109}]}]}},
[{hipe,finalize_fun_sequential,3,
[{file,"hipe.erl"},{line,829}]},
{hipe,'-finalize_fun_concurrent/3-fun-3-',4,
[{file,"hipe.erl"},{line,795}]}]}
I found this code in :http://erlang.2086793.n4.nabble.com/internal-error-in-native-compile-td2298937.html.But I can't get more information about why this code crashes the compiler.
One of the BEAM float-point operations' optimisations is to group float-point operations
into blocks,if some intermediate operation failed,the BEAM will signal badarith. It relies on headware.
I think it's a bug of the exception handler. You can use the no_inline_fp HiPE compiler option at
the expense of disabling all float-point optimisations.

Does anybody know the reason why QuotationEvaluator is so slow?

It's common knowledge in the F# community that the PowerPack's quotation compiling facility produces very slow code, so slow in fact that it performs even worse than naive interpretation. I've been looking into the reasons for this, but I haven't been able to find a convincing answer so far. There have been claims that this happens either because of inefficient representation of things like pattern matches in quotations or because of an inherent inefficiency with Expression Trees used by the library. I'd like to illustrate why I think neither is true with a simple example:
#r "FSharp.Powerpack.Linq.dll"
open System
open System.Linq.Expressions
open Microsoft.FSharp.Quotations.Patterns
let powerpack = Microsoft.FSharp.Linq.QuotationEvaluator.Compile <# 1 + 1 #>
// explicitly rewrite above quotation with expression trees
let expressionTree =
let (Call(_,addM,_)) = <# 1 + 1 #>
let constExpr (x : 'T) = Expression.Constant(box x, typeof<'T>)
let eval = Expression.Call(addM, constExpr 1, constExpr 1)
let lambda = Expression.Lambda<Func<int>>(eval)
lambda.Compile()
// reflection - based evaluation
let reflection =
let (Call(_,addM,_)) = <# 1 + 1 #>
fun () -> addM.Invoke(null, [| 1 :> obj ; 1 :> obj |]) :?> int
#time
// QuotationEvaluator ~ 2.5 secs
for i in 1 .. 1000000 do
powerpack () |> ignore
// native evaluation ~ 1 msec
for i in 1 .. 1000000 do
(fun () -> 1 + 1) () |> ignore
// reflection evaluation ~ 700 msec
for i in 1 .. 1000000 do
reflection () |> ignore
// naive expression tree ~ 19 msec
for i in 1 .. 1000000 do
expressionTree.Invoke () |> ignore
Something is clearly going wrong here. The question is, what?
EDIT: the same behaviour also occurs with the FSharpx.Linq compiler
Below is the implementation of the compile:
let CompileImpl (e: #Expr, eraseEquality) =
let ty = e.Type
let e = Expr.NewDelegate(GetFuncType([|typeof<unit>; ty |]), [new Var("unit",typeof<unit>)],e)
let linqExpr = Conv (e,eraseEquality)
let linqExpr = (linqExpr :?> LambdaExpression)
let d = linqExpr.Compile()
(fun () ->
try
d.DynamicInvoke [| box () |]
with :? System.Reflection.TargetInvocationException as exn ->
raise exn.InnerException)
Notice the use of DynamicInvoke on the delegate, which is much slower than Invoke and the reason for the result you are getting.

How to rewrite this function using the pipeline operator

These are the function definitions.
func1: 'a -> unit
func2: 'b -> 'a
func3: string -> 'b list
The current function
let f = Seq.iter((fun a -> func1(func2 a)) func3(s)
This is as far as I got
let f = func3(s)
|> ((fun a -> func2 a
|> func1)
|> Seq.iter)
I have the feeling it should be possible to loose the lambda and the parens'.
You can do without pipes, simply
Seq.iter (func1 << func2) << func3
(this is a function with some arguments [same than func3] and same output than Seq.iter).
You can test it
let func1 x = printfn "Number: %d" x
let func2 (a, b) = a + b
let func3 = Seq.map (fun n -> (n, 2 * n))
let f : (seq<_> -> unit) = Seq.iter (func1 << func2) << func3
f [1..5]
with output
Number: 3
Number: 6
Number: 9
Number: 12
Number: 15
val func1 : x:int -> unit
val func2 : a:int * b:int -> int
val func3 : (seq<int> -> seq<int * int>)
val f : (seq<int> -> unit)
val it : unit = ()
:)
You can use function composition operator >>:
func3() |> Seq.iter (func2 >> func1)
I think the question is, why do you want to use the pipeline operator?
I find your original code quite readable. You should not try to use pipeline operator (or function composition) just for the sake of using them. Now, in your code, the input s comes at the end, which is a bit unfortunate (you cannot quite see what is the main input for the code). I would probably rewrite it as (also, s is not really a descriptive name):
s |> func3
|> Seq.iter (fun a -> func1 (func2 a))
You can use function composition too - but I do not use it very often, because it does not (always) help with readability. But using it in the argument of Seq.iter is probably quite reasonable.
On a completely unrelated note, you could just use for loop and write:
for a in func3 s do
func1 (func2 a)
I actually find this more readable than any other version of the code here (if F# gives you a language feature for iterating over sequences that does exactly what you need, why not use it?)

Resources