Haskell中的手动类型推断

时间:2018-06-09 19:41:22

标签: haskell types type-inference

考虑功能

f g h x y = g (g x) (h y)

它的类型是什么?显然我可以使用:t f来查找,但如果我需要手动推断,那么最好的方法是什么呢?

我所展示的方法是为参数分配类型并从那里推导 - 例如x :: ay :: bg :: a -> ch :: b -> d提供了cd g xh yc = a然后我们继续从那里做出推论(g (g x) (h y)来自x等)。

然而,这有时只会变成一个巨大的混乱,我常常不确定如何进一步扣除或在我完成后解决。其他问题有时会发生 - 例如,在这种情况下^([a-zżźćńółęąś]+)\s+-\s+(\d+\.\d+)$ 将会变成一个函数,但在作弊和查找类型之前,这对我来说并不明显。

是否有一种特定的算法始终有效(并且人类可以快速执行)?否则,是否有一些我缺少的启发式或技巧?

4 个答案:

答案 0 :(得分:9)

让我们检查顶层的功能:

f g h x y = g (g x) (h y)

我们将首先为类型指定名称,然后继续并专门化它们,因为我们会更多地了解函数。

首先,让我们为顶级表达式指定一个类型。我们称之为a

g (g x) (h y) :: a

让我们取出第一个参数并分别指定类型:

-- 'expanding'  (g (g x)) (h y) :: a
h y :: b
g (g x) :: b -> a

再次

-- 'expanding' g (g x) :: b -> a
g x :: c
g :: c -> b -> a

再次

-- 'expanding' g x :: c
x :: d
g :: d -> c

但请坚持:我们现在拥有g :: c -> b -> ag :: d -> c。因此,通过检查,我们知道cd是等效的(写为c ~ d),还有c ~ b -> a

这可以通过简单地比较我们推断的g的两种类型来推断。请注意,这是类型矛盾,因为类型变量通常足以适合它们的等价物。如果我们在某处推断出Int ~ Bool,那么会出现矛盾。

所以我们现在总共得到以下信息:(省略一点工作)

y :: e
h :: e -> b
x :: b -> a             -- Originally d, applied d ~ b -> a.
g :: (b -> a) -> b -> a -- Originally c -> b -> a, applied c ~ b -> a

这是通过替换每个类型变量的最具体形式来完成的,即将cd替换为更具体的b -> a

因此,只需检查哪些参数在哪里,我们就会看到

f :: ((b -> a) -> b -> a) -> (e -> b) -> (b -> a) -> e -> a

GHC证实了这一点。

答案 1 :(得分:5)

这个功能是:

f g h x y = g (g x) (h y)

或更详细:

f g h x y = (g (g x)) (h y)

最初我们假设所有四个参数(ghxy)都有不同的类型。我们还为函数引入了一个输出类型(这里是t):

g :: a
h :: b
x :: c
y :: d
f g h x y :: t

但现在我们要进行一些推断。我们看到例如g x,因此这意味着函数应用程序具有g函数,x参数。这意味着g是一个函数,输入类型为c,因此我们将g的类型重新定义为:

g :: a ~ (c -> e)
h :: b
x :: c
y :: d
f g h x y :: t

(此处,代字号~表示两种类型相同,因此ac -> e)相同。)

由于g的类型为g :: c -> e,而x的类型为c,因此这意味着函数应用g x的结果类型为{{ 1}}。

我们看到另一个函数应用程序,g x :: e作为函数,g作为参数。所以这意味着g x的输入类型(g)应该等于c的类型(g x),因此我们知道{ {1}},现在的类型是:

e

现在我们看到一个函数应用程序c ~ e函数, c ~ e g :: a ~ (c -> c) h :: b x :: c y :: d f g h x y :: t 参数。这意味着h是一个函数,输入类型与y的类型相同,因此h的类型为y :: d,这意味着:

h

最后我们看到一个函数应用程序d -> f函数, c ~ e g :: a ~ (c -> c) h :: b ~ (d -> f) x :: c y :: d f g h x y :: t 参数,这意味着g (g x)的输出类型应该是一个函数,{{1} }作为输入类型,h y作为输出类型,这意味着g (g x) :: c,因此:

f

这意味着,由于t包含c ~ (f -> t) c ~ e c ~ (f -> t) g :: a ~ (c -> c) ~ ((f -> t) -> (f -> t)) h :: b ~ (d -> f) x :: (f -> t) y :: d f g h x y :: t fg这些参数,因此h的类型为:

x

答案 2 :(得分:2)

您已经描述了如何操作,但也许您错过了统一步骤。也就是说,有时我们知道两个变量是相同的:

map2 = folium.Map(location=[43.7, -79.4], tiles='cartodbpositron', 
zoom_start=11) # set default location on the map

marker_cluster = folium.plugins.MarkerCluster().add_to(map2) # add the 
location to the marker cluster

for point in range(len(locationlist)): # loop through the plots

    # include popup
    popup1 = folium.Popup(df['Condo Address'][point], parse_html=True)

    # include icon
    icon1 = folium.Icon(color=df['color'][point])

    # mark every addresses in the map from the data
folium.Marker(locationlist[point],popup=popup1,icon = 
icon1).add_to(marker_cluster)

我们知道x :: a y :: b g :: a -> b -- from g x h :: c -> d -- from h y a ~ b -- from g (g x) a是相同的,因为我们将bg x传递给b,期望{{1} }}。所以现在我们用g替换所有a,然后继续我们考虑所有子表达式......

关于你的“大混乱”评论,我有几点要说:

  1. 这是做到这一点的方法。如果它太难了,你只需要练习,它就会变得更容易。你将开始发展直觉,它将更容易实现。
  2. 这个特殊功能不是一件容易的事。我已经编写了12年的Haskell编程,我仍然需要在纸上完成统一算法。它如此抽象的事实并没有帮助 - 如果我知道这个功能的目的是什么,那就更容易了。

答案 3 :(得分:1)

只需记下它们下面的所有实体类型:

f g h x y = g (g x)   (h y) 
                 x :: x  y :: y
                       h :: y -> a            , h y :: a
               g :: x -> b                    , g x :: b
            g    :: b -> (a -> t)             , x ~ b , b ~ (a -> t)
f :: (x -> b) -> (y -> a) -> x -> y -> t      , x ~ b , b ~ (a -> t)
f :: (b -> b) -> (y -> a) -> b -> y -> t      , b ~ (a -> t)
--       g           h       x    y

因此f :: ((a -> t) -> (a -> t)) -> (y -> a) -> (a -> t) -> y -> t。就是这样。

实际上,

~> :t let f g h x y = g (g x) (h y) in f
    :: ((t1 -> t) -> t1 -> t) -> (t2 -> t1) -> (t1 -> t) -> t2 -> t

这是这样的:

  1. x必须有某种类型,我们称之为xx :: x
  2. y必须有某种类型,我们称之为yy :: y
  3. h y必须有某种类型,我们称之为ah y :: a。因此h :: y -> a
  4. g x必须有某种类型,我们称之为bg x :: b。因此g :: x -> b
  5. g _ _必须有某种类型,我们称之为t。因此g :: b -> a -> t
    g :: b -> (a -> t)相同。
  6. g的两种类型签名必须统一,即在所涉及的类型变量的某些替换下相同,因为这两个签名描述了相同的实体,g
    因此我们必须x ~ b, b ~ (a -> t)。这是替代。
  7. 拥有f的所有类型的参数,我们知道它会产生g生成的内容,即t。所以我们可以写下它的类型(x -> b) -> (y -> a) -> x -> y -> t
  8. 最后,我们根据替换来替换类型,以减少所涉及的类型变量的数量。因此,我们首先将b替换为x,然后将a -> t替换为b,每次都从替换中删除已删除的类型变量。
  9. 如果替换为空,我们就完成了。
  10. 当然,我们可以选择先将b替换为x,最后替换为x ~ (a -> t),然后我们最终会使用相同的类型,如果我们总是将更简单的类型替换为更复杂的类型(例如,将b替换为(a -> t),将替换为,反之亦然)。< / p>

    简单的步骤,保证结果。