简体   繁体   English

为什么要在数据类型定义中对函数进行编码?

[英]Why encode function in data type definition?

I find it hard to get the intuition about encoding function in data type definition. 我发现很难获得关于数据类型定义中编码功能的直觉。 This is done in the definition of the State and IO types, for eg 这是在StateIO类型的定义中完成的,例如

data State s a = State s -> (a,s)
type IO a = RealWorld -> (a, RealWorld) -- this is type synonym though, not new type

I would like to see a more trivial example to understand its value so I could possibly build on this to have more complex examples. 我希望看到一个更简单的示例来理解其价值,因此我可以在此基础上构建更复杂的示例。 For eg say I have a data structure, would that make any sense to encode a function in one of the data constructor. 例如说我有一个数据结构,在其中一个数据构造函数中编码一个函数是否有意义。

data Tree = Node Int (Tree) (Tree) (? -> ?) | E

I am not sure what I am trying to do here, but what could be an example of a function that I can encode in such a type? 我不确定在这里要做什么,但是可以以这种类型编码的函数的示例可能是什么? And why would I have to encode it in the type, but not use it as a normal function, I don't know, maybe passed as argument when needed? 为什么我必须在类型中对其进行编码,却不将其用作普通函数,我不知道,可能在需要时作为参数传递呢?

Really, functions are just data like anything else. 实际上,功能就像其他数据一样。

Prelude> :i (->) 前奏>:i(->)
data (->) ab -- Defined in `GHC.Prim' data (->) ab -- Defined in 'GHC.Prim'中data (->) ab -- Defined in
instance Monad ((->) r) -- Defined in `GHC.Base' instance Monad ((->) r) -- Defined in `GHC.Base'中instance Monad ((->) r) -- Defined in
instance Functor ((->) r) -- Defined in `GHC.Base' instance Functor ((->) r) -- Defined in 'GHC.Base'中instance Functor ((->) r) -- Defined in

This comes out very naturally and without anything conceptually surprising if you consider only functions from, say, Int . 如果您只考虑Int函数,这是非常自然的,并且在概念上不会令人感到惊讶。 I'll give them a strange name: (remember that (->) ab means a->b ) 我给他们起一个奇怪的名字:( 记住(->) ab表示a->b

type Array = (->) Int

What ? 什么 Well, what's the most important operation on an array? 那么,对数组最重要的操作是什么?

Prelude> :t (Data.Array.!) Prelude>:t(Data.Array。!)
(Data.Array.!) :: GHC.Arr.Ix i => GHC.Arr.Array ie -> i -> e (Data.Array。!):: GHC.Arr.Ix i => GHC.Arr.Array即-> i-> e
Prelude> :t (Data.Vector.!) Prelude>:t(Data.Vector。!)
(Data.Vector.!) :: Data.Vector.Vector a -> Int -> a (Data.Vector。!):: Data.Vector.Vector a-> Int-> a

Let's define something like that for our own array type: 让我们为自己的数组类型定义类似的内容:

(!) :: Array a -> Int -> a
(!) = ($)

Now we can do 现在我们可以做

test :: Array String
test 0 = "bla"
test 1 = "foo"

FnArray> test ! FnArray>测试! 0 0
"bla" “ bla”
FnArray> test ! FnArray>测试! 1 1个
"foo" “ foo”
FnArray> test ! FnArray>测试! 2 2
"*** Exception: :8:5-34: Non-exhaustive patterns in function test “ ***例外:: 8:5-34:功能测试中的非穷举模式

Compare this to 比较一下

Prelude Data.Vector> let test = fromList ["bla", "foo"] 前奏Data.Vector> let test = fromList [“ bla”,“ foo”]
Prelude Data.Vector> test ! 前奏Data.Vector> test! 0 0
"bla" “ bla”
Prelude Data.Vector> test ! 前奏Data.Vector> test! 1 1个
"foo" “ foo”
Prelude Data.Vector> test ! 前奏Data.Vector> test! 2 2
"*** Exception: ./Data/Vector/Generic.hs:244 ((!)): index out of bounds (2,2) “ ***例外:./ Data / Vector / Generic.hs:244((!)):索引超出范围(2,2)

Not all that different, right? 没什么不同吧? It's Haskell's enforcement of referential transparency that guarantees us the return values of a function can actually be interpreted as inhabitant values of some container. Haskell对引用透明性的强制执行保证了我们函数的返回值实际上可以解释为某些容器的居民值。 This is one common way to look at the Functor instance: fmap transform f applies some transformation to the values "included" in f (as result values). 这是一个常见的方式看Functor实例: fmap transform f应用一些转变的价值观“包含”在f (如结果值)。 This works by simply composing the transformation after the target function: 这可以通过在目标函数之后简单地构成转换来实现:

instance Functor (r ->) where
  fmap transform f x = transform $ f x

(though you'd of course better write this simply fmap = (.) .) (尽管您当然最好将其简单地写为fmap = (.) 。)


Now, what's a bit more confusing is that the (->) type constructor has one more type argument: the argument type . 现在,更令人困惑的是(->)类型构造函数还有一个类型实参: arguments type Let's focus on that by defining 让我们专注于定义

{-# LANGUAGE TypeOperators #-}

newtype (:<-) a b = BackFunc (b->a)

To get some feel for it: 获得一些感觉:

show' :: Show a  =>  String :<- a
show' = BackFunc show

ie it's really just function arrows written the other way around. 即,实际上只是功能箭头以相反的方式编写。

Is (:<-) Int some sort of container, similarly to how (->) Int resembles an array? (:<-) Int某种容器,类似于(->) Int类似于数组吗? Not quite. 不完全的。 We can't define instance Functor (a :<-) . 我们无法定义instance Functor (a :<-) Yet, mathematically speaking, (a :<-) is a functor, but of a different kind: a contravariant functor . 但是,从数学上讲, (a :<-) 一个函子,但是却是另一种: 逆变函子

instance Contravariant (a :<-) where
  contramap transform (BackFunc f) = BackFunc $ f . transform

"Ordinary" functors OTOH are covariant functors . “普通”函子OTOH是协变函子 The naming is rather easy to understand if you compare directly: 如果直接比较,则命名很容易理解:

fmap      :: Functor f       => (a->b) -> f a->f b
contramap :: Contravariant f => (b->a) -> f a->f b

While contravariant functors aren't nearly as commonly used as covariant ones, you can use them in much the same way when reasoning about data flow etc.. When using functions in data fields, it's really covariant vs. contravariant you should foremostly think about, not functions vs. values – because really, there is nothing special about functions compared to "static values" in a purely functional language. 尽管协变函子不像协变函子那样普遍使用,但是在推理数据流等时,您可以以几乎相同的方式使用它们。在数据字段中使用函数时,您首先应该考虑的是协变vs.不是函数与值–因为与纯函数式语言中的“静态值”相比,函数确实没有什么特别之处。


About your Tree type 关于Tree类型

I don't think this data type could be made something really useful, but we can do something stupid with a similar type that may illustrate the points I made above: 我认为这种数据类型不能真正有用,但是我们可以用类似的类型来做一些愚蠢的事情,以说明我在上面提到的观点:

data Tree' = Node Int (Bool -> Tree) | E

That is, disconsidering performance, isomorphic to the usual 也就是说,令人讨厌的表现,与通常的同构

data Tree = Node Int Tree Tree | E

Why? 为什么? Well, Bool -> Tree is similar to Array Tree , except we don't use Int s for indexing but Bool s. 好吧, Bool -> TreeArray Tree相似,除了我们不使用Int进行索引,而使用Bool进行索引。 And there are only two evaluatable boolean values. 而且只有两个可评估的布尔值。 Arrays with fixed size 2 are usually called tuples. 固定大小为2的数组通常称为元组。 And with Bool->Tree ≅ (Tree, Tree) we have Node Int (Bool->Tree) ≅ Node Int Tree Tree . 通过Bool->Tree ≅ (Tree, Tree)我们有了Node Int (Bool->Tree) ≅ Node Int Tree Tree

Admittedly this isn't all that interesting. 诚然,这并不是那么有趣。 With functions from a fixed domain the isomorphism are usually obvious. 使用来自固定域的函数,同构通常很明显。 The interesting cases are polymorphic on the function domain and/or codomain, which always leads to somewhat abstract results such as the state monad. 有趣的情况是在功能域和/或共域上是多态的,这总是导致某种抽象的结果,例如状态单子。 But even in those cases, you can remember that nothing really seperates functions from other data types in Haskell. 但是即使在那种情况下,您也可以记住,Haskell中没有将功能与其他数据类型真正分开的功能。

You generally start FP learning with 2 concepts - data types and functions . 通常,您从2个概念( 数据类型函数)开始FP学习。 Once you have good confidence level of designing programs using these 2 concepts I would suggest you start using only 1 concept ie of types which means: 一旦您对使用这2个概念设计程序有足够的信心,我建议您仅开始使用1个概念,即类型 ,这意味着:

  • You define new types by combining the existing types or type constructors in the language. 您可以通过组合语言中的现有类型或类型构造函数来定义新类型。
  • You define new type constructors to abstract out a general concept in your problem domain. 您定义新的类型构造函数以抽象出问题域中的一般概念。
  • Function is a just a type which maps a particular type to another type. 函数只是特定类型映射到另一种类型的一种类型。 Which basically means that the types which the functions maps could themselves be functions and so on (because we just said that functions are type). 这基本上意味着函数映射的类型本身可以是函数,依此类推(因为我们只是说函数是类型)。 This is what people generally call higher oreder functions and also this gives you the illusion that a function takes multiple parameters, whereas reality is that a function type always map a type to another type (ie it is a unary function), but we know that the another type can itself be a function type. 这就是人们通常所说的高级oreder函数,这也给您一种函数需要多个参数的错觉,而现实是函数类型总是将一个类型映射到另一个类型(即它是一元函数),但是我们知道另一个类型本身可以是函数类型。

    Example : add :: Int -> Int -> Int is same as add :: Int -> (Int -> Int) . 示例: add :: Int -> Int -> Intadd :: Int -> (Int -> Int) add is (function) type which maps an Integer to a (function) type which maps an Integer to an Integer. add是将一个Integer映射到一个(函数)类型(将一个Integer映射到一个Integer)的(函数)类型。

  • To create a Function type we use the (->) type constructor provided by Haskell. 要创建一个函数类型,我们使用Haskell提供的(->)类型构造函数。

Thinking in terms of above points you will find that the line between data types and functions is no more there. 从以上几点考虑,您会发现数据类型和函数之间的界线不再存在。

As far as which type to choose is concerned, it solely depends on the problem domain you are trying to solve. 至于选择哪种类型,它仅取决于您要解决的问题领域。 Basically, when ever there is a need where you find that you need some sort of mapping from one type to another, you will use the (->) type. 基本上,当需要查找从一种类型到另一种类型的某种映射时 ,将使用(->)类型。

The State is defined using function type because the way we represent state in FP is "a mapping which takes current state and returns a value and new state", as you can see that there is a mapping happening here and hence the use of (->) type. 状态是使用函数类型定义的,因为我们在FP中表示状态的方式是“获取当前状态并返回值和新状态的映射 ”,如您所见,此处正在发生映射 ,因此使用(->)类型。

Let's see if this helps. 让我们看看这是否有帮助。 Unfortunately for beginners, the definition of State quotes State both on the left and right hand side, but they have different meaning: one is the name of the type, the other is the name of the constructor. 不幸的是,对于初学者来说,状态的定义在左右两侧都引用了状态,但是它们具有不同的含义:一个是类型的名称,另一个是构造函数的名称。 So the definition is really: 因此,定义实际上是:

        data State s a = St (s -> (a,s))

Which means you can construct a value of type State sa using constructor St and passing it a function from s to (a,s), that is, a function that can construct a value of some type a and a value of next state s from the previous state. 这意味着您可以使用构造函数St构造State类型的值,并将其从s传递到(a,s)的函数,即,可以构造某种类型a的值和下一个state的值的函数以前的状态。 This is a simple way to represent a state transition. 这是表示状态转换的一种简单方法。

In order to see why passing a function is useful, you need to study how the rest of it works. 为了了解传递函数为什么有用的原因,您需要研究其余函数的工作原理。 For example, we can construct new value of type State sa given two other values by composing the functions. 例如,我们可以通过组合函数来构造给定其他两个值的状态类型为sa的新值。 By composing such States, such state transition functions, you get a state machine, which then can be used to compute a value and final state, given an initial state. 通过组成这样的状态,这样的状态转换函数,您将得到一个状态机,然后可以将其用于计算值和最终状态(给定初始状态)。

        runStateMachine :: State s a -> s -> (a,s)
        runStateMachine (St f) x = f x   -- or shorter, runStateMachine (St f) = f -- just unwrap the function from the constructor

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

相关问题 c 和 c++ function 声明和定义:指定数据类型 - c and c++ function declaration & definition : specifying data type 在PHP中的函数定义中指定类型 - Specifing type in function definition in PHP 在 function 定义或原型中要求返回值的数据类型规范的目的是什么? 类型铸造? - What is the purpose of requiring a data type specification of the returned value in a function definition or prototype? Type casting? 从函数定义Haskell推断函数类型 - Inferring function type from function definition Haskell 不确定为什么我不在我的 void 函数定义中调用返回类型 - Unsure of why I do not call the return type here in my void function definition 为什么这个Mathematica函数定义会返回错误? - Why is this Mathematica function definition returning an error? Javascript-为什么下面的函数定义错误呢? - Javascript - Why below function definition errors out? 为什么变量定义在JS中不是悬空而是function - Why is variable definition not hoisted in JS but function 为什么 function 定义中接受符号字符? - Why is the sign character accepted in the function definition? 可以在函数原型中指定返回类型,而在c中的函数定义中可以吗? - Is it okay to specify return type in function prototype and not in function definition in c?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM