Introduction to F#
10 March 2016When I remember the first time I looked at functional(-first) languages like F#, ML, Haskell and others. The typical reaction that I had, and I always see from other people is: This is unreadable, it must be hard to read, it feels complicated and hard.
After spending some time in F# I cannot agree to that at all anymore. Often the syntax itself is easier (for example compared to C#), shorter and in my opinion more readable. The problem is more over that most functional languages shares a syntax that is completely different compared to languages like C, C++, C#, Java, JavaScript and other more mainstream languages. The problem is more that it is just unfamiliar.
In this post I want you to give a quick overview over the most common and important concepts. With this overview it should be easy to understand the most basic part to read and understand functional code.
For better understanding I will provide some C# to F# code examples.
Variables
Definition C#
Variables are an important concept, in C# you can define variables in two ways. First with an explicit type. You can optionally initialize it with a value.
1: 2: 3: |
|
The second way is to use the var
keyword. It uses type-inference to automatically determine the
type of a variable. You are also forced to specify a value in this way.
1: 2: 3: |
|
Definition in F#
In F# we usually only use the second form of definition. But instead of var
we write let
.
1: 2: 3: |
|
We already can see some important differences.
- Semicolons are not needed to end/separate commands.
- We don't have to specify
new
to create an object.
We still can add type-annotations if we want.
1: 2: 3: |
|
(Im)mutability
(Im)mutability in C#
One important difference is that every variable in C# is mutable by default. This means you can change a variable at any time you want
1: 2: 3: |
|
In C# you otherwise only can create immutable class fields with the readonly
keyword.
You cannot create immutable local-variables.
(Im)mutability in F#
In F# on the other hand, everything is immutable by default. You cannot change a variable by default.
If you want to create a mutable variable you have to mark a variable as mutable
1: 2: 3: |
|
You change the content of a variable with <-
instead of =
. Equal is only used to specify or comparison.
There is no operator like +=
in F#. F# doesn't try to make mutability convenient.
1: 2: 3: |
|
Functions / Static Methods
Definition in C#
In C# you define static methods as part of a class.
1: 2: 3: 4: 5: |
|
Definition in F#
In F# you put functions inside modules.
1: 2: |
|
We can see once again some important differences.
- You also use
let
for the definition of a function - Arguments
x
andy
will just be separated by spaces instead of(x, y)
- Type-inference also works for functions.
- There doesn't exists a
return
keyword. The last expression is automatically returned as a value.
You also can add explicit type-annotations.
1: 2: |
|
Calling functions in C#
1:
|
|
Calling functions in F#
1:
|
|
The only difference is that you don't use braces and commas to separate the arguments. You just provide the arguments as-is.
Generics
One important concept that you will see more often in F# (compared to C#) is the usage of generics. Because type-inference also works with functions. F# often automatically generalize a function with generic arguments, instead of specific-types. And overall generics are more important in functional languages.
Generics in C#
1: 2: 3: |
|
Generics in F#
1:
|
|
Generics in F# are just annotated like normal types. The only difference is that all of them start
with an apostrophe. Instead of T
, TIn
, TOut
and so on, as often used in C#, in F# they will
be just letters 'a
, 'b
, 'c
...
As stated previously. You also don't need to annotate generics. If you have written a generic function, F# will automatically infer a generic type for you. So overall you also could just write.
1:
|
|
Data-Types
Other than object-oriented languages the key concept of functional programming is a separation between data and behaviour. In OO programming we use classes. Classes can contain public/private fields to save data, and provide methods for working with this data.
In a functional-language instead we usually define our data-types as separate immutable data. We then provide (pure) functions that gets immutable data as input, and generate some new data as output. Because working with data is so important, a functional language offers more than just classes to define data-types. Besides classes we can use tuples, records and discriminated unions.
Tuples in C#
Tuples are also present in C#. There already exists as a Tuple
class. But working with them
is not so convenient as in F#. Anyway let's quickly look at how you use them.
1: 2: 3: 4: |
|
Tuples are a good way for intermediate types. If you easily want to pass some values as one unit to a function. But more often they are used as a result. So you can easily return multiple different values from a function.
Tuples in F#
Working with Tuples is much easier in F#
1: 2: |
|
You create Tuples just by separating values with a comma. You can extract a Tuple with
a let
definition. This way you can easily create a function that return multiple data
at once. Tuples don't must contain the same types.
1:
|
|
This function for example returns a Tuple with two elements. The input itself, and the input multiplied by Two.
1: 2: |
|
We also can write it in one line
1:
|
|
Other than C#, tuples have its own type signature. Instead of a Generic Class like Tuple<int,int,int>
a tuple is built into the language itself. They will be represented as int * int * int
as a type.
int * float * string * Person
would be a four element tuple (quadruple) that contains an int
a float
a string
and a Person
in that exact order.
Records in F#
Working with tuples is good for intermediate function, for example if you create Pipelines like you see them with LINQ in C#. They are also good for grouping two or three elements, but as soon you have have more elements, they are unhandy to work with. An alternative to this is a Record type. If you know JavaScript you can compare them just to an object. Or a hash in Perl. The only difference is that they are static typed. So you must define a type beforehand.
Records are planned as a feature in C# 7.
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: |
|
This code will produce the output:
1: 2: 3: |
|
Defining a Record needs explicit type annotations. Creating
a Record is pretty easy. You just use the { ... }
syntax. This is nearly identical to JavaScript.
As functional-languages prefer immutability a Record type itself is also immutable by default.
It also has default equality and comparison implementations.
There exists a special copy and update operation. It is {record with newField = newValue }
. You
also can set multiple fields at once. As seen in the example. This creates a new record and doesn't
modify the old record.
You can access member of a record with a dot. Records also can be deeply nested, so you can create hierarchical data-structures.
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: |
|
Discriminated Unions in F#
A Discriminated Union (DU) also doesn't currently exists in C#, but they are also planed as a feature for C# 7. A DU is important as they provide a OR type. When you look at classes, tuples or records all of them are basically AND types. All of those types group some data-together, but you always have all of them at the same time. But what happens if you want to express some kind of Either A or B? The closest thing you can get are enums in C#, but enums cannot contain additional values for each case.
DU are important, because if a language supports both kinds, we also say that it has an Algebraic type-system. Let's assume we have a shopping system, and we want to express that a user can pay with different methods.
- Cash -- No additional data needed
- PayPal -- We need the email address
- CreditCard -- We need the credit card number
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: |
|
The above code will produce an output like
1: 2: 3: |
|
Here inform
is a function with one argument payment
. Still note that we don't need any kind of
type-annotation. We use pattern matching on payment. Just the fact that we use Cash
, PayPal
and CreditCard
the F# Compiler can automatically infer that the argument has to be of type Payment
.
Pattern matching is a kind of switch statement but more powerful, because it not only matches on the different cases, you also can extract the additional values that are carried within each case.
Also note the syntax inform (PayPal "foo@example.com")
We need the braces here not for invocations.
We need them for grouping. This is probably one source of confusion for people coming from C-style
languages. If we wouldn't use the braces and write something like inform PayPal "foo@example.com"
we would try to invoke the inform
function with two arguments. The first argument would be PayPal
and the second argument would be "foo@exmaple.com"
. That would fail because inform
is not a two
argument function. We first need to create a value. That is just done with PayPal "foo@example.com"
and we want the result to pass to our function. That is why we need to add braces around our call.
This is comparable to just simple maths. 3 + 4 * 5
would yield in 23
. If we otherwise write
(3 + 4) * 5
we would get 35
. Braces are just grouping constructs! This becomes more important
if we have something like these.
1:
|
|
This would be a Function call with two arguments. The first argument is the result of Foo x
, the
second argument would be the Result of Bar z
. Coming from a C-style language people often try to read it as
1:
|
|
as a single function invocation with one argument, and they see a trailing (Bar z)
and they don't know
what it stands for. Actually converting such a function call to C# would result in something like this
1:
|
|
The big advantage of Discriminated Unions is that each case can contain objects, tuples, records or other discriminated unions as values. It even can contain itself as an element. In this way you can easily build recursive data-structures.
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: |
|
Running the above code will produce us the following output
1:
|
|
So we can easily create hierarchical data-structures, and with Pattern Matching we can easily write recursive function to traverse them.
List in F#
The example above already introduced lists. Otherwise a list in F# is different to the C# List<T>
type.
In C# you create a mutable List<T>
object and you can directly Add
items to. In F# on the other hand you
create lists just with the syntax [ ... ]
(Like in JavaScript). Otherwise elements get separated by ;
instead of ,
. This is often a source of confusion, because both styles are allowed but they mean something
different.
1:
|
|
This is a List of int
. And it contains four elements.
1:
|
|
This is a List of a Tuple int * int * int * int
and it contains a single element. Remember ,
is
for creating Tuples!
Additional lists in F# are also immutable. They also provide default implementations of equality, comparison
and so on. If you want to add elements to a list you have to create a new list. This can be easily done with
::
.
1: 2: |
|
oneMore
is now
1:
|
|
note that data
is unchanged and is still a four element list. The way how lists are build (immutable
and as linked-list) means adding and removing from the beginning is an efficient operation O(1).
There are various functions inside the List
module to transform lists itself. With [|1;2;3|]
we also can create mutable fixed-size array. There also exists a Array
Module with nearly the same
functions as in the List
module.
Composition and Piping
The last concepts we look at in our introduction is the concept of Composition and Piping. Both
are very important in functional languages, as more complex logic is achieved by composing of functions.
Compose ability is actually pretty easy. Let's assume we have a function that takes an int
as its input
and a string
as its output. In C# we would usually define such a method interface in that way.
1:
|
|
This could be for example part of an interface
definition in C#. In F# we would define such an interface
just as
1:
|
|
This definition means. A function that has an int
as an input, and will return a string
. Note that
we don't specify a function name. Every function itself is actually an interface of its own. Something
like this also exists in C#. Usually expressed as either Action
or Func
. We also could have written.
1:
|
|
In C# Action
and Func
types are usually used in Methods if we want to expect a function as an argument.
In C# you need Action
to describe function with a void
return value.
1: 2: |
|
And Action means
1: 2: |
|
In F# we just have a sepcial type named unit
to express Nothing. So we can write
1: 2: 3: |
|
The Last line can be read as. A function with two arguments int
and string
and it will
return unit
(Nothing).
Now let's assume we have two functions with the following signatures
1: 2: |
|
So we have a function that has a string
as it's input, and a int list
(List of int) as its
output. Our second functions takes a int list
as its input, and will produce just a int
as its output. Looking at those signatures we now can compose them. Even if we don't now what
those functions do. We just know that the output of the first function can be directly given
as the input of the second function.
We can directly create a function with a string
input returning an int
.
This kind of idea is what we name composing. In F# we have a special operator for this
kind of composition. The >>
operator.
But let's work step by step to it. Let's assume we have a parseInts
function
that takes a string, splits a string on ',' and parses every number as an int
and returns int list
. The signature would be string -> int list
.
We then have another function sumList
that just takes a int list
and sums all
numbers together returning an int
. We could use those two functions like this:
1: 2: |
|
We also could create a new function that combines these two steps into a new function
1: 2: 3: 4: |
|
We now have a function strToSum
that goes directly from string -> int
But these kind of operation is actually pretty generic. As this kind of composing works for any kind of function with any kind of type. In general we can say. When we have two functions.
1: 2: |
|
we can compose those two into a new function
1:
|
|
So let's write a compose
function that does that kind of stuff for us.
1:
|
|
So let's look at the implementation. We have a function compose
with three arguments. f
is expected to be a function. The same is true for g
. x
is just some kind of value. What we
first do is
1:
|
|
meaning we will call our f
function with the x
value. The Result of that is passed to the g
function. The result of the g
function is then returned as a value. We also could have written it
like this.
1: 2: 3: 4: |
|
The F# compiler automatically infers that f
and g
are functions.
Just by using it like f x
or g y
the compiler knows that f
and g
must be
functions with a single argument.
But what kind of types do we have here? The answer is, they are generic. When we look at the type signature that the compiler created for us, it looks some kind of scary first. We have.
1:
|
|
Let's go over it step-by-step
Argument |
Signature |
Meaning |
---|---|---|
f |
('a -> 'b) |
A function that goes from 'a to 'b |
g |
('b -> 'c) |
A function that goes from 'b to 'c |
x |
'a |
A value of type 'a |
'c |
It will return 'c |
Just by looking at the types we can examine what the function does. We have 'a
as a value and two
functions. And we need to return a 'c
. So how do we get a 'c
?
At first the only thing that function can do is pass the x
value (a 'a
) into the f
function. That will
return a 'b
value. After we have a 'b
value it only can pass that value into the g
function.
Finally this returns a 'c
that the compose
function then returns.
We now could use compose
like this.
1:
|
|
Here we now call compose
with three arguments. We provide the parseInts
function itself as a value.
We then provide the sumList
function as a the second argument. And our third argument is our "1,2,3,4,5"
string.
The last thing we can do now. F# supports omitting arguments from a function call. If you omit a value, you
get a function back with the remaining arguments. Currently our compose
function is a three arguments
function. So what happens if we just provide the first two functions as arguments? We get a function back
that is still waiting for the last third argument.
1: 2: |
|
This kind of composing is so common that we have a special operator >>
for this. So all we really need to
do is put >> between two functions, and we get a new function back! So what we are doing is
1:
|
|
and we just get a string -> int
back.
1: 2: |
|
So we can easily create new functions out of smaller functions. This is the essence of functional programming.
We have immutable data-types that gets transformed from one type to another. And we compose functions
together to create new functions. Note that we also could create such a compose
function in C#.
But because of a lack of some features in C#, such a function is less practical as it seems.
1: 2: 3: |
|
But it is can help to understand what composition means.
The remaining part is now Piping that is used more often in F#. Piping can be compared with Linux/Bash Pipes. For example in Bash you can do stuff like this.
1:
|
|
It basically means that it prints out the file file.txt line by line. The output is passed into grep "foo"
that only shows the line that contains a foo
. And that output is finally sorted by sort
. F# has the operator
|>
to provide such a functionality. |>
just means, pass the value on the left to the function on the right.
So instead of
1:
|
|
We also could write
1:
|
|
Having this kind of Piping means we also could have written strToSum
like this
1:
|
|
instead of
1:
|
|
Both styles means the same. In the |>
we just provide the input argument explcitily. x |> parseInts |> sumList
also can be read as. Take argument x
and pass it to the parseInts
function. The result of parseInts
is then
passed into the sumList
function. This kind of style is often what you see with List
manipulations.
1: 2: 3: 4: 5: 6: 7: 8: 9: |
|
This style of composing is also what you see with LINQ in C# or Java 8 Stream interface. The above code could also be implemented in this way with C# LINQ feature.
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: |
|
Final Note
I covered quite a lot of topics. But I hope that now functional languages looks less scary to you. By understanding all of the topics you basically already made a big step in understanding F# in general.
Full name: Main.DateTime
type DateTime =
struct
new : ticks:int64 -> DateTime + 10 overloads
member Add : value:TimeSpan -> DateTime
member AddDays : value:float -> DateTime
member AddHours : value:float -> DateTime
member AddMilliseconds : value:float -> DateTime
member AddMinutes : value:float -> DateTime
member AddMonths : months:int -> DateTime
member AddSeconds : value:float -> DateTime
member AddTicks : value:int64 -> DateTime
member AddYears : value:int -> DateTime
...
end
Full name: System.DateTime
--------------------
System.DateTime()
(+0 other overloads)
System.DateTime(ticks: int64) : unit
(+0 other overloads)
System.DateTime(ticks: int64, kind: System.DateTimeKind) : unit
(+0 other overloads)
System.DateTime(year: int, month: int, day: int) : unit
(+0 other overloads)
System.DateTime(year: int, month: int, day: int, calendar: System.Globalization.Calendar) : unit
(+0 other overloads)
System.DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int) : unit
(+0 other overloads)
System.DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, kind: System.DateTimeKind) : unit
(+0 other overloads)
System.DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, calendar: System.Globalization.Calendar) : unit
(+0 other overloads)
System.DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, millisecond: int) : unit
(+0 other overloads)
System.DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, millisecond: int, kind: System.DateTimeKind) : unit
(+0 other overloads)
Full name: Main.StringBuilder
type StringBuilder =
new : unit -> StringBuilder + 5 overloads
member Append : value:string -> StringBuilder + 18 overloads
member AppendFormat : format:string * arg0:obj -> StringBuilder + 4 overloads
member AppendLine : unit -> StringBuilder + 1 overload
member Capacity : int with get, set
member Chars : int -> char with get, set
member Clear : unit -> StringBuilder
member CopyTo : sourceIndex:int * destination:char[] * destinationIndex:int * count:int -> unit
member EnsureCapacity : capacity:int -> int
member Equals : sb:StringBuilder -> bool
...
Full name: System.Text.StringBuilder
--------------------
System.Text.StringBuilder() : unit
System.Text.StringBuilder(capacity: int) : unit
System.Text.StringBuilder(value: string) : unit
System.Text.StringBuilder(value: string, capacity: int) : unit
System.Text.StringBuilder(capacity: int, maxCapacity: int) : unit
System.Text.StringBuilder(value: string, startIndex: int, length: int, capacity: int) : unit
type Person =
new : name:string -> Person
member Name : string
member Name : string with set
Full name: Main.Person
--------------------
new : name:string -> Person
Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.set
Full name: Main.num
Full name: Main.name
Full name: Main.person
val int : value:'T -> int (requires member op_Explicit)
Full name: Microsoft.FSharp.Core.Operators.int
--------------------
type int = int32
Full name: Microsoft.FSharp.Core.int
--------------------
type int<'Measure> = int
Full name: Microsoft.FSharp.Core.int<_>
val string : value:'T -> string
Full name: Microsoft.FSharp.Core.Operators.string
--------------------
type string = System.String
Full name: Microsoft.FSharp.Core.string
Full name: Main.num
Full name: Main.name
Full name: Main.person
Full name: introductioninfsharp.MyOperations.add
from introductioninfsharp
Full name: introductioninfsharp.result
Full name: introductioninfsharp.someFunction
Full name: Main.position
Full name: Main.x
Full name: Main.y
Full name: Main.z
Full name: Main.someFunction
Full name: Main.result
{Id: int;
FirstName: string;
LastName: string;
Born: DateTime;}
Full name: Main.Human
Full name: Main.me
Full name: Main.age
Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printfn
Full name: Main.newMe
{Strength: int;
Dexterity: int;
Intelligence: int;
Vitality: int;}
Full name: Main.Attributes
{Name: string;
Attribute: Attributes;}
Full name: Main.CaharacterSheet
Full name: Main.warrior
| Cash
| PayPal of string
| CreditCard of string
Full name: Main.Payment
Full name: Main.inform
| NewLine
| Literal of string
| Bold of string
| InlineCode of string
| Block of Markdown list
Full name: Main.Markdown
union case Markdown.Literal: string -> Markdown
--------------------
type LiteralAttribute =
inherit Attribute
new : unit -> LiteralAttribute
Full name: Microsoft.FSharp.Core.LiteralAttribute
--------------------
new : unit -> LiteralAttribute
Full name: Microsoft.FSharp.Collections.list<_>
Full name: Main.document
Full name: Main.produceHtml
(+0 other overloads)
System.Text.StringBuilder.Append(value: obj) : System.Text.StringBuilder
(+0 other overloads)
System.Text.StringBuilder.Append(value: uint64) : System.Text.StringBuilder
(+0 other overloads)
System.Text.StringBuilder.Append(value: uint32) : System.Text.StringBuilder
(+0 other overloads)
System.Text.StringBuilder.Append(value: uint16) : System.Text.StringBuilder
(+0 other overloads)
System.Text.StringBuilder.Append(value: decimal) : System.Text.StringBuilder
(+0 other overloads)
System.Text.StringBuilder.Append(value: float) : System.Text.StringBuilder
(+0 other overloads)
System.Text.StringBuilder.Append(value: float32) : System.Text.StringBuilder
(+0 other overloads)
System.Text.StringBuilder.Append(value: int64) : System.Text.StringBuilder
(+0 other overloads)
System.Text.StringBuilder.Append(value: int) : System.Text.StringBuilder
(+0 other overloads)
Full name: Microsoft.FSharp.Core.Operators.ignore
System.Text.StringBuilder.AppendFormat(format: string, arg0: obj) : System.Text.StringBuilder
System.Text.StringBuilder.AppendFormat(provider: System.IFormatProvider, format: string, [<System.ParamArray>] args: obj []) : System.Text.StringBuilder
System.Text.StringBuilder.AppendFormat(format: string, arg0: obj, arg1: obj) : System.Text.StringBuilder
System.Text.StringBuilder.AppendFormat(format: string, arg0: obj, arg1: obj, arg2: obj) : System.Text.StringBuilder
Full name: Main.html
System.Text.StringBuilder.ToString(startIndex: int, length: int) : string
Full name: Microsoft.FSharp.Core.unit
module String
from Microsoft.FSharp.Core
--------------------
type String = System.String
Full name: Main.String
type String =
new : value:char -> string + 7 overloads
member Chars : int -> char
member Clone : unit -> obj
member CompareTo : value:obj -> int + 1 overload
member Contains : value:string -> bool
member CopyTo : sourceIndex:int * destination:char[] * destinationIndex:int * count:int -> unit
member EndsWith : value:string -> bool + 2 overloads
member Equals : obj:obj -> bool + 2 overloads
member GetEnumerator : unit -> CharEnumerator
member GetHashCode : unit -> int
...
Full name: System.String
--------------------
System.String(value: nativeptr<char>) : unit
System.String(value: nativeptr<sbyte>) : unit
System.String(value: char []) : unit
System.String(c: char, count: int) : unit
System.String(value: nativeptr<char>, startIndex: int, length: int) : unit
System.String(value: nativeptr<sbyte>, startIndex: int, length: int) : unit
System.String(value: char [], startIndex: int, length: int) : unit
System.String(value: nativeptr<sbyte>, startIndex: int, length: int, enc: System.Text.Encoding) : unit
Full name: Main.parseInt
struct
member CompareTo : value:obj -> int + 1 overload
member Equals : obj:obj -> bool + 1 overload
member GetHashCode : unit -> int
member GetTypeCode : unit -> TypeCode
member ToString : unit -> string + 3 overloads
static val MaxValue : int
static val MinValue : int
static member Parse : s:string -> int + 3 overloads
static member TryParse : s:string * result:int -> bool + 1 overload
end
Full name: System.Int32
System.Int32.Parse(s: string, provider: System.IFormatProvider) : int
System.Int32.Parse(s: string, style: System.Globalization.NumberStyles) : int
System.Int32.Parse(s: string, style: System.Globalization.NumberStyles, provider: System.IFormatProvider) : int
Full name: Main.parseInts
System.String.Split(separator: string [], options: System.StringSplitOptions) : string []
System.String.Split(separator: char [], options: System.StringSplitOptions) : string []
System.String.Split(separator: char [], count: int) : string []
System.String.Split(separator: string [], count: int, options: System.StringSplitOptions) : string []
System.String.Split(separator: char [], count: int, options: System.StringSplitOptions) : string []
from Microsoft.FSharp.Collections
Full name: Microsoft.FSharp.Collections.Array.map
module List
from Microsoft.FSharp.Collections
--------------------
type List<'T> =
| ( [] )
| ( :: ) of Head: 'T * Tail: 'T list
interface IEnumerable
interface IEnumerable<'T>
member GetSlice : startIndex:int option * endIndex:int option -> 'T list
member Head : 'T
member IsEmpty : bool
member Item : index:int -> 'T with get
member Length : int
member Tail : 'T list
static member Cons : head:'T * tail:'T list -> 'T list
static member Empty : 'T list
Full name: Microsoft.FSharp.Collections.List<_>
Full name: Microsoft.FSharp.Collections.List.ofArray
Full name: Main.sumList
Full name: Microsoft.FSharp.Collections.List.sum
Full name: Main.nums
Full name: Main.sum
Full name: Main.strToSum
Full name: Main.compose
Full name: Main.strToSum
Full name: Main.result
Full name: Main.strToStum
Full name: Main.numbers
Full name: Microsoft.FSharp.Collections.List.map
Full name: Microsoft.FSharp.Collections.List.filter
Full name: Microsoft.FSharp.Collections.List.take
Full name: Main.result