简体   繁体   English

在Typed / Racket中键入函数类型的谓词

[英]Type Predicates for Function Types in Typed/Racket

I'm at the early stages of designing a framework and am fooling around with typed/racket . 我正处于设计框架的早期阶段,并且正在使用typed/racket愚弄。 Suppose I have the following types: 假设我有以下类型:

   (define-type Calculate-with-one-number (-> Number Number))
   (define-type Calculate-with-two-numbers (-> Number Number Number))

And I want to have a function that dispatches on type: 我想要一个派生类型的函数:

(: dispatcher (-> (U Calculate-with-one-number Calculate-with-two-numbers) Number))

(define (dispatcher f args)
   (cond [(Calculate-with-one-number? f)
          (do-something args)]
         [(Calculate-with-two-numbers? f)
          (do-something-else args)]
         [else 42]))

How do I create the type-predicates Calculate-with-one-number? 如何创建类型谓词Calculate-with-one-number? and Calculate-with-two-numbers? Calculate-with-two-numbers? in Typed/Racket ? Typed/Racket For non-function predicates I can use define-predicate . 对于非函数谓词,我可以使用define-predicate But it's not clear how to implement predicates for function types. 但目前尚不清楚如何实现函数类型的谓词。

Since I am self answering, I'm taking the liberty to clarify the gist of my question in light of the discussion of arity as a solution. 由于我是自我回答,我冒昧地根据对作为解决方案的讨论来澄清我的问题的要点。 The difference in arity was due to my not considering its implications when specifying the question. arity的差异是由于我在指定问题时没有考虑其含义。

The Problem 问题

In #lang typed/racket as in many Lisps, functions, or more properly: procedures , are first class dataypes. #lang typed/racket就像许多Lisps,函数或更正确的: 过程一样 ,是一流的数据表。

By default, #lang racket types procedures by arity and any additional specificity in argument types must be done by contract. 默认情况下, #lang racket由类型的程序参数数量和参数类型的任何其他特异性必须通过合同来完成。 In #lang typed/racket procedures are typed both by arity and by the types of their arguments and return values due to the language's "baked-in contracts". #lang typed/racket程序由arity及其参数类型和返回值键入,因为语言是“烘焙合同”。

Math as an example 以数学为例

The Typed Racket Guide provides an example using define-type to define a procedure type: Typed Racket Guide提供了一个使用define-type定义过程类型的示例

 (define-type NN (-> Number Number))

This allows specifying a procedure more succinctly: 这允许更简洁地指定过程:

 ;; Takes two numbers, returns a number
 (define-type 2NN (-> Number Number Number))

 (: trigFunction1 2NN)
 (define (trigFunction1 x s)
   (* s (cos x)))

 (: quadraticFunction1 2NN)
 (define (quadraticFunction1 x b)
   (let ((x1 x))
     (+ b (* x1 x1))))

The Goal 目标

In a domain like mathematics, it would be nice to work with more abstract procedure types because knowing that a function is cyclical between upper and lower bounds (like cos ) versus having only one bound (eg our quadratic function) versus asymptotic (eg a hyperbolic function) provides for clearer reasoning about the problem domain. 在像数学这样的领域中,使用更抽象的过程类型会更好,因为知道函数在上限和下限之间是周期性的(比如cos )而只有一个约束(例如我们的二次函数)与渐近(例如双曲线)功能)提供有关问题域的更清晰的推理。 It would be nice to have access to abstractions something like: 能够访问抽象类似于:

 (define-type Cyclic2NN (-> Number Number Number))
 (define-type SingleBound2NN (-> Number Number Number))

 (: trigFunction1 Cyclic2NN)
 (define (trigFunction1 x s)
   (* s (cos x)))

 (: quadraticFunction1 SingleBound2NN)
 (define (quadraticFunction1 x b)
   (let ((x1 x))
     (+ b (* x1 x1))))

 (: playTone (-> Cyclic2NN))
 (define (playTone waveform)
   ...)

 (: rabbitsOnFarmGraph (-> SingleBound2NN)
 (define (rabbitsOnFarmGraph populationSize)
   ...)

Alas, define-type does not deliver this level of granularity when it comes to procedures. 唉,当涉及到程序时, define-type不提供这种级别的粒度。 Even moreover, the brief false hope that we might easily wring such type differentiation for procedures manually using define-predicate is dashed by: 此外,我们可能很容易使用define-predicate手动对程序进行类型区分的简短错误希望破灭:

Evaluates to a predicate for the type t, with the type (Any -> Boolean : t). 使用类型(Any - > Boolean:t)计算类型t的谓词。 t may not contain function types, or types that may refer to mutable data such as (Vectorof Integer). t可能不包含函数类型或可能引用可变数据的类型,例如(Vectorof Integer)。

Fundamentally, types have uses beyond static checking and contracts. 从根本上说,类型具有静态检查和合同之外的用途。 As first class members of the language, we want to be able to dispatch our finer grained procedure types. 作为该语言的一等成员,我们希望能够调度我们更精细的程序类型。 Conceptually, what is needed are predicates along the lines of Cyclic2NN? 从概念上讲,所需要的是Cyclic2NN?谓词Cyclic2NN? and SingleBound2NN? SingleBound2NN? . Having only arity for dispatch using case-lambda just isn't enough. 仅使用case-lambda进行调度是不够的。

Guidance from Untyped Racket 来自Untyped Racket的指导

Fortunately, Lisps are domain specific languages for writing Lisps once we peal back the curtain to reveal the wizard, and in the end we can get what we want. 幸运的是,Lisps是用于编写Lisps的领域特定语言,一旦我们重新开启以展示向导,最终我们可以获得我们想要的东西。 The key is to come at the issue the other way and ask "How canwe use the predicates typed/racket gives us for procedures?" 关键是以另一种方式处理问题并询问“我们如何使用typed/racket的谓词为我们提供程序?”

Structures are Racket's user defined data types and are the basis for extending it's type system. 结构是Racket用户定义的数据类型,是扩展其类型系统的基础。 Structures are so powerful that even in the class based object system, " classes and objects are implemented in terms of structure types ." 结构非常强大,即使在基于类的对象系统中,“ 类和对象也是根据结构类型实现的 ”。

In #lang racket structures can be applied as procedures giving the #:property keyword using prop:procedure followed by a procedure for it's value. #lang racket结构可以作为过程应用,使用prop:procedure提供#:property关键字,后跟一个值的过程。 The documentation provides two examples: 该文档提供了两个示例:

The first example specifies a field of the structure to be applied as a procedure. 一个示例指定要作为过程应用的结构的字段。 Obviously, at least once it has been pointed out, that field must hold a value that evaluates to a procedure. 显然,至少有一次指出,该字段必须包含一个评估过程的值。

> ;; #lang racket
> (struct annotated-proc (base note)
     #:property prop:procedure
      (struct-field-index base))
> (define plus1 (annotated-proc
     (lambda (x) (+ x 1))
     "adds 1 to its argument"))
> (procedure? plus1)
#t
> (annotated-proc? plus1)
#t
> (plus1 10)
11
> (annotated-proc-note plus1)
"adds 1 to its argument"

In the second example an anonymous procedure [lambda] is provided directly as part of the property value. 第二示例中 ,匿名过程λ直接作为属性值的一部分提供。 The lambda takes an operand in the first position which is resolved to the value of the structure being used as a procedure. lambda在第一个位置获取一个操作数,该操作数被解析为用作过程的结构的值。 This allows accessing any value stored in any field of the structure including those which evaluate to procedures. 这允许访问存储在结构的任何字段中的任何值,包括评估过程的那些值。

> ;; #lang racket
> (struct greeter (name)
    #:property prop:procedure
    (lambda (self other)
       (string-append
         "Hi " other
          ", I'm " (greeter-name self))))
> (define joe-greet (greeter "Joe"))
> (greeter-name joe-greet)
"Joe"
> (joe-greet "Mary")
"Hi Mary, I'm Joe"
> (joe-greet "John")
"Hi John, I'm Joe

Applying it to typed/racket 将其应用于打字/球拍

Alas, neither syntax works with struct as implemented in typed/racket . 唉,这两种语法都不适用于在typed/racket实现的struct The problem it seems is that the static type checker as currently implemented cannot both define the structure and resolve its signature as a procedure at the same time. 似乎问题是当前实现的静态类型检查器不能同时定义结构并将其签名解析为过程。 The right information does not appear to be available at the right phase when using typed/racket 's struct special form. 在使用typed/racketstruct特殊形式时,正确的信息似乎不适用于正确的阶段。

To get around this, typed/racket provides define-struct/exec which roughly corresponds to the second syntactic form from #lang racket less the keyword argument and property definition: 为了解决这个问题, typed/racket提供了define-struct/exec ,它大致对应于#lang racket的第二个句法形式,而不是关键字参数和属性定义:

    (define-struct/exec name-spec ([f : t] ...) [e : proc-t])

      name-spec     =       name
                    |       (name parent)

Like define-struct, but defines a procedural structure. 与define-struct类似,但定义了一个过程结构。 The procdure e is used as the value for prop:procedure, and must have type proc-t. procd e用作prop:procedure的值,并且必须具有proc-t类型。

Not only does it give us strongly typed procedural forms, it's a bit more elegant than the keyword syntax found in #lang racket . 它不仅为我们提供了强类型的过程形式,而且比#lang racket的关键字语法更优雅。 Example code to resolve the question as restated here in this answer is: 解决此问题的示例代码在此答案中重申:

#lang typed/racket

(define-type 2NN (-> Number Number Number))

(define-struct/exec Cyclic2NN
   ((f : 2NN))
   ((lambda(self x s)
     ((Cyclic2NN-f self) x s))
      : (-> Cyclic2NN Number Number Number)))

 (define-struct/exec SingleBound2NN
   ((f : 2NN))
   ((lambda(self x s)
     ((SingleBound2NN-f self) x s))
       : (-> SingleBound2NN Number Number Number)))

 (define trigFunction1 
   (Cyclic2NN 
    (lambda(x s) 
      (* s (cos x)))))

(define quadraticFunction1
  (SingleBound2NN
    (lambda (x b)
      (let ((x1 x))
        (+ b (* x1 x1)))))

The defined procedures are strongly typed in the sense that: 定义的过程在以下意义上是强类型的:

> (SingleBound2NN? trigFunction1)
- : Boolean
#f
>  (SingleBound2NN? quadraticFunction1)
- : Boolean
#t

All that remains is writing a macro to simplify specification. 剩下的就是编写一个宏来简化规范。

In the general case, what you want is impossible due to how types are implemented in Racket. 一般情况下,由于Racket中的类型实现方式,您想要的是不可能的。 Racket has contracts , which are run-time wrappers that guard parts of a program from other parts. Racket有合同 ,它是运行时包装器,可以保护程序的其他部分。 A function contract is a wrapper that treats the function as a black box - a contract of the form (-> number? number?) can wrap any function and the new wrapper function first checks that it receives one number? 函数契约是一个将函数视为黑盒的包装器 - 表单的契约(-> number? number?)可以包装任何函数,新的包装函数首先检查它是否收到一个number? and then passes it to the wrapped function, then checks that the wrapped function returns a number? 然后将它传递给包装函数,然后检查包装函数返回一个number? . This is all done dynamically , every single time the function is called. 这一切都是动态完成的,每次调用函数。 Typed Racket adds a notion of types that are statically checked, but since it can provide and require values to and from untyped modules, those values are guarded with contracts that represent their type. Typed Racket添加了静态检查类型的概念,但由于它可以提供和要求非类型化模块的值,因此这些值受到代表其类型的合同的保护。

In your function dispatcher , you accept a function f dynamically , at run time and then want to do something based on what kind of function you got. 在函数dispatcher ,您在运行时 动态接受函数f ,然后根据您获得的函数类型执行某些操作。 But functions are black boxes - contracts don't actually know anything about the functions they wrap, they just check that they behave properly. 但功能是黑盒子 - 合同实际上并不知道它们包装的功能,他们只是检查它们的行为是否正常。 There's no way to tell if dispatcher was given a function of the form (-> number? number?) or a function of the form (-> string? string?) . 没有办法判断dispatcher是否具有表单函数(-> number? number?)或表单函数(-> string? string?) Since dispatcher can accept any possible function , the functions are black boxes with no information about what they accept or promise. 由于dispatcher可以接受任何可能的功能 ,因此功能是黑盒子,没有关于他们接受或承诺的信息。 dispatcher can only assume the function is correct with a contract and try to use it. dispatcher只能假定合同中的函数是正确的并尝试使用它。 This is also why define-type doesn't make a predicate automatically for function types - there's no way to prove a function has the type dynamically, you can only wrap it in a contract and assume it behaves. 这也是为什么define-type不会自动为函数类型创建谓词的原因 - 没有办法证明函数具有动态类型,你只能将它包装在契约中并假设它的行为。

The exception to this is arity information - all functions know how many arguments they accept. 例外情况是arity信息 - 所有函数都知道它们接受了多少个参数。 The procedure-arity function will give you this information. procedure-arity函数将为您提供此信息。 So while you can't dispatch on function types at run-time in general , you can dispatch on function arity . 所以,当你不能在一般运行时的函数类型调度, 可以在调度功能参数数量 This is what case-lambda does - it makes a function that dispatches based on the number of arguments it receives: 这就是case-lambda所做的事情 - 它根据收到的参数数量调度函数:

(: dispatcher (case-> [-> Calculate-with-one-number Number Void]
                      [-> Calculate-with-two-numbers Number Number Void]))

(define dispatcher
  (case-lambda
    [([f : Calculate-with-one-number]
      [arg : Number])
     (do-something arg)]
    [([f : Calculate-with-two-numbers]
      [arg1 : Number]
      [arg2 : Number])
     (do-something-else arg1 arg2)]
    [else 42]))

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM