Scott Nimrod
Scott Nimrod

Reputation: 11595

Does a colon followed by a type at the end of a signature have a different meaning based on context?

Does a colon followed by a type at the end of a signature have a different meaning depending on if it's appended to a function versus a method?

This is related to a question I posted in the past. However, that question was based on the context of a method.

For example, take the following functions:

type StateResponse<'state, 'event> = ('state * 'event) -> 'state

// Account * Event -> Account
let update1 : StateResponse<Account,Event> = function
    | account , LoggedIn  credentials ->   account
    | account , Deposited balance     -> { account with Balance=account.Balance + balance }
    | account , Withdrew  balance     -> { account with Balance=account.Balance - balance }

// Account ** Doesn't compile**
let update2 : Account = function
    | account , LoggedIn  credentials ->   account
    | account , Deposited balance     -> { account with Balance=account.Balance + balance }
    | account , Withdrew  balance     -> { account with Balance=account.Balance - balance }

// Account * Event -> Account
let update3 = function
    | account , LoggedIn  credentials ->   account
    | account , Deposited balance     -> { account with Balance=account.Balance + balance }
    | account , Withdrew  balance     -> { account with Balance=account.Balance - balance }

// state:Account * event:Event -> Account
let update4 (state , event) =
    match   state , event with
    | account , LoggedIn  credentials ->   account
    | account , Deposited balance     -> { account with Balance=account.Balance + balance }
    | account , Withdrew  balance     -> { account with Balance=account.Balance - balance }

// state:StateResponse<Account,Event> * event:Event -> StateResponse<Account,Event> ** Doesn't compile**
let update5 (state , event) : StateResponse<Account,Event> =
    match   state , event with
    | account , LoggedIn  credentials ->   account
    | account , Deposited balance     -> { account with Balance=account.Balance + balance }
    | account , Withdrew  balance     -> { account with Balance=account.Balance - balance }

Function Context - Denotes an alias to a function?

I assume that when a colon followed by a function type is appended to the end of a function name, then that function name becomes the alias to the function type that comes after the colon. This is the same convention that is used to provide aliases for parameters of a function or tuple elements.

NOTE:

I assume that a type that does NOT reflect a function cannot not be appended after the colon of a function signature (i.e. update2).

I also have reason to believe that a function name that aliases a function signature can not have explicit parameters declared before the colon (i.e. update5).

Method Context - Denotes a return type?

I assume that when a colon followed by a type is appended to the end of a method name followed by parameters, then that type that's appended at the end can be an arbitrary type (regardless if it's a function signature). This is contrary to the function context if applied.

Are my assumptions accurate?

Appendix:

namespace Domain

module EventSourcing =

    type StateResponse<'state, 'event> = ('state * 'event) -> 'state

module Account =

    open EventSourcing

    type AccountId = AccountId of string
    type FirstName = FirstName of string
    type LastName =  LastName  of string

    type Credentials =   { UserId:string; Password:string }
    type Account = {
         AccountId: AccountId
         FirstName: FirstName
         LastName:  LastName
         Balance:   decimal }

    and Event = 
        | LoggedIn  of Credentials
        | Deposited of balance: decimal
        | Withdrew  of balance: decimal

    (*Functions*)
    let getAccount credentials = {
        AccountId= AccountId "myAccountId"
        FirstName= FirstName "Scott"
        LastName= LastName   "Nimrod"
        Balance= 20000m }

    let update1 : StateResponse<Account,Event> = function
        | account , LoggedIn  credentials ->   account
        | account , Deposited balance     -> { account with Balance=account.Balance + balance }
        | account , Withdrew  balance     -> { account with Balance=account.Balance - balance }

    let update2 : Account = function
        | account , LoggedIn  credentials ->   account
        | account , Deposited balance     -> { account with Balance=account.Balance + balance }
        | account , Withdrew  balance     -> { account with Balance=account.Balance - balance }

    let update3 = function
        | account , LoggedIn  credentials ->   account
        | account , Deposited balance     -> { account with Balance=account.Balance + balance }
        | account , Withdrew  balance     -> { account with Balance=account.Balance - balance }

    let update4 (state , event) =
        match   state , event with
        | account , LoggedIn  credentials ->   account
        | account , Deposited balance     -> { account with Balance=account.Balance + balance }
        | account , Withdrew  balance     -> { account with Balance=account.Balance - balance }

    let update5 (state , event) : StateResponse<Account,Event> =
        match   state , event with
        | account , LoggedIn  credentials ->   account
        | account , Deposited balance     -> { account with Balance=account.Balance + balance }
        | account , Withdrew  balance     -> { account with Balance=account.Balance - balance }

Upvotes: 0

Views: 277

Answers (1)

sepp2k
sepp2k

Reputation: 370112

A colon denotes the type of what's left of it. So when you write let x : T = ..., you're saying that the type of x is T. Here T should match the type of whatever comes after the =. So if that's a function, T should be a compatible function type, if it's something else, T should be that something else.

For example let x : int = 42 and let x : int -> int = function y -> y+1 are both valid while let x : int -> int = 42 or let x : int = function y -> y+1 would not be because 42 does not have the type int -> int and function y -> y+1 does not have the type int.

Now when you use the shortcut syntax for defining functions, like let f (x: ParameterType) : T, then what's left of the last colon is f(x : ParameterType), not f. So you're now saying what the type of f(x) is (or in other words what the return type of f is), not what the type of f is. And again this type should match the type of whatever comes after the =.

PS: Both let f = function x -> ... (or for that matter let f = fun x -> ...) and let f x = ... are exactly equivalent and define functions. Methods are defined using the member keyword and don't seem to be related to your question.

Upvotes: 6

Related Questions