功能程序通过类型分析“自己编写”的例子

时间:2009-11-12 06:34:26

标签: f# functional-programming

(背景:我一直在考虑做关于F#和函数式编程的演示。从经验来看,我认为模式匹配和类型推断的'哇'因素不一定足以抵消'帮助' !'因素'我的大括号和分号在哪里,我的代码将脱离边缘!“。这让我想到真正令人惊叹的因素 - 对我来说 - 这是1)如果它编译,通常意味着它的工作原理和2)您经常可以从类型推断实现

Channel9与Brian Beckman和Erik Meijer有一个video,他们在那里提到了实现有时只是“掉出”函数的类型签名。我过去也经历过这种情况,但是无法提出一个很好的例子,可以很容易地呈现给没有任何功能经验的人。

有没有人有一个很好的例子可以分享? (它不必在F#中)

更新

如果有任何帮助,我认为我们需要以不同的方式思考:实际的难题如下:

我有一些给定类型的数据,我想将它转换为另一种类型,我有一组函数给定的符号。

这是你必须插在一起的'lego'。

9 个答案:

答案 0 :(得分:6)

从最简单的功能开始:identity :: 'a -> 'a。你能想到多少个实现?如果你给我一个a,我只能做一件事就是给你一个a。我给你回复你给我的a,所以:

let id x = x

配对也一样。 fst :: ('a,'b) -> 'a。你有多少种方法可以实现? snd :: ('a, 'b) -> 'b怎么样?每种方法只能存在一种实现方式。

类似地,取一个列表的头部和尾部落在fstsnd之外。如果head :: 'a list -> atail :: 'a list -> 'a list以及'a list只是一对('a, 'a list)(或空列表),那么显然要满足这些类型,您将返回第一个和第二个列表的一部分。

另一个与高阶函数有关的示例:compose :: ('a -> 'b) -> ('c -> 'a) -> 'c -> 'b。只有一个实现,它完全属于类型。您将获得c和两个函数。你能用c做什么?那么,你可以申请(c -> a)。你可以用a做什么?你唯一能做的就是申请(a -> b),瞧,你已经满意了。

let compose f g x = f (g x)

答案 1 :(得分:5)

这是第三个例子......

假设我想写一个函数

p : 'a -> ('a -> 'b) -> 'b

也就是说,我将一个类型为a的值作为参数,并将一个带有'a并返回'b的函数作为参数。我的结果应该是'b。好吧,再次,modulo无限循环和异常以及默认初始化,只有一个实现:

let p x f = f x
在您意识到它是管道运算符(|>)之前,

'p'可能看起来不太有用。

嗯,我觉得到目前为止这些例子并没有给人留下深刻的印象。

答案 2 :(得分:5)

地图

还要考虑几个......

om2 : ('a -> 'b) -> 'a option -> 'b option

唯一有趣的实现是Option.map:

let om f xo =
    match xo with
    | None -> None
    | Some x -> Some(f x)

现在我可以写了

let om (f:'a -> 'b) (xo:'a option) : 'b option =
    None

并忽略两个参数并始终返回None。但这并不有趣。有人向我们传递了所有这些有用的论据,当然我们打算用它们做点什么,对吧?因此,上面的第一个实现是唯一的一个(再次模拟其他答案中提到的小事,由于循环,效果等)。

类似地

lm : ('a -> 'b) -> 'a list -> 'b list

你很难写出除List.map以外的任何东西。你总是可以返回空列表,但是它会忽略这两个参数。你可以写

let lm f xs =
    match xs with
    | [] -> []
    | h::t -> [f h]

但是有人传递这个完整有用的列表似乎很奇怪,我们忽略了除第一个元素之外的所有内容。如果您认为“意味着”'使用'所有数据,List.map就是明显的实现。 (虽然没有什么可以阻止你映射两次或三次并返回一个2x或3x与原始列表一样长的新列表。再一次,有一种感觉/美学,其中有一个'最简单明显'的实现与类型签名匹配并使用传入的数据,这个“明显”的实现是有用的。当你看到签名

('a -> 'b) -> 'a list -> 'b list

你只是想'List.map',甚至没有人会考虑所有其他理论上可能的实现,因为其他的在软件工程环境中几乎都是无意义的。)

我不知道这是否有说服力。

答案 3 :(得分:4)

身份

假设我想写一个函数f

f : 'a -> 'a

在F#中,我认为唯一有趣的实现是:

let f x = x

上面的身份函数是大多数语言的自然实现。

正如在我的另一个答案中,你也可以循环,抛出或默认初始化。但那些不那么有趣。那些“后门”在所有这些“类型衍生”计算的工作中都会有所作为,所以最好忽略它们。

这个中的关键是,对于所有类型'a',你对类型一无所知,所以“获得”该类型的真实对象的唯一方法是让某人已经给你一个。因此,身份功能是该签名的唯一合理实现。

答案 4 :(得分:3)

好的,这不是一个非常好的例子,但是'好',也许会有助于获得其他一些想法......

假设

f : unit -> 'a

也就是说,我想编写一个不传递参数的函数,它返回任何类型的值。这个功能有什么作用?

请注意,我不能只返回'new obj()',因为类型签名是通用的,例如我可以用f< int>来调用它然后返回一个int,例如。

放弃?这是最常见的可能性:

let rec f() = f()

这是一个无限循环。它永远不会返回,因此返回类型无关紧要。你可以做很多种语言。

在像Haskell这样的语言中,“例外”将是由类型系统控制的“效果”,但在F#中:

let f() = failwith "kaboom!"

是另一个例子。如果我们再次抛出异常,则返回类型无关紧要。

最后,许多运行时的实现细节允许任何类型的“默认初始化”,例如,在F#

let f() = Unchecked.defaultof<'a>

也可以。我想也许这些是F#中唯一可能的三种实现方式。

答案 5 :(得分:2)

(a -> b) -> [a] -> [b]

实际上这种类型就是实现。

答案 6 :(得分:2)

愚蠢的首发示例,继续我的更新:

假设我有一个List<string>,我怎样才能使用当前函数进入Array<float>(其中一个函数被混淆了!)

fn1: string -> float
fn2: List<'a> -> Array<'a>
fn3: Array<'a> -> List<'a>
fn4: ('a -> 'b) -> Array<'a> -> Array<'b>

好吧,让我们看看:

//Start at the beginning
let input:List<string> = myData
// the only thing that I can apply to this is a 
//     function that starts with List<something>, so...

input |> fn2 // List<'a> -> Array<'b>, so now I have Array<string>
// At this point, it looks like I have no choice but fn3, but that takes 
//   me back where I came from. However, there is an Array<'a> in 
//   the middle of fn4.

input |> fn2 |> fn4 ???
//oops, I need something else here, a function that goes ('a -> 'b).
//   But all my functions go ('a -> 'b)! However, in this case my 'a is a string,
//   so that limits my choice to fn1:

input |> fn2 |> fn4 fn1 // so here I have Array<float> yoohoo!

//recapitulate with the real function names
let convert input = 
    input |> Array.ofList    //List<string> -> Array<string>
          |> Array.map float //Array<string> -> (string -> float) -> Array<float>

答案 7 :(得分:2)

Best way to condense a list of option type down to only elements that are not none?

简而言之,目标是在这里解决f

> [Some 4; None; Some 2; None] |> f;; 
val it : int list = [4; 2] 

(只展示Some中的list值的函数。我对解决方案的评论(List.choose id)是

  

让这些类型指导你。您需要一个在其签名中包含选项的函数,但只返回一个列表(不是选项列表)。看看API,并且只有一个这样的函数,choose,现在你已经95%了。

答案 8 :(得分:0)

另一个答案来自Wes Dyer,他在解释Select for IObservable时提到了这个精确的现象。签名是:

IObservable<U> Select<T, U>(this IObservable<T> source, Func<T, U> selector)

我会让你解决这个问题...(我对自己非常满意,我设法写了自己的选择和在哪里)