1

I'm building a interpreter in F#. I'm trying to be clever and generalize the interpretation of primitive operators as function calls (in this case, as reductions).

This is the idea:

let reduce fx values =
    Array.reduce values fx

let primitivesEnv = 
    let r = Dictionary<string,'T -> 'T -> 'T>()
    r.["int_+"] <- reduce (fun(l:int, r:int) -> l + r)
    r.["int_-"] <- reduce (fun(l:int, r:int) -> l - r)
    r

So I could later do this:

env.["int_+"]([| 1, 2 |])

Of course, the type-checker reject this with

Warning FS0064: This construct causes code to be less generic than indicated by the type annotations. The type variable 'T has been constrained to be type ''a -> 'a -> 'a'. Error FS0001: Type mismatch. Expecting a ('a -> 'a -> 'a) -> ('a -> 'a -> 'a) -> 'a -> 'a -> 'a but given a ('a -> 'a -> 'a) -> 'a The resulting type would be infinite when unifying ''a' and '('a -> 'a -> 'a) -> 'a -> 'a -> 'a'

P.D: I know how do this as a simple interpreter, but trying to build a solution that let me build dozens of methods in a generic way, without make a MATCH for each one.

Cœur
  • 37,241
  • 25
  • 195
  • 267
mamcx
  • 15,916
  • 26
  • 101
  • 189
  • by simple interpreter you mean big step interpreter ? – nicolas Dec 30 '15 at 16:38
  • I am not sure what you mean by "solution that let me build dozens of methods in a generic way" There are quite a few way s to build interpreters, so you need to be more precise here – nicolas Dec 30 '15 at 16:52
  • If this where python, I could store the functions in a dict, and dispatch them using it. Now the problem for me in F# is how dispatch based in the value types, hopefully, without coding all the cases (so, I can get the built-in functionally of F# ready for the interpreter). – mamcx Dec 30 '15 at 17:11
  • 1
    we should go back more here : why would you store them in a dict, what are you really trying to do ? dispatching on the value *type* sounds like you are using some untyped object. That would be bad, better dispatch on value's value, using case analysis. – nicolas Dec 30 '15 at 17:38
  • 1
    if you want to store in a dict (but why?) you would need to type that dict, and we dont have heterogeneous type directory in fsharp, or you would need to use existential types.. it begs the question of what you are really trying to do – nicolas Dec 30 '15 at 17:41

1 Answers1

1

First of all, there are some issues with your code. You have added a type annotation to your dictionary of 'T -> 'T -> 'T but it seems to me that it should return a function of type 'T[] -> 'T since you are trying to give it an array and evaluate a reduction.

Also, [| 1, 2 |] is an array of one tuple, you need an array of multiple values, such as this one : [| 1; 2 |]

I fixed your code like this:

let reduce values =
    Array.reduce values

let primitivesEnv = 
    let r = System.Collections.Generic.Dictionary<string,'T[] ->'T>()
    r.["int_+"] <- reduce ((+) : int -> int -> int)
    r.["int_-"] <- reduce ((-) : int -> int -> int)
    r

let result = primitivesEnv.["int_+"] [| 1; 2 |]

Unfortunately, this isn't the end of the problems.

We might have said that the dictionary is of type 'T[] ->'T but it isn't, the only valid type for the dictionary is int[] -> int, the first int -> int -> int type annotation creates that constraint. If we leave out that type annotation, 'T is constrained to int when we use it with an int[].

The type parameter 'T must always be resolved to a fixed type in some way, it isn't a wild card that lets you use anything.

This means that the dictionary approach is fine until you want to add float (or some other type) in addition to just int. Your only option, when using a dictionary, is to either choose one type or throw away some of your type-safety and resolve the specific type at runtime.

The simplest change to do that would be to create some union cases to describe the different reduction types:

type Reduction =
    |IntReduction of (int[] -> int)
    |FloatReduction of (float[] -> float)

You then create a Dictionary<string, Reduction> instead of a Dictionary<string,'T[] ->'T>.


When it comes to the more general problem of creating an interpreter in F#, I would start by creating a structured set of discriminated unions to describe the expressions and structure of the mini-language you wish to interpret, this is called an Abstract Syntax Tree (AST).

You can then define a run function that walks the tree and performs all the computations described by the AST.

I would also use a parser combinator library (I'd recommend FParsec) to parse whatever structured text into an abstract syntax tree of the union cases you defined in the step above.

Phillip Trelford has an example online of how to do this using FParsec for a simple C# AST: http://www.fssnip.net/lf This is probably far more powerful than what you need but hopefully it'll give you a starting point.

TheInnerLight
  • 12,034
  • 1
  • 29
  • 52
  • I use a dict for familiarity (that is what is used in python). If exist a better way, I'm open to suggestion. I'm looking for a way to dispatch methods, if that make things a bit more concrete – mamcx Dec 30 '15 at 20:00
  • @mamcx You can't write your code in such a way that you can accept different type args depending upon the value of a string which I think is what you're trying to do. You can obviously do it by using union cases or `obj` and checking the types are valid at runtime but that obviously lacks robustness compared to a fully type safe solution. What are you going to use this for if/when complete? – TheInnerLight Dec 30 '15 at 21:18
  • This is for a interpreter – mamcx Dec 30 '15 at 21:43
  • @mamcx I've updated with some general tips for creating an interpreter in F#. – TheInnerLight Dec 30 '15 at 22:44
  • Thanks for the input. However, I'm already doing something like that. I'm triying to fill the interpreter with the "prelude", hopefully, reusing what is in .NET already. For example see how in https://curiosity-driven.org/continuations?hn (See: *Predefined functions*) is easy to lift functionally from the host to the interpreter. This is trivial for me in python, but not see how do the same in F#. – mamcx Dec 31 '15 at 02:12
  • @mamcx I think you want to write an interpreter but you want to use strings, or untyped, because you have not formalized your AST. Although that would be a bad idea if you want to do so, just put some dynamic casting everywhere.but as you saw, you might be better off using untyped/dynamic stuff if you absolutely insist on giving up on type system. another option is to show your syntax and tell what you intend to really do... – nicolas Dec 31 '15 at 08:32
  • This is what I have now:https://gist.github.com/mamcx/fe4cf6c5a4452a341983. Is rough because I'm experimenting with it. I'm triying to solve how lift F# functions to the interpreter. The language will be array based, so I tough that I can do a lot using folds, maps, etc. – mamcx Jan 01 '16 at 17:08
  • I have wonder if a Flexible Type (https://msdn.microsoft.com/en-us/library/dd233198.aspx) could work here? – mamcx Jan 03 '16 at 20:26