Общая функция высшего порядка - PullRequest
6 голосов
/ 27 августа 2011

Есть ли причина, по которой я могу использовать универсальную функцию с аргументами другого типа, когда передаю ее как локальное значение, а не как параметр?Например:

let f = id

let g (x,y) = (f x, f y)

g ( 1, '2')

работает нормально, но если я попытаюсь передать функцию в качестве параметра

let g f (x,y) = (f x, f y)

g id ( 1, '2')

, то произойдет сбой, поскольку она принимает версию f и пытается применитьэто дважды.

Я нашел обходной путь, но он заставляет меня дважды написать функцию, которую я передаю:

let g f1 f2 (x,y) = (f1 x, f2 y)

g id id ( 1, '2')

Это второй раз, когда я сталкиваюсь с этой проблемой.

Почему он ведет себя так, он не должен быть одинаковым, если функция является локальным значением или передается в качестве параметра?

Есть ли способ сделать это без дублирования функции?

Хак, может быть, с использованием явных ограничений типа, встроенной магии, цитат?

Ответы [ 5 ]

7 голосов
/ 29 августа 2011

Вот встроенная магия. Давайте возьмем код kvb и определим одну gmap функцию, которая обрабатывает все случаи:

let inline gmap f (x, y) = f $ x, f $ y

type One = One with static member ($) (One, x) = 1  // Example1 ConvertAll
type Id  = Id  with static member ($) (Id , x) = x  // Example2 PassThrough

type SeqSingleton  = SeqSingleton  with static member ($) (SeqSingleton , x) = seq [x]
type ListSingleton = ListSingleton with static member ($) (ListSingleton, x) = [x]
type ListHead      = ListHead      with static member ($) (ListHead, x) = List.head x

// Usage
let pair1 = gmap One ("test", true)
let pair2 = gmap Id  ("test", true)
let pair3 = gmap SeqSingleton  ("test", true)
let pair4 = gmap ListSingleton ("test", true)
let pair5 = gmap ListHead (["test";"test2"], [true;false])

let pair6 = ("test", true) |> gmap ListSingleton |> gmap ListHead

(* results
val pair1 : int * int = (1, 1)
val pair2 : string * bool = ("test", true)
val pair3 : seq<string> * seq<bool> = (["test"], [true])
val pair4 : string list * bool list = (["test"], [true])
val pair5 : string * bool = ("test", true)
val pair6 : string * bool = ("test", true)
*)

ОБНОВЛЕНИЕ

Также возможно использовать еще более общую функцию gmap, определенную здесь , тогда она также будет работать с n-uples (n <9). </p>

6 голосов
/ 28 августа 2011

Как упомянул Рхайров в комментарии, вывод типов невозможен, когда вы можете иметь типы с более высоким рейтингом. В вашем примере у вас есть

let g f (x,y) = (f x, f y)

Вот два возможных типа для g, которые несовместимы (написаны в виде гибридного синтаксиса F # / Haskell):

  1. для 'b,' c, 'd. ((далее 'a.' a -> 'b) ->' c * 'd ->' b * 'b)
  2. для всех 'c,' d. (далее 'a.' a -> 'a) ->' c * 'd ->' c * 'd)

Учитывая первый тип, мы можем вызвать g (fun x -> 1) ("test", true) и получить (1,1). Учитывая второй тип, мы можем вызвать g id ("test", true) и получить ("test", true). Ни один тип не является более общим, чем другой.

Если вы хотите использовать более высокие ранговые типы в F #, вы можете, но вы должны быть явными и использовать промежуточный номинальный тип. Вот один из способов закодировать каждую из указанных выше возможностей:

module Example1 = 
    type ConvertAll<'b> =
        abstract Invoke<'a> : 'a -> 'b

    let g (f:ConvertAll<'b>) (x,y) = (f.Invoke x, f.Invoke y)

    //usage
    let pair = g { new ConvertAll<int> with member __.Invoke(x) = 1 } ("test", true)

module Example2 = 
    type PassThrough =
        abstract Invoke<'a> : 'a -> 'a

    let g (f:PassThrough) (x,y) = (f.Invoke x, f.Invoke y)

    //usage
    let pair = g { new PassThrough with member __.Invoke(x) = x } ("test", true)
2 голосов
/ 27 августа 2011

Я предполагаю, что это ожидаемое поведение:

В первом случае вы вызываете две разные версии f (одну с int, другую с char), во втором случае вы используете одно и то же для обоих, и компилятор выводит это (сверху вниз слева направо - помнишь?) быть int->int

Проблема в том, что общая версия будет переведена компилятором в конкретную. Я не вижу обходного пути для этого - даже с inline, но, возможно, кто-то может сотворить здесь магию;)

0 голосов
/ 27 августа 2011

Причина этого в том, что когда f передается в качестве параметра g, компилятор пытается определить тип f.Основываясь на использовании f в теле функции g, компилятор делает вывод, что функция применяется как к x, так и к y, что означает, что оба параметра x и y должны быть одного типа, и это тип параметра f.В вашем примере кода вы передаете x как целое число, и это заставляет компилятор сделать вывод, что y также будет иметь тип integer.

UPDATE:

Вы можете сделать что-то вроде:

let g (f:obj -> obj) (x : 'a,y : 'b) = (f x :?> 'a ,f y :?> 'b)
g id (1,"1") |> printfn "%A"

Функция f должна убедиться, что она возвращает тот же основной тип, который получает как тип obj

0 голосов
/ 27 августа 2011

Я не думаю, что даже встроенная магия сработает, например, попытка

 let inline g f (x:^a,y:^b) = (f x,f y);;

в FSI завершается ошибкой, так как компилятор делает вывод, что ^b должен совпадать с ^a

Думая об этом, сбой становится немного более очевидным, так как мы берем функцию f и применяем ее к x, она должна иметь сигнатуру типа ^a -> 'c, которая потерпит неудачу, если мы попытаемся применить функцию к элементу с типом ^b.

Если вы подумаете об этой проблеме, я не могу определить функцию, которая принимает int->string и ожидает, что она будет работать для char->string без перегрузок, и вы не можете передать перегрузки в качестве аргументов.

...